diff --git a/.appveyor.yml b/.appveyor.yml index 49611c66a254..a637fe545466 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,7 +1,7 @@ # With infos from # http://tjelvarolsson.com/blog/how-to-continuously-test-your-python-code-on-windows-using-appveyor/ # https://packaging.python.org/en/latest/appveyor/ -# https://github.com/rmcgibbo/python-appveyor-conda-example +--- # Backslashes in quotes need to be escaped: \ -> "\\" branches: @@ -9,33 +9,32 @@ branches: - /auto-backport-.*/ - /^v\d+\.\d+\.[\dx]+-doc$/ +skip_commits: + message: /\[ci doc\]/ + files: + - doc/ + - galleries/ + clone_depth: 50 +image: Visual Studio 2019 + environment: global: PYTHONFAULTHANDLER: 1 PYTHONIOENCODING: UTF-8 - PYTEST_ARGS: -raR --numprocesses=auto --timeout=300 --durations=25 + PYTEST_ARGS: -rfEsXR --numprocesses=auto --timeout=300 --durations=25 --cov-report= --cov=lib --log-level=DEBUG matrix: - # In theory we could use a single CONDA_INSTALL_LOCN because we construct - # the envs anyway. But using one for the right python version hopefully - # making things faster due to package caching. - - PYTHON_VERSION: "3.7" - CONDA_INSTALL_LOCN: "C:\\Miniconda37-x64" - TEST_ALL: "no" - EXTRAREQS: "-r requirements/testing/travis_extra.txt" - - PYTHON_VERSION: "3.8" - CONDA_INSTALL_LOCN: "C:\\Miniconda37-x64" - TEST_ALL: "no" - EXTRAREQS: "-r requirements/testing/travis_extra.txt" + - PYTHON_VERSION: "3.11" + TEST_ALL: "yes" # We always use a 64-bit machine, but can build x86 distributions # with the PYTHON_ARCH variable platform: - - x64 + - x64 # all our python builds have to happen in tests_script... build: false @@ -45,67 +44,54 @@ cache: - '%USERPROFILE%\.cache\matplotlib' init: - - echo %PYTHON_VERSION% %CONDA_INSTALL_LOCN% + - ps: + Invoke-Webrequest + -URI https://micro.mamba.pm/api/micromamba/win-64/latest + -OutFile C:\projects\micromamba.tar.bz2 + - ps: C:\PROGRA~1\7-Zip\7z.exe x C:\projects\micromamba.tar.bz2 -aoa -oC:\projects\ + - ps: C:\PROGRA~1\7-Zip\7z.exe x C:\projects\micromamba.tar -ttar -aoa -oC:\projects\ + - 'set PATH=C:\projects\Library\bin;%PATH%' + - micromamba shell init --shell cmd.exe + - micromamba config set always_yes true + - micromamba config prepend channels conda-forge + - micromamba info install: - - set PATH=%CONDA_INSTALL_LOCN%;%CONDA_INSTALL_LOCN%\scripts;%PATH%; - - conda config --set always_yes true - - conda config --set show_channel_urls yes - - conda config --prepend channels conda-forge - - # For building, use a new environment - - conda create -q -n test-environment python=%PYTHON_VERSION% tk - - activate test-environment - # pull pywin32 from conda because on py38 there is something wrong with finding - # the dlls when insalled from pip - - conda install -c conda-forge pywin32 - - echo %PYTHON_VERSION% %TARGET_ARCH% - # Install dependencies from PyPI. - - python -mpip install --upgrade -r requirements/testing/travis_all.txt %EXTRAREQS% %PINNEDVERS% - # Install optional dependencies from PyPI. - # Sphinx is needed to run sphinxext tests - - python -mpip install --upgrade sphinx - # Show the installed packages + versions - - conda list + - micromamba env create -f environment.yml python=%PYTHON_VERSION% pywin32 + - micromamba activate mpl-dev test_script: # Now build the thing.. - set LINK=/LIBPATH:%cd%\lib - - pip install -ve . + - pip install -v --no-build-isolation --editable .[dev] # this should show no freetype dll... - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" - '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' # this are optional dependencies so that we don't skip so many tests... - - if x%TEST_ALL% == xyes conda install -q ffmpeg inkscape miktex + - if x%TEST_ALL% == xyes micromamba install -q ffmpeg inkscape + # miktex is available on conda, but seems to fail with permission errors. # missing packages on conda-forge for imagemagick # This install sometimes failed randomly :-( - #- choco install imagemagick + # - choco install imagemagick # Test import of tkagg backend - - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" + - python -c + "import matplotlib as m; m.use('tkagg'); + import matplotlib.pyplot as plt; + print(plt.get_backend())" # tests - echo The following args are passed to pytest %PYTEST_ARGS% - pytest %PYTEST_ARGS% -after_test: - # After the tests were a success, build wheels. - # Hide the output, the copied files really clutter the build log... - - 'python setup.py bdist_wheel > NUL:' - - dir dist\ - - echo finished... - artifacts: - - path: dist\* - name: packages - - path: result_images\* name: result_images - type: zip + type: Zip on_finish: - - pip install codecov - - codecov -e PYTHON_VERSION PLATFORM + - micromamba install codecov + - codecov -e PYTHON_VERSION PLATFORM -n "%PYTHON_VERSION% Windows" on_failure: # Generate a html for visual tests diff --git a/.circleci/config.yml b/.circleci/config.yml index 48275404c92e..40ba933cf0d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ # Circle CI configuration file # https://circleci.com/docs/ - +--- version: 2.1 @@ -9,42 +9,78 @@ version: 2.1 # commands: + check-skip: + steps: + - run: + name: Check-skip + command: | + export git_log=$(git log --max-count=1 --pretty=format:"%B" | tr "\n" " ") + echo "Got commit message:" + echo "${git_log}" + if [[ -v CIRCLE_PULL_REQUEST ]] && + [[ $git_log =~ (\[skip circle\]|\[circle skip\]|\[skip doc\]|\[doc skip\]) ]]; then + echo "Skip detected, exiting job ${CIRCLE_JOB} for PR ${CIRCLE_PULL_REQUEST}." + circleci-agent step halt; + fi + + merge: + steps: + - run: + name: Merge with upstream + command: | + if ! git remote -v | grep upstream; then + git remote add upstream https://github.com/matplotlib/matplotlib.git + fi + git fetch upstream + if [[ "$CIRCLE_BRANCH" != "main" ]] && \ + [[ "$CIRCLE_PR_NUMBER" != "" ]]; then + echo "Merging ${CIRCLE_PR_NUMBER}" + git pull --ff-only upstream "refs/pull/${CIRCLE_PR_NUMBER}/merge" + fi + apt-install: steps: - run: name: Install apt packages command: | - sudo apt -qq update - sudo apt install -y \ - inkscape \ - ffmpeg \ + sudo apt-get -qq update + sudo apt-get install -yy --no-install-recommends \ + cm-super \ dvipng \ + ffmpeg \ + fonts-crosextra-carlito \ + fonts-freefont-otf \ + fonts-noto-cjk \ + fonts-wqy-zenhei \ + graphviz \ + inkscape \ lmodern \ - cm-super \ + ninja-build \ + optipng \ + texlive-fonts-recommended \ texlive-latex-base \ texlive-latex-extra \ - texlive-fonts-recommended \ texlive-latex-recommended \ texlive-pictures \ - texlive-xetex \ - graphviz \ - fonts-crosextra-carlito \ - fonts-freefont-otf \ - fonts-humor-sans \ - optipng + texlive-xetex fonts-install: steps: - restore_cache: - key: fonts-2 + key: fonts-4 - run: name: Install custom fonts command: | mkdir -p ~/.local/share/fonts - wget -nc https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O ~/.local/share/fonts/Felipa-Regular.ttf || true + wget -nc \ + https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true \ + -O ~/.local/share/fonts/Felipa-Regular.ttf || true + wget -nc \ + https://github.com/ipython/xkcd-font/blob/master/xkcd-script/font/xkcd-script.ttf?raw=true \ + -O ~/.local/share/fonts/xkcd-Script.ttf || true fc-cache -f -v - save_cache: - key: fonts-2 + key: fonts-4 paths: - ~/.local/share/fonts/ @@ -54,11 +90,11 @@ commands: - run: name: Upgrade pip, setuptools, wheel command: | - python -mpip install --upgrade --user pip - python -mpip install --upgrade --user wheel - python -mpip install --upgrade --user setuptools + python -m pip install --upgrade --user pip + python -m pip install --upgrade --user wheel + python -m pip install --upgrade --user 'setuptools!=60.6.0' - deps-install: + doc-deps-install: parameters: numpy_version: type: string @@ -67,14 +103,31 @@ commands: - run: name: Install Python dependencies command: | - python -mpip install --user numpy<< parameters.numpy_version >> codecov coverage - python -mpip install --user -r requirements/doc/doc-requirements.txt + python -m pip install --user -r requirements/dev/build-requirements.txt + python -m pip install --user \ + numpy<< parameters.numpy_version >> \ + -r requirements/doc/doc-requirements.txt + python -m pip install --no-deps --user \ + git+https://github.com/matplotlib/mpl-sphinx-theme.git mpl-install: steps: - run: name: Install Matplotlib - command: python -mpip install --user -ve . + command: | + if [[ "$CIRCLE_BRANCH" == v*-doc ]]; then + # The v*-doc branches must build against the specified release. + version=${CIRCLE_BRANCH%-doc} + version=${version#v} + python -m pip install matplotlib==${version} + else + python -m pip install --user --verbose \ + --no-build-isolation --editable .[dev] + fi + - save_cache: + key: build-deps-2 + paths: + - subprojects/packagecache doc-build: steps: @@ -87,7 +140,14 @@ commands: command: | # Set epoch to date of latest tag. export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" - make html O=-T + # Set release mode only when deploying to devdocs. + if [ "$CIRCLE_PROJECT_USERNAME" = "matplotlib" ] && \ + [ "$CIRCLE_BRANCH" = "main" ] && \ + [ "$CIRCLE_PR_NUMBER" = "" ]; then + export RELEASE_TAG='-t release' + fi + mkdir -p logs + make html O="-T $RELEASE_TAG -j4 -w /tmp/sphinxerrorswarnings.log" rm -r build/html/_sources working_directory: doc - save_cache: @@ -95,88 +155,99 @@ commands: paths: - doc/build/doctrees + doc-show-errors-warnings: + steps: + - run: + name: Extract possible build errors and warnings + command: | + (grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log || + echo "No errors or warnings") + # Save logs as an artifact, and convert from absolute paths to + # repository-relative paths. + sed "s~$PWD/~~" /tmp/sphinxerrorswarnings.log > \ + doc/logs/sphinx-errors-warnings.log + when: always + - store_artifacts: + path: doc/logs/sphinx-errors-warnings.log + + doc-show-deprecations: + steps: + - run: + name: Extract possible deprecation warnings in examples and tutorials + command: | + (grep -rl DeprecationWarning doc/build/html/gallery || + echo "No deprecation warnings in gallery") + (grep -rl DeprecationWarning doc/build/html/plot_types || + echo "No deprecation warnings in plot_types") + (grep -rl DeprecationWarning doc/build/html/tutorials || + echo "No deprecation warnings in tutorials") + # Save deprecations that are from this absolute directory, and + # convert to repository-relative paths. + (grep -Ero --no-filename "$PWD/.+DeprecationWarning.+$" \ + doc/build/html/{gallery,plot_types,tutorials} || echo) | \ + sed "s~$PWD/~~" > doc/logs/sphinx-deprecations.log + when: always + - store_artifacts: + path: doc/logs/sphinx-deprecations.log + doc-bundle: steps: - run: name: Bundle sphinx-gallery documentation artifacts - command: tar cf doc/build/sphinx-gallery-files.tar.gz doc/api/_as_gen doc/gallery doc/tutorials + command: > + tar cf doc/build/sphinx-gallery-files.tar.gz + doc/api/_as_gen + doc/gallery + doc/plot_types + doc/tutorials when: always - store_artifacts: path: doc/build/sphinx-gallery-files.tar.gz + deploy-docs: + steps: + - run: + name: "Deploy new docs" + command: ./.circleci/deploy-docs.sh + ########################################## # Here is where the real jobs are defined. # jobs: - docs-python37: - docker: - - image: circleci/python:3.7 - steps: - - checkout - - - apt-install - - fonts-install - - pip-install - - - deps-install - - mpl-install - - - doc-build - - - doc-bundle - - - store_artifacts: - path: doc/build/html - - docs-python38-min: + docs-python3: docker: - - image: circleci/python:3.8 + - image: cimg/python:3.12 + resource_class: large steps: - checkout + - check-skip + - merge - apt-install - fonts-install - pip-install - - deps-install: - numpy_version: "==1.16.0" - - mpl-install - - - doc-build - - - doc-bundle - - - store_artifacts: - path: doc/build/html - - docs-python38: - docker: - - image: circleci/python:3.8 - steps: - - checkout - - - apt-install - - fonts-install - - pip-install - - - deps-install + - doc-deps-install - mpl-install - doc-build + - doc-show-errors-warnings + - doc-show-deprecations - doc-bundle - store_artifacts: path: doc/build/html + - store_test_results: + path: doc/build/test-results - add_ssh_keys: fingerprints: - - "78:13:59:08:61:a9:e5:09:af:df:3a:d8:89:c2:84:c0" - - deploy: - name: "Deploy new docs" - command: ./.circleci/deploy-docs.sh + - "be:c3:c1:d8:fb:a1:0e:37:71:72:d7:a3:40:13:8f:14" + + - deploy-docs ######################################### # Defining workflows gets us parallelism. @@ -186,6 +257,6 @@ workflows: version: 2 build: jobs: - - docs-python37 - - docs-python38 - - docs-python38-min + # NOTE: If you rename this job, then you must update the `if` condition + # and `circleci-jobs` option in `.github/workflows/circleci.yml`. + - docs-python3 diff --git a/.circleci/deploy-docs.sh b/.circleci/deploy-docs.sh index 83037d2561a4..8801d5fd073e 100755 --- a/.circleci/deploy-docs.sh +++ b/.circleci/deploy-docs.sh @@ -3,10 +3,10 @@ set -e if [ "$CIRCLE_PROJECT_USERNAME" != "matplotlib" ] || \ - [ "$CIRCLE_BRANCH" != "master" ] || \ + [ "$CIRCLE_BRANCH" != "main" ] || \ [[ "$CIRCLE_PULL_REQUEST" == https://github.com/matplotlib/matplotlib/pull/* ]]; then echo "Not uploading docs for ${CIRCLE_SHA1}"\ - "from non-master branch (${CIRCLE_BRANCH})"\ + "from non-main branch (${CIRCLE_BRANCH})"\ "or pull request (${CIRCLE_PULL_REQUEST})"\ "or non-Matplotlib org (${CIRCLE_PROJECT_USERNAME})." exit diff --git a/.circleci/fetch_doc_logs.py b/.circleci/fetch_doc_logs.py new file mode 100644 index 000000000000..0a5552a7721c --- /dev/null +++ b/.circleci/fetch_doc_logs.py @@ -0,0 +1,66 @@ +""" +Download artifacts from CircleCI for a documentation build. + +This is run by the :file:`.github/workflows/circleci.yml` workflow in order to +get the warning/deprecation logs that will be posted on commits as checks. Logs +are downloaded from the :file:`docs/logs` artifact path and placed in the +:file:`logs` directory. + +Additionally, the artifact count for a build is produced as a workflow output, +by appending to the file specified by :env:`GITHUB_OUTPUT`. + +If there are no logs, an "ERROR" message is printed, but this is not fatal, as +the initial 'status' workflow runs when the build has first started, and there +are naturally no artifacts at that point. + +This script should be run by passing the CircleCI build URL as its first +argument. In the GitHub Actions workflow, this URL comes from +``github.event.target_url``. +""" +import json +import os +from pathlib import Path +import sys +from urllib.parse import urlparse +from urllib.request import URLError, urlopen + + +if len(sys.argv) != 2: + print('USAGE: fetch_doc_results.py CircleCI-build-url') + sys.exit(1) + +target_url = urlparse(sys.argv[1]) +*_, organization, repository, build_id = target_url.path.split('/') +print(f'Fetching artifacts from {organization}/{repository} for {build_id}') + +artifact_url = ( + f'https://circleci.com/api/v2/project/gh/' + f'{organization}/{repository}/{build_id}/artifacts' +) +print(artifact_url) +try: + with urlopen(artifact_url) as response: + artifacts = json.load(response) +except URLError: + artifacts = {'items': []} +artifact_count = len(artifacts['items']) +print(f'Found {artifact_count} artifacts') + +with open(os.environ['GITHUB_OUTPUT'], 'w+') as fd: + fd.write(f'count={artifact_count}\n') + +logs = Path('logs') +logs.mkdir(exist_ok=True) + +found = False +for item in artifacts['items']: + path = item['path'] + if path.startswith('doc/logs/'): + path = Path(path).name + print(f'Downloading {path} from {item["url"]}') + with urlopen(item['url']) as response: + (logs / path).write_bytes(response.read()) + found = True + +if not found: + print('ERROR: Did not find any artifact logs!') diff --git a/.coveragerc b/.coveragerc index cd41bdd06a92..f8d90f93e600 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,7 +7,10 @@ omit = matplotlib/_version.py [report] exclude_lines = + pragma: no cover raise NotImplemented def __str__ def __repr__ if __name__ == .__main__.: + if TYPE_CHECKING: + if typing.TYPE_CHECKING: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..ddec2754d03a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,38 @@ +{ + "hostRequirements": { + "memory": "8gb", + "cpus": 4 + }, + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "ghcr.io/devcontainers/features/desktop-lite:1": {}, + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": "inkscape,ffmpeg,dvipng,lmodern,cm-super,texlive-latex-base,texlive-latex-extra,texlive-fonts-recommended,texlive-latex-recommended,texlive-pictures,texlive-xetex,fonts-wqy-zenhei,graphviz,fonts-crosextra-carlito,fonts-freefont-otf,fonts-comic-neue,fonts-noto-cjk,optipng" + } + }, + "onCreateCommand": ".devcontainer/setup.sh", + "postCreateCommand": "", + "forwardPorts": [6080], + "portsAttributes": { + "6080": { + "label": "desktop" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "yy0931.mplstyle", + "eamodio.gitlens", + "ms-vscode.live-server" + ], + "settings": {} + }, + "codespaces": { + "openFiles": [ + "README.md", + "doc/devel/codespaces.md" + ] + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 000000000000..88da5baf69e2 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +"${SHELL}" <(curl -Ls micro.mamba.pm/install.sh) < /dev/null + +conda init --all +micromamba shell init -s bash +micromamba env create -f environment.yml --yes +# Note that `micromamba activate mpl-dev` doesn't work, it must be run by the +# user (same applies to `conda activate`) +echo "envs_dirs: + - /home/codespace/micromamba/envs" > /opt/conda/.condarc diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 20f404db4104..000000000000 --- a/.flake8 +++ /dev/null @@ -1,284 +0,0 @@ -[flake8] -max-line-length = 79 -select = - # flake8 default - C90, E, F, W, - # docstring-convention=numpy - D100, D101, D102, D103, D104, D105, D106, - D200, D201, D202, D204, D205, D206, D207, D208, - D209, D210, D211, D214, D215, - D300, D301, D302, - D400, D401, D403, D404, D405, D406, D407, D408, - D409, D410, D411, D412, D414, - # matplotlib-specific extra pydocstyle errors - D213, -ignore = - # flake8 default - E121,E123,E126,E226,E24,E704,W503,W504, - # Additional ignores: - E122, E127, E131, - E265, E266, - E305, E306, - E722, E741, - F841, - # Some new flake8 ignores: - N801, N802, N803, N806, N812, - # pydocstyle - D100, D101, D102, D103, D104, D105, D106, D107, - D200, D202, D203, D204, D205, D207, D212, - D301, - D400, D401, D402, D403, D404, D413, - -exclude = - .git - build - doc/gallery - doc/tutorials - # External files. - versioneer.py - tools/gh_api.py - tools/github_stats.py - .tox - .eggs - -per-file-ignores = - setup.py: E402 - setupext.py: E501 - tests.py: F401 - - tools/subset.py: E221, E251, E261, E302, E501 - - lib/matplotlib/__init__.py: F401 - lib/matplotlib/_cm.py: E202, E203, E302 - lib/matplotlib/_mathtext_data.py: E203, E261 - lib/matplotlib/animation.py: F401 - lib/matplotlib/axes/__init__.py: F401, F403 - lib/matplotlib/axes/_axes.py: F401 - lib/matplotlib/backends/backend_*.py: F401 - lib/matplotlib/backends/qt_editor/formlayout.py: F401, F403 - lib/matplotlib/cbook/__init__.py: F401 - lib/matplotlib/font_manager.py: E221, E251, E501 - lib/matplotlib/image.py: F401, F403 - lib/matplotlib/lines.py: F401 - lib/matplotlib/mathtext.py: E221, E251 - lib/matplotlib/pylab.py: F401, F403 - lib/matplotlib/pyplot.py: F401, F811 - lib/matplotlib/style/__init__.py: F401 - lib/matplotlib/testing/conftest.py: F401 - lib/matplotlib/tests/conftest.py: F401 - lib/matplotlib/tests/test_backend_qt.py: F401 - lib/matplotlib/tests/test_mathtext.py: E501 - lib/matplotlib/text.py: F401 - lib/matplotlib/transforms.py: E201, E202, E203 - lib/matplotlib/tri/__init__.py: F401, F403 - lib/matplotlib/tri/triinterpolate.py: E201, E221 - lib/mpl_toolkits/axes_grid/*: F401, F403 - lib/mpl_toolkits/axes_grid1/__init__.py: F401 - lib/mpl_toolkits/axes_grid1/axes_size.py: E272 - lib/mpl_toolkits/axisartist/__init__.py: F401 - lib/mpl_toolkits/axisartist/angle_helper.py: E221 - lib/mpl_toolkits/axisartist/axes_divider.py: F401 - lib/mpl_toolkits/axisartist/axes_rgb.py: F401 - lib/mpl_toolkits/axisartist/axislines.py: F401 - lib/mpl_toolkits/mplot3d/__init__.py: F401 - lib/mpl_toolkits/tests/conftest.py: F401 - lib/pylab.py: F401, F403 - - doc/conf.py: E402, E501 - tutorials/advanced/path_tutorial.py: E402 - tutorials/advanced/patheffects_guide.py: E402 - tutorials/advanced/transforms_tutorial.py: E402, E501 - tutorials/colors/colormaps.py: E501 - tutorials/colors/colors.py: E402 - tutorials/colors/colormap-manipulation.py: E402 - tutorials/intermediate/artists.py: E402 - tutorials/intermediate/constrainedlayout_guide.py: E402 - tutorials/intermediate/gridspec.py: E402 - tutorials/intermediate/legend_guide.py: E402 - tutorials/intermediate/tight_layout_guide.py: E402 - tutorials/introductory/customizing.py: E501 - tutorials/introductory/images.py: E402, E501 - tutorials/introductory/pyplot.py: E402, E501 - tutorials/introductory/sample_plots.py: E501 - tutorials/introductory/usage.py: E501 - tutorials/text/annotations.py: E501 - tutorials/text/text_intro.py: E402 - tutorials/text/text_props.py: E501 - tutorials/text/usetex.py: E501 - tutorials/toolkits/axes_grid.py: E501 - tutorials/toolkits/axisartist.py: E501 - - examples/animation/frame_grabbing_sgskip.py: E402 - examples/axes_grid1/inset_locator_demo.py: E402 - examples/axes_grid1/scatter_hist_locatable_axes.py: E402 - examples/axisartist/demo_curvelinear_grid.py: E402 - examples/color/color_by_yvalue.py: E402 - examples/color/color_cycle_default.py: E402 - examples/color/color_cycler.py: E402 - examples/color/color_demo.py: E402 - examples/color/colorbar_basics.py: E402 - examples/color/colormap_reference.py: E402 - examples/color/custom_cmap.py: E402 - examples/color/named_colors.py: E402 - examples/images_contours_and_fields/affine_image.py: E402 - examples/images_contours_and_fields/barb_demo.py: E402 - examples/images_contours_and_fields/barcode_demo.py: E402 - examples/images_contours_and_fields/contour_corner_mask.py: E402 - examples/images_contours_and_fields/contour_demo.py: E402 - examples/images_contours_and_fields/contour_image.py: E402 - examples/images_contours_and_fields/contourf_demo.py: E402 - examples/images_contours_and_fields/contourf_hatching.py: E402 - examples/images_contours_and_fields/contourf_log.py: E402 - examples/images_contours_and_fields/demo_bboximage.py: E402 - examples/images_contours_and_fields/image_antialiasing.py: E402 - examples/images_contours_and_fields/image_clip_path.py: E402 - examples/images_contours_and_fields/image_demo.py: E402 - examples/images_contours_and_fields/image_masked.py: E402 - examples/images_contours_and_fields/image_transparency_blend.py: E402 - examples/images_contours_and_fields/image_zcoord.py: E402 - examples/images_contours_and_fields/interpolation_methods.py: E402 - examples/images_contours_and_fields/irregulardatagrid.py: E402 - examples/images_contours_and_fields/layer_images.py: E402 - examples/images_contours_and_fields/matshow.py: E402 - examples/images_contours_and_fields/multi_image.py: E402 - examples/images_contours_and_fields/pcolor_demo.py: E402 - examples/images_contours_and_fields/plot_streamplot.py: E402 - examples/images_contours_and_fields/quadmesh_demo.py: E402 - examples/images_contours_and_fields/quiver_demo.py: E402 - examples/images_contours_and_fields/quiver_simple_demo.py: E402 - examples/images_contours_and_fields/shading_example.py: E402 - examples/images_contours_and_fields/specgram_demo.py: E402 - examples/images_contours_and_fields/spy_demos.py: E402 - examples/images_contours_and_fields/tricontour_demo.py: E201, E402 - examples/images_contours_and_fields/tricontour_smooth_delaunay.py: E402 - examples/images_contours_and_fields/tricontour_smooth_user.py: E402 - examples/images_contours_and_fields/trigradient_demo.py: E402 - examples/images_contours_and_fields/triinterp_demo.py: E402 - examples/images_contours_and_fields/tripcolor_demo.py: E201, E402 - examples/images_contours_and_fields/triplot_demo.py: E201, E402 - examples/images_contours_and_fields/watermark_image.py: E402 - examples/lines_bars_and_markers/curve_error_band.py: E402 - examples/lines_bars_and_markers/errorbar_limits_simple.py: E402 - examples/lines_bars_and_markers/fill.py: E402 - examples/lines_bars_and_markers/fill_between_demo.py: E402 - examples/lines_bars_and_markers/filled_step.py: E402 - examples/lines_bars_and_markers/horizontal_barchart_distribution.py: E402 - examples/lines_bars_and_markers/joinstyle.py: E402 - examples/lines_bars_and_markers/scatter_hist.py: E402 - examples/lines_bars_and_markers/scatter_piecharts.py: E402 - examples/lines_bars_and_markers/scatter_with_legend.py: E402 - examples/lines_bars_and_markers/span_regions.py: E402 - examples/lines_bars_and_markers/stem_plot.py: E402 - examples/lines_bars_and_markers/step_demo.py: E402 - examples/lines_bars_and_markers/timeline.py: E402 - examples/lines_bars_and_markers/xcorr_acorr_demo.py: E402 - examples/misc/agg_buffer.py: E402 - examples/misc/histogram_path.py: E402 - examples/misc/print_stdout_sgskip.py: E402 - examples/misc/svg_filter_line.py: E402 - examples/misc/svg_filter_pie.py: E402 - examples/misc/table_demo.py: E201 - examples/mplot3d/surface3d.py: E402 - examples/pie_and_polar_charts/bar_of_pie.py: E402 - examples/pie_and_polar_charts/nested_pie.py: E402 - examples/pie_and_polar_charts/pie_and_donut_labels.py: E402 - examples/pie_and_polar_charts/pie_demo2.py: E402 - examples/pie_and_polar_charts/pie_features.py: E402 - examples/pie_and_polar_charts/polar_bar.py: E402 - examples/pie_and_polar_charts/polar_demo.py: E402 - examples/pie_and_polar_charts/polar_legend.py: E402 - examples/pie_and_polar_charts/polar_scatter.py: E402 - examples/pyplots/align_ylabels.py: E402 - examples/pyplots/annotate_transform.py: E251, E402 - examples/pyplots/annotation_basic.py: E402 - examples/pyplots/annotation_polar.py: E402 - examples/pyplots/auto_subplots_adjust.py: E302, E402 - examples/pyplots/axline.py: E402 - examples/pyplots/boxplot_demo_pyplot.py: E402 - examples/pyplots/dollar_ticks.py: E402 - examples/pyplots/fig_axes_customize_simple.py: E402 - examples/pyplots/fig_axes_labels_simple.py: E402 - examples/pyplots/fig_x.py: E402 - examples/pyplots/pyplot_formatstr.py: E402 - examples/pyplots/pyplot_mathtext.py: E402 - examples/pyplots/pyplot_scales.py: E402 - examples/pyplots/pyplot_simple.py: E402 - examples/pyplots/pyplot_text.py: E402 - examples/pyplots/pyplot_three.py: E402 - examples/pyplots/pyplot_two_subplots.py: E402 - examples/pyplots/text_commands.py: E402 - examples/pyplots/text_layout.py: E402 - examples/pyplots/whats_new_1_subplot3d.py: E402 - examples/pyplots/whats_new_98_4_fill_between.py: E402 - examples/pyplots/whats_new_98_4_legend.py: E402 - examples/pyplots/whats_new_99_axes_grid.py: E402 - examples/pyplots/whats_new_99_mplot3d.py: E402 - examples/pyplots/whats_new_99_spines.py: E402 - examples/scales/power_norm.py: E402 - examples/scales/scales.py: E402 - examples/shapes_and_collections/artist_reference.py: E402 - examples/shapes_and_collections/collections.py: E402 - examples/shapes_and_collections/compound_path.py: E402 - examples/shapes_and_collections/dolphin.py: E402 - examples/shapes_and_collections/donut.py: E402 - examples/shapes_and_collections/ellipse_collection.py: E402 - examples/shapes_and_collections/ellipse_demo.py: E402 - examples/shapes_and_collections/fancybox_demo.py: E402 - examples/shapes_and_collections/hatch_demo.py: E402 - examples/shapes_and_collections/hatch_style_reference.py: E402 - examples/shapes_and_collections/line_collection.py: E402 - examples/shapes_and_collections/marker_path.py: E402 - examples/shapes_and_collections/patch_collection.py: E402 - examples/shapes_and_collections/path_patch.py: E402 - examples/shapes_and_collections/quad_bezier.py: E402 - examples/shapes_and_collections/scatter.py: E402 - examples/showcase/anatomy.py: E402 - examples/showcase/bachelors_degrees_by_gender.py: E402 - examples/showcase/firefox.py: E501 - examples/specialty_plots/anscombe.py: E402 - examples/specialty_plots/radar_chart.py: E402 - examples/specialty_plots/sankey_basics.py: E402 - examples/specialty_plots/sankey_links.py: E402 - examples/specialty_plots/sankey_rankine.py: E402 - examples/specialty_plots/skewt.py: E402 - examples/style_sheets/bmh.py: E501 - examples/style_sheets/ggplot.py: E501 - examples/style_sheets/plot_solarizedlight2.py: E501 - examples/subplots_axes_and_figures/axes_margins.py: E402 - examples/subplots_axes_and_figures/axes_zoom_effect.py: E402 - examples/subplots_axes_and_figures/custom_figure_class.py: E402 - examples/subplots_axes_and_figures/demo_constrained_layout.py: E402 - examples/subplots_axes_and_figures/demo_tight_layout.py: E402 - examples/subplots_axes_and_figures/figure_size_units.py: E402 - examples/subplots_axes_and_figures/secondary_axis.py: E402 - examples/subplots_axes_and_figures/two_scales.py: E402 - examples/subplots_axes_and_figures/zoom_inset_axes.py: E402 - examples/text_labels_and_annotations/date_index_formatter.py: E402 - examples/text_labels_and_annotations/demo_text_rotation_mode.py: E402 - examples/text_labels_and_annotations/custom_legends.py: E402 - examples/text_labels_and_annotations/fancyarrow_demo.py: E402 - examples/text_labels_and_annotations/font_family_rc_sgskip.py: E402 - examples/text_labels_and_annotations/font_file.py: E402 - examples/text_labels_and_annotations/legend.py: E402 - examples/text_labels_and_annotations/line_with_text.py: E402 - examples/text_labels_and_annotations/mathtext_asarray.py: E402 - examples/text_labels_and_annotations/tex_demo.py: E402 - examples/text_labels_and_annotations/watermark_text.py: E402 - examples/ticks_and_spines/custom_ticker1.py: E402 - examples/ticks_and_spines/date_concise_formatter.py: E402 - examples/ticks_and_spines/major_minor_demo.py: E402 - examples/ticks_and_spines/tick-formatters.py: E402 - examples/ticks_and_spines/tick_labels_from_values.py: E402 - examples/user_interfaces/canvasagg.py: E402 - examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py: E402 - examples/user_interfaces/embedding_in_gtk3_sgskip.py: E402 - examples/user_interfaces/embedding_in_qt_sgskip.py: E402 - examples/user_interfaces/gtk_spreadsheet_sgskip.py: E402 - examples/user_interfaces/mathtext_wx_sgskip.py: E402 - examples/user_interfaces/mpl_with_glade3_sgskip.py: E402 - examples/user_interfaces/pylab_with_gtk_sgskip.py: E302, E402 - examples/user_interfaces/toolmanager_sgskip.py: E402 - examples/userdemo/connectionstyle_demo.py: E402 - examples/userdemo/custom_boxstyle01.py: E402 - examples/userdemo/pgf_preamble_sgskip.py: E402 - examples/widgets/textbox.py: E402 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..611431e707ab --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,17 @@ +# style: end-of-file-fixer pre-commit hook +c1a33a481b9c2df605bcb9bef9c19fe65c3dac21 + +# style: trailing-whitespace pre-commit hook +213061c0804530d04bbbd5c259f10dc8504e5b2b + +# style: check-docstring-first pre-commit hook +046533797725293dfc2a6edb9f536b25f08aa636 + +# chore: fix spelling errors +686c9e5a413e31c46bb049407d5eca285bcab76d + +# chore: pyupgrade --py39-plus +4d306402bb66d6d4c694d8e3e14b91054417070e + +# style: docstring parameter indentation +68daa962de5634753205cba27f21d6edff7be7a2 diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 000000000000..3994ec0a83ea --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes index 8a1657abfab3..a0c2c8627af7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto +*.m diff=objc *.ppm binary *.svg binary *.svg linguist-language=true -lib/matplotlib/_version.py export-subst +.git_archival.txt export-subst diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c4198cdcdf14..cb27bbf19d46 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1 +1 @@ -Please refer to the [developers guide](https://matplotlib.org/devel/index.html). +Please refer to the [contributing guide](https://matplotlib.org/devel/index.html). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 2bef7ab95a56..a474d51d6f64 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ +--- # These are supported funding model platforms -github: [numfocus] +github: [matplotlib, numfocus] custom: https://numfocus.org/donate-to-matplotlib diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dbd568cba01b..000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: Bug Report -about: Report a bug or issue with Matplotlib ---- - - - - -### Bug report - -**Bug summary** - - - -**Code for reproduction** - - - -```python -# Paste your code here -# -# -``` - -**Actual outcome** - - - -``` -# If applicable, paste the console output here -# -# -``` - -**Expected outcome** - - - - -**Matplotlib version** - - * Operating system: - * Matplotlib version: - * Matplotlib backend (`print(matplotlib.get_backend())`): - * Python version: - * Jupyter version (if applicable): - * Other libraries: - - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000000..a19b6d2346e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,87 @@ +--- +name: Bug Report +description: Report a bug or issue with Matplotlib. +title: "[Bug]: " +body: + - type: textarea + id: summary + attributes: + label: Bug summary + description: Describe the bug in 1-2 short sentences + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Code for reproduction + description: >- + If possible, please provide a minimum self-contained example. If you + have used generative AI as an aid see + https://matplotlib.org/devdocs/devel/contribute.html#restrictions-on-generative-ai-usage + placeholder: Paste your code here. This field is automatically formatted as Python code. + render: Python + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual outcome + description: >- + Paste the output produced by the code provided above, e.g. + console output, images/videos produced by the code, any relevant screenshots/screencasts, etc. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected outcome + description: Describe (or provide a visual example of) the expected outcome from the code snippet. + validations: + required: true + - type: textarea + id: details + attributes: + label: Additional information + description: | + - What are the conditions under which this bug happens? input parameters, edge cases, etc? + - Has this worked in earlier versions? + - Do you know why this bug is happening? + - Do you maybe even know a fix? + - type: input + id: operating-system + attributes: + label: Operating system + description: Windows, OS/X, Arch, Debian, Ubuntu, etc. + - type: input + id: matplotlib-version + attributes: + label: Matplotlib Version + description: "From Python prompt: `import matplotlib; print(matplotlib.__version__)`" + validations: + required: true + - type: input + id: matplotlib-backend + attributes: + label: Matplotlib Backend + description: "From Python prompt: `import matplotlib; print(matplotlib.get_backend())`" + - type: input + id: python-version + attributes: + label: Python version + description: "In console: `python --version`" + - type: input + id: jupyter-version + attributes: + label: Jupyter version + description: "In console: `jupyter notebook --version` or `jupyter lab --version`" + - type: dropdown + id: install + attributes: + label: Installation + description: How did you install matplotlib? + options: + - pip + - conda + - Linux package manager + - from source (.tar.gz) + - git checkout diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..dc80f6d7c91d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +# Reference: +# https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser +--- +blank_issues_enabled: true # default +contact_links: + - name: Question/Support/Other + url: https://discourse.matplotlib.org + about: If you have a usage question + - name: Chat with devs + url: https://gitter.im/matplotlib/matplotlib + about: Ask short questions about contributing to Matplotlib diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index ae5a0f341950..000000000000 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Documentation improvement -about: Create a report to help us improve the documentation -labels: Documentation ---- - - - - -### Problem - - - - -### Suggested Improvement - - diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 000000000000..5f7a0d6c7176 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,33 @@ +--- +name: Documentation +description: Create a report to help us improve the documentation +title: "[Doc]: " +labels: [Documentation] +body: + - type: input + id: link + attributes: + label: Documentation Link + description: >- + Link to any documentation or examples that you are referencing. Suggested improvements should be based + on [the development version of the docs](https://matplotlib.org/devdocs/) + placeholder: https://matplotlib.org/devdocs/... + - type: textarea + id: problem + attributes: + label: Problem + description: What is missing, unclear, or wrong in the documentation? + placeholder: | + * I found [...] to be unclear because [...] + * [...] made me think that [...] when really it should be [...] + * There is no example showing how to do [...] + validations: + required: true + - type: textarea + id: improvement + attributes: + label: Suggested improvement + placeholder: | + * This line should be be changed to say [...] + * Include a paragraph explaining [...] + * Add a figure showing [...] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 6ca57f1ce8fa..000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: Feature Request -about: Suggest something to add to Matplotlib -labels: New feature ---- - - - -### Problem - - - -### Proposed Solution - - - -### Additional context and prior art - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000000..e174fb8994aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,27 @@ +--- +name: Feature Request +description: Suggest something to add to Matplotlib! +title: "[ENH]: " +labels: [New feature] +body: + - type: markdown + attributes: + value: >- + Please search the [issues](https://github.com/matplotlib/matplotlib/issues) for relevant feature + requests before creating a new feature request. + - type: textarea + id: problem + attributes: + label: Problem + description: Briefly describe the problem this feature will solve. (2-4 sentences) + placeholder: | + * I'm always frustrated when [...] because [...] + * I would like it if [...] happened when I [...] because [...] + * Here is a sample image of what I am asking for [...] + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: Describe a way to accomplish the goals of this feature request. diff --git a/.github/ISSUE_TEMPLATE/maintenance.md b/.github/ISSUE_TEMPLATE/maintenance.md deleted file mode 100644 index a72282892d85..000000000000 --- a/.github/ISSUE_TEMPLATE/maintenance.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Maintenance -about: Help improve performance, usability and/or consistency. -labels: Maintenance ---- - -### Describe the issue - -**Summary** - - - -### Proposed fix - diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml new file mode 100644 index 000000000000..6ebb64c0c3e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintenance.yml @@ -0,0 +1,18 @@ +--- +name: Maintenance +description: Help improve performance, usability and/or consistency. +title: "[MNT]: " +labels: [Maintenance] +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Please provide 1-2 short sentences that succinctly describes what could be improved. + validations: + required: true + - type: textarea + id: fix + attributes: + label: Proposed fix + description: Please describe how you think this could be improved. diff --git a/.github/ISSUE_TEMPLATE/questions.md b/.github/ISSUE_TEMPLATE/questions.md deleted file mode 100644 index 72c8327fd2c9..000000000000 --- a/.github/ISSUE_TEMPLATE/questions.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Question/Support/Other -about: If you have a usage question -labels: Community support -# This template is based on the scikit-learn template -# https://github.com/scikit-learn/scikit-learn/blob/8e61534f1087703f476414d8dbd3688282f8eebf/.github/ISSUE_TEMPLATE/usage_question.md ---- - - diff --git a/.github/ISSUE_TEMPLATE/tag_proposal.yml b/.github/ISSUE_TEMPLATE/tag_proposal.yml new file mode 100644 index 000000000000..2bb856d26be6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tag_proposal.yml @@ -0,0 +1,28 @@ +--- +name: Tag Proposal +description: Suggest a new tag or subcategory for the gallery of examples +title: "[Tag]: " +labels: ["Documentation: tags"] +body: + - type: markdown + attributes: + value: >- + Please search the [tag glossary]() for relevant tags before creating a new tag proposal. + - type: textarea + id: need + attributes: + label: Need + description: Briefly describe the need this tag will fill. (1-4 sentences) + placeholder: | + * A tag is needed for examples that share [...] + * Existing tags do not work because [...] + * Current gallery examples that would use this tag include [...] + * Indicate which subcategory this tag falls under, or whether a new subcategory is proposed. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: >- + What should the tag be? All tags are in the format `subcategory: tag` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ccc8eb320e54..bf483dbdd4f4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,44 +1,37 @@ -## PR Summary - -## PR Checklist + - +## PR summary + -- The PR title should summarize the changes, for example "Raise ValueError on - non-numeric input to set_xlim". Avoid non-descriptive titles such as - "Addresses issue #8576". -- The summary should provide at least 1-2 sentences describing the pull request - in detail (Why is this change required? What problem does it solve?) and - link to any relevant issues. +## PR checklist + -- If you are contributing fixes to docstrings, please pay attention to - http://matplotlib.org/devel/documenting_mpl.html#formatting. In particular, - note the difference between using single backquotes, double backquotes, and - asterisks in the markup. +- [ ] "closes #0000" is in the body of the PR description to [link the related issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) +- [ ] new and changed code is [tested](https://matplotlib.org/devdocs/devel/testing.html) +- [ ] *Plotting related* features are demonstrated in an [example](https://matplotlib.org/devdocs/devel/document.html#write-examples-and-tutorials) +- [ ] *New Features* and *API Changes* are noted with a [directive and release note](https://matplotlib.org/devdocs/devel/api_changes.html#announce-changes-deprecations-and-new-features) +- [ ] Documentation complies with [general](https://matplotlib.org/devdocs/devel/document.html#write-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/document.html#write-docstrings) guidelines -We understand that PRs can sometimes be overwhelming, especially as the + +back on your PR.--> diff --git a/.github/codecov.yml b/.github/codecov.yml index 45beaf2d2f7b..00e7612bd1e6 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,10 +1,11 @@ # codecov used to be able to find this anywhere, now we have to manually # tell it where to look +--- comment: false codecov: notify: - require_ci_to_pass: no + require_ci_to_pass: false coverage: status: @@ -13,18 +14,20 @@ coverage: target: 50% if_no_uploads: error if_not_found: success - if_ci_failed: failure + if_ci_failed: error project: default: false library: target: 50% if_no_uploads: error if_not_found: success - if_ci_failed: failure - paths: '!lib/.*/tests/.*' + if_ci_failed: error + paths: + - '!lib/.*/tests/.*' tests: target: auto if_no_uploads: error if_not_found: success - if_ci_failed: failure - paths: 'lib/.*/tests/.*' + if_ci_failed: error + paths: + - 'lib/.*/tests/.*' diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..34902e5236df --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000000..75adfed57f43 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,282 @@ +--- +"CI: Run cibuildwheel": + - changed-files: + - any-glob-to-any-file: ['.github/workflows/cibuildwheel.yml'] +"CI: Run cygwin": + - changed-files: + - any-glob-to-any-file: ['.github/workflows/cygwin.yml'] + +"backend: agg": + - changed-files: + - any-glob-to-any-file: + - 'extern/agg24-svn/' + - 'lib/matplotlib/backends/_backend_agg.pyi' + - 'lib/matplotlib/backends/backend_agg.py*' + - 'src/_backend_agg*' +"backend: cairo": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/backend_*cairo.py*' +"backend: pdf": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/_backend_pdf_ps.py' + - 'lib/matplotlib/backends/backend_pdf.py' +"backend: pgf": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/backend_pgf.py' +"backend: ps": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/_backend_pdf_ps.py' + - 'lib/matplotlib/backends/backend_ps.py' +"backend: svg": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/backend_svg.py' + +"GUI: gtk": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/_backend_gtk.py*' + - 'lib/matplotlib/backends/backend_gtk*' +"GUI: MacOSX": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/*_macosx.py*' + - 'src/_macosx.m' +"GUI: nbagg": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/*_nbagg*.py*' + - 'lib/matplotlib/backends/web_backend/js/nbagg_mpl.js' +"GUI: Qt": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/backend_qt*' + - 'lib/matplotlib/backends/qt_compat.py' + - 'lib/matplotlib/backends/qt_editor/**' +"GUI: tk": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/*backend_tk*' + - 'lib/matplotlib/backends/_tkagg.pyi' + - 'src/_tkagg.cpp' + - 'src/_tkmini.h' +"GUI: webagg": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/*_webagg*.py*' + - 'lib/matplotlib/backends/web_backend/**' +"GUI: wx": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backends/backend_wx*.py*' + +"Documentation: API": + - all: + - changed-files: + - any-glob-to-any-file: + # Also files in lib/**, but we can't be sure those are only documentation. + - 'doc/api/**' + - all-globs-to-all-files: + - '!doc/api/next_api_changes/**' + +"Documentation: build": + - changed-files: + - any-glob-to-any-file: + - 'doc/conf.py' + - 'doc/Makefile' + - 'doc/make.bat' + - 'doc/sphinxext/**' +"Documentation: devdocs": + - changed-files: + - any-glob-to-any-file: + - 'doc/devel/**' +"Documentation: examples": + - changed-files: + - any-glob-to-any-file: + - 'galleries/examples/**' +"Documentation: plot types": + - changed-files: + - any-glob-to-any-file: + - 'galleries/plot_types/**' +"Documentation: tutorials": + - changed-files: + - any-glob-to-any-file: + - 'galleries/tutorials/**' +"Documentation: user guide": + - all: + - changed-files: + - any-glob-to-any-file: + - 'doc/users/**' + - 'galleries/users_explain/**' + - all-globs-to-all-files: + - '!doc/users/next_whats_new/**' + +"topic: animation": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/animation.py*' + - 'lib/matplotlib/_animation_data.py*' +"topic: axes": + - changed-files: + - any-glob-to-any-file: + # Note, axes.py is not included here because it contains many plotting + # methods, for which changes would not be considered on topic. + - 'lib/matplotlib/axes/_base.py*' +"topic: canvas and figure manager": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backend_bases.py*' +"topic: categorical": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/category.py*' +"topic: collections and mappables": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/collections.py*' +"topic: color/color & colormaps": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/colorbar.py*' + - 'lib/matplotlib/colors.py*' + - 'lib/matplotlib/_color_data.py*' + - 'lib/matplotlib/cm.py*' + - 'lib/matplotlib/_cm.py*' + - 'lib/matplotlib/_cm_listed.py*' +"topic: contour": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/contour.py*' + - 'src/_qhull_wrapper.cpp' +"topic: date handling": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/dates.py*' +"topic: figures and subfigures": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/figure.py*' +"topic: geometry manager": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/_constrained_layout.py*' + - 'lib/matplotlib/_layoutgrid.py*' + - 'lib/matplotlib/_tight_bbox.py*' + - 'lib/matplotlib/_tight_layout.py*' + - 'lib/matplotlib/gridspec.py*' + - 'lib/matplotlib/layout_engine.py*' +"topic: hatch": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/hatch.py*' +"topic: images": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/image.py*' + - 'lib/matplotlib/_image.pyi' + - 'src/_image_*' +"topic: legend": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/legend.py*' + - 'lib/matplotlib/legend_handler.py*' +"topic: markers": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/markers.py*' +"topic: mpl_toolkit": + - all: + - changed-files: + - any-glob-to-any-file: + - 'lib/mpl_toolkits/**' + - all-globs-to-all-files: + - '!lib/mpl_toolkits/mplot3d/**' +"topic: mplot3d": + - changed-files: + - any-glob-to-any-file: + - 'lib/mpl_toolkits/mplot3d/**' +"topic: path handling": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/path.py*' + - 'lib/matplotlib/patheffects.py*' + - 'lib/matplotlib/_path.pyi' + - 'src/*path*' +"topic: polar": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/projections/polar.py*' +"topic: pyplot API": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/pyplot.py' + - 'lib/matplotlib/_pylab_helpers.py*' +"topic: rcparams": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/rcsetup.py*' +"topic: sankey": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/sankey.py*' +"topic: sphinx extension": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/sphinxext/**' +"topic: styles": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/mpl-data/stylelib/**' + - 'lib/matplotlib/style/**' +"topic: table": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/table.py*' +"topic: text": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/text.py*' + - 'lib/matplotlib/textpath.py*' +"topic: text/fonts": + - changed-files: + - any-glob-to-any-file: + - 'src/checkdep_freetype2.c' + - 'src/ft2font*' +"topic: text/mathtext": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/mathtext.py*' + - 'lib/matplotlib/_mathtext.py*' + - 'lib/matplotlib/_mathtext_data.py*' +"topic: ticks axis labels": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/axis.py*' + - 'lib/matplotlib/ticker.py*' +"topic: toolbar": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/backend_managers.py*' + - 'lib/matplotlib/backend_tools.py*' +"topic: transforms and scales": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/scale.py*' + - 'lib/matplotlib/transforms.py*' +"topic: tri": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/tri/**' + - 'src/tri/**' +"topic: units and array ducktypes": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/units.py*' +"topic: widgets/UI": + - changed-files: + - any-glob-to-any-file: + - 'lib/matplotlib/widgets.py*' diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9ee39d0c7841..a05d3ccc330c 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -1,79 +1,216 @@ +--- name: Build CI wheels on: + # Save CI by only running this on release branches or tags. push: branches: - - master + - main - v[0-9]+.[0-9]+.x tags: - v* + # Also allow running this action on PRs if requested by applying the + # "Run cibuildwheel" label. + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + +permissions: + contents: read jobs: - build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-18.04, windows-latest, macos-latest] + build_sdist: + if: >- + github.repository == 'matplotlib/matplotlib' && ( + github.event_name == 'push' || + github.event_name == 'pull_request' && ( + ( + github.event.action == 'labeled' && + github.event.label.name == 'CI: Run cibuildwheel' + ) || + contains(github.event.pull_request.labels.*.name, + 'CI: Run cibuildwheel') + ) + ) + name: Build sdist + runs-on: ubuntu-latest + outputs: + SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - - uses: actions/setup-python@v2 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 name: Install Python with: - python-version: '3.7' + python-version: '3.11' - - name: Install cibuildwheel - run: | - python -m pip install cibuildwheel==1.5.5 + # Something changed somewhere that prevents the downloaded-at-build-time + # licenses from being included in built wheels, so pre-download them so + # that they exist before the build and are included. + - name: Pre-download bundled licenses + run: > + curl -Lo LICENSE/LICENSE_QHULL + https://github.com/qhull/qhull/raw/2020.2/COPYING.txt - - name: Copy setup.cfg to configure wheel - run: | - cp setup.cfg.template setup.cfg + - name: Install dependencies + run: python -m pip install build twine - - name: Build wheels for CPython + - name: Build sdist + id: sdist run: | - python -m cibuildwheel --output-dir dist + python -m build --sdist + python ci/export_sdist_name.py + + - name: Check README rendering for PyPI + run: twine check dist/* + + - name: Upload sdist result + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: cibw-sdist + path: dist/*.tar.gz + if-no-files-found: error + + build_wheels: + if: >- + github.repository == 'matplotlib/matplotlib' && ( + github.event_name == 'push' || + github.event_name == 'pull_request' && ( + ( + github.event.action == 'labeled' && + github.event.label.name == 'CI: Run cibuildwheel' + ) || + contains(github.event.pull_request.labels.*.name, + 'CI: Run cibuildwheel') + ) + ) + needs: build_sdist + name: Build wheels on ${{ matrix.os }} for ${{ matrix.cibw_archs }} + runs-on: ${{ matrix.os }} + env: + CIBW_BEFORE_BUILD: >- + rm -rf {package}/build + CIBW_BEFORE_BUILD_WINDOWS: >- + pip install delvewheel && + rm -rf {package}/build + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- + delvewheel repair -w {dest_dir} {wheel} + CIBW_AFTER_BUILD: >- + twine check {wheel} && + python {package}/ci/check_wheel_licenses.py {wheel} + # On Windows, we explicitly request MSVC compilers (as GitHub Action runners have + # MinGW on PATH that would be picked otherwise), switch to a static build for + # runtimes, but use dynamic linking for `VCRUNTIME140.dll`, `VCRUNTIME140_1.dll`, + # and the UCRT. This avoids requiring specific versions of `MSVCP140.dll`, while + # keeping shared state with the rest of the Python process/extensions. + CIBW_CONFIG_SETTINGS_WINDOWS: >- + setup-args="--vsenv" + setup-args="-Db_vscrt=mt" + setup-args="-Dcpp_link_args=['ucrt.lib','vcruntime.lib','/nodefaultlib:libucrt.lib','/nodefaultlib:libvcruntime.lib']" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + CIBW_SKIP: "*-musllinux_aarch64" + CIBW_TEST_COMMAND: >- + python {package}/ci/check_version_number.py + MACOSX_DEPLOYMENT_TARGET: "10.12" + strategy: + matrix: + include: + - os: ubuntu-latest + cibw_archs: "x86_64" + - os: ubuntu-24.04-arm + cibw_archs: "aarch64" + - os: windows-latest + cibw_archs: "auto64" + - os: macos-13 + cibw_archs: "x86_64" + - os: macos-14 + cibw_archs: "arm64" + + steps: + - name: Download sdist + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: cibw-sdist + path: dist/ + + - name: Build wheels for CPython 3.13 + uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: - CIBW_BUILD: "cp3?-*" - CIBW_SKIP: "cp35-* cp36-*" - CIBW_MANYLINUX_X86_64_IMAGE: manylinux1 - CIBW_MANYLINUX_I686_IMAGE: manylinux1 - CIBW_BEFORE_BUILD: pip install numpy==1.15 - MPL_DISABLE_FH4: "yes" - - - name: Build wheels for CPython 3.6 - run: | - python -m cibuildwheel --output-dir dist + CIBW_BUILD: "cp313-* cp313t-*" + CIBW_ENABLE: cpython-freethreading + # No free-threading wheels available for aarch64 on Pillow. + CIBW_TEST_SKIP: "cp313t-manylinux_aarch64" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + + - name: Build wheels for CPython 3.12 + uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: - CIBW_BUILD: "cp36-*" - CIBW_MANYLINUX_X86_64_IMAGE: manylinux1 - CIBW_MANYLINUX_I686_IMAGE: manylinux1 - CIBW_BEFORE_BUILD: pip install numpy==1.15 - MPL_DISABLE_FH4: "yes" - if: > - startsWith(github.ref, 'refs/heads/v3.3') || - startsWith(github.ref, 'refs/tags/v3.3') + CIBW_BUILD: "cp312-*" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + + - name: Build wheels for CPython 3.11 + uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} + env: + CIBW_BUILD: "cp311-*" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + - name: Build wheels for PyPy - run: | - python -m cibuildwheel --output-dir dist + uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: - CIBW_BUILD: "pp3?-*" - CIBW_BEFORE_BUILD: pip install numpy==1.15 - if: > - runner.os != 'Windows' && ( - startsWith(github.ref, 'refs/heads/v3.3') || - startsWith(github.ref, 'refs/tags/v3.3') ) - - - name: Validate that LICENSE files are included in wheels - run: | - python ./ci/check_wheel_licenses.py + CIBW_BUILD: "pp311-*" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + CIBW_ENABLE: pypy + # No wheels available for Pillow with pp311 yet. + CIBW_TEST_SKIP: "pp311*" + if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: cibw-wheels-${{ runner.os }}-${{ matrix.cibw_archs }} + path: ./wheelhouse/*.whl + if-no-files-found: error + + publish: + if: github.repository == 'matplotlib/matplotlib' && github.event_name == 'push' && github.ref_type == 'tag' + name: Upload release to PyPI + needs: [build_sdist, build_wheels] + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + attestations: write + contents: read + steps: + - name: Download packages + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - name: wheels - path: ./dist/*.whl + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Print out packages + run: ls dist + + - name: Generate artifact attestation for sdist and wheel + uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + with: + subject-path: dist/matplotlib-* + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index f11792486640..d61db3f14345 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -1,12 +1,75 @@ +--- +name: "CircleCI artifact handling" on: [status] jobs: circleci_artifacts_redirector_job: + if: "${{ github.event.context == 'ci/circleci: docs-python3' }}" + permissions: + statuses: write runs-on: ubuntu-latest name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: larsoner/circleci-artifacts-redirector-action@master + uses: + scientific-python/circleci-artifacts-redirector-action@7eafdb60666f57706a5525a2f5eb76224dc8779b # v1.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + api-token: ${{ secrets.CIRCLECI_TOKEN }} artifact-path: 0/doc/build/html/index.html - circleci-jobs: docs-python36,docs-python37,docs-python38 + circleci-jobs: docs-python3 + job-title: View the built docs + + post_warnings_as_review: + if: "${{ github.event.context == 'ci/circleci: docs-python3' }}" + permissions: + contents: read + checks: write + pull-requests: write + runs-on: ubuntu-latest + name: Post warnings/errors as review + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Fetch result artifacts + id: fetch-artifacts + env: + target_url: "${{ github.event.target_url }}" + run: | + python .circleci/fetch_doc_logs.py "${target_url}" + + - name: Set up reviewdog + if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" + uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.2 + with: + reviewdog_version: latest + + - name: Post review + if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REVIEWDOG_SKIP_DOGHOUSE: "true" + CI_COMMIT: ${{ github.event.sha }} + CI_REPO_OWNER: ${{ github.event.repository.owner.login }} + CI_REPO_NAME: ${{ github.event.repository.name }} + run: | + # The 'status' event does not contain information in the way that + # reviewdog expects, so we unset those so it reads from the + # environment variables we set above. + unset GITHUB_ACTIONS GITHUB_EVENT_PATH + cat logs/sphinx-errors-warnings.log | \ + reviewdog \ + -efm '%f\:%l: %tEBUG: %m' \ + -efm '%f\:%l: %tNFO: %m' \ + -efm '%f\:%l: %tARNING: %m' \ + -efm '%f\:%l: %tRROR: %m' \ + -efm '%f\:%l: %tEVERE: %m' \ + -efm '%f\:%s: %tARNING: %m' \ + -efm '%f\:%s: %tRROR: %m' \ + -name=sphinx -tee -fail-on-error=false \ + -reporter=github-check -filter-mode=nofilter + cat logs/sphinx-deprecations.log | \ + reviewdog \ + -efm '%f\:%l: %m' \ + -name=examples -tee -reporter=github-check -filter-mode=nofilter diff --git a/.github/workflows/clean_pr.yml b/.github/workflows/clean_pr.yml new file mode 100644 index 000000000000..fc9021c920c0 --- /dev/null +++ b/.github/workflows/clean_pr.yml @@ -0,0 +1,54 @@ +--- +name: PR cleanliness +on: [pull_request] + +permissions: + contents: read + +jobs: + pr_clean: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: '0' + persist-credentials: false + - name: Check for added-and-deleted files + run: | + git fetch --quiet origin "$GITHUB_BASE_REF" + base="$(git merge-base "origin/$GITHUB_BASE_REF" 'HEAD^2')" + ad="$(git log "$base..HEAD^2" --pretty=tformat: --name-status --diff-filter=AD | + cut --fields 2 | sort | uniq --repeated)" + if [[ -n "$ad" ]]; then + printf 'The following files were both added and deleted in this PR:\n%s\n' "$ad" + exit 1 + fi + - name: Check for added-and-modified images + run: | + git fetch --quiet origin "$GITHUB_BASE_REF" + base="$(git merge-base "origin/$GITHUB_BASE_REF" 'HEAD^2')" + am="$(git log "$base..HEAD^2" --pretty=tformat: --name-status --diff-filter=AM | + cut --fields 2 | sort | uniq --repeated | + grep -E '\.(png|pdf|ps|eps|svg)' || true)" + if [[ -n "$am" ]]; then + printf 'The following images were both added and modified in this PR:\n%s\n' "$am" + exit 1 + fi + - name: Check for invalid backports to -doc branches + if: endsWith(github.base_ref, '-doc') + run: | + git fetch --quiet origin "$GITHUB_BASE_REF" + base="$(git merge-base "origin/$GITHUB_BASE_REF" 'HEAD^2')" + lib="$(git log "$base..HEAD^2" --pretty=tformat: --name-status -- lib src | + cut --fields 2 | sort || true)" + if [[ -n "$lib" ]]; then + printf 'Changes to the following files have no effect and should not be backported:\n%s\n' "$lib" + exit 1 + fi + - name: Check for branches opened against main + if: github.ref_name == 'main' + run: | + echo 'PR branch should not be main.' + echo 'See https://matplotlib.org/devdocs/devel/development_workflow.html#make-a-new-feature-branch' + exit 1 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000000..feed44a51146 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,46 @@ +--- +name: "CodeQL" + +on: + push: + branches: [main, v*.x] + pull_request: + # The branches below must be a subset of the branches above + branches: [main] + schedule: + - cron: '45 19 * * 1' + +jobs: + analyze: + if: github.repository == 'matplotlib/matplotlib' + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['c-cpp', 'javascript', 'python'] + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + with: + languages: ${{ matrix.language }} + + - name: Build compiled code + if: matrix.language == 'c-cpp' + run: | + pip install --user --upgrade pip + pip install --user -v . + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml new file mode 100644 index 000000000000..f4a687cd28d7 --- /dev/null +++ b/.github/workflows/conflictcheck.yml @@ -0,0 +1,24 @@ +--- +name: "Maintenance" +on: + # So that PRs touching the same files as the push are updated + push: + # So that the `dirtyLabel` is removed if conflicts are resolve + # We recommend `pull_request_target` so that github secrets are available. + # In `pull_request` we wouldn't be able to change labels of fork PRs + pull_request_target: + types: [synchronize] + +jobs: + main: + if: github.repository == 'matplotlib/matplotlib' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Check if PRs have merge conflicts + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 + with: + dirtyLabel: "status: needs rebase" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + retryMax: 10 diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml new file mode 100644 index 000000000000..4a5b79c0538e --- /dev/null +++ b/.github/workflows/cygwin.yml @@ -0,0 +1,250 @@ +--- +name: Cygwin Tests +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + - v[0-9]+.[0-9]+.[0-9x]+ + tags: + - v* + paths: + - 'src/**' + - '.github/workflows/cygwin.yml' + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + branches-ignore: + - v[0-9]+.[0-9]+.[0-9x]+-doc + paths: + - 'src/**' + - '.github/workflows/cygwin.yml' + schedule: + # 5:47 UTC on Saturdays + - cron: "47 5 * * 6" + workflow_dispatch: + +permissions: + contents: read + +env: + NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test. + OPENBLAS_NUM_THREADS: 1 + PYTHONFAULTHANDLER: 1 + SHELLOPTS: igncr + CYGWIN_NOWINPATH: 1 + CHERE_INVOKING: 1 + TMP: /tmp + TEMP: /tmp + +jobs: + + test-cygwin: + runs-on: windows-latest + name: Python 3.${{ matrix.python-minor-version }} on Cygwin + # Enable these when Cygwin has Python 3.12. + if: >- + github.event_name == 'workflow_dispatch' || + (false && github.event_name == 'schedule') || + ( + false && + github.repository == 'matplotlib/matplotlib' && + !contains(github.event.head_commit.message, '[ci skip]') && + !contains(github.event.head_commit.message, '[skip ci]') && + !contains(github.event.head_commit.message, '[skip github]') && + !contains(github.event.head_commit.message, '[ci doc]') && + ( + github.event_name == 'push' || + github.event_name == 'pull_request' && + ( + ( + github.event.action == 'labeled' && + github.event.label.name == 'CI: Run cygwin' + ) || + contains(github.event.pull_request.labels.*.name, 'CI: Run cygwin') + ) + ) + ) + strategy: + matrix: + python-minor-version: [12] + + steps: + - name: Fix line endings + run: git config --global core.autocrlf input + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: cygwin/cygwin-install-action@f61179d72284ceddc397ed07ddb444d82bf9e559 # v5 + with: + packages: >- + ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel + libgeos-devel libQt5Core-devel pkgconf libglib2.0-devel ninja + noto-cjk-fonts + python3${{ matrix.python-minor-version }}-devel + python3${{ matrix.python-minor-version }}-pip + python3${{ matrix.python-minor-version }}-wheel + python3${{ matrix.python-minor-version }}-setuptools + python3${{ matrix.python-minor-version }}-cycler + python3${{ matrix.python-minor-version }}-dateutil + python3${{ matrix.python-minor-version }}-fonttools + python3${{ matrix.python-minor-version }}-imaging + python3${{ matrix.python-minor-version }}-kiwisolver + python3${{ matrix.python-minor-version }}-numpy + python3${{ matrix.python-minor-version }}-packaging + python3${{ matrix.python-minor-version }}-pyparsing + python3${{ matrix.python-minor-version }}-sip + python3${{ matrix.python-minor-version }}-sphinx + python-cairo-devel + python3${{ matrix.python-minor-version }}-cairo + python3${{ matrix.python-minor-version }}-gi + python3${{ matrix.python-minor-version }}-matplotlib + xorg-server-extra libxcb-icccm4 libxcb-image0 + libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 + libxcb-xinerama0 + make autoconf autoconf2.5 automake automake1.10 libtool m4 + libqhull-devel libfreetype-devel + libjpeg-devel libwebp-devel + + - name: Set runner username to root and id to 0 + shell: bash.exe -eo pipefail -o igncr "{0}" + # GitHub Actions runs everything as Administrator. I don't + # know how to test for this, so set the uid for the CI job so + # that the existing unix root detection will work. + run: /bin/mkpasswd.exe -c | sed -e "s/$(id -u)/0/" >/etc/passwd + + - name: Mark test repo safe + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + git.exe config --global --add safe.directory /proc/cygdrive/d/a/matplotlib/matplotlib + git config --global --add safe.directory /cygdrive/d/a/matplotlib/matplotlib + C:/cygwin/bin/git.exe config --global --add safe.directory D:/a/matplotlib/matplotlib + /usr/bin/git config --global --add safe.directory /cygdrive/d/a/matplotlib/matplotlib + + - name: Use dash for /bin/sh + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + ls -l /bin/sh.exe /bin/bash.exe /bin/dash.exe + /bin/rm -f /bin/sh.exe || exit 1 + cp -sf /bin/dash.exe /bin/sh.exe || exit 1 + ls -l /bin/sh.exe /bin/bash.exe /bin/dash.exe + # FreeType build fails with bash, succeeds with dash + + - name: Cache pip + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: C:\cygwin\home\runneradmin\.cache\pip + key: Cygwin-py3.${{ matrix.python-minor-version }}-pip-${{ hashFiles('requirements/*/*.txt') }} + restore-keys: ${{ matrix.os }}-py3.${{ matrix.python-minor-version }}-pip- + + - name: Cache ccache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: C:\cygwin\home\runneradmin\.ccache + key: Cygwin-py3.${{ matrix.python-minor-version }}-ccache-${{ hashFiles('src/*') }} + restore-keys: Cygwin-py3.${{ matrix.python-minor-version }}-ccache- + + - name: Cache Matplotlib + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + C:\cygwin\home\runneradmin\.cache\matplotlib + !C:\cygwin\home\runneradmin\.cache\matplotlib\tex.cache + !C:\cygwin\home\runneradmin\.cache\matplotlib\test_cache + key: 1-Cygwin-py3.${{ matrix.python-minor-version }}-mpl-${{ github.ref }}-${{ github.sha }} + restore-keys: | + 1-Cygwin-py3.${{ matrix.python-minor-version }}-mpl-${{ github.ref }}- + 1-Cygwin-py3.${{ matrix.python-minor-version }}-mpl- + + - name: Ensure correct Python version + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + /usr/sbin/alternatives --set python /usr/bin/python3.${{ matrix.python-minor-version }} + /usr/sbin/alternatives --set python3 /usr/bin/python3.${{ matrix.python-minor-version }} + + - name: Install Python dependencies + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install kiwisolver 'numpy>=1.22,<1.26' pillow importlib_resources + grep -v -F -e psutil requirements/testing/all.txt >requirements_test.txt + python -m pip install meson-python pybind11 + export PATH="/usr/local/bin:$PATH" + python -m pip install --no-build-isolation 'contourpy>=1.0.1' + python -m pip install --upgrade cycler fonttools \ + packaging pyparsing python-dateutil setuptools-scm \ + -r requirements_test.txt sphinx ipython + python -m pip install --upgrade pycairo 'cairocffi>=0.8' PyGObject && + python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && + echo 'PyGObject is available' || + echo 'PyGObject is not available' + python -m pip install --upgrade pyqt5 && + python -c 'import PyQt5.QtCore' && + echo 'PyQt5 is available' || + echo 'PyQt5 is not available' + python -mpip install --upgrade pyside2 && + python -c 'import PySide2.QtCore' && + echo 'PySide2 is available' || + echo 'PySide2 is not available' + python -m pip uninstall --yes wxpython || echo 'wxPython already uninstalled' + + - name: Install Matplotlib + shell: bash.exe -eo pipefail -o igncr "{0}" + env: + AUTOCONF: /usr/bin/autoconf-2.69 + MAKEFLAGS: dw + run: | + export PATH="/usr/local/bin:$PATH" + ccache -s + git describe + # All dependencies must have been pre-installed, so that the minver + # constraints are held. + python -m pip install --no-deps --no-build-isolation --verbose \ + --config-settings=setup-args="-DrcParams-backend=Agg" \ + --editable .[dev] + + - name: Find DLLs to rebase + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + find {/usr,/usr/local}/{bin,lib/python3.*/site-packages} /usr/lib/lapack . \ + -name \*.exe -o -name \*.dll -print >files_to_rebase.txt + + - name: Rebase DLL list + shell: ash.exe "{0}" + run: "rebase --database --filelist=files_to_rebase.txt" + # Inplace modification of DLLs to assign non-overlapping load + # addresses so fork() works as expected. Ash is used as it + # does not link against any Cygwin DLLs that might need to be + # rebased. + + - name: Check that Matplotlib imports + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + /usr/bin/python -c "import matplotlib as mpl; import matplotlib.pyplot as plt" + + - name: Set ffmpeg path + shell: bash.exe -eo pipefail -o igncr "{0}" + run: | + oldmplrc=$(python -c "from matplotlib import matplotlib_fname as mplrc_file; print(mplrc_file())") + echo "${oldmplrc}" + mkdir -p ~/.matplotlib/ + sed -E \ + -e 's~#animation\.ffmpeg_path:.+~animation.ffmpeg_path: /usr/bin/ffmpeg.exe~' \ + "${oldmplrc}" >~/.matplotlib/matplotlibrc + + - name: Run pytest + shell: bash.exe -eo pipefail -o igncr "{0}" + id: cygwin-run-pytest + run: | + xvfb-run pytest-3.${{ matrix.python-minor-version }} -rfEsXR -n auto \ + --maxfail=50 --timeout=300 --durations=25 \ + --cov-report=term --cov=lib --log-level=DEBUG --color=yes diff --git a/.github/workflows/do_not_merge.yml b/.github/workflows/do_not_merge.yml new file mode 100644 index 000000000000..d8664df9ba9a --- /dev/null +++ b/.github/workflows/do_not_merge.yml @@ -0,0 +1,29 @@ +--- +name: Do Not Merge + +# action to block merging on specific labels +on: + pull_request: + types: [synchronize, opened, reopened, labeled, unlabeled] + +permissions: {} + +jobs: + do-not-merge: + name: Prevent Merging + runs-on: ubuntu-latest + env: + has_tag: >- + ${{contains(github.event.pull_request.labels.*.name, 'status: needs comment/discussion') || + contains(github.event.pull_request.labels.*.name, 'status: waiting for other PR')}} + steps: + - name: Check for label + if: ${{'true' == env.has_tag}} + run: | + echo "This PR cannot be merged because it has one of the following labels: " + echo "* status: needs comment/discussion" + echo "* status: waiting for other PR" + exit 1 + - name: Allow merging + if: ${{'false' == env.has_tag}} + run: exit 0 diff --git a/.github/workflows/good-first-issue.yml b/.github/workflows/good-first-issue.yml new file mode 100644 index 000000000000..cc15717e3351 --- /dev/null +++ b/.github/workflows/good-first-issue.yml @@ -0,0 +1,30 @@ +--- +name: Add comment on good first issues +on: + issues: + types: + - labeled +jobs: + add-comment: + if: github.event.label.name == 'Good first issue' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add comment + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ github.event.issue.number }} + body: | + ### Good first issue - notes for new contributors + + This issue is suited to new contributors because it does not require understanding of the + Matplotlib internals. To get started, please see our [contributing + guide](https://matplotlib.org/stable/devel/index). + + **We do not assign issues**. Check the *Development* section in the sidebar for linked pull + requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please + collaborate on the work by reviewing it rather than duplicating it in a competing PR. + + If something is unclear, please reach out on any of our [communication + channels](https://matplotlib.org/stable/devel/contributing.html#get-connected). diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000000..8e2002353164 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,15 @@ +--- +name: "Pull Request Labeler" +on: + - pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + with: + sync-labels: true diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml new file mode 100644 index 000000000000..92a67236fb9d --- /dev/null +++ b/.github/workflows/mypy-stubtest.yml @@ -0,0 +1,47 @@ +--- +name: Mypy Stubtest +on: [pull_request] + +permissions: + contents: read + +jobs: + mypy-stubtest: + name: mypy-stubtest + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Python 3 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.11' + + - name: Set up reviewdog + uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.9 + + - name: Install tox + run: python -m pip install tox + + - name: Run mypy stubtest + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -o pipefail + tox -e stubtest | \ + sed -e "s!.tox/stubtest/lib/python3.11/site-packages!lib!g" | \ + reviewdog \ + -efm '%Eerror: %m' \ + -efm '%CStub: in file %f:%l' \ + -efm '%CStub: in file %f' \ + -efm '%+CRuntime:%.%#' \ + -efm '%+CMISSING' \ + -efm '%+Cdef %.%#' \ + -efm '%+C<%.%#>' \ + -efm '%Z' \ + -reporter=github-check -tee -name=mypy-stubtest \ + -filter-mode=nofilter diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml new file mode 100644 index 000000000000..393ce2e73472 --- /dev/null +++ b/.github/workflows/nightlies.yml @@ -0,0 +1,65 @@ +--- +name: Upload nightly wheels to Anaconda Cloud + +on: + # Run daily at 1:23 UTC to upload nightly wheels to Anaconda Cloud + schedule: + - cron: '23 1 * * *' + # Run on demand with workflow dispatch + workflow_dispatch: + +permissions: + actions: read + +jobs: + upload_nightly_wheels: + name: Upload nightly wheels to Anaconda Cloud + runs-on: ubuntu-latest + defaults: + run: + # The login shell is necessary for the setup-micromamba setup + # to work in subsequent jobs. + # https://github.com/mamba-org/setup-micromamba#about-login-shells + shell: bash -e -l {0} + if: github.repository_owner == 'matplotlib' + + steps: + # https://github.com/actions/download-artifact/issues/3#issuecomment-1017141067 + - name: Download wheel artifacts from last build on 'main' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PROJECT_REPO="matplotlib/matplotlib" + BRANCH="main" + WORKFLOW_NAME="cibuildwheel.yml" + ARTIFACT_PATTERN="cibw-wheels-*" + + gh run --repo "${PROJECT_REPO}" \ + list --branch "${BRANCH}" \ + --workflow "${WORKFLOW_NAME}" \ + --json event,status,conclusion,databaseId > runs.json + RUN_ID=$( + jq --compact-output \ + '[ + .[] | + # Filter on "push" events to main (merged PRs) ... + select(.event == "push") | + # that have completed successfully ... + select(.status == "completed" and .conclusion == "success") + ] | + # and get ID of latest build of wheels. + sort_by(.databaseId) | reverse | .[0].databaseId' runs.json + ) + gh run --repo "${PROJECT_REPO}" view "${RUN_ID}" + gh run --repo "${PROJECT_REPO}" \ + download "${RUN_ID}" --pattern "${ARTIFACT_PATTERN}" + + mkdir dist + mv ${ARTIFACT_PATTERN}/*.whl dist/ + ls -l dist/ + + - name: Upload wheels to Anaconda Cloud as nightlies + uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2 + with: + artifacts_path: dist + anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} diff --git a/.github/workflows/pr_welcome.yml b/.github/workflows/pr_welcome.yml new file mode 100644 index 000000000000..3bb172ca70e7 --- /dev/null +++ b/.github/workflows/pr_welcome.yml @@ -0,0 +1,37 @@ +--- +name: PR Greetings + +on: [pull_request_target] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/first-interaction@34f15e814fe48ac9312ccf29db4e74fa767cbab7 # v1.3.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: >+ + Thank you for opening your first PR into Matplotlib! + + + If you have not heard from us in a week or so, please leave a new + comment below and that should bring it to our attention. + Most of our reviewers are volunteers and sometimes things fall + through the cracks. + + + You can also join us [on + gitter](https://gitter.im/matplotlib/matplotlib) for real-time + discussion. + + + For details on testing, writing docs, and our review process, + please see [the developer + guide](https://matplotlib.org/devdocs/devel/index.html) + + + We strive to be a welcoming and open project. Please follow our + [Code of + Conduct](https://github.com/matplotlib/matplotlib/blob/main/CODE_OF_CONDUCT.md). diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index ba142edd6aed..bfad14923b82 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -1,46 +1,97 @@ +--- name: Linting on: [pull_request] +permissions: + contents: read + jobs: - flake8: - name: flake8 + pre-commit: + name: precommit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.x" + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + with: + extra_args: --hook-stage manual --all-files + + ruff: + name: ruff runs-on: ubuntu-latest + permissions: + checks: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@v1 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: - python-version: 3.8 + python-version: '3.11' - - name: Install flake8 - run: pip3 install -r requirements/testing/flake8.txt + - name: Install ruff + run: pip3 install ruff - name: Set up reviewdog + uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.9 + + - name: Run ruff + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mkdir -p $HOME/bin - curl -sfL \ - https://github.com/reviewdog/reviewdog/raw/master/install.sh | \ - sh -s -- -b $HOME/bin - echo ::add-path::$HOME/bin + set -o pipefail + ruff check --output-format rdjson | \ + reviewdog -f=rdjson \ + -tee -reporter=github-check -filter-mode nofilter + mypy: + name: mypy + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Python 3 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.11' + + - name: Install mypy + run: pip3 install -r requirements/testing/mypy.txt -r requirements/testing/all.txt + + - name: Set up reviewdog + uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.9 - - name: Run flake8 + - name: Run mypy env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -o pipefail - flake8 --docstring-convention=all | \ - reviewdog -f=pep8 -name=flake8 \ + mypy --config pyproject.toml | \ + reviewdog -f=mypy -name=mypy \ -tee -reporter=github-check -filter-mode nofilter + eslint: name: eslint runs-on: ubuntu-latest + permissions: + checks: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: eslint - uses: reviewdog/action-eslint@v1 + uses: reviewdog/action-eslint@2fee6dd72a5419ff4113f694e2068d2a03bb35dd # v1.33.2 with: filter_mode: nofilter github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale-tidy.yml b/.github/workflows/stale-tidy.yml new file mode 100644 index 000000000000..bc50dc892155 --- /dev/null +++ b/.github/workflows/stale-tidy.yml @@ -0,0 +1,24 @@ +--- +name: 'Close inactive issues' +on: + schedule: + - cron: '30 1 * * 2,4,6' + +jobs: + stale: + if: github.repository == 'matplotlib/matplotlib' + runs-on: ubuntu-latest + steps: + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + operations-per-run: 300 + days-before-stale: -1 + stale-pr-label: "status: inactive" + days-before-pr-close: -1 + stale-issue-label: "status: inactive" + close-issue-label: "status: closed as inactive" + days-before-issue-close: 30 + ascending: true + exempt-issue-labels: "keep" + exempt-pr-labels: "keep,status: orphaned PR" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..b65b44a59e88 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,38 @@ +--- +name: 'Label inactive PRs' +on: + schedule: + - cron: '30 1 * * 1,3,5' + +jobs: + stale: + if: github.repository == 'matplotlib/matplotlib' + runs-on: ubuntu-latest + steps: + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + operations-per-run: 20 + stale-pr-message: >- + Since this Pull Request has not been updated in 60 days, it has been marked "inactive." This does + not mean that it will be closed, though it may be moved to a "Draft" state. This helps maintainers + prioritize their reviewing efforts. You can pick the PR back up anytime - please ping us if you + need a review or guidance to move the PR forward! If you do not plan on continuing the work, please + let us know so that we can either find someone to take the PR over, or close it. + stale-pr-label: "status: inactive" + days-before-pr-stale: 60 + days-before-pr-close: -1 + stale-issue-message: >- + This issue has been marked "inactive" because it has been 365 days since the last comment. If this + issue is still present in recent Matplotlib releases, or the feature request is still wanted, + please leave a comment and this label will be removed. If there are no updates in another 30 days, + this issue will be automatically closed, but you are free to re-open or create a new issue if + needed. We value issue reports, and this procedure is meant to help us resurface and prioritize + issues that have not been addressed yet, not make them disappear. Thanks for your help! + stale-issue-label: "status: inactive" + close-issue-label: "status: closed as inactive" + days-before-issue-stale: 365 + days-before-issue-close: 30 + ascending: true + exempt-issue-labels: "keep" + exempt-pr-labels: "keep,status: orphaned PR" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000000..2a48276707ce --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,436 @@ +--- +name: Tests +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} + cancel-in-progress: true + +on: + push: + branches-ignore: + - auto-backport-of-pr-[0-9]+ + - v[0-9]+.[0-9]+.[0-9x]+-doc + - dependabot/** + pull_request: + branches-ignore: + - v[0-9]+.[0-9]+.[0-9x]+-doc + paths-ignore: + # Skip running tests if changes are only in documentation directories + - 'doc/**' + - 'galleries/**' + schedule: + # 5:47 UTC on Saturdays + - cron: "47 5 * * 6" + workflow_dispatch: + +env: + NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test. + OPENBLAS_NUM_THREADS: 1 + PYTHONFAULTHANDLER: 1 + +jobs: + test: + if: >- + github.event_name == 'workflow_dispatch' || + ( + github.repository == 'matplotlib/matplotlib' && + !contains(github.event.head_commit.message, '[ci skip]') && + !contains(github.event.head_commit.message, '[skip ci]') && + !contains(github.event.head_commit.message, '[skip github]') && + !contains(github.event.head_commit.message, '[ci doc]') + ) + permissions: + contents: read + name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.name-suffix }}" + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + - name-suffix: "(Minimum Versions)" + os: ubuntu-22.04 + python-version: '3.11' + extra-requirements: '-c requirements/testing/minver.txt' + delete-font-cache: true + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - os: ubuntu-22.04 + python-version: '3.11' + CFLAGS: "-fno-lto" # Ensure that disabling LTO works. + extra-requirements: '-r requirements/testing/extra.txt' + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - os: ubuntu-22.04-arm + python-version: '3.12' + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - name-suffix: "(Extra TeX packages)" + os: ubuntu-22.04 + python-version: '3.13' + extra-packages: 'texlive-fonts-extra texlive-lang-cyrillic' + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - name-suffix: "Free-threaded" + os: ubuntu-22.04 + python-version: '3.13t' + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - os: ubuntu-24.04 + python-version: '3.12' + - os: macos-13 # This runner is on Intel chips. + # merge numpy and pandas install in nighties test when this runner is dropped + python-version: '3.11' + - os: macos-14 # This runner is on M1 (arm64) chips. + python-version: '3.12' + # https://github.com/matplotlib/matplotlib/issues/29732 + pygobject-ver: '<3.52.0' + - os: macos-14 # This runner is on M1 (arm64) chips. + python-version: '3.13' + # https://github.com/matplotlib/matplotlib/issues/29732 + pygobject-ver: '<3.52.0' + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Install OS dependencies + run: | + case "${{ runner.os }}" in + Linux) + echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries + sudo apt-get update -yy + sudo apt-get install -yy --no-install-recommends \ + ccache \ + cm-super \ + dvipng \ + fonts-freefont-otf \ + fonts-noto-cjk \ + fonts-wqy-zenhei \ + gdb \ + gir1.2-gtk-3.0 \ + graphviz \ + inkscape \ + language-pack-de \ + lcov \ + libcairo2 \ + libcairo2-dev \ + libffi-dev \ + libgeos-dev \ + libnotify4 \ + libsdl2-2.0-0 \ + libxkbcommon-x11-0 \ + libxcb-cursor0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-randr0 \ + libxcb-render-util0 \ + libxcb-xinerama0 \ + lmodern \ + ninja-build \ + pkg-config \ + qtbase5-dev \ + texlive-fonts-recommended \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-latex-recommended \ + texlive-luatex \ + texlive-pictures \ + texlive-xetex \ + ${{ matrix.extra-packages }} + if [[ "${{ matrix.name-suffix }}" != '(Minimum Versions)' ]]; then + sudo apt-get install -yy --no-install-recommends ffmpeg poppler-utils + fi + if [[ "${{ matrix.os }}" = ubuntu-22.04 || "${{ matrix.os }}" = ubuntu-22.04-arm ]]; then + sudo apt-get install -yy --no-install-recommends \ + gir1.2-gtk-4.0 \ + libgirepository1.0-dev + else # ubuntu-24.04 + sudo apt-get install -yy --no-install-recommends \ + libgirepository-2.0-dev + fi + ;; + macOS) + brew update + # Periodically, Homebrew updates Python and fails to overwrite the + # existing not-managed-by-Homebrew copy without explicitly being told + # to do so. GitHub/Azure continues to avoid fixing their runner images: + # https://github.com/actions/runner-images/issues/9966 + # so force an overwrite even if there are no Python updates. + # We don't even care about Homebrew's Python because we use the one + # from actions/setup-python. + for python_package in $(brew list | grep python@); do + brew unlink ${python_package} + brew link --overwrite ${python_package} + done + # Workaround for https://github.com/actions/runner-images/issues/10984 + brew uninstall --ignore-dependencies --force pkg-config@0.29.2 + brew install ccache ffmpeg ghostscript gobject-introspection gtk4 imagemagick ninja + brew install --cask font-noto-sans-cjk font-noto-sans-cjk-sc inkscape + ;; + esac + + - name: Cache pip + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + if: startsWith(runner.os, 'Linux') + with: + path: ~/.cache/pip + key: ${{ matrix.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements/*/*.txt') }} + restore-keys: | + ${{ matrix.os }}-py${{ matrix.python-version }}-pip- + - name: Cache pip + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + if: startsWith(runner.os, 'macOS') + with: + path: ~/Library/Caches/pip + key: ${{ matrix.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements/*/*.txt') }} + restore-keys: | + ${{ matrix.os }}-py${{ matrix.python-version }}-pip- + - name: Cache ccache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ~/.ccache + key: ${{ matrix.os }}-py${{ matrix.python-version }}-ccache-${{ hashFiles('src/*') }} + restore-keys: | + ${{ matrix.os }}-py${{ matrix.python-version }}-ccache- + - name: Cache Matplotlib + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + ~/.cache/matplotlib + !~/.cache/matplotlib/tex.cache + !~/.cache/matplotlib/test_cache + key: 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} + restore-keys: | + 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- + 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl- + + - name: Install Python dependencies + run: | + # Upgrade pip and setuptools and wheel to get as clean an install as + # possible. + python -m pip install --upgrade pip setuptools wheel + + # Install pre-release versions during our weekly upcoming dependency tests. + if [[ "${{ github.event_name }}" == 'schedule' + && "${{ matrix.name-suffix }}" != '(Minimum Versions)' ]]; then + PRE="--pre" + fi + + # Install dependencies from PyPI. + # Preinstall build requirements to enable no-build-isolation builds. + python -m pip install --upgrade $PRE \ + 'contourpy>=1.0.1' cycler fonttools kiwisolver importlib_resources \ + packaging pillow 'pyparsing!=3.1.0' python-dateutil setuptools-scm \ + 'meson-python>=0.13.1' 'pybind11>=2.13.2' \ + -r requirements/testing/all.txt \ + ${{ matrix.extra-requirements }} + + # Install optional dependencies from PyPI. + # Sphinx is needed to run sphinxext tests + python -m pip install --upgrade sphinx!=6.1.2 + + if [[ "${{ matrix.python-version }}" != '3.13t' ]]; then + # GUI toolkits are pip-installable only for some versions of Python + # so don't fail if we can't install them. Make it easier to check + # whether the install was successful by trying to import the toolkit + # (sometimes, the install appears to be successful but shared + # libraries cannot be loaded at runtime, so an actual import is a + # better check). + python -m pip install --upgrade pycairo 'cairocffi>=0.8' 'PyGObject${{ matrix.pygobject-ver }}' && + ( + python -c 'import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk' && + echo 'PyGObject 4 is available' || echo 'PyGObject 4 is not available' + ) && ( + python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && + echo 'PyGObject 3 is available' || echo 'PyGObject 3 is not available' + ) + + # PyQt5 does not have any wheels for ARM on Linux. + if [[ "${{ matrix.os }}" != 'ubuntu-22.04-arm' ]]; then + python -mpip install --upgrade --only-binary :all: pyqt5 && + python -c 'import PyQt5.QtCore' && + echo 'PyQt5 is available' || + echo 'PyQt5 is not available' + fi + # Even though PySide2 wheels can be installed on Python 3.12+, they are broken and since PySide2 is + # deprecated, they are unlikely to be fixed. For the same deprecation reason, there are no wheels + # on M1 macOS, so don't bother there either. + if [[ "${{ matrix.os }}" != 'macos-14' + && "${{ matrix.python-version }}" != '3.12' && "${{ matrix.python-version }}" != '3.13' ]]; then + python -mpip install --upgrade pyside2 && + python -c 'import PySide2.QtCore' && + echo 'PySide2 is available' || + echo 'PySide2 is not available' + fi + python -mpip install --upgrade --only-binary :all: pyqt6 && + python -c 'import PyQt6.QtCore' && + echo 'PyQt6 is available' || + echo 'PyQt6 is not available' + python -mpip install --upgrade --only-binary :all: pyside6 && + python -c 'import PySide6.QtCore' && + echo 'PySide6 is available' || + echo 'PySide6 is not available' + + python -mpip install --upgrade --only-binary :all: \ + -f "https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }}" \ + wxPython && + python -c 'import wx' && + echo 'wxPython is available' || + echo 'wxPython is not available' + + fi # Skip backends on Python 3.13t. + + - name: Install the nightly dependencies + # Only install the nightly dependencies during the scheduled event + if: github.event_name == 'schedule' && matrix.name-suffix != '(Minimum Versions)' + run: | + python -m pip install pytz tzdata # Must be installed for Pandas. + python -m pip install \ + --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ + --upgrade --only-binary=:all: numpy + # wheels for intel osx is not always available on nightly wheels index, merge this back into + # the above install command when the OSX-13 (intel) runners are dropped. + python -m pip install \ + --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ + --upgrade --only-binary=:all: pandas || true + + + - name: Install Matplotlib + run: | + ccache -s + git describe + + # Set flag in a delayed manner to avoid issues with installing other + # packages + if [[ "${{ runner.os }}" == 'macOS' ]]; then + export CPPFLAGS='-fprofile-instr-generate=default.%m.profraw' + export CPPFLAGS="$CPPFLAGS -fcoverage-mapping" + else + export CPPFLAGS='--coverage -fprofile-abs-path' + fi + + python -m pip install --no-deps --no-build-isolation --verbose \ + --config-settings=setup-args="-DrcParams-backend=Agg" \ + --editable .[dev] + + if [[ "${{ runner.os }}" != 'macOS' ]]; then + unset CPPFLAGS + fi + + - name: Clear font cache + run: | + rm -rf ~/.cache/matplotlib + if: matrix.delete-font-cache + + - name: Run pytest + run: | + if [[ "${{ matrix.python-version }}" == '3.13t' ]]; then + export PYTHON_GIL=0 + fi + pytest -rfEsXR -n auto \ + --maxfail=50 --timeout=300 --durations=25 \ + --cov-report=xml --cov=lib --log-level=DEBUG --color=yes + + - name: Cleanup non-failed image files + if: failure() + run: | + function remove_files() { + local extension=$1 + find ./result_images -type f -name "*-expected*.$extension" | while read file; do + if [[ $file == *"-expected_pdf"* ]]; then + base=${file%-expected_pdf.$extension}_pdf + elif [[ $file == *"-expected_eps"* ]]; then + base=${file%-expected_eps.$extension}_eps + elif [[ $file == *"-expected_svg"* ]]; then + base=${file%-expected_svg.$extension}_svg + else + base=${file%-expected.$extension} + fi + if [[ ! -e "${base}-failed-diff.$extension" ]]; then + if [[ -e "$file" ]]; then + rm "$file" + echo "Removed $file" + fi + if [[ -e "${base}.$extension" ]]; then + rm "${base}.$extension" + echo " Removed ${base}.$extension" + fi + fi + done + } + + remove_files "png"; remove_files "svg"; remove_files "pdf"; remove_files "eps"; + + if [ "$(find ./result_images -mindepth 1 -type d)" ]; then + find ./result_images/* -type d -empty -delete + fi + + - name: Filter C coverage + if: ${{ !cancelled() && github.event_name != 'schedule' }} + run: | + if [[ "${{ runner.os }}" != 'macOS' ]]; then + LCOV_IGNORE_ERRORS=',' # do not ignore any lcov errors by default + if [[ "${{ matrix.os }}" = ubuntu-24.04 ]]; then + # filter mismatch and unused-entity errors detected by lcov 2.x + LCOV_IGNORE_ERRORS='mismatch,unused' + fi + lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ + --capture --directory . --output-file coverage.info + lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ + --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/lib/'*' + lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ + --list coverage.info + find . -name '*.gc*' -delete + else + xcrun llvm-profdata merge -sparse default.*.profraw \ + -o default.profdata + xcrun llvm-cov export -format="lcov" build/*/src/*.so \ + -instr-profile default.profdata > info.lcov + fi + - name: Upload code coverage + if: ${{ !cancelled() && github.event_name != 'schedule' }} + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + with: + name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}" + token: ${{ secrets.CODECOV_TOKEN }} + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: failure() + with: + name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }} result images" + path: ./result_images + + # Separate dependent job to only upload one issue from the matrix of jobs + create-issue: + if: ${{ failure() && github.event_name == 'schedule' }} + needs: [test] + permissions: + issues: write + runs-on: ubuntu-latest + name: "Create issue on failure" + + steps: + - name: Create issue on failure + uses: imjohnbo/issue-bot@572eed14422c4d6ca37e870f97e7da209422f5bd # v3.4.4 + with: + title: "[TST] Upcoming dependency test failures" + body: | + The weekly build with nightly wheels from numpy and pandas + has failed. Check the logs for any updates that need to be + made in matplotlib. + https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} + + pinned: false + close-previous: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml deleted file mode 100644 index c8c280e92029..000000000000 --- a/.github/workflows/wheels.yml +++ /dev/null @@ -1,34 +0,0 @@ -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' -jobs: - trigger_wheel_builds: - runs-on: ubuntu-latest - name: Trigger macOS and manylinux wheel builds - if: github.repository == 'matplotlib/matplotlib' - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - repository: MacPython/matplotlib-wheels - ssh-key: ${{ secrets.WHEEL_DEPLOY_KEY }} - fetch-depth: 0 - - name: Prepare build commit - id: commit - shell: bash - run: | - TAG="${GITHUB_REF#refs/tags/}" - BRANCH="$(echo ${TAG} | sed 's/^v\([0-9]\+\.[0-9]\+\)\.[0-9]\+.*$/build-\1.x/')" - echo "${BRANCH}" - echo "##[set-output name=branch;]${BRANCH}" - git branch ${BRANCH} master || true - git checkout ${BRANCH} - sed -i -e "s/\(- BUILD_COMMIT=\).\+/\1${TAG}/g" .travis.yml - git add .travis.yml - git config --global user.name 'Matplotlib Actions Bot' - git config --global user.email 'matplotlib@users.noreply.github.com' - git commit -m "REL: $TAG" - - name: Push to matplotlib-wheels - run: | - git push origin ${{ steps.commit.outputs.branch }} diff --git a/.gitignore b/.gitignore index ee4a0eb7aef6..1d30ba69aeaa 100644 --- a/.gitignore +++ b/.gitignore @@ -29,10 +29,11 @@ # Python files # ################ -# setup.py working directory +# meson-python working directory build +.mesonpy* -# setup.py dist directory +# meson-python/build frontend dist directory dist # Egg metadata *.egg-info @@ -41,7 +42,9 @@ dist pip-wheel-metadata/* # tox testing tool .tox -setup.cfg +# build subproject files +subprojects/*/ +!subprojects/packagefiles/ # OS generated files # ###################### @@ -54,10 +57,8 @@ Thumbs.db # Things specific to this project # ################################### -lib/matplotlib/mpl-data/matplotlib.conf -lib/matplotlib/mpl-data/matplotlibrc -tutorials/intermediate/CL01.png -tutorials/intermediate/CL02.png +galleries/tutorials/intermediate/CL01.png +galleries/tutorials/intermediate/CL02.png # Documentation generated files # ################################# @@ -67,19 +68,23 @@ doc/api/_as_gen # autogenerated by sphinx-gallery doc/examples doc/gallery -doc/tutorials doc/modules +doc/plot_types doc/pyplots/tex_demo.png +doc/tutorials +doc/users/explain lib/dateutil -examples/*/*.eps -examples/*/*.pdf -examples/*/*.png -examples/*/*.svg -examples/*/*.svgz -examples/tests/* -!examples/tests/backend_driver_sgskip.py +galleries/examples/*/*.bmp +galleries/examples/*/*.eps +galleries/examples/*/*.pdf +galleries/examples/*/*.png +galleries/examples/*/*.svg +galleries/examples/*/*.svgz result_images doc/_static/constrained_layout*.png +doc/.mpl_skip_subdirs.yaml +doc/_tags +sg_execution_times.rst # Nose/Pytest generated files # ############################### @@ -87,8 +92,10 @@ doc/_static/constrained_layout*.png .cache/ .coverage .coverage.* +*.py,cover cover/ .noseids +__pycache__ # Conda files # ############### @@ -96,6 +103,16 @@ __conda_version__.txt lib/png.lib lib/z.lib +# Environments # +################ +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + # Jupyter files # ################# @@ -105,3 +122,5 @@ lib/z.lib ######################### lib/matplotlib/backends/web_backend/node_modules/ lib/matplotlib/backends/web_backend/package-lock.json + +LICENSE/LICENSE_QHULL diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index b48a4f003db0..000000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,14 +0,0 @@ -path_classifiers: - examples: - # Exclude example files from analysis by tagging them. - # We intentionally use multiple imports and unused variables in the - # examples to make them more explicit. These generate a lot of alerts. - # Since lgtm does not yet support suppressing selected alerts per - # file, we currently deactivate analysis completely here. May be - # reactivated when we can selectively suppress - # - py/import-and-import-from - # - py/repeated-import - # - py/unused-local-variable - # - py/multiple-definition - - examples - - tutorials diff --git a/.matplotlib-repo b/.matplotlib-repo new file mode 100644 index 000000000000..0b1d699bcdb1 --- /dev/null +++ b/.matplotlib-repo @@ -0,0 +1,3 @@ +The existence of this file signals that the code is a matplotlib source repo +and not an installed version. We use this in __init__.py for gating version +detection. diff --git a/.meeseeksdev.yml b/.meeseeksdev.yml index 8bfd1b8e4257..f9d44d44cfdf 100644 --- a/.meeseeksdev.yml +++ b/.meeseeksdev.yml @@ -1,3 +1,4 @@ +--- users: Carreau: can: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000000..86a9a0f45440 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,121 @@ +--- +ci: + autofix_prs: false + autoupdate_schedule: 'quarterly' +exclude: | + (?x)^( + extern| + LICENSE| + lib/matplotlib/mpl-data| + doc/devel/gitwash| + doc/users/prev| + doc/api/prev| + lib/matplotlib/tests/data/tinypages + ) +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + - id: check-docstring-first + exclude: lib/matplotlib/typing.py # docstring used for attribute flagged by check + - id: end-of-file-fixer + exclude_types: [svg] + - id: mixed-line-ending + - id: name-tests-test + args: ["--pytest-test-first"] + - id: no-commit-to-branch # Default is master and main. + - id: trailing-whitespace + exclude_types: [svg] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.15.0 + hooks: + - id: mypy + additional_dependencies: + - pandas-stubs + - types-pillow + - types-python-dateutil + - types-psutil + - types-docutils + - types-PyYAML + args: ["--config-file=pyproject.toml", "lib/matplotlib"] + files: lib/matplotlib # Only run when files in lib/matplotlib are changed. + pass_filenames: false + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.5 + hooks: + # Run the linter. + - id: ruff + args: [--fix, --show-fixes] + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + files: ^.*\.(py|c|cpp|h|m|md|rst|yml)$ + args: + - "--ignore-words" + - "ci/codespell-ignore-words.txt" + - "--skip" + - "doc/project/credits.rst" + - repo: https://github.com/pycqa/isort + rev: 6.0.1 + hooks: + - id: isort + name: isort (python) + files: ^galleries/tutorials/|^galleries/examples/|^galleries/plot_types/ + - repo: https://github.com/rstcheck/rstcheck + rev: v6.2.4 + hooks: + - id: rstcheck + additional_dependencies: + - sphinx>=1.8.1 + - tomli + - repo: https://github.com/adrienverge/yamllint + rev: v1.37.0 + hooks: + - id: yamllint + args: ["--strict", "--config-file=.yamllint.yml"] + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.33.0 + hooks: + # TODO: Re-enable this when https://github.com/microsoft/azure-pipelines-vscode/issues/567 is fixed. + # - id: check-azure-pipelines + - id: check-dependabot + - id: check-github-workflows + # NOTE: If any of the below schema files need to be changed, be sure to + # update the `ci/vendor_schemas.py` script. + - id: check-jsonschema + name: "Validate AppVeyor config" + files: ^\.appveyor\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/appveyor.json"] + - id: check-jsonschema + name: "Validate CircleCI config" + files: ^\.circleci/config\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/circleciconfig.json"] + - id: check-jsonschema + name: "Validate GitHub funding file" + files: ^\.github/FUNDING\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/github-funding.json"] + - id: check-jsonschema + name: "Validate GitHub issue config" + files: ^\.github/ISSUE_TEMPLATE/config\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/github-issue-config.json"] + - id: check-jsonschema + name: "Validate GitHub issue templates" + files: ^\.github/ISSUE_TEMPLATE/.*\.yml$ + exclude: ^\.github/ISSUE_TEMPLATE/config\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/github-issue-forms.json"] + - id: check-jsonschema + name: "Validate CodeCov config" + files: ^\.github/codecov\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/codecov.json"] + - id: check-jsonschema + name: "Validate GitHub labeler config" + files: ^\.github/labeler\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/pull-request-labeler-5.json"] + - id: check-jsonschema + name: "Validate Conda environment file" + files: ^environment\.yml$ + args: ["--verbose", "--schemafile", "ci/schemas/conda-environment.json"] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 613c80c15b9a..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,199 +0,0 @@ -language: python - -dist: xenial - -services: - - xvfb - -branches: - except: - - /^auto-backport-of-pr-\d*/ - - /^v\d+\.\d+\.[\dx]+-doc$/ - -cache: - pip: true - directories: - - $HOME/.ccache # https://github.com/travis-ci/travis-ci/issues/5853 - - $HOME/.cache/matplotlib - -addons: - artifacts: - paths: - - result_images.tar.bz2 - apt: - packages: - - cm-super - - dvipng - - ffmpeg - - gdb - - gir1.2-gtk-3.0 - - graphviz - - inkscape - - lcov - - libcairo2 - - libcairo2-dev - - libffi-dev - - libgeos-dev - - libgirepository1.0-dev - - libsdl2-2.0-0 - - libxkbcommon-x11-0 - - libxcb-icccm4 - - libxcb-image0 - - libxcb-keysyms1 - - libxcb-randr0 - - libxcb-render-util0 - - libxcb-xinerama0 - - lmodern - - fonts-freefont-otf - - texlive-pictures - - pkg-config - - qtbase5-dev - - texlive-fonts-recommended - - texlive-latex-base - - texlive-latex-extra - - texlive-latex-recommended - - texlive-luatex - - texlive-xetex - - ttf-wqy-zenhei - -env: - global: - - ARTIFACTS_AWS_REGION=us-east-1 - - ARTIFACTS_BUCKET=matplotlib-test-results - - secure: RgJI7BBL8aX5FTOQe7xiXqWHMxWokd6GNUWp1NUV2mRLXPb9dI0RXqZt3UJwKTAzf1z/OtlHDmEkBoTVK81E9iUxK5npwyyjhJ8yTJmwfQtQF2n51Q1Ww9p+XSLORrOzZc7kAo6Kw6FIXN1pfctgYq2bQkrwJPRx/oPR8f6hcbY= - - secure: E7OCdqhZ+PlwJcn+Hd6ns9TDJgEUXiUNEI0wu7xjxB2vBRRIKtZMbuaZjd+iKDqCKuVOJKu0ClBUYxmgmpLicTwi34CfTUYt6D4uhrU+8hBBOn1iiK51cl/aBvlUUrqaRLVhukNEBGZcyqAjXSA/Qsnp2iELEmAfOUa92ZYo1sk= - - secure: dfjNqGKzQG5bu3FnDNwLG8H/C4QoieFo4PfFmZPdM2RY7WIzukwKFNT6kiDfOrpwt+2bR7FhzjOGlDECGtlGOtYPN8XuXGjhcP4a4IfakdbDfF+D3NPIpf5VlE6776k0VpvcZBTMYJKNFIMc7QPkOwjvNJ2aXyfe3hBuGlKJzQU= - - EXTRAREQS= # Location of an extra pip requirements file. - - PINNEDVERS= # Location of a pip constraints file. - # Variables controlling the test run. - - DELETE_FONT_CACHE= - - NO_AT_BRIDGE=1 # Necessary for GTK3 interactive test. - - OPENBLAS_NUM_THREADS=1 - - PYTHONFAULTHANDLER=1 - -matrix: - include: - - python: 3.7 - env: - - PINNEDVERS='-c requirements/testing/minver.txt' - - DELETE_FONT_CACHE=1 - - python: 3.7 - env: - - EXTRAREQS='-r requirements/testing/travis_extra.txt' - - python: 3.8 - env: - - EXTRAREQS='-r requirements/testing/travis_extra.txt' - - python: "nightly" - env: - - PRE=--pre - - os: osx - osx_image: xcode9 - language: generic # https://github.com/travis-ci/travis-ci/issues/2312 - only: master - cache: - # As for now travis caches only "$HOME/.cache/pip" - # https://docs.travis-ci.com/user/caching/#pip-cache - pip: false - directories: - - $HOME/Library/Caches/pip - # `cache` does not support `env`-like `global` so copy-paste from top - - $HOME/.ccache # https://github.com/travis-ci/travis-ci/issues/5853 - - $HOME/.cache/matplotlib - allow_failures: - - python: "nightly" - -before_install: -- | - # Install OS dependencies and set up ccache - case "$TRAVIS_OS_NAME" in - linux) - export PATH=/usr/lib/ccache:$PATH - ;; - osx) - ci/osx-deps - ;; - esac - -install: - - | - # Setup environment. - ccache -s - git describe - # Upgrade pip and setuptools and wheel to get as clean an install as possible. - python -mpip install --upgrade pip setuptools wheel - - | - # Install dependencies from PyPI. - python -mpip install --upgrade $PRE -r requirements/testing/travis_all.txt $EXTRAREQS $PINNEDVERS - - | - # Install optional dependencies from PyPI. - # Sphinx is needed to run sphinxext tests - python -mpip install --upgrade sphinx - # GUI toolkits are pip-installable only for some versions of Python so - # don't fail if we can't install them. Make it easier to check whether the - # install was successful by trying to import the toolkit (sometimes, the - # install appears to be successful but shared libraries cannot be loaded at - # runtime, so an actual import is a better check). - python -mpip install --upgrade pycairo cairocffi>=0.8 - python -mpip install --upgrade PyGObject && - python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && - echo 'PyGObject is available' || - echo 'PyGObject is not available' - python -mpip install --upgrade pyqt5 && - python -c 'import PyQt5.QtCore' && - echo 'PyQt5 is available' || - echo 'PyQt5 is not available' - python -mpip install --upgrade pyside2 && - python -c 'import PySide2.QtCore' && - echo 'PySide2 is available' || - echo 'PySide2 is not available' - python -mpip install --upgrade \ - -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 \ - wxPython && - python -c 'import wx' && - echo 'wxPython is available' || - echo 'wxPython is not available' - - # Set flag in a delayed manner to avoid issues with installing other packages - - | - if [[ $TRAVIS_OS_NAME != 'osx' ]]; then - export CPPFLAGS=--coverage - fi - - | - python -mpip install -e . # Install Matplotlib. - - | - if [[ $TRAVIS_OS_NAME != 'osx' ]]; then - unset CPPFLAGS - fi - -before_script: | - if [[ $DELETE_FONT_CACHE == 1 ]]; then - rm -rf ~/.cache/matplotlib - fi - -script: - # Each script we want to run need to go in its own section and the program - # you want to fail travis needs to be the last thing called. - - | - # The number of processes is hardcoded (-n2), because using too many - # causes the Travis VM to run out of memory (since so many copies of - # inkscape and ghostscript are running at the same time). - python -mpytest -raR --maxfail=50 --timeout=300 --durations=25 --cov-report= --cov=lib -n2 --log-level=DEBUG - -before_cache: | - rm -rf $HOME/.cache/matplotlib/tex.cache - rm -rf $HOME/.cache/matplotlib/test_cache - -after_failure: | - if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]]; then - tar cjf result_images.tar.bz2 result_images - echo 'See "Uploading Artifacts" near the end of the log for the download URL' - else - echo "The result images will only be uploaded if they are on the matplotlib/matplotlib repo - this is for security reasons to prevent arbitrary PRs echoing security details." - fi - -after_success: - - lcov --capture --directory . --output-file coverage.info - - lcov --remove coverage.info --output-file coverage.info '/usr/*' '/home/travis/build/matplotlib/matplotlib/build/*' '/home/travis/build/matplotlib/matplotlib/extern/*' # filter system-files - - lcov --list coverage.info - # Uploading to CodeCov but excluding gcov reports - - bash <(curl -s https://codecov.io/bash) -f "!*.gcov" -X gcov -e TRAVIS_PYTHON_VERSION || echo "Codecov did not collect coverage reports" diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 000000000000..2be81b28c7fb --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,9 @@ +--- +extends: default + +rules: + line-length: + max: 120 + allow-non-breakable-words: true + truthy: + check-keys: false diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 000000000000..f9c78873bce3 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,14 @@ +@Article{Hunter:2007, + Author = {Hunter, J. D.}, + Title = {Matplotlib: A 2D graphics environment}, + Journal = {Computing in Science \& Engineering}, + Volume = {9}, + Number = {3}, + Pages = {90--95}, + abstract = {Matplotlib is a 2D graphics package used for Python for + application development, interactive scripting, and publication-quality + image generation across user interfaces and operating systems.}, + publisher = {IEEE COMPUTER SOC}, + doi = {10.1109/MCSE.2007.55}, + year = 2007 +} diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000000..ad7af5f76681 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,27 @@ +cff-version: 1.2.0 +message: 'If Matplotlib contributes to a project that leads to a scientific publication, please acknowledge this fact by citing J. D. Hunter, "Matplotlib: A 2D Graphics Environment", Computing in Science & Engineering, vol. 9, no. 3, pp. 90-95, 2007.' +title: 'Matplotlib: Visualization with Python' +authors: + - name: The Matplotlib Development Team + website: https://matplotlib.org/ +type: software +url: 'https://matplotlib.org/' +repository-code: 'https://github.com/matplotlib/matplotlib/' +preferred-citation: + type: article + authors: + - family-names: Hunter + given-names: John D. + title: "Matplotlib: A 2D graphics environment" + year: 2007 + date-published: 2007-06-18 + journal: Computing in Science & Engineering + volume: 9 + issue: 3 + start: 90 + end: 95 + doi: 10.1109/MCSE.2007.55 + publisher: + name: IEEE Computer Society + website: 'https://www.computer.org/' + abstract: Matplotlib is a 2D graphics package used for Python for application development, interactive scripting, and publication-quality image generation across user interfaces and operating systems. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..8fbbe8e7d6f3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,6 @@ + + +Our Code of Conduct is at +https://matplotlib.org/stable/project/code_of_conduct.html + +It is rendered from `doc/project/code_of_conduct.rst` diff --git a/INSTALL.rst b/INSTALL.rst index 0145863cfbcc..3fb01c58d259 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1,290 +1 @@ -================== -Installation Guide -================== - -.. note:: - - If you wish to contribute to the project, it's recommended you - :ref:`install the latest development version`. - -.. contents:: - -Installing an official release -============================== - -Matplotlib and its dependencies are available as wheel packages for macOS, -Windows and Linux distributions:: - - python -m pip install -U pip - python -m pip install -U matplotlib - -If this command results in Matplotlib being compiled from source and -there's trouble with the compilation, you can add ``--prefer-binary`` to -select the newest version of Matplotlib for which there is a -precompiled wheel for your OS and Python. - -.. note:: - - The following backends work out of the box: Agg, ps, pdf, svg - - Python is typically shipped with tk bindings which are used by - TkAgg. - - For support of other GUI frameworks, LaTeX rendering, saving - animations and a larger selection of file formats, you need to - install :ref:`additional dependencies `. - -Although not required, we suggest also installing ``IPython`` for -interactive use. To easily install a complete Scientific Python -stack, see :ref:`install_scipy_dists` below. - -Test data ---------- - -The wheels (:file:`*.whl`) on the `PyPI download page -`_ do not contain test data -or example code. - -If you want to try the many demos that come in the Matplotlib source -distribution, download the :file:`*.tar.gz` file and look in the -:file:`examples` subdirectory. - -To run the test suite: - -* extract the :file:`lib/matplotlib/tests` or :file:`lib/mpl_toolkits/tests` - directories from the source distribution. -* install test dependencies: `pytest `_, - MiKTeX, GhostScript, ffmpeg, avconv, ImageMagick, and `Inkscape - `_. -* run ``python -mpytest``. - -Third-party distributions of Matplotlib -======================================= - -.. _install_scipy_dists: - -Scientific Python Distributions -------------------------------- - -`Anaconda `_ and `ActiveState -`_ are excellent -choices that "just work" out of the box for Windows, macOS and common -Linux platforms. `WinPython `_ is an -option for Windows users. All of these distributions include -Matplotlib and *lots* of other useful (data) science tools. - -Linux: using your package manager ---------------------------------- - -If you are on Linux, you might prefer to use your package manager. Matplotlib -is packaged for almost every major Linux distribution. - -* Debian / Ubuntu: ``sudo apt-get install python3-matplotlib`` -* Fedora: ``sudo dnf install python3-matplotlib`` -* Red Hat: ``sudo yum install python3-matplotlib`` -* Arch: ``sudo pacman -S python-matplotlib`` - -.. _install_from_source: - -Installing from source -====================== - -If you are interested in contributing to Matplotlib development, -running the latest source code, or just like to build everything -yourself, it is not difficult to build Matplotlib from source. - -The easiest way to get the latest development version to start contributing -is to go to the git `repository `_ -and run:: - - git clone https://github.com/matplotlib/matplotlib.git - -If you're developing, it's better to do it in editable mode. The reason why -is that pytest's test discovery only works for Matplotlib -if installation is done this way. Also, editable mode allows your code changes -to be instantly propagated to your library code without reinstalling (though -you will have to restart your python process / kernel):: - - python -m pip install -e . - -If you're not developing, it can be installed from the source directory with -a simple:: - - python -m pip install . - -To run the tests you will need to install some additional dependencies:: - - python -m pip install -r requirements/dev/dev-requirements.txt - -.. warning:: - - The following instructions in this section are for very custom - installations of Matplotlib. Proceed with caution because these instructions - may result in your build producing unexpected behavior and/or causing - local testing to fail. - -If you would like to build from a tarball, grab the latest *tar.gz* release -file from `the PyPI files page `_. - -We provide a `setup.cfg`_ file which you can use to customize the build -process. For example, which default backend to use, whether some of the -optional libraries that Matplotlib ships with are installed, and so on. This -file will be particularly useful to those packaging Matplotlib. - -.. _setup.cfg: https://raw.githubusercontent.com/matplotlib/matplotlib/master/setup.cfg.template - -.. _install_requirements: - -Dependencies ------------- - -Matplotlib will automatically install dependencies when you install with -``pip``, so this section is mostly for your reference. - -Matplotlib requires the following dependencies: - -* `Python `_ (>= 3.7) -* `NumPy `_ (>= 1.16) -* `setuptools `_ -* `cycler `_ (>= 0.10.0) -* `dateutil `_ (>= 2.7) -* `kiwisolver `_ (>= 1.0.1) -* `Pillow `_ (>= 6.2) -* `pyparsing `_ (>=2.2.1) - -Optionally, you can also install a number of packages to enable better user -interface toolkits. See :ref:`what-is-a-backend` for more details on the -optional Matplotlib backends and the capabilities they provide. - -* Tk_ (>= 8.3, != 8.6.0 or 8.6.1): for the Tk-based backends. -* PyQt4_ (>= 4.6) or PySide_ (>= 1.0.3) [#]_: for the Qt4-based backends. -* PyQt5_ or PySide2_: for the Qt5-based backends. -* PyGObject_: for the GTK3-based backends [#]_. -* wxPython_ (>= 4) [#]_: for the wx-based backends. -* pycairo_ (>= 1.11.0) or cairocffi_ (>= 0.8): for the GTK3 and/or cairo-based - backends. -* Tornado_: for the WebAgg backend. - -.. _Tk: https://docs.python.org/3/library/tk.html -.. _PyQt4: https://pypi.org/project/PyQt4 -.. _PySide: https://pypi.org/project/PySide -.. _PyQt5: https://pypi.org/project/PyQt5 -.. _PySide2: https://pypi.org/project/PySide2 -.. _PyGObject: https://pygobject.readthedocs.io/en/latest/ -.. _wxPython: https://www.wxpython.org/ -.. _pycairo: https://pycairo.readthedocs.io/en/latest/ -.. _cairocffi: https://cairocffi.readthedocs.io/en/latest/ -.. _Tornado: https://pypi.org/project/tornado - -.. [#] PySide cannot be pip-installed on Linux (but can be conda-installed). -.. [#] If using pip (and not conda), PyGObject must be built from source; see - https://pygobject.readthedocs.io/en/latest/devguide/dev_environ.html. -.. [#] If using pip (and not conda) on Linux, wxPython wheels must be manually - downloaded from https://wxpython.org/pages/downloads/. - -For better support of animation output format and image file formats, LaTeX, -etc., you can install the following: - -* `ffmpeg `_: for saving movies. -* `ImageMagick `_: for saving - animated gifs. -* `LaTeX `_ (with `cm-super - `__ ) and `GhostScript (>=9.0) - `_ : for rendering text with - LaTeX. -* `fontconfig `_ (>= 2.7): for detection of system - fonts on Linux. - -FreeType and Qhull ------------------- - -Matplotlib depends on `FreeType `_ (>= -2.3), a font rendering library, and on `Qhull -`_ (>= 2015.2), a library for computing -triangulations. By default (except on AIX) Matplotlib downloads and -builds its own copy of FreeType (this is necessary to run the test -suite, because different versions of FreeType rasterize characters -differently), and uses its own copy of Qhull. - -To force Matplotlib to use a copy of FreeType or Qhull already installed in -your system, create a :file:`setup.cfg` file with the following contents: - -.. code-block:: cfg - - [libs] - system_freetype = true - system_qhull = true - -before running ``python -m pip install .``. - -In this case, you need to install the FreeType and Qhull library and headers. -This can be achieved using a package manager, e.g. for FreeType: - -.. code-block:: sh - - # Pick ONE of the following: - sudo apt install libfreetype6-dev # Debian/Ubuntu - sudo dnf install freetype-devel # Fedora - brew install freetype # macOS with Homebrew - conda install freetype # conda, any OS - -(adapt accordingly for Qhull). - -On Linux and macOS, it is also recommended to install pkg-config_, a helper -tool for locating FreeType: - -.. code-block:: sh - - # Pick ONE of the following: - sudo apt install pkg-config # Debian/Ubuntu - sudo dnf install pkgconf # Fedora - brew install pkg-config # macOS with Homebrew - conda install pkg-config # conda - # Or point the PKG_CONFIG environment variable to the path to pkg-config: - export PKG_CONFIG=... - -.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ - -If not using pkg-config (in particular on Windows), you may need to set the -include path (to the library headers) and link path (to the libraries) -explicitly, if they are not in standard locations. This can be done using -standard environment variables -- on Linux and OSX: - -.. code-block:: sh - - export CFLAGS='-I/directory/containing/ft2build.h' - export LDFLAGS='-L/directory/containing/libfreetype.so' - -and on Windows: - -.. code-block:: bat - - set CL=/IC:\directory\containing\ft2build.h - set LINK=/LIBPATH:C:\directory\containing\freetype.lib - -.. note:: - - Matplotlib always uses its own copies of the following libraries: - - - ``Agg``: the Anti-Grain Geometry C++ rendering engine; - - ``ttconv``: a TrueType font utility. - -If you go this route but need to reset and rebuild to change your settings, -remember to clear your artifacts before re-building:: - - git clean -xfd - -Building on Windows -------------------- - -Compiling Matplotlib (or any other extension module, for that matter) requires -Visual Studio 2015 or later. - -If you are building your own Matplotlib wheels (or sdists), note that any DLLs -that you copy into the source tree will be packaged too. - -Conda packages --------------- - -The conda packaging scripts for Matplotlib are available at -https://github.com/conda-forge/matplotlib-feedstock. +See doc/install/index.rst diff --git a/LICENSE/LICENSE_BAKOMA b/LICENSE/LICENSE_BAKOMA index 801e20cd736f..6200f085b9d6 100644 --- a/LICENSE/LICENSE_BAKOMA +++ b/LICENSE/LICENSE_BAKOMA @@ -2,7 +2,7 @@ BaKoMa Fonts Licence -------------------- - This licence covers two font packs (known as BaKoMa Fonts Colelction, + This licence covers two font packs (known as BaKoMa Fonts Collection, which is available at `CTAN:fonts/cm/ps-type1/bakoma/'): 1) BaKoMa-CM (1.1/12-Nov-94) diff --git a/LICENSE/LICENSE_COLORBREWER b/LICENSE/LICENSE_COLORBREWER index 568afe883ece..7557bb7e769b 100644 --- a/LICENSE/LICENSE_COLORBREWER +++ b/LICENSE/LICENSE_COLORBREWER @@ -1,38 +1,13 @@ -Apache-Style Software License for ColorBrewer Color Schemes +Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes -Version 1.1 +Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. -Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania -State University. All rights reserved. Redistribution and use in source -and binary forms, with or without modification, are permitted provided -that the following conditions are met: +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at -1. Redistributions as source code must retain the above copyright notice, -this list of conditions and the following disclaimer. +http://www.apache.org/licenses/LICENSE-2.0 -2. The end-user documentation included with the redistribution, if any, -must include the following acknowledgment: "This product includes color -specifications and designs developed by Cynthia Brewer -(http://colorbrewer.org/)." Alternately, this acknowledgment may appear in -the software itself, if and wherever such third-party acknowledgments -normally appear. - -3. The name "ColorBrewer" must not be used to endorse or promote products -derived from this software without prior written permission. For written -permission, please contact Cynthia Brewer at cbrewer@psu.edu. - -4. Products derived from this software may not be called "ColorBrewer", -nor may "ColorBrewer" appear in their name, without prior written -permission of Cynthia Brewer. - -THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -CYNTHIA BREWER, MARK HARROWER, OR THE PENNSYLVANIA STATE UNIVERSITY BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/LICENSE/LICENSE_COURIERTEN b/LICENSE/LICENSE_COURIERTEN new file mode 100644 index 000000000000..c6d3fd7410a2 --- /dev/null +++ b/LICENSE/LICENSE_COURIERTEN @@ -0,0 +1,18 @@ +The Courier10PitchBT-Bold.pfb file is a Type-1 version of +Courier 10 Pitch BT Bold by Bitstream, obtained from +. It is included +here as test data only, but the following license applies. + + +(c) Copyright 1989-1992, Bitstream Inc., Cambridge, MA. + +You are hereby granted permission under all Bitstream propriety rights +to use, copy, modify, sublicense, sell, and redistribute the 4 Bitstream +Charter (r) Type 1 outline fonts and the 4 Courier Type 1 outline fonts +for any purpose and without restriction; provided, that this notice is +left intact on all copies of such fonts and that Bitstream's trademark +is acknowledged as shown below on all unmodified copies of the 4 Charter +Type 1 fonts. + +BITSTREAM CHARTER is a registered trademark of Bitstream Inc. + diff --git a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER new file mode 100644 index 000000000000..0bc1fa7060b7 --- /dev/null +++ b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER @@ -0,0 +1,108 @@ +# CC0 1.0 Universal + +## Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an “ownerâ€) of an original work of +authorship and/or a database (each, a “Workâ€). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific works +(“Commonsâ€) that the public can reliably and without fear of later claims of +infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further +production of creative, cultural and scientific works, or to gain reputation or +greater distribution for their Work in part through the use and efforts of +others. + +For these and/or other purposes and motivations, and without any expectation of +additional consideration or compensation, the person associating CC0 with a +Work (the “Affirmerâ€), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and +publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be + protected by copyright and related or neighboring rights (“Copyright and + Related Rightsâ€). Copyright and Related Rights include, but are not limited + to, the following: + 1. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + 2. moral rights retained by the original author(s) and/or performer(s); + 3. publicity and privacy rights pertaining to a person’s image or likeness + depicted in a Work; + 4. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(i), below; + 5. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + 6. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + 7. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations + thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, + applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and + unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright + and Related Rights and associated claims and causes of action, whether now + known or unknown (including existing as well as future claims and causes of + action), in the Work (i) in all territories worldwide, (ii) for the maximum + duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “Waiverâ€). Affirmer + makes the Waiver for the benefit of each member of the public at large and + to the detriment of Affirmer’s heirs and successors, fully intending that + such Waiver shall not be subject to revocation, rescission, cancellation, + termination, or any other legal or equitable action to disrupt the quiet + enjoyment of the Work by the public as contemplated by Affirmer’s express + Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be + judged legally invalid or ineffective under applicable law, then the Waiver + shall be preserved to the maximum extent permitted taking into account + Affirmer’s express Statement of Purpose. In addition, to the extent the + Waiver is so judged Affirmer hereby grants to each affected person a + royalty-free, non transferable, non sublicensable, non exclusive, + irrevocable and unconditional license to exercise Affirmer’s Copyright and + Related Rights in the Work (i) in all territories worldwide, (ii) for the + maximum duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “Licenseâ€). The License + shall be deemed effective as of the date CC0 was applied by Affirmer to the + Work. Should any part of the License for any reason be judged legally + invalid or ineffective under applicable law, such partial invalidity or + ineffectiveness shall not invalidate the remainder of the License, and in + such case Affirmer hereby affirms that he or she will not (i) exercise any + of his or her remaining Copyright and Related Rights in the Work or (ii) + assert any associated claims and causes of action with respect to the Work, + in either case contrary to Affirmer’s express Statement of Purpose. + +4. Limitations and Disclaimers. + 1. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + 2. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or + otherwise, including without limitation warranties of title, + merchantability, fitness for a particular purpose, non infringement, or + the absence of latent or other defects, accuracy, or the present or + absence of errors, whether or not discoverable, all to the greatest + extent permissible under applicable law. + 3. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person’s Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the Work. + 4. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see +http://creativecommons.org/publicdomain/zero/1.0/. diff --git a/LICENSE/LICENSE_LAST_RESORT_FONT b/LICENSE/LICENSE_LAST_RESORT_FONT new file mode 100644 index 000000000000..5fe3297bc1e1 --- /dev/null +++ b/LICENSE/LICENSE_LAST_RESORT_FONT @@ -0,0 +1,97 @@ +Last Resort High-Efficiency Font License +======================================== + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +SPDX-License-Identifier: OFL-1.1 diff --git a/LICENSE/LICENSE_STIX b/LICENSE/LICENSE_STIX index 2f7aeea331ce..6034d9474814 100644 --- a/LICENSE/LICENSE_STIX +++ b/LICENSE/LICENSE_STIX @@ -1,71 +1,124 @@ -TERMS AND CONDITIONS - - 1. Permission is hereby granted, free of charge, to any person -obtaining a copy of the STIX Fonts-TM set accompanying this license -(collectively, the "Fonts") and the associated documentation files -(collectively with the Fonts, the "Font Software"), to reproduce and -distribute the Font Software, including the rights to use, copy, merge -and publish copies of the Font Software, and to permit persons to whom -the Font Software is furnished to do so same, subject to the following -terms and conditions (the "License"). - - 2. The following copyright and trademark notice and these Terms and -Conditions shall be included in all copies of one or more of the Font -typefaces and any derivative work created as permitted under this -License: - - Copyright (c) 2001-2005 by the STI Pub Companies, consisting of -the American Institute of Physics, the American Chemical Society, the -American Mathematical Society, the American Physical Society, Elsevier, -Inc., and The Institute of Electrical and Electronic Engineers, Inc. -Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright -(c) 1990 by Elsevier, Inc. All rights reserved. STIX Fonts-TM is a -trademark of The Institute of Electrical and Electronics Engineers, Inc. - - 3. You may (a) convert the Fonts from one format to another (e.g., -from TrueType to PostScript), in which case the normal and reasonable -distortion that occurs during such conversion shall be permitted and (b) -embed or include a subset of the Fonts in a document for the purposes of -allowing users to read text in the document that utilizes the Fonts. In -each case, you may use the STIX Fonts-TM mark to designate the resulting -Fonts or subset of the Fonts. - - 4. You may also (a) add glyphs or characters to the Fonts, or modify -the shape of existing glyphs, so long as the base set of glyphs is not -removed and (b) delete glyphs or characters from the Fonts, provided -that the resulting font set is distributed with the following -disclaimer: "This [name] font does not include all the Unicode points -covered in the STIX Fonts-TM set but may include others." In each case, -the name used to denote the resulting font set shall not include the -term "STIX" or any similar term. - - 5. You may charge a fee in connection with the distribution of the -Font Software, provided that no copy of one or more of the individual -Font typefaces that form the STIX Fonts-TM set may be sold by itself. - - 6. THE FONT SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK OR OTHER RIGHT. IN NO EVENT SHALL -MICROPRESS OR ANY OF THE STI PUB COMPANIES BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, INCLUDING, BUT NOT LIMITED TO, ANY GENERAL, -SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM OR OUT OF THE USE OR -INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT -SOFTWARE. - - 7. Except as contained in the notice set forth in Section 2, the -names MicroPress Inc. and STI Pub Companies, as well as the names of the -companies/organizations that compose the STI Pub Companies, shall not be -used in advertising or otherwise to promote the sale, use or other -dealings in the Font Software without the prior written consent of the -respective company or organization. - - 8. This License shall become null and void in the event of any -material breach of the Terms and Conditions herein by licensee. - - 9. A substantial portion of the STIX Fonts set was developed by -MicroPress Inc. for the STI Pub Companies. To obtain additional -mathematical fonts, please contact MicroPress, Inc., 68-30 Harrow -Street, Forest Hills, NY 11375, USA - Phone: (718) 575-1816. +The STIX fonts distributed with matplotlib have been modified from +their canonical form. They have been converted from OTF to TTF format +using Fontforge and this script: + #!/usr/bin/env fontforge + i=1 + while ( i<$argc ) + Open($argv[i]) + Generate($argv[i]:r + ".ttf") + i = i+1 + endloop + +The original STIX Font License begins below. + +----------------------------------------------------------- + +STIX Font License + +24 May 2010 + +Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the American +Institute of Physics, the American Chemical Society, the American Mathematical +Society, the American Physical Society, Elsevier, Inc., and The Institute of +Electrical and Electronic Engineers, Inc. (www.stixfonts.org), with Reserved +Font Name STIX Fonts, STIX Fonts (TM) is a trademark of The Institute of +Electrical and Electronics Engineers, Inc. + +Portions copyright (c) 1998-2003 by MicroPress, Inc. (www.micropress-inc.com), +with Reserved Font Name TM Math. To obtain additional mathematical fonts, please +contact MicroPress, Inc., 68-30 Harrow Street, Forest Hills, NY 11375, USA, +Phone: (718) 575-1816. + +Portions copyright (c) 1990 by Elsevier, Inc. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 39e693f1014f..000000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,23 +0,0 @@ -include INSTALL.rst README.rst -include pytest.ini -include MANIFEST.in -include matplotlibrc.template setup.cfg.template -include setupext.py setup.py -include lib/matplotlib/mpl-data/matplotlibrc -include lib/matplotlib/mpl-data/images/* -include lib/matplotlib/mpl-data/fonts/ttf/* -include lib/matplotlib/mpl-data/fonts/pdfcorefonts/* -include lib/matplotlib/mpl-data/fonts/afm/* -include lib/matplotlib/mpl-data/stylelib/* -recursive-include LICENSE * -recursive-include doc * -recursive-include examples * -recursive-include extern * -recursive-include lib * -recursive-include lib/matplotlib/mpl-data/sample_data * -recursive-include src *.cpp *.c *.h *.m -recursive-include tools * -recursive-include tutorials * -include versioneer.py -include lib/matplotlib/_version.py -include tests.py diff --git a/README.md b/README.md new file mode 100644 index 000000000000..7b9c99597c0d --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +[![PyPi](https://img.shields.io/pypi/v/matplotlib)](https://pypi.org/project/matplotlib/) +[![Conda](https://img.shields.io/conda/vn/conda-forge/matplotlib)](https://anaconda.org/conda-forge/matplotlib) +[![Downloads](https://img.shields.io/pypi/dm/matplotlib)](https://pypi.org/project/matplotlib) +[![NUMFocus](https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A)](https://numfocus.org) + +[![Discourse help forum](https://img.shields.io/badge/help_forum-discourse-blue.svg)](https://discourse.matplotlib.org) +[![Gitter](https://badges.gitter.im/matplotlib/matplotlib.svg)](https://gitter.im/matplotlib/matplotlib) +[![GitHub issues](https://img.shields.io/badge/issue_tracking-github-blue.svg)](https://github.com/matplotlib/matplotlib/issues) +[![Contributing](https://img.shields.io/badge/PR-Welcome-%23FF8300.svg?)](https://matplotlib.org/stable/devel/index.html) + +[![GitHub actions status](https://github.com/matplotlib/matplotlib/workflows/Tests/badge.svg)](https://github.com/matplotlib/matplotlib/actions?query=workflow%3ATests) +[![Azure pipelines status](https://dev.azure.com/matplotlib/matplotlib/_apis/build/status/matplotlib.matplotlib?branchName=main)](https://dev.azure.com/matplotlib/matplotlib/_build/latest?definitionId=1&branchName=main) +[![AppVeyor status](https://ci.appveyor.com/api/projects/status/github/matplotlib/matplotlib?branch=main&svg=true)](https://ci.appveyor.com/project/matplotlib/matplotlib) +[![Codecov status](https://codecov.io/github/matplotlib/matplotlib/badge.svg?branch=main&service=github)](https://app.codecov.io/gh/matplotlib/matplotlib) +[![EffVer Versioning](https://img.shields.io/badge/version_scheme-EffVer-0097a7)](https://jacobtomlinson.dev/effver) + +![Matplotlib logotype](https://matplotlib.org/_static/logo2.svg) + +Matplotlib is a comprehensive library for creating static, animated, and +interactive visualizations in Python. + +Check out our [home page](https://matplotlib.org/) for more information. + +![image](https://matplotlib.org/_static/readme_preview.png) + +Matplotlib produces publication-quality figures in a variety of hardcopy +formats and interactive environments across platforms. Matplotlib can be +used in Python scripts, Python/IPython shells, web application servers, +and various graphical user interface toolkits. + +## Install + +See the [install +documentation](https://matplotlib.org/stable/users/installing/index.html), +which is generated from `/doc/install/index.rst` + +## Contribute + +You've discovered a bug or something else you want to change — excellent! + +You've worked out a way to fix it — even better! + +You want to tell us about it — best of all! + +Start at the [contributing +guide](https://matplotlib.org/devdocs/devel/contribute.html)! + +## Contact + +[Discourse](https://discourse.matplotlib.org/) is the discussion forum +for general questions and discussions and our recommended starting +point. + +Our active mailing lists (which are mirrored on Discourse) are: + +- [Users](https://mail.python.org/mailman/listinfo/matplotlib-users) + mailing list: +- [Announcement](https://mail.python.org/mailman/listinfo/matplotlib-announce) + mailing list: +- [Development](https://mail.python.org/mailman/listinfo/matplotlib-devel) + mailing list: + +[Gitter](https://gitter.im/matplotlib/matplotlib) is for coordinating +development and asking questions directly related to contributing to +matplotlib. + +## Citing Matplotlib + +If Matplotlib contributes to a project that leads to publication, please +acknowledge this by citing Matplotlib. + +[A ready-made citation +entry](https://matplotlib.org/stable/users/project/citing.html) is +available. diff --git a/README.rst b/README.rst deleted file mode 100644 index 242ee716b703..000000000000 --- a/README.rst +++ /dev/null @@ -1,102 +0,0 @@ -|PyPi|_ |Downloads|_ |NUMFocus|_ - -|DiscourseBadge|_ |Gitter|_ |GitHubIssues|_ |GitTutorial|_ - -|Travis|_ |AzurePipelines|_ |AppVeyor|_ |Codecov|_ |LGTM|_ - -.. |Travis| image:: https://travis-ci.com/matplotlib/matplotlib.svg?branch=master -.. _Travis: https://travis-ci.com/matplotlib/matplotlib - -.. |AzurePipelines| image:: https://dev.azure.com/matplotlib/matplotlib/_apis/build/status/matplotlib.matplotlib?branchName=master -.. _AzurePipelines: https://dev.azure.com/matplotlib/matplotlib/_build/latest?definitionId=1&branchName=master - -.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/github/matplotlib/matplotlib?branch=master&svg=true -.. _AppVeyor: https://ci.appveyor.com/project/matplotlib/matplotlib - -.. |Codecov| image:: https://codecov.io/github/matplotlib/matplotlib/badge.svg?branch=master&service=github -.. _Codecov: https://codecov.io/github/matplotlib/matplotlib?branch=master - -.. |LGTM| image:: https://img.shields.io/lgtm/grade/python/g/matplotlib/matplotlib.svg?logo=lgtm&logoWidth=18 -.. _LGTM: https://lgtm.com/projects/g/matplotlib/matplotlib - -.. |DiscourseBadge| image:: https://img.shields.io/badge/help_forum-discourse-blue.svg -.. _DiscourseBadge: https://discourse.matplotlib.org - -.. |Gitter| image:: https://badges.gitter.im/matplotlib/matplotlib.svg -.. _Gitter: https://gitter.im/matplotlib/matplotlib - -.. |GitHubIssues| image:: https://img.shields.io/badge/issue_tracking-github-blue.svg -.. _GitHubIssues: https://github.com/matplotlib/matplotlib/issues - -.. |GitTutorial| image:: https://img.shields.io/badge/PR-Welcome-%23FF8300.svg? -.. _GitTutorial: https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project - -.. |PyPi| image:: https://badge.fury.io/py/matplotlib.svg -.. _PyPi: https://badge.fury.io/py/matplotlib - -.. |Downloads| image:: https://pepy.tech/badge/matplotlib/month -.. _Downloads: https://pepy.tech/project/matplotlib/month - -.. |NUMFocus| image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A -.. _NUMFocus: https://numfocus.org - -.. image:: https://matplotlib.org/_static/logo2.svg - -Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. - -Check out our `home page `_ for more information. - -.. image:: https://matplotlib.org/_static/readme_preview.png - -Matplotlib produces publication-quality figures in a variety of hardcopy formats -and interactive environments across platforms. Matplotlib can be used in Python scripts, -the Python and IPython shell, web application servers, and various -graphical user interface toolkits. - - -Install -======= - -For installation instructions and requirements, see `INSTALL.rst `_ or the -`install `_ documentation. - -Test -==== - -After installation, launch the test suite:: - - python -m pytest - -Read the `testing guide `_ for more information and alternatives. - -Contribute -========== -You've discovered a bug or something else you want to change - excellent! - -You've worked out a way to fix it – even better! - -You want to tell us about it – best of all! - -Start at the `contributing guide `_! - -Contact -======= - -`Discourse `_ is the discussion forum for general questions and discussions and our recommended starting point. - -Our active mailing lists (which are mirrored on Discourse) are: - -* `Users `_ mailing list: matplotlib-users@python.org -* `Announcement `_ mailing list: matplotlib-announce@python.org -* `Development `_ mailing list: matplotlib-devel@python.org - -Gitter_ is for coordinating development and asking questions directly related -to contributing to matplotlib. - - -Citing Matplotlib -================= -If Matplotlib contributes to a project that leads to publication, please -acknowledge this by citing Matplotlib. - -`A ready-made citation entry `_ is available. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..4400a4501b51 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +## Supported Versions + +The following table lists versions and whether they are supported. Security +vulnerability reports will be accepted and acted upon for all supported +versions. + +| Version | Supported | +| ------- | ------------------ | +| 3.10.x | :white_check_mark: | +| 3.9.x | :white_check_mark: | +| 3.8.x | :x: | +| 3.7.x | :x: | +| 3.6.x | :x: | +| 3.5.x | :x: | +| < 3.5 | :x: | + + +## Reporting a Vulnerability + + +To report a security vulnerability, please use the [Tidelift security +contact](https://tidelift.com/security). Tidelift will coordinate the fix and +disclosure. + +If you have found a security vulnerability, in order to keep it confidential, +please do not report an issue on GitHub. + +We do not award bounties for security vulnerabilities. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a5c8544b7e88..d68a9d36f0d3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,117 +1,162 @@ # Python package # Create and test a Python package on multiple Python versions. -# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/python - -strategy: - matrix: - Linux_py37: - vmImage: 'ubuntu-16.04' - python.version: '3.7' - Linux_py38: - vmImage: 'ubuntu-16.04' - python.version: '3.8' - macOS_py37: - vmImage: 'macOS-10.15' - python.version: '3.7' - macOS_py38: - vmImage: 'macOS-latest' - python.version: '3.8' - Windows_py37: - vmImage: 'vs2017-win2016' - python.version: '3.7' - Windows_py38: - vmImage: 'windows-latest' - python.version: '3.8' - Windows_pyPre: - vmImage: 'vs2017-win2016' - python.version: 'Pre' - maxParallel: 4 - -pool: - vmImage: '$(vmImage)' - -steps: - -- task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: 'x64' - displayName: 'Use Python $(python.version)' - condition: and(succeeded(), ne(variables['python.version'], 'Pre')) - -- task: stevedower.python.InstallPython.InstallPython@1 - displayName: 'Use prerelease Python' - inputs: - prerelease: true - condition: and(succeeded(), eq(variables['python.version'], 'Pre')) - -- bash: | - set -e - case "$(python -c 'import sys; print(sys.platform)')" in - linux) - sudo apt update - sudo apt install \ - cm-super \ - dvipng \ - ffmpeg \ - gdb \ - gir1.2-gtk-3.0 \ - graphviz \ - inkscape \ - libcairo2 \ - libgirepository-1.0.1 \ - lmodern \ - fonts-freefont-otf \ - poppler-utils \ - texlive-pictures \ - texlive-fonts-recommended \ - texlive-latex-base \ - texlive-latex-extra \ - texlive-latex-recommended \ - texlive-xetex texlive-luatex - ;; - darwin) - brew cask install xquartz - brew install pkg-config ffmpeg imagemagick mplayer ccache - ;; - win32) - ;; - *) - exit 1 - ;; - esac - displayName: 'Install dependencies' - -- bash: | - python -m pip install --upgrade pip - python -m pip install -r requirements/testing/travis_all.txt -r requirements/testing/travis_extra.txt || - [[ "$PYTHON_VERSION" = 'Pre' ]] - displayName: 'Install dependencies with pip' - -- bash: | - python -m pip install -ve . || - [[ "$PYTHON_VERSION" = 'Pre' ]] - displayName: "Install self" - -- script: env - displayName: 'print env' - -- bash: | - PYTHONFAULTHANDLER=1 python -m pytest --junitxml=junit/test-results.xml -raR --maxfail=50 --timeout=300 --durations=25 --cov-report= --cov=lib -n 2 || - [[ "$PYTHON_VERSION" = 'Pre' ]] - displayName: 'pytest' - -- bash: | - bash <(curl -s https://codecov.io/bash) -f "!*.gcov" -X gcov - displayName: 'Upload to codecov.io' - -- task: PublishTestResults@2 - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Python $(python.version)' - condition: succeededOrFailed() - -- publish: $(System.DefaultWorkingDirectory)/result_images - artifact: $(Agent.JobName)-result_images - condition: and(failed(), ne(variables['python.version'], 'Pre')) +# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and +# more: +# https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/python?view=azure-devops + +--- +trigger: + branches: + exclude: + - v*-doc +pr: + branches: + exclude: + - v*-doc + paths: + exclude: + - doc/**/* + - galleries/**/* + +stages: + + - stage: Check + jobs: + - job: Skip + pool: + vmImage: 'ubuntu-latest' + variables: + DECODE_PERCENTS: 'false' + RET: 'true' + steps: + - bash: | + git_log=`git log --max-count=1 --skip=1 --pretty=format:"%B" | tr "\n" " "` + echo "##vso[task.setvariable variable=log]$git_log" + - bash: echo "##vso[task.setvariable variable=RET]false" + condition: >- + or(contains(variables.log, '[skip azp]'), + contains(variables.log, '[azp skip]'), + contains(variables.log, '[skip ci]'), + contains(variables.log, '[ci skip]'), + contains(variables.log, '[ci doc]')) + - bash: echo "##vso[task.setvariable variable=start_main;isOutput=true]$RET" + name: result + + - stage: Main + condition: and(succeeded(), eq(dependencies.Check.outputs['Skip.result.start_main'], 'true')) + dependsOn: Check + jobs: + - job: Pytest + strategy: + matrix: + Windows_py311: + vmImage: 'windows-2022' # Keep one job pinned to the oldest image + python.version: '3.11' + Windows_py312: + vmImage: 'windows-latest' + python.version: '3.12' + Windows_py313: + vmImage: 'windows-latest' + python.version: '3.13' + maxParallel: 4 + pool: + vmImage: '$(vmImage)' + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + displayName: 'Use Python $(python.version)' + + - bash: | + choco install ninja + displayName: 'Install dependencies' + + - bash: | + python -m pip install --upgrade pip + python -m pip install --upgrade -r requirements/dev/build-requirements.txt + python -m pip install -r requirements/testing/all.txt -r requirements/testing/extra.txt + displayName: 'Install dependencies with pip' + + - bash: | + CONFIG='--config-settings=setup-args=--vsenv' + CONFIG="$CONFIG --config-settings=setup-args=-Dcpp_link_args=-PROFILE" + CONFIG="$CONFIG --config-settings=setup-args=-Dbuildtype=debug" + + python -m pip install \ + --no-build-isolation $CONFIG \ + --verbose --editable .[dev] + displayName: "Install self" + + - script: env + displayName: 'print env' + + - script: pip list + displayName: 'print pip' + + - bash: | + set -e + SESSION_ID=$(python -c "import uuid; print(uuid.uuid4(), end='')") + echo "Coverage session ID: ${SESSION_ID}" + VS=$(ls -d /c/Program\ Files*/Microsoft\ Visual\ Studio/*/Enterprise) + echo "Visual Studio: ${VS}" + DIR="$VS/Common7/IDE/Extensions/Microsoft/CodeCoverage.Console" + # This is for MSVC 2022 (on windows-latest). + TOOL="$DIR/Microsoft.CodeCoverage.Console.exe" + for f in build/cp*/src/*.pyd; do + echo $f + echo "==============================" + "$TOOL" instrument $f --session-id $SESSION_ID \ + --log-level Verbose --log-file instrument.log + cat instrument.log + rm instrument.log + done + echo "Starting $TOOL in server mode" + "$TOOL" collect \ + --session-id $SESSION_ID --server-mode \ + --output-format cobertura --output extensions.xml \ + --log-level Verbose --log-file extensions.log & + VS_VER=2022 + + echo "##vso[task.setvariable variable=VS_COVERAGE_TOOL]$TOOL" + + PYTHONFAULTHANDLER=1 pytest -rfEsXR -n 2 \ + --maxfail=50 --timeout=300 --durations=25 \ + --junitxml=junit/test-results.xml --cov-report=xml --cov=lib + + if [[ $VS_VER == 2022 ]]; then + "$TOOL" shutdown $SESSION_ID + echo "Coverage collection log" + echo "=======================" + cat extensions.log + else + "$TOOL" shutdown -session:$SESSION_ID + fi + displayName: 'pytest' + + - bash: | + if [[ -f extensions.coverage ]]; then + # For MSVC 2019. + "$VS_COVERAGE_TOOL" analyze -output:extensions.xml \ + -include_skipped_functions -include_skipped_modules \ + extensions.coverage + rm extensions.coverage + fi + displayName: 'Filter C coverage' + condition: succeededOrFailed() + - bash: | + bash <(curl -s https://codecov.io/bash) \ + -n "$PYTHON_VERSION $AGENT_OS" \ + -f 'coverage.xml' -f 'extensions.xml' + displayName: 'Upload to codecov.io' + condition: succeededOrFailed() + + - task: PublishTestResults@2 + inputs: + testResultsFiles: '**/test-results.xml' + testRunTitle: 'Python $(python.version)' + condition: succeededOrFailed() + + - publish: $(System.DefaultWorkingDirectory)/result_images + artifact: $(Agent.JobName)-result_images + condition: failed() diff --git a/ci/check_version_number.py b/ci/check_version_number.py new file mode 100644 index 000000000000..8902fb0806c7 --- /dev/null +++ b/ci/check_version_number.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +""" +Check that the version number of the install Matplotlib does not start with 0 + +To run: + $ python3 -m build . + $ pip install dist/matplotlib*.tar.gz for sdist + $ pip install dist/matplotlib*.whl for wheel + $ ./ci/check_version_number.py +""" +import sys + +import matplotlib + + +print(f"Version {matplotlib.__version__} installed") +if matplotlib.__version__[0] == "0": + sys.exit("Version incorrectly starts with 0") diff --git a/ci/check_wheel_licenses.py b/ci/check_wheel_licenses.py index a3f8406785ab..13470dc692bd 100644 --- a/ci/check_wheel_licenses.py +++ b/ci/check_wheel_licenses.py @@ -1,36 +1,33 @@ #!/usr/bin/env python3 """ -Check that all .whl files in the dist folder have the correct LICENSE files -included. +Check that all specified .whl files have the correct LICENSE files included. To run: - $ cp setup.cfg.template setup.cfg - $ python3 setup.py bdist_wheel - $ ./ci/check_wheel_licenses.py + $ python3 -m build --wheel + $ ./ci/check_wheel_licenses.py dist/*.whl """ from pathlib import Path import sys import zipfile -EXIT_SUCCESS = 0 -EXIT_FAILURE = 1 + +if len(sys.argv) <= 1: + sys.exit('At least one wheel must be specified in command-line arguments.') project_dir = Path(__file__).parent.resolve().parent -dist_dir = project_dir / 'dist' license_dir = project_dir / 'LICENSE' -license_file_names = [path.name for path in sorted(license_dir.glob('*'))] -for wheel in dist_dir.glob('*.whl'): +license_file_names = {path.name for path in sorted(license_dir.glob('*'))} +for wheel in sys.argv[1:]: print(f'Checking LICENSE files in: {wheel}') with zipfile.ZipFile(wheel) as f: - wheel_license_file_names = [Path(path).name + wheel_license_file_names = {Path(path).name for path in sorted(f.namelist()) - if '.dist-info/LICENSE' in path] - if wheel_license_file_names != license_file_names: - print(f'LICENSE file(s) missing:\n' - f'{wheel_license_file_names} !=\n' - f'{license_file_names}') - sys.exit(EXIT_FAILURE) -sys.exit(EXIT_SUCCESS) + if '.dist-info/LICENSE' in path} + if not (len(wheel_license_file_names) and + wheel_license_file_names.issuperset(license_file_names)): + sys.exit(f'LICENSE file(s) missing:\n' + f'{wheel_license_file_names} !=\n' + f'{license_file_names}') diff --git a/ci/codespell-ignore-words.txt b/ci/codespell-ignore-words.txt new file mode 100644 index 000000000000..0ebc5211b80c --- /dev/null +++ b/ci/codespell-ignore-words.txt @@ -0,0 +1,22 @@ +aas +ABD +axises +coo +curvelinear +filll +flate +fpt +hax +inh +inout +ment +nd +oint +oly +te +thisy +ure +whis +wit +Copin +socio-economic diff --git a/ci/export_sdist_name.py b/ci/export_sdist_name.py new file mode 100644 index 000000000000..632c11a15f83 --- /dev/null +++ b/ci/export_sdist_name.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +""" +Determine the name of the sdist and export to GitHub output named SDIST_NAME. + +To run: + $ python3 -m build --sdist + $ ./ci/determine_sdist_name.py +""" +import os +from pathlib import Path +import sys + + +paths = [p.name for p in Path("dist").glob("*.tar.gz")] +if len(paths) != 1: + sys.exit(f"Only a single sdist is supported, but found: {paths}") + +print(paths[0]) +with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"SDIST_NAME={paths[0]}\n") diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt new file mode 100644 index 000000000000..46ec06e0a9f1 --- /dev/null +++ b/ci/mypy-stubtest-allowlist.txt @@ -0,0 +1,51 @@ +# Non-typed (and private) modules/functions +matplotlib\.backends\..* +matplotlib\.tests(\..*)? +matplotlib\.pylab(\..*)? +matplotlib\._.* +matplotlib\.rcsetup\._listify_validator +matplotlib\.rcsetup\._validate_linestyle +matplotlib\.ft2font\.Glyph +matplotlib\.testing\.jpl_units\..* +matplotlib\.sphinxext(\..*)? + +# set methods have heavy dynamic usage of **kwargs, with differences for subclasses +# which results in technically inconsistent signatures, but not actually a problem +matplotlib\..*\.set$ + +# Typed inline, inconsistencies largely due to imports +matplotlib\.pyplot\..* +matplotlib\.typing\..* + +# Other decorator modifying signature +# Backcompat decorator which does not modify runtime reported signature +matplotlib\.offsetbox\..*Offset[Bb]ox\.get_offset + +# Inconsistent super/sub class parameter name (maybe rename for consistency) +matplotlib\.projections\.polar\.RadialLocator\.nonsingular +matplotlib\.ticker\.LogLocator\.nonsingular +matplotlib\.ticker\.LogitLocator\.nonsingular + +# Stdlib/Enum considered inconsistent (no fault of ours, I don't think) +matplotlib\.backend_bases\._Mode\.__new__ +matplotlib\.units\.Number\.__hash__ + +# 3.6 Pending deprecations +matplotlib\.figure\.Figure\.set_constrained_layout +matplotlib\.figure\.Figure\.set_constrained_layout_pads +matplotlib\.figure\.Figure\.set_tight_layout + +# Maybe should be abstractmethods, required for subclasses, stubs define once +matplotlib\.tri\..*TriInterpolator\.__call__ +matplotlib\.tri\..*TriInterpolator\.gradient + +# TypeVar used only in type hints +matplotlib\.backend_bases\.FigureCanvasBase\._T +matplotlib\.backend_managers\.ToolManager\._T +matplotlib\.spines\.Spine\._T + +# Parameter inconsistency due to 3.10 deprecation +matplotlib\.figure\.FigureBase\.get_figure + +# getitem method only exists for 3.10 deprecation backcompatability +matplotlib\.inset\.InsetIndicator\.__getitem__ diff --git a/ci/osx-deps b/ci/osx-deps deleted file mode 100755 index 2f00bb9ffa67..000000000000 --- a/ci/osx-deps +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -set -euo pipefail -cache="$HOME"/.cache/matplotlib - -fold_start() { - key=$1 - title=$2 - echo -e "travis_fold:start:$key\e[2K" - echo -e "travis_time:start:$key\e[2K" - tick="$(date +%s)" - echo "$title" -} - -fold_end() { - key=$1 - tock="$(date +%s)" - nano=000000000 - echo -e "travis_time:end:$key:start=$tick$nano,finish=$tock$nano,duration=$((tock - tick))$nano\e[2K" - echo -e "travis_fold:end:$key\e[2K" -} - -cached_download() { - file=$1 - url=$2 - shasum=$3 - path="$cache/$file" - if [[ ! -f "$path" - || "$(shasum -a 256 "$path" | awk '{print $1}')" != "$shasum" ]] - then - curl -L -o "$path" "$url" - fi -} - -fold_start Python "Install Python 3.8 from python.org" -cached_download python-3.8.5-macosx10.9.pkg \ - https://www.python.org/ftp/python/3.8.5/python-3.8.5-macosx10.9.pkg \ - e27c5a510c10f830084fb9c60b9e9aa8719d92e4537a80e6b4252c02396f0d29 -sudo installer -package "$cache"/python-3.8.5-macosx10.9.pkg -target / -sudo ln -s /usr/local/bin/python3 /usr/local/bin/python -hash -r -fold_end Python - -fold_start ccache 'Install ccache (compile it ourselves)' -cached_download ccache-3.7.11.tar.xz \ - https://github.com/ccache/ccache/releases/download/v3.7.11/ccache-3.7.11.tar.xz \ - 8d450208099a4d202bd7df87caaec81baee20ce9dd62da91e9ea7b95a9072f68 -tar xf "$cache"/ccache-3.7.11.tar.xz -pushd ccache-3.7.11 -./configure --prefix=/usr/local -make -j2 -make install -popd -for compiler in clang clang++ cc gcc c++ g++; do - ln -sf ccache /usr/local/bin/$compiler -done -fold_end ccache - -fold_start freetype 'Install freetype (just unpack into the build directory)' -cached_download freetype-2.6.1.tar.gz \ - https://download.savannah.gnu.org/releases/freetype/freetype-2.6.1.tar.gz \ - 0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014 -mkdir -p build -tar -x -C build -f "$cache"/freetype-2.6.1.tar.gz -fold_end freetype diff --git a/ci/schemas/README.md b/ci/schemas/README.md new file mode 100644 index 000000000000..087fd31d2ab8 --- /dev/null +++ b/ci/schemas/README.md @@ -0,0 +1,5 @@ +YAML Schemas for linting and validation +======================================= + +Since pre-commit CI doesn't have Internet access, we need to bundle these files +in the repo. The schemas can be updated using `vendor_schemas.py`. diff --git a/ci/schemas/appveyor.json b/ci/schemas/appveyor.json new file mode 100644 index 000000000000..d19a10f23b75 --- /dev/null +++ b/ci/schemas/appveyor.json @@ -0,0 +1,781 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "allOf": [ + { + "$ref": "#/definitions/job" + } + ], + "definitions": { + "possiblySecretString": { + "anyOf": [ + { + "type": "string", + "description": "This value will be used directly (regular string)" + }, + { + "type": "number", + "description": "This value will be treated as a string even though it is a number" + }, + { + "title": "secret string", + "type": "object", + "additionalProperties": false, + "properties": { + "secure": { + "type": "string", + "description": "This should have been encrypted by the same user account to which the project belongs" + } + } + } + ] + }, + "commitFilter": { + "title": "commit filter", + "type": "object", + "additionalProperties": false, + "properties": { + "message": { + "type": "string", + "format": "regex", + "description": "Regex for matching commit message" + }, + "author": { + "description": "Commit author's username, name, email or regexp matching one of these.", + "anyOf": [ + { + "type": "string", + "format": "regex" + }, + { + "type": "string" + } + ] + }, + "files": { + "type": "array", + "description": "Only specific files (glob patterns)", + "items": { + "type": "string" + } + } + } + }, + "command": { + "title": "command", + "oneOf": [ + { + "type": "string", + "description": "Run a batch command" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "ps": { + "type": "string", + "description": "Run a PowerShell command" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "pwsh": { + "type": "string", + "description": "Run a PowerShell Core command" + } + } + }, + { + "type": "object", + "description": "Run a batch command", + "additionalProperties": false, + "properties": { + "cmd": { + "type": "string" + } + } + }, + { + "type": "object", + "description": "Run a Bash command", + "additionalProperties": false, + "properties": { + "sh": { + "type": "string" + } + } + } + ] + }, + "envVarHash": { + "title": "environment variable hash", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/possiblySecretString" + } + }, + "platform": { + "enum": ["x86", "x64", "ARM", "ARM64", "Win32", "Any CPU"] + }, + "configuration": { + "type": "string" + }, + "imageName": { + "enum": [ + "macOS", + "macOS-Mojave", + "macos-bigsur", + "macos-monterey", + "Previous macOS", + "Previous macOS-Mojave", + "Ubuntu", + "Ubuntu1604", + "Ubuntu1804", + "Ubuntu2004", + "Ubuntu2204", + "Previous Ubuntu", + "Previous Ubuntu1604", + "Previous Ubuntu1804", + "Previous Ubuntu2004", + "Visual Studio 2013", + "Visual Studio 2015", + "Visual Studio 2017", + "Visual Studio 2019", + "Visual Studio 2022", + "Visual Studio 2017 Preview", + "Visual Studio 2019 Preview", + "Previous Visual Studio 2013", + "Previous Visual Studio 2015", + "Previous Visual Studio 2017", + "Previous Visual Studio 2019", + "Previous Visual Studio 2022", + "zhaw18", + "WMF 5" + ] + }, + "image": { + "description": "Build worker image (VM template) -DEV_VERSION", + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/imageName" + } + }, + { + "$ref": "#/definitions/imageName" + } + ] + }, + "jobScalars": { + "title": "job scalars", + "type": "object", + "properties": { + "image": { + "$ref": "#/definitions/image" + }, + "platform": { + "description": "Build platform, i.e. x86, x64, Any CPU. This setting is optional", + "oneOf": [ + { + "$ref": "#/definitions/platform" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/platform" + } + } + ] + }, + "configuration": { + "description": "Build Configuration, i.e. Debug, Release, etc.", + "oneOf": [ + { + "$ref": "#/definitions/configuration" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/configuration" + } + } + ] + } + }, + "allOf": [ + { + "not": { + "required": ["skip_tags"] + } + }, + { + "not": { + "required": ["skip_commits"] + } + }, + { + "not": { + "required": ["skip_branch_with_pr"] + } + }, + { + "not": { + "required": ["skip_non_tags"] + } + } + ] + }, + "job": { + "title": "job", + "type": "object", + "properties": { + "version": { + "description": "Version format", + "type": "string" + }, + "branches": { + "title": "branch options", + "type": "object", + "description": "Branches to build", + "additionalProperties": false, + "properties": { + "only": { + "description": "Whitelist", + "type": "array", + "items": { + "type": "string" + } + }, + "except": { + "type": "array", + "description": "Blacklist", + "items": { + "type": "string" + } + } + } + }, + "skip_tags": { + "type": "boolean", + "description": "Do not build on tags (GitHub and BitBucket)" + }, + "skip_non_tags": { + "type": "boolean", + "description": "Start builds on tags only (GitHub and BitBucket)" + }, + "skip_commits": { + "$ref": "#/definitions/commitFilter", + "description": "Skipping commits with particular message or from specific user" + }, + "only_commits": { + "$ref": "#/definitions/commitFilter", + "description": "Including commits with particular message or from specific user" + }, + "skip_branch_with_pr": { + "type": "boolean", + "description": "Do not build feature branch with open Pull Requests" + }, + "max_jobs": { + "description": "Maximum number of concurrent jobs for the project", + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "title": "notification", + "type": "object" + } + }, + "image": { + "$ref": "#/definitions/image" + }, + "init": { + "description": "Scripts that are called at very beginning, before repo cloning", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "clone_folder": { + "type": "string", + "description": "Clone directory" + }, + "shallow_clone": { + "type": "boolean", + "description": "Fetch repository as zip archive", + "default": false + }, + "clone_depth": { + "description": "Set git clone depth", + "type": "integer" + }, + "hosts": { + "title": "host options", + "type": "object", + "description": "Setting up etc\\hosts file", + "additionalProperties": { + "type": "string", + "anyOf": [ + { + "format": "ipv4" + }, + { + "format": "ipv6" + } + ] + } + }, + "environment": { + "description": "Environment variables", + "anyOf": [ + { + "title": "environment options", + "type": "object", + "properties": { + "global": { + "$ref": "#/definitions/envVarHash", + "description": "variables defined here are no different than those defined at top level of 'environment' node" + }, + "matrix": { + "type": "array", + "description": "an array of environment variables, each member of which is one dimension in the build matrix calculation", + "items": { + "$ref": "#/definitions/envVarHash" + } + } + } + }, + { + "$ref": "#/definitions/envVarHash" + } + ] + }, + "matrix": { + "title": "matrix options", + "type": "object", + "additionalProperties": false, + "properties": { + "fast_finish": { + "type": "boolean", + "description": "Set this flag to immediately finish build once one of the jobs fails" + }, + "allow_failures": { + "type": "array", + "description": "This is how to allow failing jobs in the matrix", + "items": { + "$ref": "#/definitions/jobScalars" + } + }, + "exclude": { + "type": "array", + "description": "Exclude configuration from the matrix. Works similarly to 'allow_failures' but build not even being started for excluded combination.", + "items": { + "$ref": "#/definitions/job" + } + } + } + }, + "cache": { + "type": "array", + "description": "Build cache to preserve files/folders between builds", + "items": { + "type": "string" + } + }, + "services": { + "type": "array", + "description": "Enable service required for build/tests", + "items": { + "enum": [ + "docker", + "iis", + "mongodb", + "msmq", + "mssql", + "mssql2008r2sp2", + "mssql2008r2sp2rs", + "mssql2012sp1", + "mssql2012sp1rs", + "mssql2014", + "mssql2014rs", + "mssql2016", + "mssql2017", + "mysql", + "postgresql", + "postgresql93", + "postgresql94", + "postgresql95", + "postgresql96", + "postgresql10", + "postgresql11", + "postgresql12", + "postgresql13" + ] + } + }, + "install": { + "description": "Scripts that run after cloning repository", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "assembly_info": { + "title": "assembly options", + "type": "object", + "description": "Enable patching of AssemblyInfo.* files", + "additionalProperties": false, + "properties": { + "patch": { + "type": "boolean" + }, + "file": { + "type": "string" + }, + "assembly_version": { + "type": "string" + }, + "assembly_file_version": { + "type": "string" + }, + "assembly_informational_version": { + "type": "string" + } + } + }, + "nuget": { + "title": "NuGet options", + "type": "object", + "description": "Automatically register private account and/or project AppVeyor NuGet feeds", + "properties": { + "account_feed": { + "type": "boolean" + }, + "project_feed": { + "type": "boolean" + }, + "disable_publish_on_pr": { + "type": "boolean", + "description": "Disable publishing of .nupkg artifacts to account/project feeds for pull request builds" + } + } + }, + "platform": { + "description": "Build platform, i.e. x86, x64, Any CPU. This setting is optional", + "oneOf": [ + { + "$ref": "#/definitions/platform" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/platform" + } + } + ] + }, + "configuration": { + "description": "Build Configuration, i.e. Debug, Release, etc.", + "oneOf": [ + { + "$ref": "#/definitions/configuration" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/configuration" + } + } + ] + }, + "build": { + "oneOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "title": "build options", + "type": "object", + "additionalProperties": false, + "properties": { + "parallel": { + "type": "boolean", + "description": "Enable MSBuild parallel builds" + }, + "project": { + "type": "string", + "description": "Path to Visual Studio solution or project" + }, + "publish_wap": { + "type": "boolean", + "description": "Package Web Application Projects (WAP) for Web Deploy" + }, + "publish_wap_xcopy": { + "type": "boolean", + "description": "Package Web Application Projects (WAP) for XCopy deployment" + }, + "publish_wap_beanstalk": { + "type": "boolean", + "description": "Package Web Applications for AWS Elastic Beanstalk deployment" + }, + "publish_wap_octopus": { + "type": "boolean", + "description": "Package Web Applications for Octopus deployment" + }, + "publish_azure_webjob": { + "type": "boolean", + "description": "Package Azure WebJobs for Zip Push deployment" + }, + "publish_azure": { + "type": "boolean", + "description": "Package Azure Cloud Service projects and push to artifacts" + }, + "publish_aspnet_core": { + "type": "boolean", + "description": "Package ASP.NET Core projects" + }, + "publish_core_console": { + "type": "boolean", + "description": "Package .NET Core console projects" + }, + "publish_nuget": { + "type": "boolean", + "description": "Package projects with .nuspec files and push to artifacts" + }, + "publish_nuget_symbols": { + "type": "boolean", + "description": "Generate and publish NuGet symbol packages" + }, + "include_nuget_references": { + "type": "boolean", + "description": "Add -IncludeReferencedProjects option while packaging NuGet artifacts" + }, + "verbosity": { + "enum": ["quiet", "minimal", "normal", "detailed"], + "description": "MSBuild verbosity level" + } + } + } + ] + }, + "before_build": { + "description": "Scripts to run before build", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "before_package": { + "description": "Scripts to run *after* solution is built and *before* automatic packaging occurs (web apps, NuGet packages, Azure Cloud Services)", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "after_build": { + "description": "Scripts to run after build", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "build_script": { + "description": "To run your custom scripts instead of automatic MSBuild", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "before_test": { + "description": "Scripts to run before tests", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "test": { + "oneOf": [ + { + "type": "boolean", + "enum": [false], + "description": "To disable automatic tests" + }, + { + "title": "test options", + "description": "To run tests again only selected assemblies and/or categories", + "type": "object", + "additionalProperties": false, + "properties": { + "assemblies": { + "title": "assembly options", + "type": "object", + "additionalProperties": false, + "properties": { + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "except": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "categories": { + "oneOf": [ + { + "title": "category options", + "type": "object", + "additionalProperties": false, + "properties": { + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "except": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "description": "To run tests from different categories as separate jobs in parallel", + "type": "array", + "items": { + "oneOf": [ + { + "type": "string", + "description": "A category common for all jobs" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + ] + } + } + } + ] + }, + "test_script": { + "description": "To run your custom scripts instead of automatic tests", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "after_test": { + "type": "array", + "description": "Scripts to run after tests", + "items": { + "$ref": "#/definitions/command" + } + }, + "artifacts": { + "type": "array", + "items": { + "title": "artifact options", + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "Auto", + "WebDeployPackage", + "NuGetPackage", + "AzureCloudService", + "AzureCloudServiceConfig", + "SsdtPackage", + "Zip", + "File" + ] + } + }, + "required": ["path"] + } + }, + "before_deploy": { + "type": "array", + "description": "Scripts to run before deployment", + "items": { + "$ref": "#/definitions/command" + } + }, + "deploy": { + "oneOf": [ + { + "enum": ["off"] + }, + { + "type": "array", + "items": { + "title": "deployment options", + "type": "object" + } + } + ] + }, + "deploy_script": { + "description": "To run your custom scripts instead of provider deployments", + "type": "array", + "items": { + "$ref": "#/definitions/command" + } + }, + "after_deploy": { + "type": "array", + "description": "Scripts to run after deployment", + "items": { + "$ref": "#/definitions/command" + } + }, + "on_success": { + "type": "array", + "description": "On successful build", + "items": { + "$ref": "#/definitions/command" + } + }, + "on_failure": { + "type": "array", + "description": "On build failure", + "items": { + "$ref": "#/definitions/command" + } + }, + "on_finish": { + "type": "array", + "description": "After build failure or success", + "items": { + "$ref": "#/definitions/command" + } + } + } + } + }, + "id": "https://json.schemastore.org/appveyor.json", + "title": "JSON schema for AppVeyor CI configuration files" +} diff --git a/ci/schemas/circleciconfig.json b/ci/schemas/circleciconfig.json new file mode 100644 index 000000000000..076944098440 --- /dev/null +++ b/ci/schemas/circleciconfig.json @@ -0,0 +1,1411 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/circleciconfig.json", + "definitions": { + "logical": { + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nA logical statement to be used in dynamic configuration", + "oneOf": [ + { + "type": ["string", "boolean", "integer", "number"] + }, + { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "maxProperties": 1, + "properties": { + "and": { + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nLogical and: true when all statements in the list are true", + "type": "array", + "items": { + "$ref": "#/definitions/logical" + } + }, + "or": { + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nLogical or: true when at least one statements in the list is true", + "type": "array", + "items": { + "$ref": "#/definitions/logical" + } + }, + "not": { + "$ref": "#/definitions/logical", + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nLogical not: true when statement is false" + }, + "equal": { + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nTrue when all elements in the list are equal", + "type": "array" + }, + "matches": { + "description": "https://circleci.com/docs/configuration-reference#logic-statements \n\nTrue when value matches the pattern", + "type": "object", + "additionalProperties": false, + "properties": { + "pattern": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + ] + }, + "filter": { + "description": "A map defining rules for execution on specific branches", + "type": "object", + "additionalProperties": false, + "properties": { + "only": { + "description": "Either a single branch specifier, or a list of branch specifiers", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ignore": { + "description": "Either a single branch specifier, or a list of branch specifiers", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "orbs": { + "description": "https://circleci.com/docs/configuration-reference#orbs-requires-version-21\n\nOrbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "description": "https://circleci.com/docs/creating-orbs#semantic-versioning-in-orbs\n\nAn orb to depend on and its semver range, or volatile for the most recent release.", + "type": "string", + "pattern": "^[a-z][a-z0-9_-]+/[a-z][a-z0-9_-]+@(dev:[\\.a-z0-9_-]+|\\d+|\\d+\\.\\d+|\\d+\\.\\d+\\.\\d+|volatile)$" + }, + { + "description": "https://circleci.com/docs/creating-orbs#creating-inline-orbs\n\nInline orbs can be handy during development of an orb or as a convenience for name-spacing jobs and commands in lengthy configurations, particularly if you later intend to share the orb with others.", + "type": "object", + "properties": { + "orbs": { + "$ref": "#/definitions/orbs" + }, + "commands": { + "$ref": "#/definitions/commands" + }, + "executors": { + "$ref": "#/definitions/executors" + }, + "jobs": { + "$ref": "#/definitions/jobs" + } + } + } + ] + } + }, + "commands": { + "description": "https://circleci.com/docs/configuration-reference#commands-requires-version-21\n\nA command definition defines a sequence of steps as a map to be executed in a job, enabling you to reuse a single command definition across multiple jobs.", + "type": "object", + "additionalProperties": { + "description": "https://circleci.com/docs/configuration-reference#commands-requires-version-21\n\nDefinition of a custom command.", + "type": "object", + "required": ["steps"], + "properties": { + "steps": { + "description": "A sequence of steps run inside the calling job of the command.", + "type": "array", + "items": { + "$ref": "#/definitions/step" + } + }, + "parameters": { + "description": "https://circleci.com/docs/reusing-config#using-the-parameters-declaration\n\nA map of parameter keys.", + "type": "object", + "patternProperties": { + "^[a-z][a-z0-9_-]+$": { + "oneOf": [ + { + "description": "https://circleci.com/docs/reusing-config#string\n\nA string parameter.", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["string"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "string" + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#boolean\n\nA boolean parameter.", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["boolean"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "boolean" + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#integer\n\nAn integer parameter.", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["integer"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "integer" + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#enum\n\nThe `enum` parameter may be a list of any values. Use the `enum` parameter type when you want to enforce that the value must be one from a specific set of string values.", + "type": "object", + "required": ["type", "enum"], + "properties": { + "type": { + "enum": ["enum"] + }, + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "description": { + "type": "string" + }, + "default": { + "type": "string" + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#executor\n\nUse an `executor` parameter type to allow the invoker of a job to decide what executor it will run on.", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["executor"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "string" + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#steps\n\nSteps are used when you have a job or command that needs to mix predefined and user-defined steps. When passed in to a command or job invocation, the steps passed as parameters are always defined as a sequence, even if only one step is provided.", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["steps"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "array", + "items": { + "$ref": "#/definitions/step" + } + } + } + }, + { + "description": "https://circleci.com/docs/reusing-config#environment-variable-name\n\nThe environment variable name parameter is a string that must match a POSIX_NAME regexp (e.g. no spaces or special characters) and is a more meaningful parameter type that enables additional checks to be performed. ", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "enum": ["env_var_name"] + }, + "description": { + "type": "string" + }, + "default": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]+$" + } + } + } + ] + } + } + }, + "description": { + "description": "A string that describes the purpose of the command.", + "type": "string" + } + } + } + }, + "dockerLayerCaching": { + "description": "Set to `true` to enable [Docker Layer Caching](https://circleci.com/docs/docker-layer-caching). Note: If you haven't already, you must open a support ticket to have a CircleCI Sales representative contact you about enabling this feature on your account for an additional fee.", + "type": "boolean", + "default": "true" + }, + "dockerExecutor": { + "description": "Options for the [docker executor](https://circleci.com/docs/configuration-reference#docker)", + "required": ["docker"], + "properties": { + "docker": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "image": { + "description": "The name of a custom docker image to use", + "type": "string" + }, + "name": { + "description": "The name the container is reachable by. By default, container services are accessible through `localhost`", + "type": "string" + }, + "entrypoint": { + "description": "The command used as executable when launching the container", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "command": { + "description": "The command used as pid 1 (or args for entrypoint) when launching the container", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user": { + "description": "Which user to run the command as", + "type": "string" + }, + "environment": { + "description": "A map of environment variable names and values", + "type": "object", + "additionalProperties": { + "type": ["string", "number", "boolean"] + } + }, + "auth": { + "description": "Authentication for registries using standard `docker login` credentials", + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "aws_auth": { + "description": "Authentication for AWS EC2 Container Registry (ECR). You can use the access/secret keys or OIDC.", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_access_key_id": { + "type": "string" + }, + "aws_secret_access_key": { + "type": "string" + }, + "oidc_role_arn": { + "type": "string" + } + } + } + } + } + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. Note: A performance plan is required to access this feature.", + "type": "string", + "enum": [ + "small", + "medium", + "medium+", + "large", + "xlarge", + "2xlarge", + "2xlarge+", + "arm.medium", + "arm.large", + "arm.xlarge", + "arm.2xlarge" + ] + } + } + }, + "machineExecutor": { + "description": "Options for the [machine executor](https://circleci.com/docs/configuration-reference#machine)", + "type": "object", + "required": ["machine"], + "oneOf": [ + { + "properties": { + "machine": { + "oneOf": [ + { "const": "default" }, + { "const": true }, + { + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "image": { + "description": "The VM image to use. View [available images](https://circleci.com/docs/configuration-reference/#available-linux-machine-images-cloud). **Note:** This key is **not** supported on the installable CircleCI. For information about customizing machine executor images on CircleCI installed on your servers, see our [VM Service documentation](https://circleci.com/docs/vm-service).", + "type": "string", + "enum": [ + "ubuntu-2004:2023.10.1", + "ubuntu-2004:2023.07.1", + "ubuntu-2004:2023.04.2", + "ubuntu-2004:2023.04.1", + "ubuntu-2004:2023.02.1", + "ubuntu-2004:2022.10.1", + "ubuntu-2004:2022.07.1", + "ubuntu-2004:2022.04.2", + "ubuntu-2004:2022.04.1", + "ubuntu-2004:202201-02", + "ubuntu-2004:202201-01", + "ubuntu-2004:202111-02", + "ubuntu-2004:202111-01", + "ubuntu-2004:202107-02", + "ubuntu-2004:202104-01", + "ubuntu-2004:202101-01", + "ubuntu-2004:202010-01", + "ubuntu-2004:current", + "ubuntu-2004:edge", + "ubuntu-2204:2023.10.1", + "ubuntu-2204:2023.07.2", + "ubuntu-2204:2023.04.2", + "ubuntu-2204:2023.04.1", + "ubuntu-2204:2023.02.1", + "ubuntu-2204:2022.10.2", + "ubuntu-2204:2022.10.1", + "ubuntu-2204:2022.07.2", + "ubuntu-2204:2022.07.1", + "ubuntu-2204:2022.04.2", + "ubuntu-2204:2022.04.1", + "ubuntu-2204:current", + "ubuntu-2204:edge", + "android:2023.11.1", + "android:2023.10.1", + "android:2023.09.1", + "android:2023.08.1", + "android:2023.07.1", + "android:2023.06.1", + "android:2023.05.1", + "android:2023.04.1", + "android:2023.03.1", + "android:2023.02.1", + "android:2022.12.1", + "android:2022.09.1", + "android:2022.08.1", + "android:2022.07.1", + "android:2022.06.2", + "android:2022.06.1", + "android:2022.04.1", + "android:2022.03.1", + "android:2022.01.1", + "android:2021.12.1", + "android:2021.10.1", + "android:202102-01" + ] + }, + "docker_layer_caching": { + "$ref": "#/definitions/dockerLayerCaching" + } + } + } + ] + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. View [available resource classes](https://circleci.com/docs/configuration-reference/#linuxvm-execution-environment)", + "type": "string", + "enum": [ + "medium", + "large", + "xlarge", + "2xlarge", + "2xlarge+", + "arm.medium", + "arm.large", + "arm.xlarge", + "arm.2xlarge" + ] + } + } + }, + { + "properties": { + "machine": { + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "image": { + "description": "The VM image to use. View [available images](https://circleci.com/docs/configuration-reference/#available-linux-gpu-images). **Note:** This key is **not** supported on the installable CircleCI. For information about customizing machine executor images on CircleCI installed on your servers, see our [VM Service documentation](https://circleci.com/docs/vm-service).", + "type": "string", + "enum": ["linux-cuda-11:default", "linux-cuda-12:default"] + }, + "docker_layer_caching": { + "$ref": "#/definitions/dockerLayerCaching" + } + } + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. View [available resource classes](https://circleci.com/docs/configuration-reference/#gpu-execution-environment-linux)", + "type": "string", + "enum": ["gpu.nvidia.medium", "gpu.nvidia.large"] + } + } + }, + { + "properties": { + "machine": { + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "image": { + "description": "The VM image to use. View [available images](https://circleci.com/docs/configuration-reference/#available-windows-machine-images-cloud). **Note:** This key is **not** supported on the installable CircleCI. For information about customizing machine executor images on CircleCI installed on your servers, see our [VM Service documentation](https://circleci.com/docs/vm-service).", + "type": "string", + "enum": [ + "windows-server-2022-gui:2023.10.1", + "windows-server-2022-gui:2023.09.1", + "windows-server-2022-gui:2023.08.1", + "windows-server-2022-gui:2023.07.1", + "windows-server-2022-gui:2023.06.1", + "windows-server-2022-gui:2023.05.1", + "windows-server-2022-gui:2023.04.1", + "windows-server-2022-gui:2023.03.1", + "windows-server-2022-gui:2022.08.1", + "windows-server-2022-gui:2022.07.1", + "windows-server-2022-gui:2022.06.1", + "windows-server-2022-gui:2022.04.1", + "windows-server-2022-gui:current", + "windows-server-2022-gui:edge", + "windows-server-2019:2023.10.1", + "windows-server-2019:2023.08.1", + "windows-server-2019:2023.04.1", + "windows-server-2019:2022.08.1", + "windows-server-2019:current", + "windows-server-2019:edge" + ] + }, + "docker_layer_caching": { + "$ref": "#/definitions/dockerLayerCaching" + } + } + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. View [available resource classes](https://circleci.com/docs/configuration-reference/#windows-execution-environment)", + "type": "string", + "enum": [ + "windows.medium", + "windows.large", + "windows.xlarge", + "windows.2xlarge" + ] + } + } + }, + { + "properties": { + "machine": { + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "image": { + "description": "The VM image to use. View [available images](https://circleci.com/docs/configuration-reference/#available-windows-gpu-image). **Note:** This key is **not** supported on the installable CircleCI. For information about customizing machine executor images on CircleCI installed on your servers, see our [VM Service documentation](https://circleci.com/docs/vm-service).", + "type": "string", + "enum": [ + "windows-server-2019-cuda:current", + "windows-server-2019-cuda:edge" + ] + }, + "docker_layer_caching": { + "$ref": "#/definitions/dockerLayerCaching" + } + } + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. View [available resource classes](https://circleci.com/docs/configuration-reference/#gpu-execution-environment-windows)", + "type": "string", + "enum": ["windows.gpu.nvidia.medium"] + } + } + } + ] + }, + "macosExecutor": { + "description": "Options for the [macOS executor](https://circleci.com/docs/configuration-reference#macos)", + "type": "object", + "required": ["macos"], + "properties": { + "macos": { + "type": "object", + "additionalProperties": false, + "required": ["xcode"], + "properties": { + "xcode": { + "description": "The version of Xcode that is installed on the virtual machine, see the [Supported Xcode Versions section of the Testing iOS](https://circleci.com/docs/testing-ios#supported-xcode-versions) document for the complete list.", + "type": "string", + "enum": [ + "15.2.0", + "15.1.0", + "15.0.0", + "14.3.1", + "14.2.0", + "14.1.0", + "14.0.1", + "13.4.1", + "12.5.1" + ] + } + } + }, + "resource_class": { + "description": "Amount of CPU and RAM allocated for each job. View [available resource classes](https://circleci.com/docs/configuration-reference/#macos-execution-environment)", + "type": "string", + "enum": [ + "macos.x86.medium.gen2", + "macos.m1.medium.gen1", + "macos.m1.large.gen1" + ] + } + } + }, + "executorChoice": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/dockerExecutor" + }, + { + "$ref": "#/definitions/machineExecutor" + }, + { + "$ref": "#/definitions/macosExecutor" + } + ] + }, + "executors": { + "description": "Executors define the environment in which the steps of a job will be run, allowing you to reuse a single executor definition across multiple jobs.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/executorChoice", + "type": "object", + "properties": { + "shell": { + "description": "Shell to use for execution command in all steps. Can be overridden by shell in each step (default: See [Default Shell Options](https://circleci.com/docs/configuration-reference#default-shell-options)", + "type": "string" + }, + "working_directory": { + "description": "In which directory to run the steps.", + "type": "string" + }, + "environment": { + "description": "A map of environment variable names and values.", + "type": "object", + "additionalProperties": { + "type": ["string", "number"] + } + } + } + } + }, + "builtinSteps": { + "documentation": { + "run": { + "description": "https://circleci.com/docs/configuration-reference#run\n\nUsed for invoking all command-line programs, taking either a map of configuration values, or, when called in its short-form, a string that will be used as both the `command` and `name`. Run commands are executed using non-login shells by default, so you must explicitly source any dotfiles as part of the command." + }, + "checkout": { + "description": "https://circleci.com/docs/configuration-reference#checkout\n\nSpecial step used to check out source code to the configured `path` (defaults to the `working_directory`). The reason this is a special step is because it is more of a helper function designed to make checking out code easy for you. If you require doing git over HTTPS you should not use this step as it configures git to checkout over ssh." + }, + "setup_remote_docker": { + "description": "https://circleci.com/docs/configuration-reference#setup_remote_docker\n\nCreates a remote Docker environment configured to execute Docker commands." + }, + "save_cache": { + "description": "https://circleci.com/docs/configuration-reference#save_cache\n\nGenerates and stores a cache of a file or directory of files such as dependencies or source code in our object storage. Later jobs can restore this cache using the `restore_cache` step." + }, + "restore_cache": { + "description": "https://circleci.com/docs/configuration-reference#restore_cache\n\nRestores a previously saved cache based on a `key`. Cache needs to have been saved first for this key using the `save_cache` step." + }, + "deploy": { + "description": "https://circleci.com/docs/configuration-reference#deploy\n\nSpecial step for deploying artifacts. `deploy` uses the same configuration map and semantics as run step. Jobs may have more than one deploy step. In general deploy step behaves just like run with two exceptions:\n* In a job with parallelism, the deploy step will only be executed by node #0 and only if all nodes succeed. Nodes other than #0 will skip this step.\n* In a job that runs with SSH, the deploy step will not execute" + }, + "store_artifacts": { + "description": "https://circleci.com/docs/configuration-reference#store_artifacts\n\nStep to store artifacts (for example logs, binaries, etc) to be available in the web app or through the API." + }, + "store_test_results": { + "description": "https://circleci.com/docs/configuration-reference#storetestresults\n\nSpecial step used to upload test results so they display in builds' Test Summary section and can be used for timing analysis. To also see test result as build artifacts, please use the `store_artifacts` step." + }, + "persist_to_workspace": { + "description": "https://circleci.com/docs/configuration-reference#persist_to_workspace\n\nSpecial step used to persist a temporary file to be used by another job in the workflow" + }, + "attach_workspace": { + "description": "https://circleci.com/docs/configuration-reference#attach_workspace\n\nSpecial step used to attach the workflow's workspace to the current container. The full contents of the workspace are downloaded and copied into the directory the workspace is being attached at." + }, + "add_ssh_keys": { + "description": "https://circleci.com/docs/configuration-reference#add_ssh_keys\n\nSpecial step that adds SSH keys from a project's settings to a container. Also configures SSH to use these keys." + }, + "when": { + "description": "https://circleci.com/docs/configuration-reference#the-when-step-requires-version-21 \n\nConditional step to run on custom conditions (determined at config-compile time) that are checked before a workflow runs" + }, + "unless": { + "description": "https://circleci.com/docs/configuration-reference#the-when-step-requires-version-21 \n\nConditional step to run when custom conditions aren't met (determined at config-compile time) that are checked before a workflow runs" + } + }, + "configuration": { + "run": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/run" + } + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "required": ["command"], + "properties": { + "command": { + "description": "Command to run via the shell", + "type": "string" + }, + "name": { + "description": "Title of the step to be shown in the CircleCI UI (default: full `command`)", + "type": "string" + }, + "shell": { + "description": "Shell to use for execution command", + "type": "string" + }, + "environment": { + "description": "Additional environmental variables, locally scoped to command", + "type": "object", + "additionalProperties": { + "type": ["string", "number"] + } + }, + "background": { + "description": "Whether or not this step should run in the background (default: false)", + "default": false, + "type": "boolean" + }, + "working_directory": { + "description": "In which directory to run this step (default: `working_directory` of the job", + "type": "string" + }, + "no_output_timeout": { + "description": "Elapsed time the command can run without output. The string is a decimal with unit suffix, such as \"20m\", \"1.25h\", \"5s\" (default: 10 minutes)", + "type": "string", + "pattern": "\\d+(\\.\\d+)?[mhs]", + "default": "10m" + }, + "when": { + "description": "Specify when to enable or disable the step. Takes the following values: `always`, `on_success`, `on_fail` (default: `on_success`)", + "enum": ["always", "on_success", "on_fail"] + } + } + } + ] + }, + "checkout": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/checkout" + } + ], + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "path": { + "description": "Checkout directory (default: job's `working_directory`)", + "type": "string" + } + } + }, + "setup_remote_docker": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/setup_remote_docker" + } + ], + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "docker_layer_caching": { + "description": "When `docker_layer_caching` is set to `true`, CircleCI will try to reuse Docker Images (layers) built during a previous job or workflow (Paid feature)", + "type": "boolean", + "default": false + }, + "version": { + "description": "If your build requires a specific docker image, you can set it as an image attribute", + "anyOf": [ + { + "type": "string", + "enum": [ + "20.10.24", + "20.10.23", + "20.10.18", + "20.10.17", + "20.10.14", + "20.10.12", + "20.10.11", + "20.10.7", + "20.10.6", + "20.10.2", + "19.03.13" + ] + }, + { + "type": "string" + } + ] + } + } + }, + "save_cache": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/save_cache" + } + ], + "type": "object", + "additionalProperties": false, + "required": ["paths", "key"], + "properties": { + "paths": { + "description": "List of directories which should be added to the cache", + "type": "array", + "items": { + "type": "string" + } + }, + "key": { + "description": "Unique identifier for this cache", + "type": "string" + }, + "name": { + "type": "string", + "description": "Title of the step to be shown in the CircleCI UI (default: 'Saving Cache')" + }, + "when": { + "description": "Specify when to enable or disable the step. Takes the following values: `always`, `on_success`, `on_fail` (default: `on_success`)", + "enum": ["always", "on_success", "on_fail"] + } + } + }, + "restore_cache": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/restore_cache" + } + ], + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["key"], + "properties": { + "key": { + "type": "string", + "description": "Single cache key to restore" + }, + "name": { + "type": "string", + "description": "Title of the step to be shown in the CircleCI UI (default: 'Restoring Cache')" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["keys"], + "properties": { + "name": { + "type": "string", + "description": "Title of the step to be shown in the CircleCI UI (default: 'Restoring Cache')" + }, + "keys": { + "description": "List of cache keys to lookup for a cache to restore. Only first existing key will be restored.", + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + }, + "deploy": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/deploy" + }, + { + "$ref": "#/definitions/builtinSteps/configuration/run" + } + ] + }, + "store_artifacts": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/store_artifacts" + } + ], + "type": "object", + "additionalProperties": false, + "required": ["path"], + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "path": { + "description": "Directory in the primary container to save as job artifacts", + "type": "string" + }, + "destination": { + "description": "Prefix added to the artifact paths in the artifacts API (default: the directory of the file specified in `path`)", + "type": "string" + } + } + }, + "store_test_results": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/store_test_results" + } + ], + "type": "object", + "additionalProperties": false, + "required": ["path"], + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "path": { + "description": "Path (absolute, or relative to your `working_directory`) to directory containing subdirectories of JUnit XML or Cucumber JSON test metadata files", + "type": "string" + } + } + }, + "persist_to_workspace": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/persist_to_workspace" + } + ], + "type": "object", + "additionalProperties": false, + "required": ["root", "paths"], + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "root": { + "description": "Either an absolute path or a path relative to `working_directory`", + "type": "string" + }, + "paths": { + "description": "Glob identifying file(s), or a non-glob path to a directory to add to the shared workspace. Interpreted as relative to the workspace root. Must not be the workspace root itself.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "attach_workspace": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/attach_workspace" + } + ], + "type": "object", + "additionalProperties": false, + "required": ["at"], + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "at": { + "description": "Directory to attach the workspace to", + "type": "string" + } + } + }, + "add_ssh_keys": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/add_ssh_keys" + } + ], + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Title of the step to be shown in the CircleCI UI", + "type": "string" + }, + "fingerprints": { + "description": "Directory to attach the workspace to", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "when": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/when" + } + ], + "type": "object", + "additionalProperties": false, + "properties": { + "condition": { + "$ref": "#/definitions/logical" + }, + "steps": { + "description": "A list of steps to be performed", + "type": "array", + "items": { + "$ref": "#/definitions/step" + } + } + }, + "required": ["condition", "steps"] + }, + "unless": { + "allOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/unless" + } + ], + "type": "object", + "additionalProperties": false, + "properties": { + "condition": { + "$ref": "#/definitions/logical" + }, + "steps": { + "description": "A list of steps to be performed", + "type": "array", + "items": { + "$ref": "#/definitions/step" + } + } + }, + "required": ["condition", "steps"] + } + } + }, + "step": { + "anyOf": [ + { + "$ref": "#/definitions/builtinSteps/documentation/checkout", + "enum": ["checkout"] + }, + { + "$ref": "#/definitions/builtinSteps/documentation/setup_remote_docker", + "enum": ["setup_remote_docker"] + }, + { + "$ref": "#/definitions/builtinSteps/documentation/add_ssh_keys", + "enum": ["add_ssh_keys"] + }, + { + "description": "https://circleci.com/docs/reusing-config#invoking-reusable-commands\n\nA custom command defined via the top level commands key", + "type": "string", + "pattern": "^[a-z][a-z0-9_-]+$" + }, + { + "description": "https://circleci.com/docs/using-orbs#commands\n\nA custom command defined via an orb.", + "type": "string", + "pattern": "^[a-z][a-z0-9_-]+/[a-z][a-z0-9_-]+$" + }, + { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "run": { + "$ref": "#/definitions/builtinSteps/configuration/run" + }, + "checkout": { + "$ref": "#/definitions/builtinSteps/configuration/checkout" + }, + "setup_remote_docker": { + "$ref": "#/definitions/builtinSteps/configuration/setup_remote_docker" + }, + "save_cache": { + "$ref": "#/definitions/builtinSteps/configuration/save_cache" + }, + "restore_cache": { + "$ref": "#/definitions/builtinSteps/configuration/restore_cache" + }, + "deploy": { + "$ref": "#/definitions/builtinSteps/configuration/deploy" + }, + "store_artifacts": { + "$ref": "#/definitions/builtinSteps/configuration/store_artifacts" + }, + "store_test_results": { + "$ref": "#/definitions/builtinSteps/configuration/store_test_results" + }, + "persist_to_workspace": { + "$ref": "#/definitions/builtinSteps/configuration/persist_to_workspace" + }, + "attach_workspace": { + "$ref": "#/definitions/builtinSteps/configuration/attach_workspace" + }, + "add_ssh_keys": { + "$ref": "#/definitions/builtinSteps/configuration/add_ssh_keys" + }, + "when": { + "$ref": "#/definitions/builtinSteps/configuration/when" + }, + "unless": { + "$ref": "#/definitions/builtinSteps/configuration/unless" + } + }, + "patternProperties": { + "^[a-z][a-z0-9_-]+$": { + "description": "https://circleci.com/docs/reusing-config#invoking-reusable-commands\n\nA custom command defined via the top level commands key" + }, + "^[a-z][a-z0-9_-]+/[a-z][a-z0-9_-]+$": { + "description": "https://circleci.com/docs/using-orbs#commands\n\nA custom command defined via an orb." + } + } + } + ] + }, + "jobRef": { + "description": "Run a job as part of this workflow", + "type": "object", + "additionalProperties": true, + "properties": { + "requires": { + "description": "Jobs are run in parallel by default, so you must explicitly require any dependencies by their job name.", + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "description": "The name key can be used to ensure build numbers are not appended when invoking the same job multiple times (e.g., sayhello-1, sayhello-2). The name assigned needs to be unique, otherwise numbers will still be appended to the job name", + "type": "string" + }, + "context": { + "description": "Either a single context name, or a list of contexts. The default name is `org-global`", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": "org-global" + }, + "type": { + "description": "A job may have a `type` of `approval` indicating it must be manually approved before downstream jobs may proceed.", + "enum": ["approval"] + }, + "filters": { + "description": "A map defining rules for execution on specific branches", + "type": "object", + "additionalProperties": false, + "properties": { + "branches": { + "$ref": "#/definitions/filter" + }, + "tags": { + "$ref": "#/definitions/filter" + } + } + }, + "matrix": { + "description": "https://circleci.com/docs/configuration-reference#matrix-requires-version-21\n\nThe matrix stanza allows you to run a parameterized job multiple times with different arguments.", + "type": "object", + "additionalProperties": false, + "required": ["parameters"], + "properties": { + "parameters": { + "description": "A map of parameter names to every value the job should be called with", + "type": "object", + "additionalProperties": { + "type": "array" + } + }, + "exclude": { + "description": "A list of argument maps that should be excluded from the matrix", + "type": "array", + "items": { + "type": "object" + } + }, + "alias": { + "description": "An alias for the matrix, usable from another job's requires stanza. Defaults to the name of the job being executed", + "type": "string" + } + } + } + } + }, + "jobs": { + "description": "Jobs are collections of steps. All of the steps in the job are executed in a single unit, either within a fresh container or VM.", + "type": "object", + "additionalProperties": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/executorChoice" + }, + { + "type": "object", + "required": ["executor"], + "properties": { + "executor": { + "description": "The name of the executor to use (defined via the top level executors map).", + "type": "string" + } + } + }, + { + "type": "object", + "required": ["executor"], + "properties": { + "executor": { + "description": "Executor stanza to use for the job", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The name of the executor to use (defined via the top level executors map).", + "type": "string" + } + } + } + } + } + ], + "required": ["steps"], + "properties": { + "shell": { + "description": "Shell to use for execution command in all steps. Can be overridden by shell in each step", + "type": "string" + }, + "steps": { + "description": "A list of steps to be performed", + "type": "array", + "items": { + "$ref": "#/definitions/step" + } + }, + "working_directory": { + "description": "In which directory to run the steps. (default: `~/project`. `project` is a literal string, not the name of the project.) You can also refer the directory with `$CIRCLE_WORKING_DIRECTORY` environment variable.", + "type": "string", + "default": "~/project" + }, + "parallelism": { + "description": "Number of parallel instances of this job to run (default: 1)", + "default": 1, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string", + "pattern": "^<<.+\\..+>>$" + } + ] + }, + "environment": { + "description": "A map of environment variable names and variables (NOTE: these will override any environment variables you set in the CircleCI web interface).", + "type": "object", + "additionalProperties": { + "type": ["string", "number"] + } + }, + "branches": { + "description": "A map defining rules for whitelisting/blacklisting execution of specific branches for a single job that is **not** in a workflow (default: all whitelisted). See Workflows for configuring branch execution for jobs in a workflow.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "properties": { + "version": { + "description": "The version field is intended to be used in order to issue warnings for deprecation or breaking changes.", + "default": 2.1, + "enum": [2, 2.1] + }, + "orbs": { + "$ref": "#/definitions/orbs" + }, + "commands": { + "$ref": "#/definitions/commands" + }, + "executors": { + "$ref": "#/definitions/executors" + }, + "jobs": { + "$ref": "#/definitions/jobs" + }, + "workflows": { + "description": "Used for orchestrating all jobs. Each workflow consists of the workflow name as a key and a map as a value", + "type": "object", + "properties": { + "version": { + "description": "The Workflows `version` field is used to issue warnings for deprecation or breaking changes during v2 Beta. It is deprecated as of CircleCI v2.1", + "enum": [2] + } + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "triggers": { + "description": "Specifies which triggers will cause this workflow to be executed. Default behavior is to trigger the workflow when pushing to a branch.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "schedule": { + "description": "A workflow may have a schedule indicating it runs at a certain time, for example a nightly build that runs every day at 12am UTC:", + "type": "object", + "properties": { + "cron": { + "description": "See the [crontab man page](http://pubs.opengroup.org/onlinepubs/7908799/xcu/crontab.html)", + "type": "string" + }, + "filters": { + "description": "A map defining rules for execution on specific branches", + "type": "object", + "additionalProperties": false, + "properties": { + "branches": { + "$ref": "#/definitions/filter" + } + } + } + } + } + } + } + }, + "jobs": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/jobRef", + "type": "object" + } + } + ] + } + }, + "when": { + "$ref": "#/definitions/logical", + "description": "Specify when to run the workflow." + }, + "unless": { + "$ref": "#/definitions/logical", + "description": "Specify when *not* to run the workflow." + } + } + } + } + }, + "required": ["version"], + "title": "JSON schema for CircleCI configuration files", + "type": "object" +} diff --git a/ci/schemas/codecov.json b/ci/schemas/codecov.json new file mode 100644 index 000000000000..98decea44415 --- /dev/null +++ b/ci/schemas/codecov.json @@ -0,0 +1,620 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/codecov", + "definitions": { + "default": { + "$comment": "See https://docs.codecov.com/docs/commit-status#basic-configuration", + "properties": { + "target": { + "type": ["string", "number"], + "pattern": "^(([0-9]+\\.?[0-9]*|\\.[0-9]+)%?|auto)$", + "default": "auto" + }, + "threshold": { + "type": "string", + "default": "0%", + "pattern": "^([0-9]+\\.?[0-9]*|\\.[0-9]+)%?$" + }, + "base": { + "type": "string", + "default": "auto", + "deprecated": true + }, + "flags": { + "type": "array", + "default": [] + }, + "paths": { + "type": ["array", "string"], + "default": [] + }, + "branches": { + "type": "array", + "default": [] + }, + "if_not_found": { + "type": "string", + "enum": ["failure", "success"], + "default": "success" + }, + "informational": { + "type": "boolean", + "default": false + }, + "only_pulls": { + "type": "boolean", + "default": false + }, + "if_ci_failed": { + "type": "string", + "enum": ["error", "success"] + }, + "flag_coverage_not_uploaded_behavior": { + "type": "string", + "enum": ["include", "exclude", "pass"] + } + } + }, + "flag": { + "type": "object", + "properties": { + "joined": { + "type": "boolean" + }, + "required": { + "type": "boolean" + }, + "ignore": { + "type": "array", + "items": { + "type": "string" + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "assume": { + "type": ["boolean", "array"], + "items": { + "type": "string" + } + } + } + }, + "layout": { + "anyOf": [ + {}, + { + "enum": [ + "header", + "footer", + "diff", + "file", + "files", + "flag", + "flags", + "reach", + "sunburst", + "uncovered" + ] + } + ] + }, + "notification": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "description": "Schema for codecov.yml files.", + "properties": { + "codecov": { + "description": "See https://docs.codecov.io/docs/codecov-yaml for details", + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "bot": { + "description": "Team bot. See https://docs.codecov.io/docs/team-bot for details", + "type": "string" + }, + "branch": { + "type": "string" + }, + "ci": { + "description": "Detecting CI services. See https://docs.codecov.io/docs/detecting-ci-services for details.", + "type": "array", + "items": { + "type": "string" + } + }, + "assume_all_flags": { + "type": "boolean" + }, + "strict_yaml_branch": { + "type": "string" + }, + "max_report_age": { + "type": ["string", "integer", "boolean"] + }, + "disable_default_path_fixes": { + "type": "boolean" + }, + "require_ci_to_pass": { + "type": "boolean" + }, + "allow_pseudo_compare": { + "type": "boolean" + }, + "archive": { + "type": "object", + "properties": { + "uploads": { + "type": "boolean" + } + } + }, + "notify": { + "type": "object", + "properties": { + "after_n_builds": { + "type": "integer" + }, + "countdown": { + "type": "integer" + }, + "delay": { + "type": "integer" + }, + "wait_for_ci": { + "type": "boolean" + } + } + }, + "ui": { + "type": "object", + "properties": { + "hide_density": { + "type": ["boolean", "array"], + "items": { + "type": "string" + } + }, + "hide_complexity": { + "type": ["boolean", "array"], + "items": { + "type": "string" + } + }, + "hide_contextual": { + "type": "boolean" + }, + "hide_sunburst": { + "type": "boolean" + }, + "hide_search": { + "type": "boolean" + } + } + } + } + }, + "coverage": { + "description": "Coverage configuration. See https://docs.codecov.io/docs/coverage-configuration for details.", + "type": "object", + "properties": { + "precision": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "round": { + "enum": ["down", "up", "nearest"] + }, + "range": { + "type": "string" + }, + "notify": { + "description": "Notifications. See https://docs.codecov.io/docs/notifications for details.", + "type": "object", + "properties": { + "irc": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "channel": { + "type": "string" + }, + "password": { + "type": "string" + }, + "nickserv_password": { + "type": "string" + }, + "notice": { + "type": "boolean" + } + } + }, + "slack": { + "description": "Slack. See https://docs.codecov.io/docs/notifications#section-slack for details.", + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "attachments": { + "$ref": "#/definitions/layout" + } + } + }, + "gitter": { + "description": "Gitter. See https://docs.codecov.io/docs/notifications#section-gitter for details.", + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "hipchat": { + "description": "Hipchat. See https://docs.codecov.io/docs/notifications#section-hipchat for details.", + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "card": { + "type": "boolean" + }, + "notify": { + "type": "boolean" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "email": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "branches": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "message": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "base": { + "enum": ["parent", "pr", "auto"] + }, + "only_pulls": { + "type": "boolean" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "layout": { + "$ref": "#/definitions/layout" + }, + "+to": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "status": { + "description": "Commit status. See https://docs.codecov.io/docs/commit-status for details.", + "type": ["boolean", "object"], + "additionalProperties": false, + "properties": { + "default_rules": { + "type": "object" + }, + "project": { + "properties": { + "default": { + "$ref": "#/definitions/default", + "type": ["object", "boolean"] + } + }, + "additionalProperties": { + "$ref": "#/definitions/default", + "type": ["object", "boolean"] + } + }, + "patch": { + "anyOf": [ + { + "$ref": "#/definitions/default", + "type": "object" + }, + { + "type": "string", + "enum": ["off"] + }, + { + "type": "boolean" + } + ] + }, + "changes": { + "$ref": "#/definitions/default", + "type": ["object", "boolean"] + } + } + } + } + }, + "ignore": { + "description": "Ignoring paths. see https://docs.codecov.io/docs/ignoring-paths for details.", + "type": "array", + "items": { + "type": "string" + } + }, + "fixes": { + "description": "Fixing paths. See https://docs.codecov.io/docs/fixing-paths for details.", + "type": "array", + "items": { + "type": "string" + } + }, + "flags": { + "description": "Flags. See https://docs.codecov.io/docs/flags for details.", + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/flag" + } + }, + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/flag" + } + } + ] + }, + "comment": { + "description": "Pull request comments. See https://docs.codecov.io/docs/pull-request-comments for details.", + "oneOf": [ + { + "type": "object", + "properties": { + "layout": { + "$ref": "#/definitions/layout" + }, + "require_changes": { + "type": "boolean" + }, + "require_base": { + "type": "boolean" + }, + "require_head": { + "type": "boolean" + }, + "branches": { + "type": "array", + "items": { + "type": "string" + } + }, + "behavior": { + "enum": ["default", "once", "new", "spammy"] + }, + "flags": { + "type": "array", + "items": { + "$ref": "#/definitions/flag" + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "const": false + } + ] + }, + "github_checks": { + "description": "GitHub Checks. See https://docs.codecov.com/docs/github-checks for details.", + "anyOf": [ + { + "type": "object", + "properties": { + "annotations": { + "type": "boolean" + } + } + }, + { "type": "boolean" }, + { "type": "string", "enum": ["off"] } + ] + } + }, + "title": "JSON schema for Codecov configuration files", + "type": "object" +} diff --git a/ci/schemas/conda-environment.json b/ci/schemas/conda-environment.json new file mode 100644 index 000000000000..458676942a44 --- /dev/null +++ b/ci/schemas/conda-environment.json @@ -0,0 +1,53 @@ +{ + "title": "conda environment file", + "description": "Support for conda's enviroment.yml files (e.g. `conda env export > environment.yml`)", + "id": "https://raw.githubusercontent.com/Microsoft/vscode-python/main/schemas/conda-environment.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "channel": { + "type": "string" + }, + "package": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "properties": { + "name": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/channel" + } + }, + "dependencies": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/package" + }, + { + "type": "object", + "properties": { + "pip": { + "type": "array", + "items": { + "$ref": "#/definitions/package" + } + } + }, + "required": ["pip"] + } + ] + } + }, + "prefix": { + "$ref": "#/definitions/path" + } + } +} diff --git a/ci/schemas/github-funding.json b/ci/schemas/github-funding.json new file mode 100644 index 000000000000..d146d692c483 --- /dev/null +++ b/ci/schemas/github-funding.json @@ -0,0 +1,113 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/github-funding.json", + "$comment": "https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository", + "additionalProperties": false, + "definitions": { + "github_username": { + "type": "string", + "maxLength": 39, + "pattern": "^[a-zA-Z0-9](-?[a-zA-Z0-9])*$", + "examples": ["SampleUserName"] + }, + "nullable_string": { + "type": ["string", "null"] + } + }, + "description": "You can add a sponsor button in your repository to increase the visibility of funding options for your open source project.", + "properties": { + "community_bridge": { + "$ref": "#/definitions/nullable_string", + "title": "CommunityBridge", + "description": "Project name on CommunityBridge.", + "minLength": 1 + }, + "github": { + "title": "GitHub Sponsors", + "description": "Username or usernames on GitHub.", + "oneOf": [ + { + "$ref": "#/definitions/github_username" + }, + { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/github_username" + } + } + ] + }, + "issuehunt": { + "$ref": "#/definitions/nullable_string", + "title": "IssueHunt", + "description": "Username on IssueHunt.", + "minLength": 1 + }, + "ko_fi": { + "$ref": "#/definitions/nullable_string", + "title": "Ko-fi", + "description": "Username on Ko-fi.", + "minLength": 1 + }, + "liberapay": { + "$ref": "#/definitions/nullable_string", + "title": "Liberapay", + "description": "Username on Liberapay.", + "minLength": 1 + }, + "open_collective": { + "$ref": "#/definitions/nullable_string", + "title": "Open Collective", + "description": "Username on Open Collective.", + "minLength": 1 + }, + "otechie": { + "$ref": "#/definitions/nullable_string", + "title": "Otechie", + "description": "Username on Otechie.", + "minLength": 1 + }, + "patreon": { + "$ref": "#/definitions/nullable_string", + "title": "Patreon", + "description": "Username on Pateron.", + "minLength": 1, + "maxLength": 100 + }, + "tidelift": { + "$ref": "#/definitions/nullable_string", + "title": "Tidelift", + "description": "Platform and package on Tidelift.", + "pattern": "^(npm|pypi|rubygems|maven|packagist|nuget)/.+$" + }, + "lfx_crowdfunding": { + "$ref": "#/definitions/nullable_string", + "title": "LFX Crowdfunding", + "description": "Project name on LFX Crowdfunding.", + "minLength": 1 + }, + "polar": { + "$ref": "#/definitions/github_username", + "title": "Polar", + "description": "Username on Polar.", + "minLength": 1 + }, + "custom": { + "title": "Custom URL", + "description": "Link or links where funding is accepted on external locations.", + "type": ["string", "array", "null"], + "format": "uri-reference", + "items": { + "title": "Link", + "description": "Link to an external location.", + "type": "string", + "format": "uri-reference" + }, + "uniqueItems": true + } + }, + "title": "GitHub Funding", + "type": "object" +} diff --git a/ci/schemas/github-issue-config.json b/ci/schemas/github-issue-config.json new file mode 100644 index 000000000000..b46556bb04a5 --- /dev/null +++ b/ci/schemas/github-issue-config.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/github-issue-config.json", + "$comment": "https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "properties": { + "blank_issues_enabled": { + "description": "Specify whether allow blank issue creation\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "type": "boolean" + }, + "contact_links": { + "title": "contact links", + "description": "Contact links\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["name", "url", "about"], + "properties": { + "name": { + "description": "A link title\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "type": "string", + "minLength": 1, + "examples": ["Sample name"] + }, + "url": { + "description": "A link URL\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "type": "string", + "pattern": "^https?://", + "examples": ["https://sample/url"] + }, + "about": { + "description": "A link description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser", + "type": "string", + "minLength": 1, + "examples": ["Sample description"] + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "title": "GitHub issue template chooser config file schema", + "type": "object" +} diff --git a/ci/schemas/github-issue-forms.json b/ci/schemas/github-issue-forms.json new file mode 100644 index 000000000000..c928818dfdd1 --- /dev/null +++ b/ci/schemas/github-issue-forms.json @@ -0,0 +1,1295 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/github-issue-forms.json", + "$comment": "https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms", + "additionalProperties": false, + "definitions": { + "type": { + "description": "A form item type\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#keys", + "type": "string", + "enum": ["checkboxes", "dropdown", "input", "markdown", "textarea"] + }, + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "examples": ["SampleId"] + }, + "validations": { + "title": "validation options", + "type": "object", + "properties": { + "required": { + "description": "Specify whether require a form item", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "assignee": { + "type": "string", + "maxLength": 39, + "pattern": "^[a-zA-Z0-9](-?[a-zA-Z0-9])*$", + "examples": ["SampleAssignee"] + }, + "label": { + "type": "string", + "minLength": 1, + "examples": ["Sample label"] + }, + "description": { + "type": "string", + "default": "", + "examples": ["Sample description"] + }, + "placeholder": { + "type": "string", + "default": "", + "examples": ["Sample placeholder"] + }, + "value": { + "type": "string", + "minLength": 1, + "examples": ["Sample value"] + }, + "form_item": { + "title": "form item", + "description": "A form item\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#about-githubs-form-schema", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "$ref": "#/definitions/type" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "markdown" + } + } + }, + "then": { + "$comment": "For `additionalProperties` to work `type` must also be present here.", + "title": "markdown", + "description": "Markdown\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#markdown", + "type": "object", + "required": ["type", "attributes"], + "properties": { + "type": { + "$ref": "#/definitions/type" + }, + "attributes": { + "title": "markdown attributes", + "description": "Markdown attributes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes", + "type": "object", + "required": ["value"], + "properties": { + "value": { + "description": "A markdown code\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes", + "type": "string", + "minLength": 1, + "examples": ["Sample code"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "const": "textarea" + } + } + }, + "then": { + "$comment": "For `additionalProperties` to work `type` must also be present here.", + "title": "textarea", + "description": "Textarea\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#textarea", + "type": "object", + "required": ["type", "attributes"], + "properties": { + "type": { + "$ref": "#/definitions/type" + }, + "id": { + "$ref": "#/definitions/id", + "description": "A textarea id\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#keys" + }, + "attributes": { + "title": "textarea attributes", + "description": "Textarea attributes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1", + "type": "object", + "required": ["label"], + "properties": { + "label": { + "$ref": "#/definitions/label", + "description": "A short textarea description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1" + }, + "description": { + "$ref": "#/definitions/description", + "description": "A long textarea description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1" + }, + "placeholder": { + "$ref": "#/definitions/placeholder", + "description": "A textarea placeholder\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1" + }, + "value": { + "$ref": "#/definitions/value", + "description": "A textarea value\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1" + }, + "render": { + "description": "A textarea syntax highlighting mode\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-1", + "type": "string", + "enum": [ + "1C Enterprise", + "4D", + "ABAP CDS", + "ABAP", + "ABNF", + "AFDKO", + "AGS Script", + "AIDL", + "AL", + "AMPL", + "ANTLR", + "API Blueprint", + "APL", + "ASL", + "ASN.1", + "ASP.NET", + "ATS", + "ActionScript", + "Ada", + "Alloy", + "Alpine Abuild", + "Altium Designer", + "AngelScript", + "Ant Build System", + "ApacheConf", + "Apex", + "Apollo Guidance Computer", + "AppleScript", + "Arc", + "AsciiDoc", + "AspectJ", + "Assembly", + "Astro", + "Asymptote", + "Augeas", + "AutoHotkey", + "AutoIt", + "AutoIt3", + "AutoItScript", + "Avro IDL", + "Awk", + "BASIC", + "Ballerina", + "Batchfile", + "Beef", + "Befunge", + "BibTeX", + "Bicep", + "Bison", + "BitBake", + "Blade", + "BlitzBasic", + "BlitzMax", + "Boo", + "Boogie", + "Brainfuck", + "Brightscript", + "Browserslist", + "C", + "C#", + "C++", + "C-ObjDump", + "C2hs Haskell", + "CIL", + "CLIPS", + "CMake", + "COBOL", + "CODEOWNERS", + "COLLADA", + "CSON", + "CSS", + "CSV", + "CUE", + "CWeb", + "Cabal Config", + "Cabal", + "Cap'n Proto", + "Carto", + "CartoCSS", + "Ceylon", + "Chapel", + "Charity", + "ChucK", + "Cirru", + "Clarion", + "Classic ASP", + "Clean", + "Click", + "Clojure", + "Closure Templates", + "Cloud Firestore Security Rules", + "CoNLL", + "CoNLL-U", + "CoNLL-X", + "ColdFusion CFC", + "ColdFusion", + "Common Lisp", + "Common Workflow Language", + "Component Pascal", + "Containerfile", + "Cool", + "Coq", + "Cpp-ObjDump", + "Crystal", + "Csound Document", + "Csound Score", + "Csound", + "Cuda", + "Cue Sheet", + "Cycript", + "Cython", + "D-ObjDump", + "DIGITAL Command Language", + "DM", + "DTrace", + "Dafny", + "Darcs Patch", + "Dart", + "DataWeave", + "Dhall", + "Diff", + "Dlang", + "Dockerfile", + "Dogescript", + "Dylan", + "E", + "E-mail", + "EBNF", + "ECL", + "ECLiPSe", + "EJS", + "EQ", + "Eagle", + "Earthly", + "Easybuild", + "Ecere Projects", + "EditorConfig", + "Eiffel", + "Elixir", + "Elm", + "Emacs Lisp", + "EmberScript", + "Erlang", + "F#", + "F*", + "FIGfont", + "FIGlet Font", + "FLUX", + "Factor", + "Fancy", + "Fantom", + "Faust", + "Fennel", + "Filebench WML", + "Filterscript", + "Fluent", + "Formatted", + "Forth", + "Fortran Free Form", + "Fortran", + "FreeBasic", + "Frege", + "Futhark", + "G-code", + "GAML", + "GAMS", + "GAP", + "GCC Machine Description", + "GDB", + "GDScript", + "GEDCOM", + "GLSL", + "GN", + "Game Maker Language", + "Gemfile.lock", + "Genie", + "Genshi", + "Gentoo Eclass", + "Gerber Image", + "Gettext Catalog", + "Gherkin", + "Git Config", + "Glyph Bitmap Distribution Format", + "Glyph", + "Gnuplot", + "Go Checksums", + "Go Module", + "Go", + "Golo", + "Gosu", + "Grace", + "Gradle", + "Grammatical Framework", + "Graph Modeling Language", + "GraphQL", + "Graphviz (DOT)", + "Groovy Server Pages", + "Groovy", + "HAProxy", + "HCL", + "HTML", + "HTML+ECR", + "HTML+EEX", + "HTML+ERB", + "HTML+PHP", + "HTML+Razor", + "HTTP", + "HXML", + "Hack", + "Haml", + "Handlebars", + "Harbour", + "HashiCorp Configuration Language", + "Haskell", + "Haxe", + "HiveQL", + "HolyC", + "Hy", + "IDL", + "IGOR Pro", + "IPython Notebook", + "Idris", + "Ignore List", + "ImageJ Macro", + "Inform 7", + "Io", + "Ioke", + "Isabelle ROOT", + "Isabelle", + "J", + "JAR Manifest", + "JFlex", + "JSON with Comments", + "JSON", + "JSON5", + "JSONLD", + "JSONiq", + "Jasmin", + "Java Properties", + "Java Server Pages", + "Java", + "JavaScript", + "JavaScript+ERB", + "Jest Snapshot", + "Jinja", + "Jison Lex", + "Jison", + "Jolie", + "Jsonnet", + "Julia", + "Jupyter Notebook", + "Kaitai Struct", + "KakouneScript", + "KiCad Layout", + "KiCad Legacy Layout", + "KiCad Schematic", + "Kit", + "Kotlin", + "Kusto", + "LFE", + "LLVM", + "LOLCODE", + "LSL", + "LTspice Symbol", + "LabVIEW", + "Lark", + "Lasso", + "Lean", + "Less", + "Lex", + "LilyPond", + "Limbo", + "Linker Script", + "Linux Kernel Module", + "Liquid", + "Literate Agda", + "Literate CoffeeScript", + "Literate Haskell", + "LiveScript", + "Logos", + "Logtalk", + "LookML", + "LoomScript", + "Lua", + "M", + "M4", + "M4Sugar", + "MATLAB", + "MAXScript", + "MLIR", + "MQL4", + "MQL5", + "MTML", + "MUF", + "Macaulay2", + "Makefile", + "Mako", + "Markdown", + "Marko", + "Mathematica", + "Max", + "Mercury", + "Meson", + "Metal", + "Microsoft Developer Studio Project", + "Microsoft Visual Studio Solution", + "MiniD", + "Mirah", + "Modelica", + "Modula-2", + "Modula-3", + "Module Management System", + "Monkey", + "Moocode", + "MoonScript", + "Motoko", + "Motorola 68K Assembly", + "Muse", + "Myghty", + "NASL", + "NCL", + "NEON", + "NPM Config", + "NSIS", + "NWScript", + "Nearley", + "Nemerle", + "NeoSnippet", + "NetLinx", + "NetLinx+ERB", + "NetLogo", + "NewLisp", + "Nextflow", + "Nginx", + "Ninja", + "Nit", + "Nix", + "NumPy", + "Nunjucks", + "ObjDump", + "Object Data Instance Notation", + "ObjectScript", + "Objective-C", + "Objective-C++", + "Objective-J", + "Odin", + "Omgrofl", + "Opa", + "Opal", + "Open Policy Agent", + "OpenCL", + "OpenEdge ABL", + "OpenQASM", + "OpenRC runscript", + "OpenSCAD", + "OpenStep Property List", + "OpenType Feature File", + "Org", + "Ox", + "Oxygene", + "Oz", + "P4", + "PEG.js", + "PHP", + "PLpgSQL", + "POV-Ray SDL", + "Pan", + "Papyrus", + "Parrot Assembly", + "Parrot Internal Representation", + "Parrot", + "Pascal", + "Pawn", + "Pep8", + "Perl", + "Pickle", + "PicoLisp", + "PigLatin", + "Pike", + "PlantUML", + "Pod 6", + "Pod", + "PogoScript", + "Pony", + "PostCSS", + "PostScript", + "PowerShell", + "Prisma", + "Processing", + "Proguard", + "Prolog", + "Promela", + "Propeller Spin", + "Protocol Buffer", + "Protocol Buffers", + "Public Key", + "Pug", + "Puppet", + "Pure Data", + "PureBasic", + "PureScript", + "Python", + "Q#", + "QMake", + "Qt Script", + "Quake", + "R", + "RAML", + "RDoc", + "REALbasic", + "REXX", + "RMarkdown", + "RPC", + "RPM Spec", + "Racket", + "Ragel", + "Raw token data", + "ReScript", + "Readline Config", + "Reason", + "Rebol", + "Record Jar", + "Red", + "Redirect Rules", + "Regular Expression", + "RenderScript", + "Rich Text Format", + "Ring", + "Riot", + "RobotFramework", + "Roff", + "Rouge", + "Rscript", + "Ruby", + "Rust", + "SAS", + "SCSS", + "SELinux Kernel Policy Language", + "SELinux Policy", + "SMT", + "SPARQL", + "SQF", + "SQL", + "SQLPL", + "SRecode Template", + "SSH Config", + "STON", + "SVG", + "SWIG", + "Sage", + "SaltStack", + "Sass", + "Scala", + "Scaml", + "Scheme", + "Scilab", + "Self", + "ShaderLab", + "Shell", + "ShellCheck Config", + "Sieve", + "Singularity", + "Slash", + "Slice", + "Slim", + "SmPL", + "Smalltalk", + "SnipMate", + "Solidity", + "Soong", + "SourcePawn", + "Spline Font Database", + "Squirrel", + "Stan", + "Standard ML", + "Starlark", + "StringTemplate", + "Stylus", + "SubRip Text", + "SugarSS", + "SuperCollider", + "Svelte", + "Swift", + "SystemVerilog", + "TI Program", + "TLA", + "TOML", + "TSQL", + "TSV", + "TSX", + "TXL", + "Tcl", + "Tcsh", + "TeX", + "Tea", + "Terra", + "Texinfo", + "Text", + "TextMate Properties", + "Textile", + "Thrift", + "Turing", + "Turtle", + "Twig", + "Type Language", + "TypeScript", + "UltiSnip", + "UltiSnips", + "Unified Parallel C", + "Unity3D Asset", + "Unix Assembly", + "Uno", + "UnrealScript", + "Ur", + "Ur/Web", + "UrWeb", + "V", + "VBA", + "VCL", + "VHDL", + "Vala", + "Valve Data Format", + "Verilog", + "Vim Help File", + "Vim Script", + "Vim Snippet", + "Visual Basic .NET", + "Vue", + "Wavefront Material", + "Wavefront Object", + "Web Ontology Language", + "WebAssembly", + "WebVTT", + "Wget Config", + "Wikitext", + "Windows Registry Entries", + "Wollok", + "World of Warcraft Addon Data", + "X BitMap", + "X Font Directory Index", + "X PixMap", + "X10", + "XC", + "XCompose", + "XML Property List", + "XML", + "XPages", + "XProc", + "XQuery", + "XS", + "XSLT", + "Xojo", + "Xonsh", + "Xtend", + "YAML", + "YANG", + "YARA", + "YASnippet", + "Yacc", + "ZAP", + "ZIL", + "Zeek", + "ZenScript", + "Zephir", + "Zig", + "Zimpl", + "abl", + "abuild", + "acfm", + "aconf", + "actionscript 3", + "actionscript3", + "ada2005", + "ada95", + "adobe composite font metrics", + "adobe multiple font metrics", + "advpl", + "ags", + "ahk", + "altium", + "amfm", + "amusewiki", + "apache", + "apkbuild", + "arexx", + "as3", + "asm", + "asp", + "aspx", + "aspx-vb", + "ats2", + "au3", + "autoconf", + "b3d", + "bash session", + "bash", + "bat", + "batch", + "bazel", + "blitz3d", + "blitzplus", + "bmax", + "bplus", + "bro", + "bsdmake", + "byond", + "bzl", + "c++-objdump", + "c2hs", + "cURL Config", + "cake", + "cakescript", + "cfc", + "cfm", + "cfml", + "chpl", + "clipper", + "coccinelle", + "coffee", + "coffee-script", + "coldfusion html", + "console", + "cperl", + "cpp", + "csharp", + "csound-csd", + "csound-orc", + "csound-sco", + "cucumber", + "curlrc", + "cwl", + "dcl", + "delphi", + "desktop", + "dircolors", + "django", + "dosbatch", + "dosini", + "dpatch", + "dtrace-script", + "eC", + "ecr", + "editor-config", + "edn", + "eeschema schematic", + "eex", + "elisp", + "emacs muse", + "emacs", + "email", + "eml", + "erb", + "fb", + "fish", + "flex", + "foxpro", + "fsharp", + "fstar", + "ftl", + "fundamental", + "gf", + "git-ignore", + "gitattributes", + "gitconfig", + "gitignore", + "gitmodules", + "go mod", + "go sum", + "go.mod", + "go.sum", + "golang", + "groff", + "gsp", + "hbs", + "heex", + "help", + "html+django", + "html+jinja", + "html+ruby", + "htmlbars", + "htmldjango", + "hylang", + "i7", + "ignore", + "igor", + "igorpro", + "ijm", + "inc", + "inform7", + "inputrc", + "irc logs", + "irc", + "java server page", + "jq", + "jruby", + "js", + "jsonc", + "jsp", + "kak", + "kakscript", + "keyvalues", + "ksy", + "lassoscript", + "latex", + "leex", + "lhaskell", + "lhs", + "lisp", + "litcoffee", + "live-script", + "ls", + "m2", + "m68k", + "mIRC Script", + "macruby", + "mail", + "make", + "man page", + "man", + "man-page", + "manpage", + "markojs", + "max/msp", + "maxmsp", + "mbox", + "mcfunction", + "mdoc", + "mediawiki", + "mf", + "mma", + "mumps", + "mupad", + "nanorc", + "nasm", + "ne-on", + "nesC", + "nette object notation", + "nginx configuration file", + "nixos", + "njk", + "node", + "npmrc", + "nroff", + "nush", + "nvim", + "obj-c", + "obj-c++", + "obj-j", + "objc", + "objc++", + "objectivec", + "objectivec++", + "objectivej", + "objectpascal", + "objj", + "octave", + "odin-lang", + "odinlang", + "oncrpc", + "ooc", + "openedge", + "openrc", + "osascript", + "pandoc", + "pasm", + "pcbnew", + "perl-6", + "perl6", + "pir", + "plain text", + "posh", + "postscr", + "pot", + "pov-ray", + "povray", + "progress", + "protobuf", + "pwsh", + "pycon", + "pyrex", + "python3", + "q", + "ql", + "qsharp", + "ragel-rb", + "ragel-ruby", + "rake", + "raw", + "razor", + "rb", + "rbx", + "reStructuredText", + "readline", + "red/system", + "redirects", + "regex", + "regexp", + "renpy", + "rhtml", + "robots txt", + "robots", + "robots.txt", + "rpcgen", + "rs", + "rs-274x", + "rss", + "rst", + "rusthon", + "salt", + "saltstate", + "sed", + "sepolicy", + "sh", + "shell-script", + "shellcheckrc", + "sml", + "snippet", + "sourcemod", + "soy", + "specfile", + "splus", + "squeak", + "terraform", + "tl", + "tm-properties", + "troff", + "ts", + "udiff", + "vb .net", + "vb.net", + "vb6", + "vbnet", + "vdf", + "vim", + "vimhelp", + "viml", + "visual basic 6", + "visual basic for applications", + "visual basic", + "vlang", + "wasm", + "wast", + "wdl", + "wgetrc", + "wiki", + "winbatch", + "wisp", + "wl", + "wolfram lang", + "wolfram language", + "wolfram", + "wsdl", + "xBase", + "xbm", + "xdr", + "xhtml", + "xml+genshi", + "xml+kid", + "xpm", + "xsd", + "xsl", + "xten", + "yas", + "yml", + "zsh" + ] + } + }, + "additionalProperties": false + }, + "validations": { + "$ref": "#/definitions/validations", + "title": "textarea validations", + "description": "Textarea validations\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#validations" + } + }, + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "const": "input" + } + } + }, + "then": { + "$comment": "For `additionalProperties` to work `type` must also be present here.", + "title": "input", + "description": "Input\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#input", + "type": "object", + "required": ["type", "attributes"], + "properties": { + "type": { + "$ref": "#/definitions/type" + }, + "id": { + "$ref": "#/definitions/id", + "description": "An input id\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#keys" + }, + "attributes": { + "title": "input attributes", + "description": "Input attributes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-2", + "type": "object", + "required": ["label"], + "properties": { + "label": { + "$ref": "#/definitions/label", + "description": "A short input description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-2" + }, + "description": { + "$ref": "#/definitions/description", + "description": "A long input description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-2" + }, + "placeholder": { + "$ref": "#/definitions/placeholder", + "description": "An input placeholder\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-2" + }, + "value": { + "$ref": "#/definitions/value", + "description": "An input value\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-2" + } + }, + "additionalProperties": false + }, + "validations": { + "$ref": "#/definitions/validations", + "title": "input validations", + "description": "Input validations\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#validations-1" + } + }, + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "const": "dropdown" + } + } + }, + "then": { + "$comment": "For `additionalProperties` to work `type` must also be present here.", + "title": "dropdown", + "description": "dropdown\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#dropdown", + "type": "object", + "required": ["type", "attributes"], + "properties": { + "type": { + "$ref": "#/definitions/type" + }, + "id": { + "$ref": "#/definitions/id", + "description": "A dropdown id\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#keys" + }, + "attributes": { + "title": "dropdown attributes", + "description": "Dropdown attributes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3", + "type": "object", + "required": ["label", "options"], + "properties": { + "label": { + "$ref": "#/definitions/label", + "description": "A short dropdown description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3" + }, + "description": { + "$ref": "#/definitions/description", + "description": "A long dropdown description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3" + }, + "multiple": { + "description": "Specify whether allow a multiple choices\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3", + "type": "boolean", + "default": false + }, + "options": { + "description": "Dropdown choices\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "examples": ["Sample choice"] + } + }, + "default": { + "description": "Index of the default option\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-3", + "type": "integer", + "examples": [0] + } + }, + "additionalProperties": false + }, + "validations": { + "$ref": "#/definitions/validations", + "title": "dropdown validations", + "description": "Dropdown validations\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#validations-2" + } + }, + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "const": "checkboxes" + } + } + }, + "then": { + "$comment": "For `additionalProperties` to work `type` must also be present here.", + "title": "checkboxes", + "description": "Checkboxes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#checkboxes", + "type": "object", + "required": ["type", "attributes"], + "properties": { + "type": { + "$ref": "#/definitions/type" + }, + "id": { + "$ref": "#/definitions/id", + "description": "Checkbox list id\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#keys" + }, + "attributes": { + "title": "checkbox list attributes", + "description": "Checkbox list attributes\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4", + "type": "object", + "required": ["label", "options"], + "properties": { + "label": { + "$ref": "#/definitions/label", + "description": "A short checkbox list description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4" + }, + "description": { + "$ref": "#/definitions/description", + "description": "A long checkbox list description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4" + }, + "options": { + "description": "Checkbox list choices\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4", + "type": "array", + "minItems": 1, + "items": { + "title": "checkbox list choice", + "description": "Checkbox list choice\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4", + "type": "object", + "required": ["label"], + "properties": { + "label": { + "description": "A short checkbox list choice description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4", + "type": "string", + "minLength": 1, + "examples": ["Sample label"] + }, + "required": { + "description": "Specify whether a choice is required\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#attributes-4", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } + ] + } + }, + "properties": { + "name": { + "description": "An issue template name\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "type": "string", + "minLength": 1, + "examples": ["Sample name"] + }, + "description": { + "description": "An issue template description\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "type": "string", + "minLength": 1, + "examples": ["Sample description"] + }, + "body": { + "description": "An issue template body\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_item" + } + }, + "assignees": { + "description": "An issue template assignees\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "oneOf": [ + { + "$ref": "#/definitions/assignee" + }, + { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/assignee" + } + } + ] + }, + "labels": { + "description": "An issue template labels\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "examples": [ + "Sample label", + "bug", + "documentation", + "duplicate", + "enhancement", + "good first issue", + "help wanted", + "invalid", + "question", + "wontfix" + ] + } + }, + "title": { + "description": "An issue template title\nhttps://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax", + "type": "string", + "minLength": 1, + "examples": ["Sample title", "Bug: ", "Feature: "] + } + }, + "required": ["name", "description", "body"], + "title": "GitHub issue forms config file schema", + "type": "object" +} diff --git a/ci/schemas/pull-request-labeler-5.json b/ci/schemas/pull-request-labeler-5.json new file mode 100644 index 000000000000..22ad7955814f --- /dev/null +++ b/ci/schemas/pull-request-labeler-5.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/pull-request-labeler-5.json", + "$comment": "https://github.com/actions/labeler", + "$defs": { + "stringOrStringArray": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "match": { + "title": "Match", + "type": "object", + "properties": { + "changed-files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "any-glob-to-any-file": { "$ref": "#/$defs/stringOrStringArray" }, + "any-glob-to-all-files": { + "$ref": "#/$defs/stringOrStringArray" + }, + "all-globs-to-any-file": { + "$ref": "#/$defs/stringOrStringArray" + }, + "all-globs-to-all-files": { + "$ref": "#/$defs/stringOrStringArray" + } + }, + "oneOf": [ + { "required": ["any-glob-to-any-file"] }, + { "required": ["any-glob-to-all-files"] }, + { "required": ["all-globs-to-any-file"] }, + { "required": ["all-globs-to-all-files"] } + ], + "additionalProperties": false + } + }, + "base-branch": { "$ref": "#/$defs/stringOrStringArray" }, + "head-branch": { "$ref": "#/$defs/stringOrStringArray" } + }, + "oneOf": [ + { "required": ["changed-files"] }, + { "required": ["base-branch"] }, + { "required": ["head-branch"] } + ], + "additionalProperties": false + } + }, + "additionalProperties": { + "title": "Label", + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "all": { + "title": "All", + "type": "array", + "items": { "$ref": "#/$defs/match" } + } + }, + "additionalProperties": false, + "required": ["all"] + }, + { + "type": "object", + "properties": { + "any": { + "title": "Any", + "type": "array", + "items": { "$ref": "#/$defs/match" } + } + }, + "additionalProperties": false, + "required": ["any"] + }, + { "$ref": "#/$defs/match" } + ] + } + }, + "description": "A GitHub Action for automatically labelling pull requests.", + "title": "Pull Request Labeler", + "type": "object" +} diff --git a/ci/schemas/vendor_schemas.py b/ci/schemas/vendor_schemas.py new file mode 100644 index 000000000000..a40e262e69f7 --- /dev/null +++ b/ci/schemas/vendor_schemas.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +""" +Download YAML Schemas for linting and validation. + +Since pre-commit CI doesn't have Internet access, we need to bundle these files +in the repo. +""" + +import os +import pathlib +import urllib.request + + +HERE = pathlib.Path(__file__).parent +SCHEMAS = [ + 'https://json.schemastore.org/appveyor.json', + 'https://json.schemastore.org/circleciconfig.json', + 'https://json.schemastore.org/github-funding.json', + 'https://json.schemastore.org/github-issue-config.json', + 'https://json.schemastore.org/github-issue-forms.json', + 'https://json.schemastore.org/codecov.json', + 'https://json.schemastore.org/pull-request-labeler-5.json', + 'https://github.com/microsoft/vscode-python/raw/' + 'main/schemas/conda-environment.json', +] + + +def print_progress(block_count, block_size, total_size): + size = block_count * block_size + if total_size != -1: + size = min(size, total_size) + width = 50 + percent = size / total_size * 100 + filled = int(percent // (100 // width)) + percent_str = '\N{Full Block}' * filled + '\N{Light Shade}' * (width - filled) + print(f'{percent_str} {size:6d} / {total_size:6d}', end='\r') + + +# First clean up existing files. +for json in HERE.glob('*.json'): + os.remove(json) + +for schema in SCHEMAS: + path = HERE / schema.rsplit('/', 1)[-1] + print(f'Downloading {schema} to {path}') + urllib.request.urlretrieve(schema, filename=path, reporthook=print_progress) + print() + # This seems weird, but it normalizes line endings to the current platform, + # so that Git doesn't complain about it. + path.write_text(path.read_text()) diff --git a/ci/silence b/ci/silence deleted file mode 100755 index 4889e5d1bd58..000000000000 --- a/ci/silence +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Run a command, hiding its standard output and error if its exit -# status is zero. - -stdout=$(mktemp -t stdout) || exit 1 -stderr=$(mktemp -t stderr) || exit 1 -"$@" >$stdout 2>$stderr -code=$? -if [[ $code != 0 ]]; then - cat $stdout - cat $stderr >&2 - exit $code -fi diff --git a/doc/Makefile b/doc/Makefile index bc0c99e417ad..baed196a3ee2 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -18,15 +18,32 @@ help: clean: @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) rm -rf "$(SOURCEDIR)/build" + rm -rf "$(SOURCEDIR)/_tags" rm -rf "$(SOURCEDIR)/api/_as_gen" rm -rf "$(SOURCEDIR)/gallery" + rm -rf "$(SOURCEDIR)/plot_types" rm -rf "$(SOURCEDIR)/tutorials" + rm -rf "$(SOURCEDIR)/users/explain" rm -rf "$(SOURCEDIR)/savefig" rm -rf "$(SOURCEDIR)/sphinxext/__pycache__" + rm -f $(SOURCEDIR)/_static/constrained_layout*.png + rm -f $(SOURCEDIR)/sg_execution_times.rst show: @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/build/html/index.html')" +html-noplot: + $(SPHINXBUILD) -D plot_gallery=0 -b html $(SOURCEDIR) $(BUILDDIR)/html $(SPHINXOPTS) $(O) + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +# This will skip the subdirectories listed in .mpl_skip_subdirs.yaml If +# this file does not exist, one will be created for you. This option useful +# to quickly build parts of the docs, but the resulting build will not +# have all the crosslinks etc. +html-skip-subdirs: + $(SPHINXBUILD) -D skip_sub_dirs=1 -b html $(SOURCEDIR) $(BUILDDIR)/html $(SPHINXOPTS) $(O) + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile diff --git a/doc/README.txt b/doc/README.txt index bb8b809cea3b..c34dbd769712 100644 --- a/doc/README.txt +++ b/doc/README.txt @@ -9,30 +9,58 @@ See :file:`doc/devel/documenting_mpl.rst` for instructions to build the docs. Organization ------------ -This is the top level build directory for the Matplotlib -documentation. All of the documentation is written using sphinx, a -python documentation system built on top of ReST. This directory contains +This is the top level directory for the Matplotlib +documentation. All of the documentation is written using Sphinx, a +python documentation system based on reStructuredText. This directory contains the +following -* users - the user documentation, e.g., plotting tutorials, configuration - tips, etc. +Files +^^^^^ -* devel - documentation for Matplotlib developers +* index.rst - the top level include document (and landing page) for the Matplotlib docs -* faq - frequently asked questions +* conf.py - the sphinx configuration -* api - placeholders to automatically generate the api documentation +* docutils.conf - htmnl output configuration -* mpl_toolkits - documentation of individual toolkits that ship with - Matplotlib +* Makefile and make.bat - entry points for building the docs -* index.rst - the top level include document for Matplotlib docs +* matplotlibrc - rcParam configuration for docs -* conf.py - the sphinx configuration +* missing-references.json - list of known missing/broken references -* Makefile and make.bat - entry points for building the docs -* _static - used by the sphinx build system +Content folders +^^^^^^^^^^^^^^^ + +* api - templates for generating the api documentation + +* devel - documentation for contributing to Matplotlib + +* project - about Matplotlib, e.g. mission, code of conduct, licenses, history, etc. -* _templates - used by the sphinx build system +* users - usage documentation, e.g., installation, tutorials, faq, explanations, etc. + +* thirdpartypackages - redirect to + +Build folders +^^^^^^^^^^^^^ + +* _static - supplementary files; e.g. images, CSS, etc. + +* _templates - Sphinx page templates * sphinxext - Sphinx extensions for the Matplotlib docs + +Symlinks +-------- + +During the documentation build, sphinx-gallery creates symlinks from the source folders +in `/galleries` to target_folders in '/doc'; therefore ensure that you are editing the +real files rather than the symbolic links. + +Source files -> symlink: +* galleries/tutorials -> doc/tutorials +* galleries/plot_types -> doc/plot_types +* galleries/examples -> doc/gallery +* galleries/users_explain -> doc/users/explain diff --git a/doc/_embedded_plots/axes_margins.py b/doc/_embedded_plots/axes_margins.py new file mode 100644 index 000000000000..d026840c3c15 --- /dev/null +++ b/doc/_embedded_plots/axes_margins.py @@ -0,0 +1,42 @@ +import numpy as np +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(6.5, 4)) +x = np.linspace(0, 1, 33) +y = -np.sin(x * 2*np.pi) +ax.plot(x, y, 'o') +ax.margins(0.5, 0.2) +ax.set_title("margins(x=0.5, y=0.2)") + +# fix the Axes limits so that the following helper drawings +# cannot change them further. +ax.set(xlim=ax.get_xlim(), ylim=ax.get_ylim()) + + +def arrow(p1, p2, **props): + ax.annotate("", p1, p2, + arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0, **props)) + + +axmin, axmax = ax.get_xlim() +aymin, aymax = ax.get_ylim() +xmin, xmax = x.min(), x.max() +ymin, ymax = y.min(), y.max() + +y0 = -0.8 +ax.axvspan(axmin, xmin, color=("orange", 0.1)) +ax.axvspan(xmax, axmax, color=("orange", 0.1)) +arrow((xmin, y0), (xmax, y0), color="sienna") +arrow((xmax, y0), (axmax, y0), color="orange") +ax.text((xmax + axmax)/2, y0+0.05, "x margin\n* x data range", + ha="center", va="bottom", color="orange") +ax.text(0.55, y0+0.1, "x data range", va="bottom", color="sienna") + +x0 = 0.1 +ax.axhspan(aymin, ymin, color=("tab:green", 0.1)) +ax.axhspan(ymax, aymax, color=("tab:green", 0.1)) +arrow((x0, ymin), (x0, ymax), color="darkgreen") +arrow((x0, ymax), (x0, aymax), color="tab:green") +ax.text(x0, (ymax + aymax) / 2, " y margin * y data range", + va="center", color="tab:green") +ax.text(x0, 0.5, " y data range", color="darkgreen") diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py new file mode 100644 index 000000000000..6f99a3febcdc --- /dev/null +++ b/doc/_embedded_plots/figure_subplots_adjust.py @@ -0,0 +1,34 @@ +import matplotlib.pyplot as plt + + +fig, axs = plt.subplots(2, 2, figsize=(6.5, 4)) +fig.set_facecolor('lightblue') +fig.subplots_adjust(0.1, 0.1, 0.9, 0.9, 0.4, 0.4) + +overlay = fig.add_axes([0, 0, 1, 1], zorder=100) +overlay.axis("off") +xycoords='figure fraction' +arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0) + +for ax in axs.flat: + ax.set(xticks=[], yticks=[]) + +overlay.annotate("", (0, 0.75), (0.1, 0.75), + xycoords=xycoords, arrowprops=arrowprops) # left +overlay.annotate("", (0.435, 0.25), (0.565, 0.25), + xycoords=xycoords, arrowprops=arrowprops) # wspace +overlay.annotate("", (0, 0.8), (0.9, 0.8), + xycoords=xycoords, arrowprops=arrowprops) # right +fig.text(0.05, 0.7, "left", ha="center") +fig.text(0.5, 0.3, "wspace", ha="center") +fig.text(0.05, 0.83, "right", ha="center") + +overlay.annotate("", (0.75, 0), (0.75, 0.1), + xycoords=xycoords, arrowprops=arrowprops) # bottom +overlay.annotate("", (0.25, 0.435), (0.25, 0.565), + xycoords=xycoords, arrowprops=arrowprops) # hspace +overlay.annotate("", (0.8, 0), (0.8, 0.9), + xycoords=xycoords, arrowprops=arrowprops) # top +fig.text(0.65, 0.05, "bottom", va="center") +fig.text(0.28, 0.5, "hspace", va="center") +fig.text(0.82, 0.05, "top", va="center") diff --git a/doc/_embedded_plots/grouped_bar.py b/doc/_embedded_plots/grouped_bar.py new file mode 100644 index 000000000000..f02e269328d2 --- /dev/null +++ b/doc/_embedded_plots/grouped_bar.py @@ -0,0 +1,15 @@ +import matplotlib.pyplot as plt + +categories = ['A', 'B'] +data0 = [1.0, 3.0] +data1 = [1.4, 3.4] +data2 = [1.8, 3.8] + +fig, ax = plt.subplots(figsize=(4, 2.2)) +ax.grouped_bar( + [data0, data1, data2], + tick_labels=categories, + labels=['dataset 0', 'dataset 1', 'dataset 2'], + colors=['#1f77b4', '#58a1cf', '#abd0e6'], +) +ax.legend() diff --git a/doc/_embedded_plots/hatch_classes.py b/doc/_embedded_plots/hatch_classes.py new file mode 100644 index 000000000000..cb9cd7d4b356 --- /dev/null +++ b/doc/_embedded_plots/hatch_classes.py @@ -0,0 +1,28 @@ +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle + +fig, ax = plt.subplots() + +pattern_to_class = { + '/': 'NorthEastHatch', + '\\': 'SouthEastHatch', + '|': 'VerticalHatch', + '-': 'HorizontalHatch', + '+': 'VerticalHatch + HorizontalHatch', + 'x': 'NorthEastHatch + SouthEastHatch', + 'o': 'SmallCircles', + 'O': 'LargeCircles', + '.': 'SmallFilledCircles', + '*': 'Stars', +} + +for i, (hatch, classes) in enumerate(pattern_to_class.items()): + r = Rectangle((0.1, i+0.5), 0.8, 0.8, fill=False, hatch=hatch*2) + ax.add_patch(r) + h = ax.annotate(f"'{hatch}'", xy=(1.2, .5), xycoords=r, + family='monospace', va='center', ha='left') + ax.annotate(pattern_to_class[hatch], xy=(1.5, .5), xycoords=h, + family='monospace', va='center', ha='left', color='tab:blue') + +ax.set(xlim=(0, 5), ylim=(0, i+1.5), yinverted=True) +ax.set_axis_off() diff --git a/doc/_static/.gitignore b/doc/_static/.gitignore deleted file mode 100644 index bbdc34458abc..000000000000 --- a/doc/_static/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -contour_frontpage.png -histogram_frontpage.png -membrane_frontpage.png -surface3d_frontpage.png - diff --git a/doc/_static/FigureInline.png b/doc/_static/FigureInline.png new file mode 100644 index 000000000000..6b7bd42c28f1 Binary files /dev/null and b/doc/_static/FigureInline.png differ diff --git a/doc/_static/FigureNotebook.png b/doc/_static/FigureNotebook.png new file mode 100644 index 000000000000..2d6d11cac3cc Binary files /dev/null and b/doc/_static/FigureNotebook.png differ diff --git a/doc/_static/FigureQtAgg.png b/doc/_static/FigureQtAgg.png new file mode 100644 index 000000000000..8d19e1a309ef Binary files /dev/null and b/doc/_static/FigureQtAgg.png differ diff --git a/doc/_static/John-hunter-crop-2.jpg b/doc/_static/John-hunter-crop-2.jpg deleted file mode 100644 index 48abd2e57626..000000000000 Binary files a/doc/_static/John-hunter-crop-2.jpg and /dev/null differ diff --git a/doc/_static/adjustText.png b/doc/_static/adjustText.png deleted file mode 100644 index 0549c8e50064..000000000000 Binary files a/doc/_static/adjustText.png and /dev/null differ diff --git a/doc/_static/anatomy.png b/doc/_static/anatomy.png index d3054395b06e..0809d43f7a56 100644 Binary files a/doc/_static/anatomy.png and b/doc/_static/anatomy.png differ diff --git a/doc/_static/animatplot.png b/doc/_static/animatplot.png deleted file mode 100644 index cb3b2222d890..000000000000 Binary files a/doc/_static/animatplot.png and /dev/null differ diff --git a/doc/_static/basemap_contour1.png b/doc/_static/basemap_contour1.png deleted file mode 100644 index f717686a4e8f..000000000000 Binary files a/doc/_static/basemap_contour1.png and /dev/null differ diff --git a/doc/_static/blume_table_example.png b/doc/_static/blume_table_example.png deleted file mode 100644 index fa50b1694386..000000000000 Binary files a/doc/_static/blume_table_example.png and /dev/null differ diff --git a/doc/_static/boxplot_explanation.png b/doc/_static/boxplot_explanation.png deleted file mode 100644 index d057496e4e44..000000000000 Binary files a/doc/_static/boxplot_explanation.png and /dev/null differ diff --git a/doc/_static/brokenaxes.png b/doc/_static/brokenaxes.png deleted file mode 100644 index 02807b91cdc3..000000000000 Binary files a/doc/_static/brokenaxes.png and /dev/null differ diff --git a/doc/_static/cartopy_hurricane_katrina_01_00.png b/doc/_static/cartopy_hurricane_katrina_01_00.png deleted file mode 100644 index 4b7267f24f2e..000000000000 Binary files a/doc/_static/cartopy_hurricane_katrina_01_00.png and /dev/null differ diff --git a/doc/_static/contents.png b/doc/_static/contents.png deleted file mode 100644 index 7fb82154a174..000000000000 Binary files a/doc/_static/contents.png and /dev/null differ diff --git a/doc/_static/demo_axes_grid.png b/doc/_static/demo_axes_grid.png deleted file mode 100644 index 9af9fdfe6380..000000000000 Binary files a/doc/_static/demo_axes_grid.png and /dev/null differ diff --git a/doc/_static/dna_features_viewer_screenshot.png b/doc/_static/dna_features_viewer_screenshot.png deleted file mode 100644 index 0c2b18a2ba30..000000000000 Binary files a/doc/_static/dna_features_viewer_screenshot.png and /dev/null differ diff --git a/doc/_static/eeg_large.png b/doc/_static/eeg_large.png deleted file mode 100644 index 6224f4c2de60..000000000000 Binary files a/doc/_static/eeg_large.png and /dev/null differ diff --git a/doc/_static/eeg_small.png b/doc/_static/eeg_small.png deleted file mode 100644 index fb02af5b4a36..000000000000 Binary files a/doc/_static/eeg_small.png and /dev/null differ diff --git a/doc/_static/fa/LICENSE b/doc/_static/fa/LICENSE index 81369b90fe23..ea0d11539513 100644 --- a/doc/_static/fa/LICENSE +++ b/doc/_static/fa/LICENSE @@ -1,4 +1,4 @@ -Font Awsome SVG Icons are covered by CC BY 4.0 License. +Font Awesome SVG Icons are covered by CC BY 4.0 License. https://fontawesome.com/license/free diff --git a/doc/_static/figpager.png b/doc/_static/figpager.png deleted file mode 100644 index a867d1372033..000000000000 Binary files a/doc/_static/figpager.png and /dev/null differ diff --git a/doc/_static/fonts/Carlogo-bold.ttf b/doc/_static/fonts/Carlogo-bold.ttf deleted file mode 100644 index c16254f57a89..000000000000 Binary files a/doc/_static/fonts/Carlogo-bold.ttf and /dev/null differ diff --git a/doc/_static/fonts/Carlogo-boldItalic.ttf b/doc/_static/fonts/Carlogo-boldItalic.ttf deleted file mode 100644 index d7d2507a4e6d..000000000000 Binary files a/doc/_static/fonts/Carlogo-boldItalic.ttf and /dev/null differ diff --git a/doc/_static/fonts/Carlogo-italic.ttf b/doc/_static/fonts/Carlogo-italic.ttf deleted file mode 100644 index de7824c6bd35..000000000000 Binary files a/doc/_static/fonts/Carlogo-italic.ttf and /dev/null differ diff --git a/doc/_static/fonts/Carlogo-regular.ttf b/doc/_static/fonts/Carlogo-regular.ttf deleted file mode 100644 index 6911c4ddf752..000000000000 Binary files a/doc/_static/fonts/Carlogo-regular.ttf and /dev/null differ diff --git a/doc/_static/fonts/carlogo-bold.woff b/doc/_static/fonts/carlogo-bold.woff deleted file mode 100755 index 00c68a81d84e..000000000000 Binary files a/doc/_static/fonts/carlogo-bold.woff and /dev/null differ diff --git a/doc/_static/fonts/carlogo-bold.woff2 b/doc/_static/fonts/carlogo-bold.woff2 deleted file mode 100755 index cff2d45c9000..000000000000 Binary files a/doc/_static/fonts/carlogo-bold.woff2 and /dev/null differ diff --git a/doc/_static/fonts/carlogo-bolditalic.woff b/doc/_static/fonts/carlogo-bolditalic.woff deleted file mode 100755 index 88be19f89771..000000000000 Binary files a/doc/_static/fonts/carlogo-bolditalic.woff and /dev/null differ diff --git a/doc/_static/fonts/carlogo-bolditalic.woff2 b/doc/_static/fonts/carlogo-bolditalic.woff2 deleted file mode 100755 index 3ceb3a396837..000000000000 Binary files a/doc/_static/fonts/carlogo-bolditalic.woff2 and /dev/null differ diff --git a/doc/_static/fonts/carlogo-italic.woff b/doc/_static/fonts/carlogo-italic.woff deleted file mode 100755 index 806f239385a8..000000000000 Binary files a/doc/_static/fonts/carlogo-italic.woff and /dev/null differ diff --git a/doc/_static/fonts/carlogo-italic.woff2 b/doc/_static/fonts/carlogo-italic.woff2 deleted file mode 100755 index a70e881db731..000000000000 Binary files a/doc/_static/fonts/carlogo-italic.woff2 and /dev/null differ diff --git a/doc/_static/fonts/carlogo-regular.woff b/doc/_static/fonts/carlogo-regular.woff deleted file mode 100755 index 7a8ab29f96bb..000000000000 Binary files a/doc/_static/fonts/carlogo-regular.woff and /dev/null differ diff --git a/doc/_static/fonts/carlogo-regular.woff2 b/doc/_static/fonts/carlogo-regular.woff2 deleted file mode 100755 index 272988a2bc01..000000000000 Binary files a/doc/_static/fonts/carlogo-regular.woff2 and /dev/null differ diff --git a/doc/_static/geoplot_nyc_traffic_tickets.png b/doc/_static/geoplot_nyc_traffic_tickets.png deleted file mode 100644 index f63ffccedb6e..000000000000 Binary files a/doc/_static/geoplot_nyc_traffic_tickets.png and /dev/null differ diff --git a/doc/_static/ggplot.png b/doc/_static/ggplot.png deleted file mode 100644 index 466b5d82aa5f..000000000000 Binary files a/doc/_static/ggplot.png and /dev/null differ diff --git a/doc/_static/gif_attachment_example.png b/doc/_static/gif_attachment_example.png deleted file mode 100644 index 03bbb2f81ab1..000000000000 Binary files a/doc/_static/gif_attachment_example.png and /dev/null differ diff --git a/doc/_static/gold_on_carbon.jpg b/doc/_static/gold_on_carbon.jpg deleted file mode 100644 index 935e99eac398..000000000000 Binary files a/doc/_static/gold_on_carbon.jpg and /dev/null differ diff --git a/doc/_static/holoviews.png b/doc/_static/holoviews.png deleted file mode 100644 index 7632d821bf29..000000000000 Binary files a/doc/_static/holoviews.png and /dev/null differ diff --git a/doc/_static/icon.png b/doc/_static/icon.png deleted file mode 100644 index 3ec68e5014a9..000000000000 Binary files a/doc/_static/icon.png and /dev/null differ diff --git a/doc/_static/logo2.png b/doc/_static/logo2.png deleted file mode 100644 index 72843ab1febb..000000000000 Binary files a/doc/_static/logo2.png and /dev/null differ diff --git a/doc/_static/logo2_compressed.svg b/doc/_static/logo2_compressed.svg deleted file mode 100644 index 1cb032d4c6ea..000000000000 --- a/doc/_static/logo2_compressed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/doc/_static/logo_sidebar.png b/doc/_static/logo_sidebar.png deleted file mode 100644 index edc9cbd008a5..000000000000 Binary files a/doc/_static/logo_sidebar.png and /dev/null differ diff --git a/doc/_static/logo_sidebar_horiz.png b/doc/_static/logo_sidebar_horiz.png deleted file mode 100644 index 9274331a0258..000000000000 Binary files a/doc/_static/logo_sidebar_horiz.png and /dev/null differ diff --git a/doc/_static/matplotlib_iterm2_demo.png b/doc/_static/matplotlib_iterm2_demo.png deleted file mode 100644 index 8bc93e3b4b0e..000000000000 Binary files a/doc/_static/matplotlib_iterm2_demo.png and /dev/null differ diff --git a/doc/_static/mpl-interactions-slider-animated.png b/doc/_static/mpl-interactions-slider-animated.png deleted file mode 100644 index 8c2b63804aa0..000000000000 Binary files a/doc/_static/mpl-interactions-slider-animated.png and /dev/null differ diff --git a/doc/_static/mpl-scatter-density.png b/doc/_static/mpl-scatter-density.png deleted file mode 100644 index a0408a1c09b5..000000000000 Binary files a/doc/_static/mpl-scatter-density.png and /dev/null differ diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 57505f0a972e..25bad17c3938 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -1,501 +1,25 @@ -/* - * Alternate Sphinx design - * Originally created by Armin Ronacher for Werkzeug, adapted by Georg Brandl. - */ - -/* Carlogo font (similar to Calibri in the MPL logo) */ -@font-face { - font-family: 'CarlogoRegular'; - font-style: normal; - src: local('Carlito'), - url('fonts/carlogo-regular.woff2') format('woff2'), - url('fonts/carlogo-regular.woff') format('woff'), - url('fonts/carlogo-regular.ttf') format('truetype') -} - -@font-face { - font-family: 'CarlogoBold'; - font-style: bold; - src: local('Carlito Bold'), - url('fonts/carlogo-bold.woff2') format('woff2'), - url('fonts/carlogo-bold.woff') format('woff'), - url('fonts/carlogo-bold.ttf') format('truetype') -} - - -body { - font-family: "Helvetica Neue", Helvetica, 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; - font-size: 14px; - line-height: 150%; - text-align: center; - background-color: #BFD1D4; - color: black; - padding: 0; - border: 1px solid #aaa; - color: #333; - margin: auto; - min-width: 740px; - max-width: 1200px; -} - -a { - color: #CA7900; - text-decoration: none; -} - -a:hover { - color: #2491CF; -} - -div.highlight-python a { - color: #CA7900; -} - -div.highlight-python a:hover { - color: #2491CF; -} - -strong { - font-weight: strong; -} - -pre { - font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; - font-size: 0.90em; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - letter-spacing: 0.015em; - padding: 1em; - border: 1px solid #ccc; - background-color: #f8f8f8; - line-height: 140%; - overflow-x: auto; -} - -td.linenos pre { - padding: 0.5em 0; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -cite, code, tt { - font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.95em; - letter-spacing: 0.01em; -} - -hr { - border: 1px solid #abc; - margin: 2em; -} - -tt { - background-color: #f2f2f2; - border-bottom: 1px solid #ddd; - color: #333; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; - border: 0; -} - -tt.descclassname { - background-color: transparent; - border: 0; -} - -tt.xref { - background-color: transparent; - font-weight: bold; - border: 0; -} - -a tt { - background-color: transparent; - font-weight: bold; - border: 0; - color: #CA7900; -} - -a tt:hover { - color: #2491CF; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 1px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.refcount { - color: #060; -} - -dt:target, -.highlight { - background-color: #ffffee; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -pre a { - color: inherit; - text-decoration: none; -} - -.first { - margin-top: 0 !important; -} - -div.document { - background-color: white; - text-align: left; - background-image: url(contents.png); - background-repeat: repeat-x; -} - -/* -div.documentwrapper { - width: 100%; -} -*/ - -div.related h3 { - display: none; -} - -div.related ul { - background-image: url(navigation.png); - height: 2em; - list-style: none; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 0; - padding-left: 10px; -} - -div.related ul li { - margin: 0; - padding: 0; - height: 2em; - float: left; -} - -div.related ul li.right { - float: right; - margin-right: 5px; -} - -div.related ul li a { - margin: 0; - padding: 0 5px 0 5px; - line-height: 1.75em; - color: #EE9816; -} - -div.related ul li a:hover { - color: #3CA8E7; -} - -div.body { - margin: 0; - padding: 0.5em 20px 20px 20px; -} - -div.bodywrapper { - margin: 0 240px 0 0; - border-right: 1px solid #ccc; -} - -div.sphinxsidebar { - margin: 0; - padding: 0.5em 15px 15px 0; - width: 210px; - float: right; - text-align: left; -/* margin-left: -100%; */ -} - -div.sphinxsidebar h4, div.sphinxsidebar h3 { - margin: 1em 0 0.5em 0; - font-size: 0.9em; - padding: 0.1em 0 0.1em 0.5em; - color: white; - border: 1px solid #86989B; - background-color: #AFC1C4; -} - -div.sphinxsidebar h3 a { - /* workaround for table of contents heading, which is a link */ - color: white !important; -} - -div.sphinxsidebar ul { - padding-left: 1.5em; - margin-top: 10px; - margin-bottom: 10px; - list-style: none; - padding: 0; - line-height: 130%; -} - -div.sphinxsidebar ul ul { - list-style: square; - margin-top: 6px; - margin-bottom: 6px; - margin-left: 16px; -} - -div.sphinxsidebar #searchbox input { - border: 1px solid #aaa; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox form { - display: inline-block; - width: 100% -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; -} - -div.sphinxsidebar #searchbox input[type="submit"]:hover { - background: #ddd; -} - -div.sphinxsidebar .searchformwrapper { - display: block; -} - -p { - margin: 0.8em 0 0.8em 0; -} - -h1 { - margin: 0.5em 0em; - padding-top: 0.5em; - font-size: 2em; - color: #11557C; -} - -h2 { - margin: 0.5em 0 0.2em 0; - padding-top: 0.5em; - font-size: 1.7em; -} - -h3 { - margin: 0.2em 0 0.1em 0; - padding-top: 0.5em; - font-size: 1.2em; -} - -h1, h2, h3, h4, h5, h6{ - font-family: 'CarlogoBold', 'Carlito-bold', sans-serif; - font-weight: normal; -} - -h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { - color: black!important; -} - -h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { - display: none; - margin: 0 0 0 0.3em; - padding: 0 0.2em 0 0.2em; - color: #aaa!important; -} - -h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, -h5:hover a.anchor, h6:hover a.anchor { - display: inline; -} - -h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, -h5 a.anchor:hover, h6 a.anchor:hover { - color: #777; - background-color: #eee; -} - -table { - border-collapse: collapse; - margin: 0 -0.5em 0 -0.5em; -} - -table td, table th { - padding: 0.2em 0.5em 0.2em 0.5em; -} - -div.footer { - background-color: #E3EFF1; - color: #86989B; - padding: 3px 8px 3px 0; - clear: both; - font-size: 0.8em; - text-align: center; -} - -div.footer a { - color: #86989B; - text-decoration: underline; -} - -div.pagination { - margin-top: 2em; - padding-top: 0.5em; - border-top: 1px solid black; - text-align: center; -} - -div.sphinxsidebar ul.toc { - margin: 1em 0 1em 0; - padding: 0 0 0 0.5em; - list-style: none; -} - -div.sphinxsidebar ul.toc li { - margin: 0.5em 0 0.5em 0; - font-size: 0.9em; - line-height: 130%; -} - -div.sphinxsidebar ul.toc li p { +:root { + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-secondary); + --sd-color-primary: var(--pst-color-primary); + --sd-color-primary-text: var(--pst-color-text-base); + --sd-color-secondary: #ee9040; + --sd-color-success: #28a745; + --sd-color-dark: #323232; + --sd-color-danger: #dc3545; + --sd-color-light: #c9c9c9; +} + +.simple li>p { margin: 0; - padding: 0; -} - -div.sphinxsidebar ul.toc ul { - margin: 0.2em 0 0.2em 0; - padding: 0 0 0 1.8em; -} - -div.sphinxsidebar ul.toc ul li { - padding: 0; -} - -/* admonitions */ - -div.admonition, div.deprecated { - margin: 10px 0px; - padding: 0.7em 1.4em; - border-left: 5px solid; - } - -div.note { - background-color: #eee; - border-color: #ccc; -} - -div.seealso { - background-color: #EAF1F7; - border-color: #8EADCC; - color: #3F5E7F; - } - -div.warning, div.important { - background-color: #F3E5E5; - border-color: #CC8E8E; - color: #7F1919; -} - -div.deprecated { - background-color: #f0f0f0; - border-color: #404040; - color: #606060; -} - -span.versionmodified { - font-style: italic; -} - -div.deprecated span.versionmodified { - font-weight: bold; - font-style: normal; -} - -div.green, div.hint { - background-color: #E1F2DA; - border-color: #A1CC8E; - color: #3F7F3F; -} - -div.admonition p.admonition-title { - font-size: 1.2em; - font-weight: bold; -} - -div.admonition p, div.deprecated p { - margin: 0.6em 0; - padding: 0; -} - -div.admonition pre { - margin: 0.6em 0; -} - -div.admonition ul, div.admonition ol { - margin: 0.1em 0.5em 0.5em 2em; - padding: 0; -} - -div.topic { - background-color: #f4f4f4; - border: 2px solid #ccc; - border-left: 0px; - border-right: 0px; - margin: 10px 0px; - padding-left: 1em; -} - -p.topic-title { - font-size: 1.2em; - font-weight: bold; } +/* multi column TOC */ .contents ul { list-style-type: none; padding-left: 2em; } -/* first level */ .contents > ul { padding-left: 0; } @@ -515,765 +39,184 @@ p.topic-title { -webkit-break-inside: avoid-column; } -.contents > ul > li { - padding-top: 0.3em; - padding-bottom: 0.3em; -} - -.contents ul > li::before { - content: "\25FE"; - color: #bbb; - padding-right: .3em; -} - .contents > ul > li > a { font-size: 1.0em; } +/* Hide red ¶ between the thumbnail and caption in gallery - -div.versioninfo { - margin: 1em 0 0 0; - border: 1px solid #ccc; - background-color: #DDEAF0; - padding: 8px; - line-height: 1.3em; - font-size: 0.9em; -} - -a.headerlink { - color: #c60f0f!important; - font-size: 1em; - margin-left: 6px; - padding: 0 4px 0 4px; - text-decoration: none!important; +Due the way that sphinx-gallery floats its captions the perma-link +does not float with it. +*/ +.sphx-glr-thumbcontainer p.caption:hover > a.headerlink{ visibility: hidden; } -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -a.headerlink:hover { - background-color: #ccc; - color: white!important; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -img.inheritance { - border: 0px -} - -form.pfform { - margin: 10px 0 20px 0; -} - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -.highlight span.c1 span.highlighted { - background-color: #fce5a6; -} - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -table.docutils { - border-spacing: 2px; - border-collapse: collapse; - border: 0px; -} - -table.docutils th { - border-width: 1px 0px; - border-color: #888; - background-color: #f0f0f0; - width: 100px; +/* slightly reduce horizontal margin compared to gallery.css to + * get four columns of thumbnails in the pydata-sphinx-theme. */ +.sphx-glr-thumbcontainer { + margin: 5px 2px; } -table.docutils td { - border-width: 1px 0px; - border-color: #ccc; +html[data-theme="dark"] .sphx-glr-thumbcontainer { + background-color: rgb(63, 63, 63); } -table.docutils tr:last-of-type td { - border-bottom-color: #888; +/* Set a fixed height so that lazy loading does not change heights. Without a fixed + * height lazy loading of images interferes with anchor links: Clicking a link goes to + * a certain position, but then the loaded images add content and move the anchor to a + * different position. + */ +.sphx-glr-thumbcontainer img { + height: 112px; } -table.docutils tr:first-of-type td { - border-top-color: #888; +/* hide download buttons in example headers + * https://sphinx-gallery.github.io/stable/advanced.html#hide-the-download-buttons-in-the-example-headers + */ +div.sphx-glr-download-link-note { + display: none; } -/* Section titles within classes */ -dl.class p.rubric { - font-size: 16px; +/* re-style the download button */ +div.sphx-glr-download a { + background-color: #E3F0F6; + background-image: none; + color: #11557c; + border: 0; } -/* Attribute tables */ -dl.class p.rubric + table.docutils { - margin-left: 0px; - margin-right: 0px; - margin-bottom: 1.5em; - border-top: 1px solid #888; - border-bottom: 1px solid #888; +div.sphx-glr-download a:hover { + background-color: #BCD4DF; } -dl.class p.rubric + table.docutils td { - padding-left: 0px; - border-color: #ccc; +/* Do not fold multiple figures in examples into two column layout. */ +img.sphx-glr-multi-img { + max-width: 100%; } -dl.class p.rubric + table.docutils td:first-of-type > strong { - font-family: monospace; - font-size: 14px; - font-weight: normal; +table.property-table th, +table.property-table td { + padding: 4px 10px; } +/* Fix selection of parameter names; remove when fixed in the theme + * https://github.com/sphinx-doc/sphinx/pull/9763 + */ .classifier:before { - font-style: normal; - margin: 0.2em; - content: ":"; -} - -/* module summary table */ -.longtable.docutils { - font-size: 12px; - margin-bottom: 30px; -} -.longtable.docutils, .longtable.docutils td { - border-color: #ccc; -} - -.longtable.docutils tr.row-even{ - background-color: #eff3f4; -} - -/* tables inside class descriptions */ -dl.class table.property-table { - width: 85%; - border-spacing: 2px; - border-collapse: collapse; - border: 0px; -} - -/* tables inside parameter descriptions */ -td.field-body table.property-table { - width: 100%; - border-spacing: 2px; - border-collapse: collapse; - border: 0px; -} - -td.field-body table.property-table th { - padding: 2px 10px; - border-width: 1px 0px; - border-color: #888; - background-color: #f0f0f0; -} - -td.field-body table.property-table td { - padding: 2px 10px; - border-width: 1px 0px; - border-color: #ccc; -} - -td.field-body table.property-table tr:last-of-type td { - border-bottom-color: #888; -} - - -/* function and class description */ -.descclassname { - color: #aaa; - font-weight: normal; - font-family: monospace; -} -.descname { - font-family: monospace; -} - -/*** function and class description ***/ -/* top-level definitions */ -dl.class, dl.function, dl.data, dl.exception { - border-top: 1px solid #888; - padding-top: 0px; - margin-top: 20px; -} - -dl.method, dl.classmethod, dl.staticmethod, dl.attribute { - border-top: 1px solid #ccc; - padding-top: 0px; -} - - -dl.class > dt, dl.classmethod > dt, dl.method > dt, dl.function > dt, -dl.attribute > dt, dl.staticmethod > dt, dl.data > dt, dl.exception > dt { - background-color: #eff3f4; - padding-left: 6px; - padding-right: 6px; - padding-top: 2px; - padding-bottom: 1px; -} - -em.property { - margin-right: 4px; -} - -.sig-paren { - font-size: 14px; -} - -.sig-paren ~ em { - font-weight: normal; - font-family: monospace; - font-size: 14px; -} - -dl.class big, dl.function big { - font-weight: normal; - font-family: monospace; -} - -dl.class dd, dl.function dd, dl.data dd { - padding: 10px; -} - -dl.class > dd { - padding: 10px; - padding-left: 35px; - margin-left: 0px; - border-left: 5px solid #f8f8f8; -} - -.descclassname { - color: #aaa; - font-weight: normal; - font-family: monospace; - font-size: 14px; -} - -.descname { - font-family: monospace; - font-size: 14px; -} - -/* parameter section table */ -table.docutils.field-list { - width: 100%; -} -.docutils.field-list th.field-name { - background-color: #eee; - padding: 10px; - text-align: left; - vertical-align: top; - width: 125px; -} -.docutils.field-list td.field-body { - padding: 10px 10px 10px 20px; - text-align: left; - vertical-align: top; -} -.docutils.field-list td.field-body blockquote p { - font-size: 13px; - line-height: 18px; -} -.docutils.field-list td.field-body blockquote p ul li{ - font-size: 13px; -} - -p.rubric { - font-weight: bold; - font-size: 19px; - margin: 15px 0 10px 0; -} - -#matplotlib-examples ul li{ - font-size: large; -} - -#matplotlib-examples ul li ul{ - margin-bottom:20px; - overflow:hidden; - border-top:1px solid #ccc; -} - -#matplotlib-examples ul li ul li { - font-size: small; - line-height:1.75em; - display:inline; - float: left; - width: 22em; -} - -#overview ul li ul{ - margin-bottom:20px; - overflow:hidden; - border-top:1px solid #ccc; -} - -#overview ul li ul li { - display:inline; - float: left; - width: 30em; -} - -figure { - margin: 1em; - display: inline-block; -} - -figure img { - margin-left: auto; - margin-right: auto; -} - -figcaption { - text-align: center; -} - - -/* "Go to released version" message. */ -#unreleased-message { - background: #d62728; - box-sizing: border-box; - color: #fff; - font-weight: bold; - left: 0; - min-height: 3em; - padding: 0.7em; - position: fixed; - top: 0; - width: 100%; - z-index: 10000; -} - -#unreleased-message + div { - margin-top: 3em; -} - -#unreleased-message a { - color: #fff; - text-decoration:underline; - -} - -/* top-banner style message. */ -#annc-banner { - box-sizing: border-box; - left: 0; - min-height: 3em; - padding: 0.7em; - top: 0; - width: 100%; - z-index: 10000; - background-image: linear-gradient(90deg, #440154, #482475, #414487, #355f8d, #2a788e, #21908d, #22a884, #42be71, #7ad151, #bddf26, #bddf26); - padding: 5px -} - -#annc-banner a { - font-weight: bold; -} - -#annc-banner p{ - background-color: rgba(255, 255, 255, .8); - padding: 13px; - margin:0; -} - -/* Fork me on GitHub "button" */ -#forkongithub a{ - background:#FF7F0E; - color:#fff !important; - text-decoration:none; - text-align:center; - font-weight:bold; - padding:5px 40px; - line-height:1.5rem; - position:relative; - transition:background .25s ease; -} -#forkongithub a:hover{ - background:#CA7900; -} -#forkongithub a::before,#forkongithub a::after{ - content:""; - width:100%; - display:block; - position:absolute; - top:1px; - left:0; - height:1px; - background:#fff; -} -#forkongithub a::after{ - bottom:1px; - top:auto; -} -@media screen and (min-width:700px){ - #forkongithub{ - position:absolute; - top:0; - right:0; - width:150px; - overflow:hidden; - height:150px; - z-index:9999; - } - #forkongithub a{ - width:150px; - position:absolute; - top:40px; - right:-60px; - transform:rotate(45deg); - -webkit-transform:rotate(45deg); - -ms-transform:rotate(45deg); - -moz-transform:rotate(45deg); - -o-transform:rotate(45deg); - box-shadow:4px 4px 10px rgba(0,0,0,0.8); - } -} - -.mpl-button { - background: #11557C; - font-weight: normal; display: inline-block; - padding: 0 1em; - line-height: 2.8; - font-size: 16px; - text-align: center; - cursor: pointer; - color: #fff; - text-decoration: none; - border-radius: 6px; - z-index: 1; - transition: background .25s ease; -} - -.mpl-button:hover, .mpl-button:active, .mpl-button:focus { - background: #003c63; - outline-color: #003c63; -} - -#sidebar-donations { - margin-top: 40px; -} - -.donate_button { - clear: both; - display: block; - margin: 30px auto 0; -} - -.donate_button:last-of-type { - margin: 15px auto 30px; - - -} - -div.responsive_screenshots { - /* Horizontally centered */ - display: block; - margin: auto; - - /* Do not go beyond 1:1 scale (and ensure a 1x4 tight layout) */ - max-width: 640px; /* at most 4 x 1:1 subfig width */ - max-height: 120px; /* at most 1 x 1:1 subfig height */ -} - -/* To avoid subfigure parts outside of the responsive_screenshots */ -/* element (see: https://stackoverflow.com/questions/2062258/ */ -/* floating-stuff-within-a-div-floats-outside-of-div-why) */ -span.clear_screenshots { clear: left; display: block; } - -div.responsive_subfig{ - float: left; - width: 25%; /* we want 4 subfigs in a row */ - - /* Include content, padding and border in width. This should */ - /* avoid having to use tricks like "width: 24.9999%" */ - box-sizing: border-box; -} - -div.responsive_subfig img { - /* Horizontally centered */ - display: block; - margin: auto; - - /* Possible downscaling */ - max-width: 162px; /* at most 1 x 1:1 subfig width */ - max-height: 139px; /* at most 1 x 1:1 subfig height */ - - width: 100%; -} - -@media only screen and (max-width: 930px){ - /* The value of 1000px was handcrafted to provide a more or less */ - /* smooth transition between the 1x4 and the 2x2 layouts. It is */ - /* NB: it is slightly below 1024px: so one should still have a */ - /* row in a 1024x768 window */ - - div.responsive_screenshots { - /* Do not go beyond 1:1 scale (and ensure a 2x2 tight layout) */ - max-width: 324px; /* at most 2 x 1:1 subfig width */ - max-height: 278px; /* at most 2 x 1:1 subfig height */ - } - - div.responsive_subfig { - width: 50%; /* we want 2 subfigs in a row */ - } -} - -/* Sphinx gallery display */ - -div.align-center { - margin: auto; - text-align: center; -} - -p.caption { - font-weight: bold; -} - -.sphx-glr-multi-img{ - max-width: 99% !important; -} - -.sphx-glr-thumbcontainer { - border: solid #d6d6d6 1px !important; - text-align: center !important; - font-size: 1.2em !important; -} - -div.sphx-glr-download { - width: auto !important; -} - -div.sphx-glr-download a { - background-color: #d9edf7 !important; - border: 1px solid #bce8f1 !important; - background-image: none !important; -} - -p.sphx-glr-signature { - display: none !important; -} - -div.sphx-glr-download-link-note { - display: none !important; + margin: 0 0.5em; } -.sphx-glr-thumbcontainer a.internal { - font-weight: 400; +/* Make the code examples in the API reference index the same height. */ +.api-interface-example pre { + min-height: 6.5rem; } -.viewcode-link { - float: right; +/* Make inheritance images have a scroll bar if necessary. */ +div.graphviz { + border: 1px solid lightgrey; + max-height: 50em; + overflow: auto; } - -.viewcode-back { - float: right; - font-family: "Helvetica Neue", Helvetica, 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; -} - -div.viewcode-block:target { - margin: -1px -13px; - padding: 0 10px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; - background-color: #f4debf; - +img.graphviz.inheritance { + max-width: none; } -.sidebar-announcement, -.sidebar-versions { - border: 1px solid #11557C; - background: #eff9ff; - padding: 0.2em 0.5em; - margin-top: 1em; +/* Make tables in notes horizontally scrollable if too large. */ +div.wide-table { + overflow-x: auto; } -.sidebar-announcement p { - margin: 0.4em 0 0.6em 0; +div.wide-table table th.stub { + background-color: var(--pst-color-background); + background-clip: padding-box; + left: 0; + position: sticky; } -/* new main nav */ -nav.main-nav{ - background-color: #002b47; - font-family: 'CarlogoRegular', 'Carlito', sans-serif; - font-size: 16px; - } - - nav.main-nav ul{ - margin: 0; - padding: 0; +.imrot-img { display: flex; - flex-direction: row; - } - - nav.main-nav li{ - margin: 8px 15px; - list-style-type: none; - } - - nav.main-nav a{ - color: white; - } - - nav.main-nav a:hover{ - text-decoration: underline; + margin: auto; + max-width:15em; + align-self: center; } - nav.main-nav li.nav-right{ - margin: 6px 15px 0 auto; + .imrot-cap { + text-align: center; + font-style: italic; + font-size: large; } - nav.main-nav input { - border: 0; - padding: 3px 6px; - width: 198px; - } -/* community items on main page */ -div.box { - display: flex; - flex-flow: row wrap; +.checklist { + list-style: none; + padding: 0; + margin: 0; } - -div.box-item { - flex: 0 0 45%; - padding: 4px; - margin: 8px 12px; +.checklist li { + margin-left: 24px; + padding-left: 23px; + margin-right: 6px; } - -div.box-item img { - float: left; - width: 30px; - height: 30px; - fill: #888; - +.checklist li:before { + content: "\2610\2001"; + margin-left: -24px; } -div.box-item p { - margin: 0 0 0 50px; +.checklist li p { + display: inline; } -div.box-item ul { - margin: 0 0 0 50px; - padding-left: 20px; -} +/* sdd is a custom class that strips out styling from dropdowns + * Example usage: + * + * .. dropdown:: + * :class-container: sdd + * + */ -hr.box-sep { - margin: 1em 2em; +.sdd.sd-dropdown { + box-shadow: none!important; } -@media only screen and (max-width: 930px){ - div.box-item { - flex: 0 0 90%; - } +.sdd.sd-dropdown.sd-card{ + border-style: solid !important; + border-color: var(--pst-color-border) !important; + border-width: thin !important; + border-radius: .05 } -/* bullet boxes on main page */ -div.bullet-box-container { - display: flex; - flex-wrap: wrap; - margin: 1em 0; +.sdd.sd-dropdown .sd-card-header{ + --pst-sd-dropdown-color: none; } -div.bullet-box { - flex-grow: 1; - width: 28%; - margin: 0.4em; - padding: 0 1em; - background: #eff9ff; +.sdd.sd-dropdown .sd-card-header +.sd-card-body{ + --pst-sd-dropdown-color: none; } -div.bullet-box p:first-of-type { - font-size: 1.4em; - text-align: center; +/* section-toc is a custom class that removes the page title from a toctree listing + * and shifts the resulting list left + * Example usage: + * + * .. rst-class:: section-toc + * .. toctree:: + * + */ + .section-toc.toctree-wrapper .toctree-l1>a{ + display: none; } - -div.bullet-box ul { - padding-left: 1.2em; +.section-toc.toctree-wrapper .toctree-l1>ul{ + padding-left: 0; } -div.bullet-box li { - padding-left: 0.3em; - margin-bottom: 0.3em; +.sidebar-cheatsheets { + margin-bottom: 3em; } -@media only screen and (max-width: 930px){ - div.bullet-box { - flex: 0 0 90%; - } +.sidebar-cheatsheets > h3 { + margin-top: 0; } -div#gallery.section .sphx-glr-clear:first-of-type, div#tutorials.section .sphx-glr-clear:first-of-type{ - display: none; -} \ No newline at end of file +.sidebar-cheatsheets > img { + width: 100%; +} diff --git a/doc/_static/mpl_cheatsheet1.png b/doc/_static/mpl_cheatsheet1.png new file mode 100644 index 000000000000..5b637f29e32c Binary files /dev/null and b/doc/_static/mpl_cheatsheet1.png differ diff --git a/doc/_static/mpl_cheatsheet1_2x.png b/doc/_static/mpl_cheatsheet1_2x.png new file mode 100644 index 000000000000..765ff32d2f83 Binary files /dev/null and b/doc/_static/mpl_cheatsheet1_2x.png differ diff --git a/doc/_static/mpl_template_example.png b/doc/_static/mpl_template_example.png deleted file mode 100644 index 44dcf9858ef4..000000000000 Binary files a/doc/_static/mpl_template_example.png and /dev/null differ diff --git a/doc/_static/mplot3d_view_angles.png b/doc/_static/mplot3d_view_angles.png new file mode 100644 index 000000000000..16d3c2f0d699 Binary files /dev/null and b/doc/_static/mplot3d_view_angles.png differ diff --git a/doc/_static/navigation.png b/doc/_static/navigation.png deleted file mode 100644 index 1081dc1439fb..000000000000 Binary files a/doc/_static/navigation.png and /dev/null differ diff --git a/doc/_static/numfocus_badge.png b/doc/_static/numfocus_badge.png deleted file mode 100644 index b8d8e6ca838f..000000000000 Binary files a/doc/_static/numfocus_badge.png and /dev/null differ diff --git a/doc/_static/numpngw_animated_example.png b/doc/_static/numpngw_animated_example.png deleted file mode 100644 index 2af5f9d7441a..000000000000 Binary files a/doc/_static/numpngw_animated_example.png and /dev/null differ diff --git a/doc/_static/pgf_fonts.pdf b/doc/_static/pgf_fonts.pdf deleted file mode 100644 index 9f9bf0bae67d..000000000000 Binary files a/doc/_static/pgf_fonts.pdf and /dev/null differ diff --git a/doc/_static/pgf_fonts.png b/doc/_static/pgf_fonts.png deleted file mode 100644 index d4ef689f9b33..000000000000 Binary files a/doc/_static/pgf_fonts.png and /dev/null differ diff --git a/doc/_static/pgf_texsystem.pdf b/doc/_static/pgf_texsystem.pdf deleted file mode 100644 index fbae0ea766ff..000000000000 Binary files a/doc/_static/pgf_texsystem.pdf and /dev/null differ diff --git a/doc/_static/pgf_texsystem.png b/doc/_static/pgf_texsystem.png deleted file mode 100644 index 6075e7b764dd..000000000000 Binary files a/doc/_static/pgf_texsystem.png and /dev/null differ diff --git a/doc/_static/plotnine.png b/doc/_static/plotnine.png deleted file mode 100644 index ff9b1847a7e9..000000000000 Binary files a/doc/_static/plotnine.png and /dev/null differ diff --git a/doc/_static/probscale_demo.png b/doc/_static/probscale_demo.png deleted file mode 100644 index 1e663e049978..000000000000 Binary files a/doc/_static/probscale_demo.png and /dev/null differ diff --git a/doc/_static/quiver_sizes.svg b/doc/_static/quiver_sizes.svg new file mode 100644 index 000000000000..afba2c601d09 --- /dev/null +++ b/doc/_static/quiver_sizes.svg @@ -0,0 +1,429 @@ + + + + + + + + + + image/svg+xml + + + + + + + + width + + + + + + + + + + + + + + + + + + + headaxislength + headlength + + + + + + + + + + + + headwidth + + + + + + + + + length + + + + + + + + + diff --git a/doc/_static/ridge_map_white_mountains.png b/doc/_static/ridge_map_white_mountains.png deleted file mode 100644 index ce8fd32b7a9e..000000000000 Binary files a/doc/_static/ridge_map_white_mountains.png and /dev/null differ diff --git a/doc/_static/seaborn.png b/doc/_static/seaborn.png deleted file mode 100644 index 40c6bca95495..000000000000 Binary files a/doc/_static/seaborn.png and /dev/null differ diff --git a/doc/_static/sviewgui_sample.png b/doc/_static/sviewgui_sample.png deleted file mode 100644 index e738f36c623e..000000000000 Binary files a/doc/_static/sviewgui_sample.png and /dev/null differ diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json new file mode 100644 index 000000000000..62c8ed756824 --- /dev/null +++ b/doc/_static/switcher.json @@ -0,0 +1,53 @@ +[ + { + "name": "3.10 (stable)", + "version": "3.10.3", + "url": "https://matplotlib.org/stable/", + "preferred": true + }, + { + "name": "3.11 (dev)", + "version": "dev", + "url": "https://matplotlib.org/devdocs/" + }, + { + "name": "3.9", + "version": "3.9.3", + "url": "https://matplotlib.org/3.9.3/" + }, + { + "name": "3.8", + "version": "3.8.4", + "url": "https://matplotlib.org/3.8.4/" + }, + { + "name": "3.7", + "version": "3.7.5", + "url": "https://matplotlib.org/3.7.5/" + }, + { + "name": "3.6", + "version": "3.6.3", + "url": "https://matplotlib.org/3.6.3/" + }, + { + "name": "3.5", + "version": "3.5.3", + "url": "https://matplotlib.org/3.5.3/" + }, + { + "name": "3.4", + "version": "3.4.3", + "url": "https://matplotlib.org/3.4.3/" + }, + { + "name": "3.3", + "version": "3.3.4", + "url": "https://matplotlib.org/3.3.4/" + }, + { + "name": "2.2", + "version": "2.2.4", + "url": "https://matplotlib.org/2.2.4/" + } +] diff --git a/doc/_static/transforms.png b/doc/_static/transforms.png deleted file mode 100644 index ab07fb575961..000000000000 Binary files a/doc/_static/transforms.png and /dev/null differ diff --git a/doc/_static/wcsaxes.jpg b/doc/_static/wcsaxes.jpg deleted file mode 100644 index d1cfa4b87c53..000000000000 Binary files a/doc/_static/wcsaxes.jpg and /dev/null differ diff --git a/doc/_static/yellowbrick.png b/doc/_static/yellowbrick.png deleted file mode 100644 index c30e082693c7..000000000000 Binary files a/doc/_static/yellowbrick.png and /dev/null differ diff --git a/doc/_static/zenodo_cache/10059757.svg b/doc/_static/zenodo_cache/10059757.svg new file mode 100644 index 000000000000..d5909613dd75 --- /dev/null +++ b/doc/_static/zenodo_cache/10059757.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.10059757 + + + 10.5281/zenodo.10059757 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/10150955.svg b/doc/_static/zenodo_cache/10150955.svg new file mode 100644 index 000000000000..132bc97ab61d --- /dev/null +++ b/doc/_static/zenodo_cache/10150955.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.10150955 + + + 10.5281/zenodo.10150955 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/10661079.svg b/doc/_static/zenodo_cache/10661079.svg new file mode 100644 index 000000000000..ac659bcc870f --- /dev/null +++ b/doc/_static/zenodo_cache/10661079.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.10661079 + + + 10.5281/zenodo.10661079 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/10916799.svg b/doc/_static/zenodo_cache/10916799.svg new file mode 100644 index 000000000000..ca9c0a454251 --- /dev/null +++ b/doc/_static/zenodo_cache/10916799.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.10916799 + + + 10.5281/zenodo.10916799 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/11201097.svg b/doc/_static/zenodo_cache/11201097.svg new file mode 100644 index 000000000000..70f35a7a659f --- /dev/null +++ b/doc/_static/zenodo_cache/11201097.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.11201097 + + + 10.5281/zenodo.11201097 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/12652732.svg b/doc/_static/zenodo_cache/12652732.svg new file mode 100644 index 000000000000..cde5c5f37839 --- /dev/null +++ b/doc/_static/zenodo_cache/12652732.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.12652732 + + + 10.5281/zenodo.12652732 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/13308876.svg b/doc/_static/zenodo_cache/13308876.svg new file mode 100644 index 000000000000..749bc3c19026 --- /dev/null +++ b/doc/_static/zenodo_cache/13308876.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.13308876 + + + 10.5281/zenodo.13308876 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/14249941.svg b/doc/_static/zenodo_cache/14249941.svg new file mode 100644 index 000000000000..f9165f17fdf0 --- /dev/null +++ b/doc/_static/zenodo_cache/14249941.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.14249941 + + + 10.5281/zenodo.14249941 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/14436121.svg b/doc/_static/zenodo_cache/14436121.svg new file mode 100644 index 000000000000..1e4a7cd5b7a4 --- /dev/null +++ b/doc/_static/zenodo_cache/14436121.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.14436121 + + + 10.5281/zenodo.14436121 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/14464227.svg b/doc/_static/zenodo_cache/14464227.svg new file mode 100644 index 000000000000..7126d239d6a5 --- /dev/null +++ b/doc/_static/zenodo_cache/14464227.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.14464227 + + + 10.5281/zenodo.14464227 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4030140.svg b/doc/_static/zenodo_cache/4030140.svg new file mode 100644 index 000000000000..8fcb71dead83 --- /dev/null +++ b/doc/_static/zenodo_cache/4030140.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4030140 + + + 10.5281/zenodo.4030140 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4268928.svg b/doc/_static/zenodo_cache/4268928.svg new file mode 100644 index 000000000000..e7d632a40e54 --- /dev/null +++ b/doc/_static/zenodo_cache/4268928.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4268928 + + + 10.5281/zenodo.4268928 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4475376.svg b/doc/_static/zenodo_cache/4475376.svg new file mode 100644 index 000000000000..bf223b01ddc3 --- /dev/null +++ b/doc/_static/zenodo_cache/4475376.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4475376 + + + 10.5281/zenodo.4475376 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4638398.svg b/doc/_static/zenodo_cache/4638398.svg new file mode 100644 index 000000000000..8b50f14790dd --- /dev/null +++ b/doc/_static/zenodo_cache/4638398.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4638398 + + + 10.5281/zenodo.4638398 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4649959.svg b/doc/_static/zenodo_cache/4649959.svg new file mode 100644 index 000000000000..a604de20cdd5 --- /dev/null +++ b/doc/_static/zenodo_cache/4649959.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4649959 + + + 10.5281/zenodo.4649959 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/4743323.svg b/doc/_static/zenodo_cache/4743323.svg new file mode 100644 index 000000000000..204cf037ad27 --- /dev/null +++ b/doc/_static/zenodo_cache/4743323.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4743323 + + + 10.5281/zenodo.4743323 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/5194481.svg b/doc/_static/zenodo_cache/5194481.svg new file mode 100644 index 000000000000..728ae0c15a6a --- /dev/null +++ b/doc/_static/zenodo_cache/5194481.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.5194481 + + + 10.5281/zenodo.5194481 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/5706396.svg b/doc/_static/zenodo_cache/5706396.svg new file mode 100644 index 000000000000..54718543c9c8 --- /dev/null +++ b/doc/_static/zenodo_cache/5706396.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.5706396 + + + 10.5281/zenodo.5706396 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/5773480.svg b/doc/_static/zenodo_cache/5773480.svg new file mode 100644 index 000000000000..431dbd803973 --- /dev/null +++ b/doc/_static/zenodo_cache/5773480.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.5773480 + + + 10.5281/zenodo.5773480 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/6513224.svg b/doc/_static/zenodo_cache/6513224.svg new file mode 100644 index 000000000000..fd54dfcb9abb --- /dev/null +++ b/doc/_static/zenodo_cache/6513224.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.6513224 + + + 10.5281/zenodo.6513224 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/6982547.svg b/doc/_static/zenodo_cache/6982547.svg new file mode 100644 index 000000000000..6eb000d892da --- /dev/null +++ b/doc/_static/zenodo_cache/6982547.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.6982547 + + + 10.5281/zenodo.6982547 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7084615.svg b/doc/_static/zenodo_cache/7084615.svg new file mode 100644 index 000000000000..9bb362063414 --- /dev/null +++ b/doc/_static/zenodo_cache/7084615.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7084615 + + + 10.5281/zenodo.7084615 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7162185.svg b/doc/_static/zenodo_cache/7162185.svg new file mode 100644 index 000000000000..ea0966377194 --- /dev/null +++ b/doc/_static/zenodo_cache/7162185.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7162185 + + + 10.5281/zenodo.7162185 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7275322.svg b/doc/_static/zenodo_cache/7275322.svg new file mode 100644 index 000000000000..2d0fd408b504 --- /dev/null +++ b/doc/_static/zenodo_cache/7275322.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7275322 + + + 10.5281/zenodo.7275322 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7527665.svg b/doc/_static/zenodo_cache/7527665.svg new file mode 100644 index 000000000000..3c3e0b7a8b2a --- /dev/null +++ b/doc/_static/zenodo_cache/7527665.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7527665 + + + 10.5281/zenodo.7527665 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7637593.svg b/doc/_static/zenodo_cache/7637593.svg new file mode 100644 index 000000000000..4e91dea5e805 --- /dev/null +++ b/doc/_static/zenodo_cache/7637593.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7637593 + + + 10.5281/zenodo.7637593 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/7697899.svg b/doc/_static/zenodo_cache/7697899.svg new file mode 100644 index 000000000000..b540a1909046 --- /dev/null +++ b/doc/_static/zenodo_cache/7697899.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.7697899 + + + 10.5281/zenodo.7697899 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/8118151.svg b/doc/_static/zenodo_cache/8118151.svg new file mode 100644 index 000000000000..e9d33ec5bf34 --- /dev/null +++ b/doc/_static/zenodo_cache/8118151.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.8118151 + + + 10.5281/zenodo.8118151 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/8336761.svg b/doc/_static/zenodo_cache/8336761.svg new file mode 100644 index 000000000000..24c222a8a5f5 --- /dev/null +++ b/doc/_static/zenodo_cache/8336761.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.8336761 + + + 10.5281/zenodo.8336761 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/8347255.svg b/doc/_static/zenodo_cache/8347255.svg new file mode 100644 index 000000000000..318d9e6bea73 --- /dev/null +++ b/doc/_static/zenodo_cache/8347255.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.8347255 + + + 10.5281/zenodo.8347255 + + + \ No newline at end of file diff --git a/doc/_templates/autofunctions.rst b/doc/_templates/autofunctions.rst deleted file mode 100644 index 942731b46587..000000000000 --- a/doc/_templates/autofunctions.rst +++ /dev/null @@ -1,20 +0,0 @@ - -{{ fullname | escape | underline }} - - -.. automodule:: {{ fullname }} - :no-members: - -{% block functions %} -{% if functions %} - -Functions ---------- - -.. autosummary:: - :template: autosummary.rst - :toctree: -{% for item in functions %}{% if item not in ['plotting', 'colormaps'] %} - {{ item }}{% endif %}{% endfor %} -{% endif %} -{% endblock %} diff --git a/doc/_templates/automodule.rst b/doc/_templates/automodule.rst index e9f2a755d413..df3e8283f2f6 100644 --- a/doc/_templates/automodule.rst +++ b/doc/_templates/automodule.rst @@ -1,13 +1,5 @@ {{ fullname | escape | underline}} -{% if fullname in ['mpl_toolkits.axes_grid1.colorbar'] %} -.. To prevent problems with the autosummary for the colorbar doc - treat this separately (sphinx-doc/sphinx/issues/4874) -.. automodule:: {{ fullname }} - :members: - -{% else %} - .. automodule:: {{ fullname }} :no-members: :no-inherited-members: @@ -18,11 +10,13 @@ Classes ------- -.. autosummary:: +.. autosummary:: :template: autosummary.rst :toctree: + {% for item in classes %}{% if item not in ['zip', 'map', 'reduce'] %} {{ item }}{% endif %}{% endfor %} + {% endif %} {% endblock %} @@ -32,12 +26,12 @@ Classes Functions --------- -.. autosummary:: +.. autosummary:: :template: autosummary.rst :toctree: {% for item in functions %}{% if item not in ['zip', 'map', 'reduce'] %} {{ item }}{% endif %}{% endfor %} + {% endif %} {% endblock %} -{% endif %} diff --git a/doc/_templates/autosummary.rst b/doc/_templates/autosummary.rst index bf318f1d9aff..824dbe5b9a4b 100644 --- a/doc/_templates/autosummary.rst +++ b/doc/_templates/autosummary.rst @@ -5,9 +5,10 @@ {% if objtype in ['class'] %} + .. auto{{ objtype }}:: {{ objname }} :show-inheritance: - :special-members: + :special-members: __call__ {% else %} .. auto{{ objtype }}:: {{ objname }} @@ -16,11 +17,13 @@ {% if objtype in ['class', 'method', 'function'] %} {% if objname in ['AxesGrid', 'Scalable', 'HostAxes', 'FloatingAxes', - 'ParasiteAxesAuxTrans', 'ParasiteAxes'] %} +'ParasiteAxesAuxTrans', 'ParasiteAxes'] %} + .. Filter out the above aliases to other classes, as sphinx gallery creates no example file for those (sphinx-gallery/sphinx-gallery#365) {% else %} + .. minigallery:: {{module}}.{{objname}} :add-heading: diff --git a/doc/_templates/autosummary_class_only.rst b/doc/_templates/autosummary_class_only.rst new file mode 100644 index 000000000000..d10f1b375fd3 --- /dev/null +++ b/doc/_templates/autosummary_class_only.rst @@ -0,0 +1,12 @@ +{{ fullname | escape | underline }} + + +.. currentmodule:: {{ module }} + + +{% if objtype in ['class'] %} + +.. auto{{ objtype }}:: {{ objname }} + :no-members: + +{% endif %} diff --git a/doc/_templates/cheatsheet_sidebar.html b/doc/_templates/cheatsheet_sidebar.html new file mode 100644 index 000000000000..2ca6548ddd4d --- /dev/null +++ b/doc/_templates/cheatsheet_sidebar.html @@ -0,0 +1,9 @@ + + diff --git a/doc/_templates/donate_sidebar.html b/doc/_templates/donate_sidebar.html index fc7310b70088..071c92888c3c 100644 --- a/doc/_templates/donate_sidebar.html +++ b/doc/_templates/donate_sidebar.html @@ -1,6 +1,5 @@ - - - -') + def copy(self): + """Return a copy of the colormap.""" + return self.__copy__() + class LinearSegmentedColormap(Colormap): """ @@ -754,43 +1065,69 @@ class LinearSegmentedColormap(Colormap): segments. """ - def __init__(self, name, segmentdata, N=256, gamma=1.0): + def __init__(self, name, segmentdata, N=256, gamma=1.0, *, + bad=None, under=None, over=None): """ - Create color map from linear mapping segments + Create colormap from linear mapping segments. + + Parameters + ---------- + name : str + The name of the colormap. + segmentdata : dict + A dictionary with keys "red", "green", "blue" for the color channels. + Each entry should be a list of *x*, *y0*, *y1* tuples, forming rows + in a table. Entries for alpha are optional. + + Example: suppose you want red to increase from 0 to 1 over + the bottom half, green to do the same over the middle half, + and blue over the top half. Then you would use:: + + { + 'red': [(0.0, 0.0, 0.0), + (0.5, 1.0, 1.0), + (1.0, 1.0, 1.0)], + 'green': [(0.0, 0.0, 0.0), + (0.25, 0.0, 0.0), + (0.75, 1.0, 1.0), + (1.0, 1.0, 1.0)], + 'blue': [(0.0, 0.0, 0.0), + (0.5, 0.0, 0.0), + (1.0, 1.0, 1.0)] + } - segmentdata argument is a dictionary with a red, green and blue - entries. Each entry should be a list of *x*, *y0*, *y1* tuples, - forming rows in a table. Entries for alpha are optional. + Each row in the table for a given color is a sequence of + *x*, *y0*, *y1* tuples. In each sequence, *x* must increase + monotonically from 0 to 1. For any input value *z* falling + between *x[i]* and *x[i+1]*, the output value of a given color + will be linearly interpolated between *y1[i]* and *y0[i+1]*:: - Example: suppose you want red to increase from 0 to 1 over - the bottom half, green to do the same over the middle half, - and blue over the top half. Then you would use:: + row i: x y0 y1 + / + / + row i+1: x y0 y1 - cdict = {'red': [(0.0, 0.0, 0.0), - (0.5, 1.0, 1.0), - (1.0, 1.0, 1.0)], + Hence, y0 in the first row and y1 in the last row are never used. - 'green': [(0.0, 0.0, 0.0), - (0.25, 0.0, 0.0), - (0.75, 1.0, 1.0), - (1.0, 1.0, 1.0)], + N : int + The number of RGB quantization levels. + gamma : float + Gamma correction factor for input distribution x of the mapping. + See also https://en.wikipedia.org/wiki/Gamma_correction. + bad : :mpltype:`color`, default: transparent + The color for invalid values (NaN or masked). - 'blue': [(0.0, 0.0, 0.0), - (0.5, 0.0, 0.0), - (1.0, 1.0, 1.0)]} + .. versionadded:: 3.11 - Each row in the table for a given color is a sequence of - *x*, *y0*, *y1* tuples. In each sequence, *x* must increase - monotonically from 0 to 1. For any input value *z* falling - between *x[i]* and *x[i+1]*, the output value of a given color - will be linearly interpolated between *y1[i]* and *y0[i+1]*:: + under : :mpltype:`color`, default: color of the lowest value + The color for low out-of-range values. - row i: x y0 y1 - / - / - row i+1: x y0 y1 + .. versionadded:: 3.11 - Hence y0 in the first row and y1 in the last row are never used. + over : :mpltype:`color`, default: color of the highest value + The color for high out-of-range values. + + .. versionadded:: 3.11 See Also -------- @@ -800,7 +1137,7 @@ def __init__(self, name, segmentdata, N=256, gamma=1.0): """ # True only if all colors in map are identical; needed for contouring. self.monochrome = False - super().__init__(name, N) + super().__init__(name, N, bad=bad, under=under, over=over) self._segmentdata = segmentdata self._gamma = gamma @@ -819,12 +1156,12 @@ def _init(self): self._set_extremes() def set_gamma(self, gamma): - """Set a new gamma value and regenerate color map.""" + """Set a new gamma value and regenerate colormap.""" self._gamma = gamma self._init() @staticmethod - def from_list(name, colors, N=256, gamma=1.0): + def from_list(name, colors, N=256, gamma=1.0, *, bad=None, under=None, over=None): """ Create a `LinearSegmentedColormap` from a list of colors. @@ -832,38 +1169,59 @@ def from_list(name, colors, N=256, gamma=1.0): ---------- name : str The name of the colormap. - colors : array-like of colors or array-like of (value, color) + colors : list of :mpltype:`color` or list of (value, color) If only colors are given, they are equidistantly mapped from the range :math:`[0, 1]`; i.e. 0 maps to ``colors[0]`` and 1 maps to ``colors[-1]``. If (value, color) pairs are given, the mapping is from *value* - to *color*. This can be used to divide the range unevenly. + to *color*. This can be used to divide the range unevenly. The + values must increase monotonically from 0 to 1. N : int - The number of rgb quantization levels. + The number of RGB quantization levels. gamma : float + + bad : :mpltype:`color`, default: transparent + The color for invalid values (NaN or masked). + under : :mpltype:`color`, default: color of the lowest value + The color for low out-of-range values. + over : :mpltype:`color`, default: color of the highest value + The color for high out-of-range values. """ if not np.iterable(colors): raise ValueError('colors must be iterable') - if (isinstance(colors[0], Sized) and len(colors[0]) == 2 - and not isinstance(colors[0], str)): - # List of value, color pairs - vals, colors = zip(*colors) - else: + try: + # Assume the passed colors are a list of colors + # and not a (value, color) tuple. + r, g, b, a = to_rgba_array(colors).T vals = np.linspace(0, 1, len(colors)) - - cdict = dict(red=[], green=[], blue=[], alpha=[]) - for val, color in zip(vals, colors): - r, g, b, a = to_rgba(color) - cdict['red'].append((val, r, r)) - cdict['green'].append((val, g, g)) - cdict['blue'].append((val, b, b)) - cdict['alpha'].append((val, a, a)) - - return LinearSegmentedColormap(name, cdict, N, gamma) - - def _resample(self, lutsize): - """Return a new color map with *lutsize* entries.""" + except Exception as e: + # Assume the passed values are a list of + # (value, color) tuples. + try: + _vals, _colors = itertools.zip_longest(*colors) + except Exception as e2: + raise e2 from e + vals = np.asarray(_vals) + if np.min(vals) < 0 or np.max(vals) > 1 or np.any(np.diff(vals) <= 0): + raise ValueError( + "the values passed in the (value, color) pairs " + "must increase monotonically from 0 to 1." + ) + r, g, b, a = to_rgba_array(_colors).T + + cdict = { + "red": np.column_stack([vals, r, r]), + "green": np.column_stack([vals, g, g]), + "blue": np.column_stack([vals, b, b]), + "alpha": np.column_stack([vals, a, a]), + } + + return LinearSegmentedColormap(name, cdict, N, gamma, + bad=bad, under=under, over=over) + + def resampled(self, lutsize): + """Return a new colormap with *lutsize* entries.""" new_cmap = LinearSegmentedColormap(self.name, self._segmentdata, lutsize) new_cmap._rgba_over = self._rgba_over @@ -883,8 +1241,8 @@ def reversed(self, name=None): Parameters ---------- name : str, optional - The name for the reversed colormap. If it's None the - name will be the name of the parent colormap + "_r". + The name for the reversed colormap. If None, the + name is set to ``self.name + "_r"``. Returns ------- @@ -908,105 +1266,1017 @@ def reversed(self, name=None): return new_cmap -class ListedColormap(Colormap): +class ListedColormap(Colormap): + """ + Colormap object generated from a list of colors. + + This may be most useful when indexing directly into a colormap, + but it can also be used to generate special colormaps for ordinary + mapping. + + Parameters + ---------- + colors : list of :mpltype:`color` or array + Sequence of Matplotlib color specifications (color names or RGB(A) + values). + name : str, optional + String to identify the colormap. + N : int, optional + Number of entries in the map. The default is *None*, in which case + there is one colormap entry for each element in the list of colors. + If :: + + N < len(colors) + + the list will be truncated at *N*. If :: + + N > len(colors) + + the list will be extended by repetition. + + .. deprecated:: 3.11 + + This parameter will be removed. Please instead ensure that + the list of passed colors is the required length. + + bad : :mpltype:`color`, default: transparent + The color for invalid values (NaN or masked). + + .. versionadded:: 3.11 + + under : :mpltype:`color`, default: color of the lowest value + The color for low out-of-range values. + + .. versionadded:: 3.11 + + over : :mpltype:`color`, default: color of the highest value + The color for high out-of-range values. + + .. versionadded:: 3.11 + """ + + @_api.delete_parameter( + "3.11", "N", + message="Passing 'N' to ListedColormap is deprecated since %(since)s " + "and will be removed in %(removal)s. Please ensure the list " + "of passed colors is the required length instead." + ) + def __init__(self, colors, name='from_list', N=None, *, + bad=None, under=None, over=None): + if N is None: + self.colors = colors + N = len(colors) + else: + if isinstance(colors, str): + self.colors = [colors] * N + elif np.iterable(colors): + self.colors = list( + itertools.islice(itertools.cycle(colors), N)) + else: + try: + gray = float(colors) + except TypeError: + pass + else: + self.colors = [gray] * N + super().__init__(name, N, bad=bad, under=under, over=over) + + def _init(self): + self._lut = np.zeros((self.N + 3, 4), float) + self._lut[:-3] = to_rgba_array(self.colors) + self._isinit = True + self._set_extremes() + + @property + def monochrome(self): + """Return whether all colors in the colormap are identical.""" + # Replacement for the attribute *monochrome*. This ensures a consistent + # response independent of the way the ListedColormap was created, which + # was not the case for the manually set attribute. + # + # TODO: It's a separate discussion whether we need this property on + # colormaps at all (at least as public API). It's a very special edge + # case and we only use it for contours internally. + if not self._isinit: + self._init() + + return self.N <= 1 or np.all(self._lut[0] == self._lut[1:self.N]) + + def resampled(self, lutsize): + """Return a new colormap with *lutsize* entries.""" + colors = self(np.linspace(0, 1, lutsize)) + new_cmap = ListedColormap(colors, name=self.name) + # Keep the over/under values too + new_cmap._rgba_over = self._rgba_over + new_cmap._rgba_under = self._rgba_under + new_cmap._rgba_bad = self._rgba_bad + return new_cmap + + def reversed(self, name=None): + """ + Return a reversed instance of the Colormap. + + Parameters + ---------- + name : str, optional + The name for the reversed colormap. If None, the + name is set to ``self.name + "_r"``. + + Returns + ------- + ListedColormap + A reversed instance of the colormap. + """ + if name is None: + name = self.name + "_r" + + colors_r = list(reversed(self.colors)) + new_cmap = ListedColormap(colors_r, name=name) + # Reverse the over/under values too + new_cmap._rgba_over = self._rgba_under + new_cmap._rgba_under = self._rgba_over + new_cmap._rgba_bad = self._rgba_bad + return new_cmap + + +class MultivarColormap: + """ + Class for holding multiple `~matplotlib.colors.Colormap` for use in a + `~matplotlib.cm.ScalarMappable` object + """ + def __init__(self, colormaps, combination_mode, name='multivariate colormap'): + """ + Parameters + ---------- + colormaps: list or tuple of `~matplotlib.colors.Colormap` objects + The individual colormaps that are combined + combination_mode: str, 'sRGB_add' or 'sRGB_sub' + Describe how colormaps are combined in sRGB space + + - If 'sRGB_add' -> Mixing produces brighter colors + `sRGB = sum(colors)` + - If 'sRGB_sub' -> Mixing produces darker colors + `sRGB = 1 - sum(1 - colors)` + name : str, optional + The name of the colormap family. + """ + self.name = name + + if not np.iterable(colormaps) \ + or len(colormaps) == 1 \ + or isinstance(colormaps, str): + raise ValueError("A MultivarColormap must have more than one colormap.") + colormaps = list(colormaps) # ensure cmaps is a list, i.e. not a tuple + for i, cmap in enumerate(colormaps): + if isinstance(cmap, str): + colormaps[i] = mpl.colormaps[cmap] + elif not isinstance(cmap, Colormap): + raise ValueError("colormaps must be a list of objects that subclass" + " Colormap or a name found in the colormap registry.") + + self._colormaps = colormaps + _api.check_in_list(['sRGB_add', 'sRGB_sub'], combination_mode=combination_mode) + self._combination_mode = combination_mode + self.n_variates = len(colormaps) + self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. + + def __call__(self, X, alpha=None, bytes=False, clip=True): + r""" + Parameters + ---------- + X : tuple (X0, X1, ...) of length equal to the number of colormaps + X0, X1 ...: + float or int, `~numpy.ndarray` or scalar + The data value(s) to convert to RGBA. + For floats, *Xi...* should be in the interval ``[0.0, 1.0]`` to + return the RGBA values ``X*100`` percent along the Colormap line. + For integers, *Xi...* should be in the interval ``[0, self[i].N)`` to + return RGBA values *indexed* from colormap [i] with index ``Xi``, where + self[i] is colormap i. + alpha : float or array-like or None + Alpha must be a scalar between 0 and 1, a sequence of such + floats with shape matching *Xi*, or None. + bytes : bool, default: False + If False (default), the returned RGBA values will be floats in the + interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the + interval ``[0, 255]``. + clip : bool, default: True + If True, clip output to 0 to 1 + + Returns + ------- + Tuple of RGBA values if X[0] is scalar, otherwise an array of + RGBA values with a shape of ``X.shape + (4, )``. + """ + + if len(X) != len(self): + raise ValueError( + f'For the selected colormap the data must have a first dimension ' + f'{len(self)}, not {len(X)}') + rgba, mask_bad = self[0]._get_rgba_and_mask(X[0], bytes=False) + for c, xx in zip(self[1:], X[1:]): + sub_rgba, sub_mask_bad = c._get_rgba_and_mask(xx, bytes=False) + rgba[..., :3] += sub_rgba[..., :3] # add colors + rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha + mask_bad |= sub_mask_bad + + if self.combination_mode == 'sRGB_sub': + rgba[..., :3] -= len(self) - 1 + + rgba[mask_bad] = self.get_bad() + + if clip: + rgba = np.clip(rgba, 0, 1) + + if alpha is not None: + if clip: + alpha = np.clip(alpha, 0, 1) + if np.shape(alpha) not in [(), np.shape(X[0])]: + raise ValueError( + f"alpha is array-like but its shape {np.shape(alpha)} does " + f"not match that of X[0] {np.shape(X[0])}") + rgba[..., -1] *= alpha + + if bytes: + if not clip: + raise ValueError( + "clip cannot be false while bytes is true" + " as uint8 does not support values below 0" + " or above 255.") + rgba = (rgba * 255).astype('uint8') + + if not np.iterable(X[0]): + rgba = tuple(rgba) + + return rgba + + def copy(self): + """Return a copy of the multivarcolormap.""" + return self.__copy__() + + def __copy__(self): + cls = self.__class__ + cmapobject = cls.__new__(cls) + cmapobject.__dict__.update(self.__dict__) + cmapobject._colormaps = [cm.copy() for cm in self._colormaps] + cmapobject._rgba_bad = np.copy(self._rgba_bad) + return cmapobject + + def __eq__(self, other): + if not isinstance(other, MultivarColormap): + return False + if len(self) != len(other): + return False + for c0, c1 in zip(self, other): + if c0 != c1: + return False + if not all(self._rgba_bad == other._rgba_bad): + return False + if self.combination_mode != other.combination_mode: + return False + return True + + def __getitem__(self, item): + return self._colormaps[item] + + def __iter__(self): + for c in self._colormaps: + yield c + + def __len__(self): + return len(self._colormaps) + + def __str__(self): + return self.name + + def get_bad(self): + """Get the color for masked values.""" + return np.array(self._rgba_bad) + + def resampled(self, lutshape): + """ + Return a new colormap with *lutshape* entries. + + Parameters + ---------- + lutshape : tuple of (`int`, `None`) + The tuple must have a length matching the number of variates. + For each element in the tuple, if `int`, the corresponding colorbar + is resampled, if `None`, the corresponding colorbar is not resampled. + + Returns + ------- + MultivarColormap + """ + + if not np.iterable(lutshape) or len(lutshape) != len(self): + raise ValueError(f"lutshape must be of length {len(self)}") + new_cmap = self.copy() + for i, s in enumerate(lutshape): + if s is not None: + new_cmap._colormaps[i] = self[i].resampled(s) + return new_cmap + + def with_extremes(self, *, bad=None, under=None, over=None): + """ + Return a copy of the `MultivarColormap` with modified out-of-range attributes. + + The *bad* keyword modifies the copied `MultivarColormap` while *under* and + *over* modifies the attributes of the copied component colormaps. + Note that *under* and *over* colors are subject to the mixing rules determined + by the *combination_mode*. + + Parameters + ---------- + bad: :mpltype:`color`, default: None + If Matplotlib color, the bad value is set accordingly in the copy + + under tuple of :mpltype:`color`, default: None + If tuple, the `under` value of each component is set with the values + from the tuple. + + over tuple of :mpltype:`color`, default: None + If tuple, the `over` value of each component is set with the values + from the tuple. + + Returns + ------- + MultivarColormap + copy of self with attributes set + + """ + new_cm = self.copy() + if bad is not None: + new_cm._rgba_bad = to_rgba(bad) + if under is not None: + if not np.iterable(under) or len(under) != len(new_cm): + raise ValueError("*under* must contain a color for each scalar colormap" + f" i.e. be of length {len(new_cm)}.") + else: + for c, b in zip(new_cm, under): + c.set_under(b) + if over is not None: + if not np.iterable(over) or len(over) != len(new_cm): + raise ValueError("*over* must contain a color for each scalar colormap" + f" i.e. be of length {len(new_cm)}.") + else: + for c, b in zip(new_cm, over): + c.set_over(b) + return new_cm + + @property + def combination_mode(self): + return self._combination_mode + + def _repr_png_(self): + """Generate a PNG representation of the Colormap.""" + X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]), + (_REPR_PNG_SIZE[1], 1)) + pixels = np.zeros((_REPR_PNG_SIZE[1]*len(self), _REPR_PNG_SIZE[0], 4), + dtype=np.uint8) + for i, c in enumerate(self): + pixels[i*_REPR_PNG_SIZE[1]:(i+1)*_REPR_PNG_SIZE[1], :] = c(X, bytes=True) + png_bytes = io.BytesIO() + title = self.name + ' multivariate colormap' + author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org' + pnginfo = PngInfo() + pnginfo.add_text('Title', title) + pnginfo.add_text('Description', title) + pnginfo.add_text('Author', author) + pnginfo.add_text('Software', author) + Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo) + return png_bytes.getvalue() + + def _repr_html_(self): + """Generate an HTML representation of the MultivarColormap.""" + return ''.join([c._repr_html_() for c in self._colormaps]) + + +class BivarColormap: + """ + Base class for all bivariate to RGBA mappings. + + Designed as a drop-in replacement for Colormap when using a 2D + lookup table. To be used with `~matplotlib.cm.ScalarMappable`. + """ + + def __init__(self, N=256, M=256, shape='square', origin=(0, 0), + name='bivariate colormap'): + """ + Parameters + ---------- + N : int, default: 256 + The number of RGB quantization levels along the first axis. + M : int, default: 256 + The number of RGB quantization levels along the second axis. + shape : {'square', 'circle', 'ignore', 'circleignore'} + + - 'square' each variate is clipped to [0,1] independently + - 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - 'circleignore' a circular mask is applied, but the data is not + clipped and instead assigned the 'outside' color + + origin : (float, float), default: (0,0) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + name : str, optional + The name of the colormap. + """ + + self.name = name + self.N = int(N) # ensure that N is always int + self.M = int(M) + _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'], shape=shape) + self._shape = shape + self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. + self._rgba_outside = (1.0, 0.0, 1.0, 1.0) + self._isinit = False + self.n_variates = 2 + self._origin = (float(origin[0]), float(origin[1])) + '''#: When this colormap exists on a scalar mappable and colorbar_extend + #: is not False, colorbar creation will pick up ``colorbar_extend`` as + #: the default value for the ``extend`` keyword in the + #: `matplotlib.colorbar.Colorbar` constructor. + self.colorbar_extend = False''' + + def __call__(self, X, alpha=None, bytes=False): + r""" + Parameters + ---------- + X : tuple (X0, X1), X0 and X1: float or int or array-like + The data value(s) to convert to RGBA. + + - For floats, *X* should be in the interval ``[0.0, 1.0]`` to + return the RGBA values ``X*100`` percent along the Colormap. + - For integers, *X* should be in the interval ``[0, Colormap.N)`` to + return RGBA values *indexed* from the Colormap with index ``X``. + + alpha : float or array-like or None, default: None + Alpha must be a scalar between 0 and 1, a sequence of such + floats with shape matching X0, or None. + bytes : bool, default: False + If False (default), the returned RGBA values will be floats in the + interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the + interval ``[0, 255]``. + + Returns + ------- + Tuple of RGBA values if X is scalar, otherwise an array of + RGBA values with a shape of ``X.shape + (4, )``. + """ + + if len(X) != 2: + raise ValueError( + f'For a `BivarColormap` the data must have a first dimension ' + f'2, not {len(X)}') + + if not self._isinit: + self._init() + + X0 = np.ma.array(X[0], copy=True) + X1 = np.ma.array(X[1], copy=True) + # clip to shape of colormap, circle square, etc. + self._clip((X0, X1)) + + # Native byteorder is faster. + if not X0.dtype.isnative: + X0 = X0.byteswap().view(X0.dtype.newbyteorder()) + if not X1.dtype.isnative: + X1 = X1.byteswap().view(X1.dtype.newbyteorder()) + + if X0.dtype.kind == "f": + X0 *= self.N + # xa == 1 (== N after multiplication) is not out of range. + X0[X0 == self.N] = self.N - 1 + + if X1.dtype.kind == "f": + X1 *= self.M + # xa == 1 (== N after multiplication) is not out of range. + X1[X1 == self.M] = self.M - 1 + + # Pre-compute the masks before casting to int (which can truncate) + mask_outside = (X0 < 0) | (X1 < 0) | (X0 >= self.N) | (X1 >= self.M) + # If input was masked, get the bad mask from it; else mask out nans. + mask_bad_0 = X0.mask if np.ma.is_masked(X0) else np.isnan(X0) + mask_bad_1 = X1.mask if np.ma.is_masked(X1) else np.isnan(X1) + mask_bad = mask_bad_0 | mask_bad_1 + + with np.errstate(invalid="ignore"): + # We need this cast for unsigned ints as well as floats + X0 = X0.astype(int) + X1 = X1.astype(int) + + # Set masked values to zero + # The corresponding rgb values will be replaced later + for X_part in [X0, X1]: + X_part[mask_outside] = 0 + X_part[mask_bad] = 0 + + rgba = self._lut[X0, X1] + if np.isscalar(X[0]): + rgba = np.copy(rgba) + rgba[mask_outside] = self._rgba_outside + rgba[mask_bad] = self._rgba_bad + if bytes: + rgba = (rgba * 255).astype(np.uint8) + if alpha is not None: + alpha = np.clip(alpha, 0, 1) + if bytes: + alpha *= 255 # Will be cast to uint8 upon assignment. + if np.shape(alpha) not in [(), np.shape(X0)]: + raise ValueError( + f"alpha is array-like but its shape {np.shape(alpha)} does " + f"not match that of X[0] {np.shape(X0)}") + rgba[..., -1] = alpha + # If the "bad" color is all zeros, then ignore alpha input. + if (np.array(self._rgba_bad) == 0).all(): + rgba[mask_bad] = (0, 0, 0, 0) + + if not np.iterable(X[0]): + rgba = tuple(rgba) + return rgba + + @property + def lut(self): + """ + For external access to the lut, i.e. for displaying the cmap. + For circular colormaps this returns a lut with a circular mask. + + Internal functions (such as to_rgb()) should use _lut + which stores the lut without a circular mask + A lut without the circular mask is needed in to_rgb() because the + conversion from floats to ints results in some some pixel-requests + just outside of the circular mask + + """ + if not self._isinit: + self._init() + lut = np.copy(self._lut) + if self.shape == 'circle' or self.shape == 'circleignore': + n = np.linspace(-1, 1, self.N) + m = np.linspace(-1, 1, self.M) + radii_sqr = (n**2)[:, np.newaxis] + (m**2)[np.newaxis, :] + mask_outside = radii_sqr > 1 + lut[mask_outside, 3] = 0 + return lut + + def __copy__(self): + cls = self.__class__ + cmapobject = cls.__new__(cls) + cmapobject.__dict__.update(self.__dict__) + + cmapobject._rgba_outside = np.copy(self._rgba_outside) + cmapobject._rgba_bad = np.copy(self._rgba_bad) + cmapobject._shape = self.shape + if self._isinit: + cmapobject._lut = np.copy(self._lut) + return cmapobject + + def __eq__(self, other): + if not isinstance(other, BivarColormap): + return False + # To compare lookup tables the Colormaps have to be initialized + if not self._isinit: + self._init() + if not other._isinit: + other._init() + if not np.array_equal(self._lut, other._lut): + return False + if not np.array_equal(self._rgba_bad, other._rgba_bad): + return False + if not np.array_equal(self._rgba_outside, other._rgba_outside): + return False + if self.shape != other.shape: + return False + return True + + def get_bad(self): + """Get the color for masked values.""" + return self._rgba_bad + + def get_outside(self): + """Get the color for out-of-range values.""" + return self._rgba_outside + + def resampled(self, lutshape, transposed=False): + """ + Return a new colormap with *lutshape* entries. + + Note that this function does not move the origin. + + Parameters + ---------- + lutshape : tuple of ints or None + The tuple must be of length 2, and each entry is either an int or None. + + - If an int, the corresponding axis is resampled. + - If negative the corresponding axis is resampled in reverse + - If -1, the axis is inverted + - If 1 or None, the corresponding axis is not resampled. + + transposed : bool, default: False + if True, the axes are swapped after resampling + + Returns + ------- + BivarColormap + """ + + if not np.iterable(lutshape) or len(lutshape) != 2: + raise ValueError("lutshape must be of length 2") + lutshape = [lutshape[0], lutshape[1]] + if lutshape[0] is None or lutshape[0] == 1: + lutshape[0] = self.N + if lutshape[1] is None or lutshape[1] == 1: + lutshape[1] = self.M + + inverted = [False, False] + if lutshape[0] < 0: + inverted[0] = True + lutshape[0] = -lutshape[0] + if lutshape[0] == 1: + lutshape[0] = self.N + if lutshape[1] < 0: + inverted[1] = True + lutshape[1] = -lutshape[1] + if lutshape[1] == 1: + lutshape[1] = self.M + x_0, x_1 = np.mgrid[0:1:(lutshape[0] * 1j), 0:1:(lutshape[1] * 1j)] + if inverted[0]: + x_0 = x_0[::-1, :] + if inverted[1]: + x_1 = x_1[:, ::-1] + + # we need to use shape = 'square' while resampling the colormap. + # if the colormap has shape = 'circle' we would otherwise get *outside* in the + # resampled colormap + shape_memory = self._shape + self._shape = 'square' + if transposed: + new_lut = self((x_1, x_0)) + new_cmap = BivarColormapFromImage(new_lut, name=self.name, + shape=shape_memory, + origin=self.origin[::-1]) + else: + new_lut = self((x_0, x_1)) + new_cmap = BivarColormapFromImage(new_lut, name=self.name, + shape=shape_memory, + origin=self.origin) + self._shape = shape_memory + + new_cmap._rgba_bad = self._rgba_bad + new_cmap._rgba_outside = self._rgba_outside + return new_cmap + + def reversed(self, axis_0=True, axis_1=True): + """ + Reverses both or one of the axis. + """ + r_0 = -1 if axis_0 else 1 + r_1 = -1 if axis_1 else 1 + return self.resampled((r_0, r_1)) + + def transposed(self): + """ + Transposes the colormap by swapping the order of the axis + """ + return self.resampled((None, None), transposed=True) + + def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): + """ + Return a copy of the `BivarColormap` with modified attributes. + + Note that the *outside* color is only relevant if `shape` = 'ignore' + or 'circleignore'. + + Parameters + ---------- + bad : :mpltype:`color`, optional + If given, the *bad* value is set accordingly in the copy. + + outside : :mpltype:`color`, optional + If given and shape is 'ignore' or 'circleignore', values + *outside* the colormap are colored accordingly in the copy. + + shape : {'square', 'circle', 'ignore', 'circleignore'} + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + *outside* color + - If 'circleignore' a circular mask is applied, but the data is not + clipped and instead assigned the *outside* color + + origin : (float, float) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + + Returns + ------- + BivarColormap + copy of self with attributes set + """ + new_cm = self.copy() + if bad is not None: + new_cm._rgba_bad = to_rgba(bad) + if outside is not None: + new_cm._rgba_outside = to_rgba(outside) + if shape is not None: + _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'], + shape=shape) + new_cm._shape = shape + if origin is not None: + new_cm._origin = (float(origin[0]), float(origin[1])) + + return new_cm + + def _init(self): + """Generate the lookup table, ``self._lut``.""" + raise NotImplementedError("Abstract class only") + + @property + def shape(self): + return self._shape + + @property + def origin(self): + return self._origin + + def _clip(self, X): + """ + For internal use when applying a BivarColormap to data. + i.e. cm.ScalarMappable().to_rgba() + Clips X[0] and X[1] according to 'self.shape'. + X is modified in-place. + + Parameters + ---------- + X: np.array + array of floats or ints to be clipped + shape : {'square', 'circle', 'ignore', 'circleignore'} + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap. + It is assumed that a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + and instead assigned the 'outside' color + + """ + if self.shape == 'square': + for X_part, mx in zip(X, (self.N, self.M)): + X_part[X_part < 0] = 0 + if X_part.dtype.kind == "f": + X_part[X_part > 1] = 1 + else: + X_part[X_part >= mx] = mx - 1 + + elif self.shape == 'ignore': + for X_part, mx in zip(X, (self.N, self.M)): + X_part[X_part < 0] = -1 + if X_part.dtype.kind == "f": + X_part[X_part > 1] = -1 + else: + X_part[X_part >= mx] = -1 + + elif self.shape == 'circle' or self.shape == 'circleignore': + for X_part in X: + if X_part.dtype.kind != "f": + raise NotImplementedError( + "Circular bivariate colormaps are only" + " implemented for use with with floats") + radii_sqr = (X[0] - 0.5)**2 + (X[1] - 0.5)**2 + mask_outside = radii_sqr > 0.25 + if self.shape == 'circle': + overextend = 2 * np.sqrt(radii_sqr[mask_outside]) + X[0][mask_outside] = (X[0][mask_outside] - 0.5) / overextend + 0.5 + X[1][mask_outside] = (X[1][mask_outside] - 0.5) / overextend + 0.5 + else: + X[0][mask_outside] = -1 + X[1][mask_outside] = -1 + + def __getitem__(self, item): + """Creates and returns a colorbar along the selected axis""" + if not self._isinit: + self._init() + if item == 0: + origin_1_as_int = int(self._origin[1]*self.M) + if origin_1_as_int > self.M-1: + origin_1_as_int = self.M-1 + one_d_lut = self._lut[:, origin_1_as_int] + new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0') + + elif item == 1: + origin_0_as_int = int(self._origin[0]*self.N) + if origin_0_as_int > self.N-1: + origin_0_as_int = self.N-1 + one_d_lut = self._lut[origin_0_as_int, :] + new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1') + else: + raise KeyError(f"only 0 or 1 are" + f" valid keys for BivarColormap, not {item!r}") + new_cmap._rgba_bad = self._rgba_bad + if self.shape in ['ignore', 'circleignore']: + new_cmap.set_over(self._rgba_outside) + new_cmap.set_under(self._rgba_outside) + return new_cmap + + def _repr_png_(self): + """Generate a PNG representation of the BivarColormap.""" + if not self._isinit: + self._init() + pixels = self.lut + if pixels.shape[0] < _BIVAR_REPR_PNG_SIZE: + pixels = np.repeat(pixels, + repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[0], + axis=0)[:256, :] + if pixels.shape[1] < _BIVAR_REPR_PNG_SIZE: + pixels = np.repeat(pixels, + repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[1], + axis=1)[:, :256] + pixels = (pixels[::-1, :, :] * 255).astype(np.uint8) + png_bytes = io.BytesIO() + title = self.name + ' BivarColormap' + author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org' + pnginfo = PngInfo() + pnginfo.add_text('Title', title) + pnginfo.add_text('Description', title) + pnginfo.add_text('Author', author) + pnginfo.add_text('Software', author) + Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo) + return png_bytes.getvalue() + + def _repr_html_(self): + """Generate an HTML representation of the Colormap.""" + png_bytes = self._repr_png_() + png_base64 = base64.b64encode(png_bytes).decode('ascii') + def color_block(color): + hex_color = to_hex(color, keep_alpha=True) + return (f'
') + + return ('
' + f'{self.name} ' + '
' + '
' + '
' + '
' + f'{color_block(self.get_outside())} outside' + '
' + '
' + f'bad {color_block(self.get_bad())}' + '
') + + def copy(self): + """Return a copy of the colormap.""" + return self.__copy__() + + +class SegmentedBivarColormap(BivarColormap): + """ + BivarColormap object generated by supersampling a regular grid. + + Parameters + ---------- + patch : np.array + Patch is required to have a shape (k, l, 3), and will get supersampled + to a lut of shape (N, N, 4). + N : int + The number of RGB quantization levels along each axis. + shape : {'square', 'circle', 'ignore', 'circleignore'} + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + + origin : (float, float) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + + name : str, optional + The name of the colormap. + """ + + def __init__(self, patch, N=256, shape='square', origin=(0, 0), + name='segmented bivariate colormap'): + _api.check_shape((None, None, 3), patch=patch) + self.patch = patch + super().__init__(N, N, shape, origin, name=name) + + def _init(self): + s = self.patch.shape + _patch = np.empty((s[0], s[1], 4)) + _patch[:, :, :3] = self.patch + _patch[:, :, 3] = 1 + transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\ + .scale(self.N / (s[1] - 1), self.N / (s[0] - 1)) + self._lut = np.empty((self.N, self.N, 4)) + + _image.resample(_patch, self._lut, transform, _image.BILINEAR, + resample=False, alpha=1) + self._isinit = True + + +class BivarColormapFromImage(BivarColormap): """ - Colormap object generated from a list of colors. - - This may be most useful when indexing directly into a colormap, - but it can also be used to generate special colormaps for ordinary - mapping. + BivarColormap object generated by supersampling a regular grid. Parameters ---------- - colors : list, array - List of Matplotlib color specifications, or an equivalent Nx3 or Nx4 - floating point array (*N* rgb or rgba values). + lut : nparray of shape (N, M, 3) or (N, M, 4) + The look-up-table + shape: {'square', 'circle', 'ignore', 'circleignore'} + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + + origin: (float, float) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. name : str, optional - String to identify the colormap. - N : int, optional - Number of entries in the map. The default is *None*, in which case - there is one colormap entry for each element in the list of colors. - If :: - - N < len(colors) - - the list will be truncated at *N*. If :: - - N > len(colors) + The name of the colormap. - the list will be extended by repetition. """ - def __init__(self, colors, name='from_list', N=None): - self.monochrome = False # Are all colors identical? (for contour.py) - if N is None: - self.colors = colors - N = len(colors) - else: - if isinstance(colors, str): - self.colors = [colors] * N - self.monochrome = True - elif np.iterable(colors): - if len(colors) == 1: - self.monochrome = True - self.colors = list( - itertools.islice(itertools.cycle(colors), N)) - else: - try: - gray = float(colors) - except TypeError: - pass - else: - self.colors = [gray] * N - self.monochrome = True - super().__init__(name, N) + + def __init__(self, lut, shape='square', origin=(0, 0), name='from image'): + # We can allow for a PIL.Image as input in the following way, but importing + # matplotlib.image.pil_to_array() results in a circular import + # For now, this function only accepts numpy arrays. + # i.e.: + # if isinstance(Image, lut): + # lut = image.pil_to_array(lut) + lut = np.array(lut, copy=True) + if lut.ndim != 3 or lut.shape[2] not in (3, 4): + raise ValueError("The lut must be an array of shape (n, m, 3) or (n, m, 4)", + " or a PIL.image encoded as RGB or RGBA") + + if lut.dtype == np.uint8: + lut = lut.astype(np.float32)/255 + if lut.shape[2] == 3: + new_lut = np.empty((lut.shape[0], lut.shape[1], 4), dtype=lut.dtype) + new_lut[:, :, :3] = lut + new_lut[:, :, 3] = 1. + lut = new_lut + self._lut = lut + super().__init__(lut.shape[0], lut.shape[1], shape, origin, name=name) def _init(self): - self._lut = np.zeros((self.N + 3, 4), float) - self._lut[:-3] = to_rgba_array(self.colors) self._isinit = True - self._set_extremes() - - def _resample(self, lutsize): - """Return a new color map with *lutsize* entries.""" - colors = self(np.linspace(0, 1, lutsize)) - new_cmap = ListedColormap(colors, name=self.name) - # Keep the over/under values too - new_cmap._rgba_over = self._rgba_over - new_cmap._rgba_under = self._rgba_under - new_cmap._rgba_bad = self._rgba_bad - return new_cmap - def reversed(self, name=None): - """ - Return a reversed instance of the Colormap. - Parameters - ---------- - name : str, optional - The name for the reversed colormap. If it's None the - name will be the name of the parent colormap + "_r". +class Normalize: + """ + A class which, when called, maps values within the interval + ``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of + values outside ``[vmin, vmax]`` depends on *clip*. - Returns - ------- - ListedColormap - A reversed instance of the colormap. - """ - if name is None: - name = self.name + "_r" + Examples + -------- + :: - colors_r = list(reversed(self.colors)) - new_cmap = ListedColormap(colors_r, name=name, N=self.N) - # Reverse the over/under values too - new_cmap._rgba_over = self._rgba_under - new_cmap._rgba_under = self._rgba_over - new_cmap._rgba_bad = self._rgba_bad - return new_cmap + x = [-2, -1, 0, 1, 2] + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=False) + norm(x) # [-0.5, 0., 0.5, 1., 1.5] + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=True) + norm(x) # [0., 0., 0.5, 1., 1.] -class Normalize: - """ - A class which, when called, linearly normalizes data into the - ``[0.0, 1.0]`` interval. + See Also + -------- + :ref:`colormapnorms` """ def __init__(self, vmin=None, vmax=None, clip=False): @@ -1014,26 +2284,80 @@ def __init__(self, vmin=None, vmax=None, clip=False): Parameters ---------- vmin, vmax : float or None - If *vmin* and/or *vmax* is not given, they are initialized from the - minimum and maximum value, respectively, of the first input - processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``. + Values within the range ``[vmin, vmax]`` from the input data will be + linearly mapped to ``[0, 1]``. If either *vmin* or *vmax* is not + provided, they default to the minimum and maximum values of the input, + respectively. clip : bool, default: False - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer, and masked values are - set to 1. If ``False`` masked values remain masked. + Determines the behavior for mapping values outside the range + ``[vmin, vmax]``. + + If clipping is off, values outside the range ``[vmin, vmax]`` are + also transformed, resulting in values outside ``[0, 1]``. This + behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. - Clipping silently defeats the purpose of setting the over, under, - and masked colors in a colormap, so it is likely to lead to - surprises; therefore the default is ``clip=False``. + If clipping is on, values below *vmin* are mapped to 0 and values + above *vmax* are mapped to 1. Such values become indistinguishable + from regular boundary values, which may cause misinterpretation of + the data. Notes ----- - Returns 0 if ``vmin == vmax``. + If ``vmin == vmax``, input data will be mapped to 0. + """ + self._vmin = _sanitize_extrema(vmin) + self._vmax = _sanitize_extrema(vmax) + self._clip = clip + self._scale = None + self.callbacks = cbook.CallbackRegistry(signals=["changed"]) + + @property + def vmin(self): + """Lower limit of the input data interval; maps to 0.""" + return self._vmin + + @vmin.setter + def vmin(self, value): + value = _sanitize_extrema(value) + if value != self._vmin: + self._vmin = value + self._changed() + + @property + def vmax(self): + """Upper limit of the input data interval; maps to 1.""" + return self._vmax + + @vmax.setter + def vmax(self, value): + value = _sanitize_extrema(value) + if value != self._vmax: + self._vmax = value + self._changed() + + @property + def clip(self): + """ + Determines the behavior for mapping values outside the range ``[vmin, vmax]``. + + See the *clip* parameter in `.Normalize`. + """ + return self._clip + + @clip.setter + def clip(self, value): + if value != self._clip: + self._clip = value + self._changed() + + def _changed(self): """ - self.vmin = _sanitize_extrema(vmin) - self.vmax = _sanitize_extrema(vmax) - self.clip = clip + Call this whenever the norm is changed to notify all the + callback listeners to the 'changed' signal. + """ + self.callbacks.process('changed') @staticmethod def process_value(value): @@ -1042,6 +2366,11 @@ def process_value(value): *value* can be a scalar or sequence. + Parameters + ---------- + value + Data to normalize. + Returns ------- result : masked array @@ -1072,14 +2401,15 @@ def process_value(value): def __call__(self, value, clip=None): """ - Normalize *value* data in the ``[vmin, vmax]`` interval into the - ``[0.0, 1.0]`` interval and return it. + Normalize the data and return the normalized data. Parameters ---------- value Data to normalize. - clip : bool + clip : bool, optional + See the description of the parameter *clip* in `.Normalize`. + If ``None``, defaults to ``self.clip`` (which defaults to ``False``). @@ -1093,12 +2423,13 @@ def __call__(self, value, clip=None): result, is_scalar = self.process_value(value) - self.autoscale_None(result) + if self.vmin is None or self.vmax is None: + self.autoscale_None(result) # Convert at least to float, without losing precision. (vmin,), _ = self.process_value(self.vmin) (vmax,), _ = self.process_value(self.vmax) if vmin == vmax: - result.fill(0) # Or should it be all masked? Or 0.5? + result.fill(0) # Or should it be all masked? Or 0.5? elif vmin > vmax: raise ValueError("minvalue must be less than or equal to maxvalue") else: @@ -1116,6 +2447,15 @@ def __call__(self, value, clip=None): return result def inverse(self, value): + """ + Maps the normalized value (i.e., index in the colormap) back to image + data value. + + Parameters + ---------- + value + Normalized value. + """ if not self.scaled(): raise ValueError("Not invertible until both vmin and vmax are set") (vmin,), _ = self.process_value(self.vmin) @@ -1129,20 +2469,29 @@ def inverse(self, value): def autoscale(self, A): """Set *vmin*, *vmax* to min, max of *A*.""" - A = np.asanyarray(A) - self.vmin = A.min() - self.vmax = A.max() + with self.callbacks.blocked(): + # Pause callbacks while we are updating so we only get + # a single update signal at the end + self.vmin = self.vmax = None + self.autoscale_None(A) + self._changed() def autoscale_None(self, A): - """If vmin or vmax are not set, use the min/max of *A* to set them.""" + """If *vmin* or *vmax* are not set, use the min/max of *A* to set them.""" A = np.asanyarray(A) + + if isinstance(A, np.ma.MaskedArray): + # we need to make the distinction between an array, False, np.bool_(False) + if A.mask is False or not A.mask.shape: + A = A.data + if self.vmin is None and A.size: self.vmin = A.min() if self.vmax is None and A.size: self.vmax = A.max() def scaled(self): - """Return whether vmin and vmax are set.""" + """Return whether *vmin* and *vmax* are both set.""" return self.vmin is not None and self.vmax is not None @@ -1164,7 +2513,7 @@ def __init__(self, vcenter, vmin=None, vmax=None): Defaults to the min value of the dataset. vmax : float, optional The data value that defines ``1.0`` in the normalization. - Defaults to the the max value of the dataset. + Defaults to the max value of the dataset. Examples -------- @@ -1173,15 +2522,14 @@ def __init__(self, vcenter, vmin=None, vmax=None): >>> import matplotlib.colors as mcolors >>> offset = mcolors.TwoSlopeNorm(vmin=-4000., - vcenter=0., vmax=10000) + ... vcenter=0., vmax=10000) >>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.] >>> offset(data) array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]) """ - self.vcenter = vcenter - self.vmin = vmin - self.vmax = vmax + super().__init__(vmin=vmin, vmax=vmax) + self._vcenter = vcenter if vcenter is not None and vmax is not None and vcenter >= vmax: raise ValueError('vmin, vcenter, and vmax must be in ' 'ascending order') @@ -1189,73 +2537,282 @@ def __init__(self, vcenter, vmin=None, vmax=None): raise ValueError('vmin, vcenter, and vmax must be in ' 'ascending order') + @property + def vcenter(self): + return self._vcenter + + @vcenter.setter + def vcenter(self, value): + if value != self._vcenter: + self._vcenter = value + self._changed() + def autoscale_None(self, A): """ - Get vmin and vmax, and then clip at vcenter + Get vmin and vmax. + + If vcenter isn't in the range [vmin, vmax], either vmin or vmax + is expanded so that vcenter lies in the middle of the modified range + [vmin, vmax]. """ super().autoscale_None(A) - if self.vmin > self.vcenter: - self.vmin = self.vcenter - if self.vmax < self.vcenter: - self.vmax = self.vcenter + if self.vmin >= self.vcenter: + self.vmin = self.vcenter - (self.vmax - self.vcenter) + if self.vmax <= self.vcenter: + self.vmax = self.vcenter + (self.vcenter - self.vmin) def __call__(self, value, clip=None): """ - Map value to the interval [0, 1]. The clip argument is unused. + Map value to the interval [0, 1]. The *clip* argument is unused. """ result, is_scalar = self.process_value(value) self.autoscale_None(result) # sets self.vmin, self.vmax if None if not self.vmin <= self.vcenter <= self.vmax: raise ValueError("vmin, vcenter, vmax must increase monotonically") + # note that we must extrapolate for tick locators: result = np.ma.masked_array( np.interp(result, [self.vmin, self.vcenter, self.vmax], - [0, 0.5, 1.]), mask=np.ma.getmask(result)) + [0, 0.5, 1], left=-np.inf, right=np.inf), + mask=np.ma.getmask(result)) if is_scalar: result = np.atleast_1d(result)[0] return result + def inverse(self, value): + if not self.scaled(): + raise ValueError("Not invertible until both vmin and vmax are set") + (vmin,), _ = self.process_value(self.vmin) + (vmax,), _ = self.process_value(self.vmax) + (vcenter,), _ = self.process_value(self.vcenter) + result = np.interp(value, [0, 0.5, 1], [vmin, vcenter, vmax], + left=-np.inf, right=np.inf) + return result + + +class CenteredNorm(Normalize): + def __init__(self, vcenter=0, halfrange=None, clip=False): + """ + Normalize symmetrical data around a center (0 by default). + + Unlike `TwoSlopeNorm`, `CenteredNorm` applies an equal rate of change + around the center. + + Useful when mapping symmetrical data around a conceptual center + e.g., data that range from -2 to 4, with 0 as the midpoint, and + with equal rates of change around that midpoint. + + Parameters + ---------- + vcenter : float, default: 0 + The data value that defines ``0.5`` in the normalization. + halfrange : float, optional + The range of data values that defines a range of ``0.5`` in the + normalization, so that *vcenter* - *halfrange* is ``0.0`` and + *vcenter* + *halfrange* is ``1.0`` in the normalization. + Defaults to the largest absolute difference to *vcenter* for + the values in the dataset. + clip : bool, default: False + Determines the behavior for mapping values outside the range + ``[vmin, vmax]``. + + If clipping is off, values outside the range ``[vmin, vmax]`` are + also transformed, resulting in values outside ``[0, 1]``. This + behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. + + If clipping is on, values below *vmin* are mapped to 0 and values + above *vmax* are mapped to 1. Such values become indistinguishable + from regular boundary values, which may cause misinterpretation of + the data. + + Examples + -------- + This maps data values -2 to 0.25, 0 to 0.5, and 4 to 1.0 + (assuming equal rates of change above and below 0.0): + + >>> import matplotlib.colors as mcolors + >>> norm = mcolors.CenteredNorm(halfrange=4.0) + >>> data = [-2., 0., 4.] + >>> norm(data) + array([0.25, 0.5 , 1. ]) + """ + super().__init__(vmin=None, vmax=None, clip=clip) + self._vcenter = vcenter + # calling the halfrange setter to set vmin and vmax + self.halfrange = halfrange + + def autoscale(self, A): + """ + Set *halfrange* to ``max(abs(A-vcenter))``, then set *vmin* and *vmax*. + """ + A = np.asanyarray(A) + self.halfrange = max(self._vcenter-A.min(), + A.max()-self._vcenter) + + def autoscale_None(self, A): + """Set *vmin* and *vmax*.""" + A = np.asanyarray(A) + if self.halfrange is None and A.size: + self.autoscale(A) + + @property + def vmin(self): + return self._vmin + + @vmin.setter + def vmin(self, value): + value = _sanitize_extrema(value) + if value != self._vmin: + self._vmin = value + self._vmax = 2*self.vcenter - value + self._changed() + + @property + def vmax(self): + return self._vmax + + @vmax.setter + def vmax(self, value): + value = _sanitize_extrema(value) + if value != self._vmax: + self._vmax = value + self._vmin = 2*self.vcenter - value + self._changed() -def _make_norm_from_scale(scale_cls, base_norm_cls=None, *, init=None): + @property + def vcenter(self): + return self._vcenter + + @vcenter.setter + def vcenter(self, vcenter): + if vcenter != self._vcenter: + self._vcenter = vcenter + # Trigger an update of the vmin/vmax values through the setter + self.halfrange = self.halfrange + self._changed() + + @property + def halfrange(self): + if self.vmin is None or self.vmax is None: + return None + return (self.vmax - self.vmin) / 2 + + @halfrange.setter + def halfrange(self, halfrange): + if halfrange is None: + self.vmin = None + self.vmax = None + else: + self.vmin = self.vcenter - abs(halfrange) + self.vmax = self.vcenter + abs(halfrange) + + +def make_norm_from_scale(scale_cls, base_norm_cls=None, *, init=None): """ - Decorator for building a `.Normalize` subclass from a `.Scale` subclass. + Decorator for building a `.Normalize` subclass from a `~.scale.ScaleBase` + subclass. After :: - @_make_norm_from_scale(scale_cls) - class base_norm_cls(Normalize): + @make_norm_from_scale(scale_cls) + class norm_cls(Normalize): ... - *base_norm_cls* is filled with methods so that normalization computations - are forwarded to *scale_cls* (i.e., *scale_cls* is the scale that would be - used for the colorbar of a mappable normalized with *base_norm_cls*). - - The constructor signature of *base_norm_cls* is derived from the - constructor signature of *scale_cls*, but can be overridden using *init* - (a callable which is *only* used for its signature). + *norm_cls* is filled with methods so that normalization computations are + forwarded to *scale_cls* (i.e., *scale_cls* is the scale that would be used + for the colorbar of a mappable normalized with *norm_cls*). + + If *init* is not passed, then the constructor signature of *norm_cls* + will be ``norm_cls(vmin=None, vmax=None, clip=False)``; these three + parameters will be forwarded to the base class (``Normalize.__init__``), + and a *scale_cls* object will be initialized with no arguments (other than + a dummy axis). + + If the *scale_cls* constructor takes additional parameters, then *init* + should be passed to `make_norm_from_scale`. It is a callable which is + *only* used for its signature. First, this signature will become the + signature of *norm_cls*. Second, the *norm_cls* constructor will bind the + parameters passed to it using this signature, extract the bound *vmin*, + *vmax*, and *clip* values, pass those to ``Normalize.__init__``, and + forward the remaining bound values (including any defaults defined by the + signature) to the *scale_cls* constructor. """ if base_norm_cls is None: - return functools.partial(_make_norm_from_scale, scale_cls, init=init) + return functools.partial(make_norm_from_scale, scale_cls, init=init) + + if isinstance(scale_cls, functools.partial): + scale_args = scale_cls.args + scale_kwargs_items = tuple(scale_cls.keywords.items()) + scale_cls = scale_cls.func + else: + scale_args = scale_kwargs_items = () if init is None: def init(vmin=None, vmax=None, clip=False): pass - init_signature = inspect.signature(init) + + return _make_norm_from_scale( + scale_cls, scale_args, scale_kwargs_items, + base_norm_cls, inspect.signature(init)) + + +@functools.cache +def _make_norm_from_scale( + scale_cls, scale_args, scale_kwargs_items, + base_norm_cls, bound_init_signature, +): + """ + Helper for `make_norm_from_scale`. + + This function is split out to enable caching (in particular so that + different unpickles reuse the same class). In order to do so, + + - ``functools.partial`` *scale_cls* is expanded into ``func, args, kwargs`` + to allow memoizing returned norms (partial instances always compare + unequal, but we can check identity based on ``func, args, kwargs``; + - *init* is replaced by *init_signature*, as signatures are picklable, + unlike to arbitrary lambdas. + """ class Norm(base_norm_cls): + def __reduce__(self): + cls = type(self) + # If the class is toplevel-accessible, it is possible to directly + # pickle it "by name". This is required to support norm classes + # defined at a module's toplevel, as the inner base_norm_cls is + # otherwise unpicklable (as it gets shadowed by the generated norm + # class). If either import or attribute access fails, fall back to + # the general path. + try: + if cls is getattr(importlib.import_module(cls.__module__), + cls.__qualname__): + return (_create_empty_object_of_class, (cls,), vars(self)) + except (ImportError, AttributeError): + pass + return (_picklable_norm_constructor, + (scale_cls, scale_args, scale_kwargs_items, + base_norm_cls, bound_init_signature), + vars(self)) def __init__(self, *args, **kwargs): - ba = init_signature.bind(*args, **kwargs) + ba = bound_init_signature.bind(*args, **kwargs) ba.apply_defaults() super().__init__( **{k: ba.arguments.pop(k) for k in ["vmin", "vmax", "clip"]}) - self._scale = scale_cls(axis=None, **ba.arguments) + self._scale = functools.partial( + scale_cls, *scale_args, **dict(scale_kwargs_items))( + axis=None, **ba.arguments) self._trf = self._scale.get_transform() - self._inv_trf = self._trf.inverted() + + __init__.__signature__ = bound_init_signature.replace(parameters=[ + inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD), + *bound_init_signature.parameters.values()]) def __call__(self, value, clip=None): value, is_scalar = self.process_value(value) - self.autoscale_None(value) + if self.vmin is None or self.vmax is None: + self.autoscale_None(value) if self.vmin > self.vmax: raise ValueError("vmin must be less or equal to vmax") if self.vmin == self.vmax: @@ -1281,30 +2838,86 @@ def inverse(self, value): t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax]) if not np.isfinite([t_vmin, t_vmax]).all(): raise ValueError("Invalid vmin or vmax") + value, is_scalar = self.process_value(value) rescaled = value * (t_vmax - t_vmin) rescaled += t_vmin - return self._inv_trf.transform(rescaled).reshape(np.shape(value)) - - Norm.__name__ = base_norm_cls.__name__ - Norm.__qualname__ = base_norm_cls.__qualname__ + value = (self._trf + .inverted() + .transform(rescaled) + .reshape(np.shape(value))) + return value[0] if is_scalar else value + + def autoscale_None(self, A): + # i.e. A[np.isfinite(...)], but also for non-array A's + in_trf_domain = np.extract(np.isfinite(self._trf.transform(A)), A) + if in_trf_domain.size == 0: + in_trf_domain = np.ma.masked + return super().autoscale_None(in_trf_domain) + + if base_norm_cls is Normalize: + Norm.__name__ = f"{scale_cls.__name__}Norm" + Norm.__qualname__ = f"{scale_cls.__qualname__}Norm" + else: + Norm.__name__ = base_norm_cls.__name__ + Norm.__qualname__ = base_norm_cls.__qualname__ Norm.__module__ = base_norm_cls.__module__ + Norm.__doc__ = base_norm_cls.__doc__ + return Norm -@_make_norm_from_scale(functools.partial(scale.LogScale, nonpositive="mask")) -class LogNorm(Normalize): - """Normalize a given value to the 0-1 range on a log scale.""" +def _create_empty_object_of_class(cls): + return cls.__new__(cls) - def autoscale(self, A): - # docstring inherited. - super().autoscale(np.ma.masked_less_equal(A, 0, copy=False)) - def autoscale_None(self, A): - # docstring inherited. - super().autoscale_None(np.ma.masked_less_equal(A, 0, copy=False)) +def _picklable_norm_constructor(*args): + return _create_empty_object_of_class(_make_norm_from_scale(*args)) + + +@make_norm_from_scale( + scale.FuncScale, + init=lambda functions, vmin=None, vmax=None, clip=False: None) +class FuncNorm(Normalize): + """ + Arbitrary normalization using functions for the forward and inverse. + + Parameters + ---------- + functions : (callable, callable) + two-tuple of the forward and inverse functions for the normalization. + The forward function must be monotonic. + + Both functions must have the signature :: + def forward(values: array-like) -> array-like -@_make_norm_from_scale( + vmin, vmax : float or None + If *vmin* and/or *vmax* is not given, they are initialized from the + minimum and maximum value, respectively, of the first input + processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``. + + clip : bool, default: False + Determines the behavior for mapping values outside the range + ``[vmin, vmax]``. + + If clipping is off, values outside the range ``[vmin, vmax]`` are also + transformed by the function, resulting in values outside ``[0, 1]``. + This behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. + + If clipping is on, values below *vmin* are mapped to 0 and values above + *vmax* are mapped to 1. Such values become indistinguishable from + regular boundary values, which may cause misinterpretation of the data. + """ + + +LogNorm = make_norm_from_scale( + functools.partial(scale.LogScale, nonpositive="mask"))(Normalize) +LogNorm.__name__ = LogNorm.__qualname__ = "LogNorm" +LogNorm.__doc__ = "Normalize a given value to the 0-1 range on a log scale." + + +@make_norm_from_scale( scale.SymmetricalLogScale, init=lambda linthresh, linscale=1., vmin=None, vmax=None, clip=False, *, base=10: None) @@ -1342,10 +2955,73 @@ def linthresh(self, value): self._scale.linthresh = value -class PowerNorm(Normalize): +@make_norm_from_scale( + scale.AsinhScale, + init=lambda linear_width=1, vmin=None, vmax=None, clip=False: None) +class AsinhNorm(Normalize): + """ + The inverse hyperbolic sine scale is approximately linear near + the origin, but becomes logarithmic for larger positive + or negative values. Unlike the `SymLogNorm`, the transition between + these linear and logarithmic regions is smooth, which may reduce + the risk of visual artifacts. + + .. note:: + + This API is provisional and may be revised in the future + based on early user feedback. + + Parameters + ---------- + linear_width : float, default: 1 + The effective width of the linear region, beyond which + the transformation becomes asymptotically logarithmic """ + + @property + def linear_width(self): + return self._scale.linear_width + + @linear_width.setter + def linear_width(self, value): + self._scale.linear_width = value + + +class PowerNorm(Normalize): + r""" Linearly map a given value to the 0-1 range and then apply a power-law normalization over that range. + + Parameters + ---------- + gamma : float + Power law exponent. + vmin, vmax : float or None + If *vmin* and/or *vmax* is not given, they are initialized from the + minimum and maximum value, respectively, of the first input + processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``. + clip : bool, default: False + Determines the behavior for mapping values outside the range + ``[vmin, vmax]``. + + If clipping is off, values above *vmax* are transformed by the power + function, resulting in values above 1, and values below *vmin* are linearly + transformed resulting in values below 0. This behavior is usually desirable, as + colormaps can mark these *under* and *over* values with specific colors. + + If clipping is on, values below *vmin* are mapped to 0 and values above + *vmax* are mapped to 1. Such values become indistinguishable from + regular boundary values, which may cause misinterpretation of the data. + + Notes + ----- + The normalization formula is + + .. math:: + + \left ( \frac{x - v_{min}}{v_{max} - v_{min}} \right )^{\gamma} + + For input values below *vmin*, gamma is set to one. """ def __init__(self, gamma, vmin=None, vmax=None, clip=False): super().__init__(vmin, vmax, clip) @@ -1371,9 +3047,8 @@ def __call__(self, value, clip=None): mask=mask) resdat = result.data resdat -= vmin - resdat[resdat < 0] = 0 - np.power(resdat, gamma, resdat) - resdat /= (vmax - vmin) ** gamma + resdat /= (vmax - vmin) + resdat[resdat > 0] = np.power(resdat[resdat > 0], gamma) result = np.ma.array(resdat, mask=result.mask, copy=False) if is_scalar: @@ -1383,14 +3058,21 @@ def __call__(self, value, clip=None): def inverse(self, value): if not self.scaled(): raise ValueError("Not invertible until scaled") + + result, is_scalar = self.process_value(value) + gamma = self.gamma vmin, vmax = self.vmin, self.vmax - if np.iterable(value): - val = np.ma.asarray(value) - return np.ma.power(val, 1. / gamma) * (vmax - vmin) + vmin - else: - return pow(value, 1. / gamma) * (vmax - vmin) + vmin + resdat = result.data + resdat[resdat > 0] = np.power(resdat[resdat > 0], 1 / gamma) + resdat *= (vmax - vmin) + resdat += vmin + + result = np.ma.array(resdat, mask=result.mask, copy=False) + if is_scalar: + result = result[0] + return result class BoundaryNorm(Normalize): @@ -1399,19 +3081,23 @@ class BoundaryNorm(Normalize): Unlike `Normalize` or `LogNorm`, `BoundaryNorm` maps values to integers instead of to the interval 0-1. - - Mapping to the 0-1 interval could have been done via piece-wise linear - interpolation, but using integers seems simpler, and reduces the number of - conversions back and forth between integer and floating point. """ + + # Mapping to the 0-1 interval could have been done via piece-wise linear + # interpolation, but using integers seems simpler, and reduces the number + # of conversions back and forth between int and float. + def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): """ Parameters ---------- boundaries : array-like - Monotonically increasing sequence of at least 2 boundaries. + Monotonically increasing sequence of at least 2 bin edges: data + falling in the n-th bin will be mapped to the n-th color. + ncolors : int Number of colors in the colormap to be used. + clip : bool, optional If clip is ``True``, out of range values are mapped to 0 if they are below ``boundaries[0]`` or mapped to ``ncolors - 1`` if they @@ -1421,6 +3107,7 @@ def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. + extend : {'neither', 'both', 'min', 'max'}, default: 'neither' Extend the number of bins to include one or both of the regions beyond the boundaries. For example, if ``extend`` @@ -1430,24 +3117,16 @@ def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): `~matplotlib.colorbar.Colorbar` will be drawn with the triangle extension on the left or lower end. - Returns - ------- - int16 scalar or array - Notes ----- - *boundaries* defines the edges of bins, and data falling within a bin - is mapped to the color with the same index. - - If the number of bins, including any extensions, is less than - *ncolors*, the color index is chosen by linear interpolation, mapping - the ``[0, nbins - 1]`` range onto the ``[0, ncolors - 1]`` range. + If there are fewer bins (including extensions) than colors, then the + color index is chosen by linearly interpolating the ``[0, nbins - 1]`` + range onto the ``[0, ncolors - 1]`` range, effectively skipping some + colors in the middle of the colormap. """ if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") - self.clip = clip - self.vmin = boundaries[0] - self.vmax = boundaries[-1] + super().__init__(vmin=boundaries[0], vmax=boundaries[-1], clip=clip) self.boundaries = np.asarray(boundaries) self.N = len(self.boundaries) if self.N < 2: @@ -1456,6 +3135,8 @@ def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): self.Ncmap = ncolors self.extend = extend + self._scale = None # don't use the default scale. + self._n_regions = self.N - 1 # number of colors needed self._offset = 0 if extend in ('min', 'both'): @@ -1470,6 +3151,10 @@ def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): "number of bins") def __call__(self, value, clip=None): + """ + This method behaves similarly to `.Normalize.__call__`, except that it + returns integers or arrays of int16. + """ if clip is None: clip = self.clip @@ -1483,7 +3168,7 @@ def __call__(self, value, clip=None): else: max_col = self.Ncmap # this gives us the bins in the lookup table in the range - # [0, _n_regions - 1] (the offset is baked in in the init) + # [0, _n_regions - 1] (the offset is set in the init) iret = np.digitize(xx, self.boundaries) - 1 + self._offset # if we have more colors than regions, stretch the region # index computed above to full range of the color bins. This @@ -1524,16 +3209,19 @@ class NoNorm(Normalize): indices directly in a `~matplotlib.cm.ScalarMappable`. """ def __call__(self, value, clip=None): + if np.iterable(value): + return np.ma.array(value) return value def inverse(self, value): + if np.iterable(value): + return np.ma.array(value) return value def rgb_to_hsv(arr): """ - Convert float rgb values (in the range [0, 1]), in a numpy array to hsv - values. + Convert an array of float RGB values (in the range [0, 1]) to HSV values. Parameters ---------- @@ -1542,15 +3230,15 @@ def rgb_to_hsv(arr): Returns ------- - (..., 3) ndarray - Colors converted to hsv values in range [0, 1] + (..., 3) `~numpy.ndarray` + Colors converted to HSV values in range [0, 1] """ arr = np.asarray(arr) # check length of the last dimension, should be _some_ sort of rgb if arr.shape[-1] != 3: raise ValueError("Last dimension of input array must be 3; " - "shape {} was found.".format(arr.shape)) + f"shape {arr.shape} was found.") in_shape = arr.shape arr = np.array( @@ -1558,10 +3246,24 @@ def rgb_to_hsv(arr): dtype=np.promote_types(arr.dtype, np.float32), # Don't work on ints. ndmin=2, # In case input was 1D. ) + out = np.zeros_like(arr) arr_max = arr.max(-1) + # Check if input is in the expected range + if np.any(arr_max > 1): + raise ValueError( + "Input array must be in the range [0, 1]. " + f"Found a maximum value of {arr_max.max()}" + ) + + if arr.min() < 0: + raise ValueError( + "Input array must be in the range [0, 1]. " + f"Found a minimum value of {arr.min()}" + ) + ipos = arr_max > 0 - delta = arr.ptp(-1) + delta = np.ptp(arr, -1) s = np.zeros_like(delta) s[ipos] = delta[ipos] / arr_max[ipos] ipos = delta > 0 @@ -1584,7 +3286,7 @@ def rgb_to_hsv(arr): def hsv_to_rgb(hsv): """ - Convert hsv values to rgb. + Convert HSV values to RGB. Parameters ---------- @@ -1593,7 +3295,7 @@ def hsv_to_rgb(hsv): Returns ------- - (..., 3) ndarray + (..., 3) `~numpy.ndarray` Colors converted to RGB values in range [0, 1] """ hsv = np.asarray(hsv) @@ -1601,7 +3303,7 @@ def hsv_to_rgb(hsv): # check length of the last dimension, should be _some_ sort of rgb if hsv.shape[-1] != 3: raise ValueError("Last dimension of input array must be 3; " - "shape {shp} was found.".format(shp=hsv.shape)) + f"shape {hsv.shape} was found.") in_shape = hsv.shape hsv = np.array( @@ -1680,8 +3382,8 @@ class LightSource: Angles are in degrees, with the azimuth measured clockwise from north and elevation up from the zero plane of the surface. - `shade` is used to produce "shaded" rgb values for a data array. - `shade_rgb` can be used to combine an rgb image with an elevation map. + `shade` is used to produce "shaded" RGB values for a data array. + `shade_rgb` can be used to combine an RGB image with an elevation map. `hillshade` produces an illumination map of a surface. """ @@ -1700,6 +3402,18 @@ def __init__(self, azdeg=315, altdeg=45, hsv_min_val=0, hsv_max_val=1, altdeg : float, default: 45 degrees The altitude (0-90, degrees up from horizontal) of the light source. + hsv_min_val : number, default: 0 + The minimum value ("v" in "hsv") that the *intensity* map can shift the + output image to. + hsv_max_val : number, default: 1 + The maximum value ("v" in "hsv") that the *intensity* map can shift the + output image to. + hsv_min_sat : number, default: 1 + The minimum saturation value that the *intensity* map can shift the output + image to. + hsv_max_sat : number, default: 0 + The maximum saturation value that the *intensity* map can shift the output + image to. Notes ----- @@ -1739,9 +3453,8 @@ def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.): Parameters ---------- - elevation : array-like - A 2d array (or equivalent) of the height values used to generate an - illumination map + elevation : 2D array-like + The height values used to generate an illumination map vert_exag : number, optional The amount to exaggerate the elevation values by when calculating illumination. This can be used either to correct for differences in @@ -1761,8 +3474,8 @@ def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.): Returns ------- - ndarray - A 2d array of illumination values between 0-1, where 0 is + `~numpy.ndarray` + A 2D array of illumination values between 0-1, where 0 is completely in shadow and 1 is completely illuminated. """ @@ -1804,8 +3517,8 @@ def shade_normals(self, normals, fraction=1.): Returns ------- - ndarray - A 2d array of illumination values between 0-1, where 0 is + `~numpy.ndarray` + A 2D array of illumination values between 0-1, where 0 is completely in shadow and 1 is completely illuminated. """ @@ -1837,9 +3550,8 @@ def shade(self, data, cmap, norm=None, blend_mode='overlay', vmin=None, Parameters ---------- - data : array-like - A 2d array (or equivalent) of the height values used to generate a - shaded map. + data : 2D array-like + The height values used to generate a shaded map. cmap : `~matplotlib.colors.Colormap` The colormap used to color the *data* array. Note that this must be a `~matplotlib.colors.Colormap` instance. For example, rather than @@ -1854,8 +3566,8 @@ def shade(self, data, cmap, norm=None, blend_mode='overlay', vmin=None, "overlay". Note that for most topographic surfaces, "overlay" or "soft" appear more visually realistic. If a user-defined function is supplied, it is expected to - combine an MxNx3 RGB array of floats (ranging 0 to 1) with - an MxNx1 hillshade array (also 0 to 1). (Call signature + combine an (M, N, 3) RGB array of floats (ranging 0 to 1) with + an (M, N, 1) hillshade array (also 0 to 1). (Call signature ``func(rgb, illum, **kwargs)``) Additional kwargs supplied to this function will be passed on to the *blend_mode* function. @@ -1883,12 +3595,13 @@ def shade(self, data, cmap, norm=None, blend_mode='overlay', vmin=None, full illumination or shadow (and clipping any values that move beyond 0 or 1). Note that this is not visually or mathematically the same as vertical exaggeration. - Additional kwargs are passed on to the *blend_mode* function. + **kwargs + Additional kwargs are passed on to the *blend_mode* function. Returns ------- - ndarray - An MxNx4 array of floats ranging between 0-1. + `~numpy.ndarray` + An (M, N, 4) array of floats ranging between 0-1. """ if vmin is None: vmin = data.min() @@ -1929,8 +3642,8 @@ def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv', defaults to "hsv". Note that for most topographic surfaces, "overlay" or "soft" appear more visually realistic. If a user-defined function is supplied, it is expected to combine an - MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade - array (also 0 to 1). (Call signature + (M, N, 3) RGB array of floats (ranging 0 to 1) with an (M, N, 1) + hillshade array (also 0 to 1). (Call signature ``func(rgb, illum, **kwargs)``) Additional kwargs supplied to this function will be passed on to the *blend_mode* function. @@ -1944,11 +3657,12 @@ def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv', The x-spacing (columns) of the input *elevation* grid. dy : number, optional The y-spacing (rows) of the input *elevation* grid. - Additional kwargs are passed on to the *blend_mode* function. + **kwargs + Additional kwargs are passed on to the *blend_mode* function. Returns ------- - ndarray + `~numpy.ndarray` An (m, n, 3) array of floats ranging between 0-1. """ # Calculate the "hillshade" intensity. @@ -1967,8 +3681,8 @@ def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv', try: blend = blend_mode(rgb, intensity, **kwargs) except TypeError as err: - raise ValueError('"blend_mode" must be callable or one of {}' - .format(lookup.keys)) from err + raise ValueError('"blend_mode" must be callable or one of ' + f'{lookup.keys}') from err # Only apply result where hillshade intensity isn't masked if np.ma.is_masked(intensity): @@ -1987,7 +3701,7 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None, which can then be used to plot the shaded image with imshow. The color of the resulting image will be darkened by moving the (s, v) - values (in hsv colorspace) toward (hsv_min_sat, hsv_min_val) in the + values (in HSV colorspace) toward (hsv_min_sat, hsv_min_val) in the shaded regions, or lightened by sliding (s, v) toward (hsv_max_sat, hsv_max_val) in regions that are illuminated. The default extremes are chose so that completely shaded points are nearly black (s = 1, v = 0) @@ -1995,27 +3709,29 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None, Parameters ---------- - rgb : ndarray - An MxNx3 RGB array of floats ranging from 0 to 1 (color image). - intensity : ndarray - An MxNx1 array of floats ranging from 0 to 1 (grayscale image). - hsv_max_sat : number, default: 1 - The maximum saturation value that the *intensity* map can shift the - output image to. + rgb : `~numpy.ndarray` + An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image). + intensity : `~numpy.ndarray` + An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image). + hsv_max_sat : number, optional + The maximum saturation value that the *intensity* map can shift the output + image to. If not provided, use the value provided upon initialization. hsv_min_sat : number, optional - The minimum saturation value that the *intensity* map can shift the - output image to. Defaults to 0. + The minimum saturation value that the *intensity* map can shift the output + image to. If not provided, use the value provided upon initialization. hsv_max_val : number, optional - The maximum value ("v" in "hsv") that the *intensity* map can shift - the output image to. Defaults to 1. + The maximum value ("v" in "hsv") that the *intensity* map can shift the + output image to. If not provided, use the value provided upon + initialization. hsv_min_val : number, optional - The minimum value ("v" in "hsv") that the *intensity* map can shift - the output image to. Defaults to 0. + The minimum value ("v" in "hsv") that the *intensity* map can shift the + output image to. If not provided, use the value provided upon + initialization. Returns ------- - ndarray - An MxNx3 RGB array representing the combined images. + `~numpy.ndarray` + An (M, N, 3) RGB array representing the combined images. """ # Backward compatibility... if hsv_max_sat is None: @@ -2052,38 +3768,38 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None, def blend_soft_light(self, rgb, intensity): """ - Combine an rgb image with an intensity map using "soft light" blending, + Combine an RGB image with an intensity map using "soft light" blending, using the "pegtop" formula. Parameters ---------- - rgb : ndarray - An MxNx3 RGB array of floats ranging from 0 to 1 (color image). - intensity : ndarray - An MxNx1 array of floats ranging from 0 to 1 (grayscale image). + rgb : `~numpy.ndarray` + An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image). + intensity : `~numpy.ndarray` + An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image). Returns ------- - ndarray - An MxNx3 RGB array representing the combined images. + `~numpy.ndarray` + An (M, N, 3) RGB array representing the combined images. """ return 2 * intensity * rgb + (1 - 2 * intensity) * rgb**2 def blend_overlay(self, rgb, intensity): """ - Combines an rgb image with an intensity map using "overlay" blending. + Combine an RGB image with an intensity map using "overlay" blending. Parameters ---------- - rgb : ndarray - An MxNx3 RGB array of floats ranging from 0 to 1 (color image). - intensity : ndarray - An MxNx1 array of floats ranging from 0 to 1 (grayscale image). + rgb : `~numpy.ndarray` + An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image). + intensity : `~numpy.ndarray` + An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image). Returns ------- ndarray - An MxNx3 RGB array representing the combined images. + An (M, N, 3) RGB array representing the combined images. """ low = 2 * intensity * rgb high = 1 - 2 * (1 - intensity) * (1 - rgb) @@ -2110,8 +3826,8 @@ def from_levels_and_colors(levels, colors, extend='neither'): Returns ------- - cmap : `~matplotlib.colors.Normalize` - norm : `~matplotlib.colors.Colormap` + cmap : `~matplotlib.colors.Colormap` + norm : `~matplotlib.colors.Normalize` """ slice_map = { 'both': slice(1, -1), @@ -2119,27 +3835,22 @@ def from_levels_and_colors(levels, colors, extend='neither'): 'max': slice(0, -1), 'neither': slice(0, None), } - cbook._check_in_list(slice_map, extend=extend) + _api.check_in_list(slice_map, extend=extend) color_slice = slice_map[extend] n_data_colors = len(levels) - 1 - n_expected = n_data_colors + color_slice.start - (color_slice.stop or 0) + n_extend_colors = color_slice.start - (color_slice.stop or 0) # 0, 1 or 2 + n_expected = n_data_colors + n_extend_colors if len(colors) != n_expected: raise ValueError( - f'With extend == {extend!r} and {len(levels)} levels, ' - f'expected {n_expected} colors, but got {len(colors)}') - - cmap = ListedColormap(colors[color_slice], N=n_data_colors) - - if extend in ['min', 'both']: - cmap.set_under(colors[0]) - else: - cmap.set_under('none') - - if extend in ['max', 'both']: - cmap.set_over(colors[-1]) - else: - cmap.set_over('none') + f'Expected {n_expected} colors ({n_data_colors} colors for {len(levels)} ' + f'levels, and {n_extend_colors} colors for extend == {extend!r}), ' + f'but got {len(colors)}') + + data_colors = colors[color_slice] + under_color = colors[0] if extend in ['min', 'both'] else 'none' + over_color = colors[-1] if extend in ['max', 'both'] else 'none' + cmap = ListedColormap(data_colors, under=under_color, over=over_color) cmap.colorbar_extend = extend diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi new file mode 100644 index 000000000000..eadd759bcaa3 --- /dev/null +++ b/lib/matplotlib/colors.pyi @@ -0,0 +1,465 @@ +from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from matplotlib import cbook, scale +import re + +from typing import Any, Literal, overload +from .typing import ColorType + +import numpy as np +from numpy.typing import ArrayLike + +# Explicitly export colors dictionaries which are imported in the impl +BASE_COLORS: dict[str, ColorType] +CSS4_COLORS: dict[str, ColorType] +TABLEAU_COLORS: dict[str, ColorType] +XKCD_COLORS: dict[str, ColorType] + +class _ColorMapping(dict[str, ColorType]): + cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]] + def __init__(self, mapping) -> None: ... + def __setitem__(self, key, value) -> None: ... + def __delitem__(self, key) -> None: ... + +def get_named_colors_mapping() -> _ColorMapping: ... + +class ColorSequenceRegistry(Mapping): + def __init__(self) -> None: ... + def __getitem__(self, item: str) -> list[ColorType]: ... + def __iter__(self) -> Iterator[str]: ... + def __len__(self) -> int: ... + def register(self, name: str, color_list: Iterable[ColorType]) -> None: ... + def unregister(self, name: str) -> None: ... + +_color_sequences: ColorSequenceRegistry = ... + +def is_color_like(c: Any) -> bool: ... +def same_color(c1: ColorType, c2: ColorType) -> bool: ... +def to_rgba( + c: ColorType, alpha: float | None = ... +) -> tuple[float, float, float, float]: ... +def to_rgba_array( + c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ... +) -> np.ndarray: ... +def to_rgb(c: ColorType) -> tuple[float, float, float]: ... +def to_hex(c: ColorType, keep_alpha: bool = ...) -> str: ... + +cnames: dict[str, ColorType] +hexColorPattern: re.Pattern +rgb2hex = to_hex +hex2color = to_rgb + +class ColorConverter: + colors: _ColorMapping + cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]] + @staticmethod + def to_rgb(c: ColorType) -> tuple[float, float, float]: ... + @staticmethod + def to_rgba( + c: ColorType, alpha: float | None = ... + ) -> tuple[float, float, float, float]: ... + @staticmethod + def to_rgba_array( + c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ... + ) -> np.ndarray: ... + +colorConverter: ColorConverter + +class Colormap: + name: str + N: int + colorbar_extend: bool + def __init__( + self, + name: str, + N: int = ..., + *, + bad: ColorType | None = ..., + under: ColorType | None = ..., + over: ColorType | None = ... + ) -> None: ... + @overload + def __call__( + self, X: Sequence[float] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: float, alpha: float | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload + def __call__( + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float] | np.ndarray: ... + def __copy__(self) -> Colormap: ... + def __eq__(self, other: object) -> bool: ... + def get_bad(self) -> np.ndarray: ... + def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def get_under(self) -> np.ndarray: ... + def set_under(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def get_over(self) -> np.ndarray: ... + def set_over(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def set_extremes( + self, + *, + bad: ColorType | None = ..., + under: ColorType | None = ..., + over: ColorType | None = ... + ) -> None: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + under: ColorType | None = ..., + over: ColorType | None = ... + ) -> Colormap: ... + def with_alpha(self, alpha: float) -> Colormap: ... + def is_gray(self) -> bool: ... + def resampled(self, lutsize: int) -> Colormap: ... + def reversed(self, name: str | None = ...) -> Colormap: ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... + def copy(self) -> Colormap: ... + +class LinearSegmentedColormap(Colormap): + monochrome: bool + def __init__( + self, + name: str, + segmentdata: dict[ + Literal["red", "green", "blue", "alpha"], Sequence[tuple[float, ...]] + ], + N: int = ..., + gamma: float = ..., + *, + bad: ColorType | None = ..., + under: ColorType | None = ..., + over: ColorType | None = ..., + ) -> None: ... + def set_gamma(self, gamma: float) -> None: ... + @staticmethod + def from_list( + name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ..., + *, bad: ColorType | None = ..., under: ColorType | None = ..., over: ColorType | None = ..., + ) -> LinearSegmentedColormap: ... + def resampled(self, lutsize: int) -> LinearSegmentedColormap: ... + def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ... + +class ListedColormap(Colormap): + colors: ArrayLike | ColorType + def __init__( + self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ..., + *, bad: ColorType | None = ..., under: ColorType | None = ..., over: ColorType | None = ... + ) -> None: ... + @property + def monochrome(self) -> bool: ... + def resampled(self, lutsize: int) -> ListedColormap: ... + def reversed(self, name: str | None = ...) -> ListedColormap: ... + +class MultivarColormap: + name: str + n_variates: int + def __init__(self, colormaps: list[Colormap], combination_mode: Literal['sRGB_add', 'sRGB_sub'], name: str = ...) -> None: ... + @overload + def __call__( + self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ..., clip: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload + def __call__( + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... + ) -> tuple[float, float, float, float] | np.ndarray: ... + def copy(self) -> MultivarColormap: ... + def __copy__(self) -> MultivarColormap: ... + def __eq__(self, other: Any) -> bool: ... + def __getitem__(self, item: int) -> Colormap: ... + def __iter__(self) -> Iterator[Colormap]: ... + def __len__(self) -> int: ... + def get_bad(self) -> np.ndarray: ... + def resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + under: Sequence[ColorType] | None = ..., + over: Sequence[ColorType] | None = ... + ) -> MultivarColormap: ... + @property + def combination_mode(self) -> str: ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... + +class BivarColormap: + name: str + N: int + M: int + n_variates: int + def __init__( + self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... + ) -> None: ... + @overload + def __call__( + self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload + def __call__( + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float] | np.ndarray: ... + @property + def lut(self) -> np.ndarray: ... + @property + def shape(self) -> str: ... + @property + def origin(self) -> tuple[float, float]: ... + def copy(self) -> BivarColormap: ... + def __copy__(self) -> BivarColormap: ... + def __getitem__(self, item: int) -> Colormap: ... + def __eq__(self, other: Any) -> bool: ... + def get_bad(self) -> np.ndarray: ... + def get_outside(self) -> np.ndarray: ... + def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ... + def transposed(self) -> BivarColormap: ... + def reversed(self, axis_0: bool = ..., axis_1: bool = ...) -> BivarColormap: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + outside: ColorType | None = ..., + shape: str | None = ..., + origin: None | Sequence[float] = ..., + ) -> MultivarColormap: ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... + +class SegmentedBivarColormap(BivarColormap): + def __init__( + self, patch: np.ndarray, N: int = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... + ) -> None: ... + +class BivarColormapFromImage(BivarColormap): + def __init__( + self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... + ) -> None: ... + +class Normalize: + callbacks: cbook.CallbackRegistry + def __init__( + self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ... + ) -> None: ... + @property + def vmin(self) -> float | None: ... + @vmin.setter + def vmin(self, value: float | None) -> None: ... + @property + def vmax(self) -> float | None: ... + @vmax.setter + def vmax(self, value: float | None) -> None: ... + @property + def clip(self) -> bool: ... + @clip.setter + def clip(self, value: bool) -> None: ... + @staticmethod + def process_value(value: ArrayLike) -> tuple[np.ma.MaskedArray, bool]: ... + @overload + def __call__(self, value: float, clip: bool | None = ...) -> float: ... + @overload + def __call__(self, value: np.ndarray, clip: bool | None = ...) -> np.ma.MaskedArray: ... + @overload + def __call__(self, value: ArrayLike, clip: bool | None = ...) -> ArrayLike: ... + @overload + def inverse(self, value: float) -> float: ... + @overload + def inverse(self, value: np.ndarray) -> np.ma.MaskedArray: ... + @overload + def inverse(self, value: ArrayLike) -> ArrayLike: ... + def autoscale(self, A: ArrayLike) -> None: ... + def autoscale_None(self, A: ArrayLike) -> None: ... + def scaled(self) -> bool: ... + +class TwoSlopeNorm(Normalize): + def __init__( + self, vcenter: float, vmin: float | None = ..., vmax: float | None = ... + ) -> None: ... + @property + def vcenter(self) -> float: ... + @vcenter.setter + def vcenter(self, value: float) -> None: ... + def autoscale_None(self, A: ArrayLike) -> None: ... + +class CenteredNorm(Normalize): + def __init__( + self, vcenter: float = ..., halfrange: float | None = ..., clip: bool = ... + ) -> None: ... + @property + def vcenter(self) -> float: ... + @vcenter.setter + def vcenter(self, vcenter: float) -> None: ... + @property + def halfrange(self) -> float: ... + @halfrange.setter + def halfrange(self, halfrange: float) -> None: ... + +@overload +def make_norm_from_scale( + scale_cls: type[scale.ScaleBase], + base_norm_cls: type[Normalize], + *, + init: Callable | None = ... +) -> type[Normalize]: ... +@overload +def make_norm_from_scale( + scale_cls: type[scale.ScaleBase], + base_norm_cls: None = ..., + *, + init: Callable | None = ... +) -> Callable[[type[Normalize]], type[Normalize]]: ... + +class FuncNorm(Normalize): + def __init__( + self, + functions: tuple[Callable, Callable], + vmin: float | None = ..., + vmax: float | None = ..., + clip: bool = ..., + ) -> None: ... +class LogNorm(Normalize): ... + +class SymLogNorm(Normalize): + def __init__( + self, + linthresh: float, + linscale: float = ..., + vmin: float | None = ..., + vmax: float | None = ..., + clip: bool = ..., + *, + base: float = ..., + ) -> None: ... + @property + def linthresh(self) -> float: ... + @linthresh.setter + def linthresh(self, value: float) -> None: ... + +class AsinhNorm(Normalize): + def __init__( + self, + linear_width: float = ..., + vmin: float | None = ..., + vmax: float | None = ..., + clip: bool = ..., + ) -> None: ... + @property + def linear_width(self) -> float: ... + @linear_width.setter + def linear_width(self, value: float) -> None: ... + +class PowerNorm(Normalize): + gamma: float + def __init__( + self, + gamma: float, + vmin: float | None = ..., + vmax: float | None = ..., + clip: bool = ..., + ) -> None: ... + +class BoundaryNorm(Normalize): + boundaries: np.ndarray + N: int + Ncmap: int + extend: Literal["neither", "both", "min", "max"] + def __init__( + self, + boundaries: ArrayLike, + ncolors: int, + clip: bool = ..., + *, + extend: Literal["neither", "both", "min", "max"] = ... + ) -> None: ... + +class NoNorm(Normalize): ... + +def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ... +def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ... + +class LightSource: + azdeg: float + altdeg: float + hsv_min_val: float + hsv_max_val: float + hsv_min_sat: float + hsv_max_sat: float + def __init__( + self, + azdeg: float = ..., + altdeg: float = ..., + hsv_min_val: float = ..., + hsv_max_val: float = ..., + hsv_min_sat: float = ..., + hsv_max_sat: float = ..., + ) -> None: ... + @property + def direction(self) -> np.ndarray: ... + def hillshade( + self, + elevation: ArrayLike, + vert_exag: float = ..., + dx: float = ..., + dy: float = ..., + fraction: float = ..., + ) -> np.ndarray: ... + def shade_normals( + self, normals: np.ndarray, fraction: float = ... + ) -> np.ndarray: ... + def shade( + self, + data: ArrayLike, + cmap: Colormap, + norm: Normalize | None = ..., + blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ..., + vmin: float | None = ..., + vmax: float | None = ..., + vert_exag: float = ..., + dx: float = ..., + dy: float = ..., + fraction: float = ..., + **kwargs + ) -> np.ndarray: ... + def shade_rgb( + self, + rgb: ArrayLike, + elevation: ArrayLike, + fraction: float = ..., + blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ..., + vert_exag: float = ..., + dx: float = ..., + dy: float = ..., + **kwargs + ) -> np.ndarray: ... + def blend_hsv( + self, + rgb: ArrayLike, + intensity: ArrayLike, + hsv_max_sat: float | None = ..., + hsv_max_val: float | None = ..., + hsv_min_val: float | None = ..., + hsv_min_sat: float | None = ..., + ) -> ArrayLike: ... + def blend_soft_light( + self, rgb: np.ndarray, intensity: np.ndarray + ) -> np.ndarray: ... + def blend_overlay(self, rgb: np.ndarray, intensity: np.ndarray) -> np.ndarray: ... + +def from_levels_and_colors( + levels: Sequence[float], + colors: Sequence[ColorType], + extend: Literal["neither", "min", "max", "both"] = ..., +) -> tuple[ListedColormap, BoundaryNorm]: ... diff --git a/lib/matplotlib/compat/__init__.py b/lib/matplotlib/compat/__init__.py deleted file mode 100644 index 447ceee45347..000000000000 --- a/lib/matplotlib/compat/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from matplotlib import cbook - - -cbook.warn_deprecated("3.3", name=__name__, obj_type="module") diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index ab2723b012dd..b6dd43724f34 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -1,5 +1,5 @@ +from matplotlib import cbook from matplotlib.artist import Artist -import matplotlib.cbook as cbook class Container(tuple): @@ -11,25 +11,21 @@ class Container(tuple): """ def __repr__(self): - return ("<{} object of {} artists>" - .format(type(self).__name__, len(self))) + return f"<{type(self).__name__} object of {len(self)} artists>" def __new__(cls, *args, **kwargs): return tuple.__new__(cls, args[0]) def __init__(self, kl, label=None): - self.eventson = False # fire events only if eventson - self._oid = 0 # an observer id - self._propobservers = {} # a dict from oids to funcs + self._callbacks = cbook.CallbackRegistry(signals=["pchanged"]) self._remove_method = None - self.set_label(label) + self._label = str(label) if label is not None else None def remove(self): for c in cbook.flatten( self, scalarp=lambda x: isinstance(x, Artist)): if c is not None: c.remove() - if self._remove_method: self._remove_method(self) @@ -60,11 +56,21 @@ class BarContainer(Container): A container for the error bar artists if error bars are present. *None* otherwise. + datavalues : None or array-like + The underlying data values corresponding to the bars. + + orientation : {'vertical', 'horizontal'}, default: None + If 'vertical', the bars are assumed to be vertical. + If 'horizontal', the bars are assumed to be horizontal. + """ - def __init__(self, patches, errorbar=None, **kwargs): + def __init__(self, patches, errorbar=None, *, datavalues=None, + orientation=None, **kwargs): self.patches = patches self.errorbar = errorbar + self.datavalues = datavalues + self.orientation = orientation super().__init__(patches, **kwargs) @@ -81,12 +87,12 @@ class ErrorbarContainer(Container): lines : tuple Tuple of ``(data_line, caplines, barlinecols)``. - - data_line : :class:`~matplotlib.lines.Line2D` instance of - x, y plot markers and/or line. - - caplines : tuple of :class:`~matplotlib.lines.Line2D` instances of - the error bar caps. - - barlinecols : list of :class:`~matplotlib.collections.LineCollection` - with the horizontal and vertical error ranges. + - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers + and/or line. + - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error + bar caps. + - barlinecols : A tuple of `~matplotlib.collections.LineCollection` with the + horizontal and vertical error ranges. has_xerr, has_yerr : bool ``True`` if the errorbar has x/y errors. @@ -109,13 +115,13 @@ class StemContainer(Container): Attributes ---------- - markerline : :class:`~matplotlib.lines.Line2D` + markerline : `~matplotlib.lines.Line2D` The artist of the markers at the stem heads. - stemlines : list of :class:`~matplotlib.lines.Line2D` + stemlines : `~matplotlib.collections.LineCollection` The artists of the vertical lines for all stems. - baseline : :class:`~matplotlib.lines.Line2D` + baseline : `~matplotlib.lines.Line2D` The artist of the horizontal baseline. """ def __init__(self, markerline_stemlines_baseline, **kwargs): @@ -124,7 +130,7 @@ def __init__(self, markerline_stemlines_baseline, **kwargs): ---------- markerline_stemlines_baseline : tuple Tuple of ``(markerline, stemlines, baseline)``. - ``markerline`` contains the `.LineCollection` of the markers, + ``markerline`` contains the `.Line2D` of the markers, ``stemlines`` is a `.LineCollection` of the main lines, ``baseline`` is the `.Line2D` of the baseline. """ diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi new file mode 100644 index 000000000000..c66e7ba4b4c3 --- /dev/null +++ b/lib/matplotlib/container.pyi @@ -0,0 +1,56 @@ +from matplotlib.artist import Artist +from matplotlib.lines import Line2D +from matplotlib.collections import LineCollection +from matplotlib.patches import Rectangle + +from collections.abc import Callable +from typing import Any, Literal +from numpy.typing import ArrayLike + +class Container(tuple): + def __new__(cls, *args, **kwargs): ... + def __init__(self, kl, label: Any | None = ...) -> None: ... + def remove(self) -> None: ... + def get_children(self) -> list[Artist]: ... + def get_label(self) -> str | None: ... + def set_label(self, s: Any) -> None: ... + def add_callback(self, func: Callable[[Artist], Any]) -> int: ... + def remove_callback(self, oid: int) -> None: ... + def pchanged(self) -> None: ... + +class BarContainer(Container): + patches: list[Rectangle] + errorbar: None | ErrorbarContainer + datavalues: None | ArrayLike + orientation: None | Literal["vertical", "horizontal"] + def __init__( + self, + patches: list[Rectangle], + errorbar: ErrorbarContainer | None = ..., + *, + datavalues: ArrayLike | None = ..., + orientation: Literal["vertical", "horizontal"] | None = ..., + **kwargs + ) -> None: ... + +class ErrorbarContainer(Container): + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]] + has_xerr: bool + has_yerr: bool + def __init__( + self, + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]], + has_xerr: bool = ..., + has_yerr: bool = ..., + **kwargs + ) -> None: ... + +class StemContainer(Container): + markerline: Line2D + stemlines: LineCollection + baseline: Line2D + def __init__( + self, + markerline_stemlines_baseline: tuple[Line2D, LineCollection, Line2D], + **kwargs + ) -> None: ... diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index e6a6a2437206..8c5c9b566441 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -2,55 +2,65 @@ Classes to support contour plotting and labelling for the Axes class. """ +from contextlib import ExitStack +import functools +import math from numbers import Integral import numpy as np from numpy import ma import matplotlib as mpl -import matplotlib.path as mpath +from matplotlib import _api, _docstring +from matplotlib.backend_bases import MouseButton +from matplotlib.lines import Line2D +from matplotlib.path import Path +from matplotlib.text import Text import matplotlib.ticker as ticker import matplotlib.cm as cm import matplotlib.colors as mcolors import matplotlib.collections as mcoll import matplotlib.font_manager as font_manager -import matplotlib.text as text import matplotlib.cbook as cbook -import matplotlib.mathtext as mathtext import matplotlib.patches as mpatches -import matplotlib.texmanager as texmanager import matplotlib.transforms as mtransforms - -# Import needed for adding manual selection capability to clabel -from matplotlib.blocking_input import BlockingContourLabeler -from matplotlib import docstring - -# We can't use a single line collection for contour because a line -# collection can have only a single line style, and we want to be able to have -# dashed negative contours, for example, and solid positive contours. -# We could use a single polygon collection for filled contours, but it -# seems better to keep line and filled contours similar, with one collection -# per level. - - -class ClabelText(text.Text): - """ - Unlike the ordinary text, the get_rotation returns an updated - angle in the pixel coordinate assuming that the input rotation is - an angle in data coordinate (or whatever transform set). - """ - - def get_rotation(self): - new_angle, = self.get_transform().transform_angles( - [super().get_rotation()], [self.get_position()]) - return new_angle +from . import artist + + +def _contour_labeler_event_handler(cs, inline, inline_spacing, event): + canvas = cs.axes.get_figure(root=True).canvas + is_button = event.name == "button_press_event" + is_key = event.name == "key_press_event" + # Quit (even if not in infinite mode; this is consistent with + # MATLAB and sometimes quite useful, but will require the user to + # test how many points were actually returned before using data). + if (is_button and event.button == MouseButton.MIDDLE + or is_key and event.key in ["escape", "enter"]): + canvas.stop_event_loop() + # Pop last click. + elif (is_button and event.button == MouseButton.RIGHT + or is_key and event.key in ["backspace", "delete"]): + # Unfortunately, if one is doing inline labels, then there is currently + # no way to fix the broken contour - once humpty-dumpty is broken, he + # can't be put back together. In inline mode, this does nothing. + if not inline: + cs.pop_label() + canvas.draw() + # Add new click. + elif (is_button and event.button == MouseButton.LEFT + # On macOS/gtk, some keys return None. + or is_key and event.key is not None): + if cs.axes.contains(event)[0]: + cs.add_label_near(event.x, event.y, transform=False, + inline=inline, inline_spacing=inline_spacing) + canvas.draw() class ContourLabeler: """Mixin to provide labelling capability to `.ContourSet`.""" def clabel(self, levels=None, *, - fontsize=None, inline=True, inline_spacing=5, fmt='%1.3f', + fontsize=None, inline=True, inline_spacing=5, fmt=None, colors=None, use_clabeltext=False, manual=False, rightside_up=True, zorder=None): """ @@ -69,7 +79,7 @@ def clabel(self, levels=None, *, Size in points or relative size e.g., 'smaller', 'x-large'. See `.Text.set_size` for accepted string values. - colors : color or colors or None, default: None + colors : :mpltype:`color` or colors or None, default: None The label colors: - If *None*, the color of each label matches the color of @@ -78,7 +88,7 @@ def clabel(self, levels=None, *, - If one string color, e.g., *colors* = 'r' or *colors* = 'red', all labels will be plotted in this color. - - If a tuple of colors (string, float, rgb, etc), different labels + - If a tuple of colors (string, float, RGB, etc), different labels will be plotted in different colors in the order specified. inline : bool, default: True @@ -91,14 +101,17 @@ def clabel(self, levels=None, *, This spacing will be exact for labels at locations where the contour is straight, less so for labels on curved contours. - fmt : str or dict, default: '%1.3f' - A format string for the label. + fmt : `.Formatter` or str or callable or dict, optional + How the levels are formatted: + + - If a `.Formatter`, it is used to format all levels at once, using + its `.Formatter.format_ticks` method. + - If a str, it is interpreted as a %-style format string. + - If a callable, it is called with one level at a time and should + return the corresponding label. + - If a dict, it should directly map levels to labels. - Alternatively, this can be a dictionary matching contour levels - with arbitrary strings to use for each contour level (i.e., - fmt[level]=string), or it can be any callable, such as a - `.Formatter` instance, that returns a string when called with a - numeric contour level. + The default is to use a standard `.ScalarFormatter`. manual : bool or iterable, default: False If ``True``, contour labels will be placed manually using @@ -120,10 +133,8 @@ def clabel(self, levels=None, *, or minus 90 degrees from level. use_clabeltext : bool, default: False - If ``True``, `.ClabelText` class (instead of `.Text`) is used to - create labels. `ClabelText` recalculates rotation angles - of texts during the drawing time, therefore this can be used if - aspect of the axes changes. + If ``True``, use `.Text.set_transform_rotates_text` to ensure that + label rotation is updated whenever the Axes aspect changes. zorder : float or None, default: ``(2 + contour.get_zorder())`` zorder of the contour labels. @@ -134,24 +145,22 @@ def clabel(self, levels=None, *, A list of `.Text` instances for the labels. """ - # clabel basically takes the input arguments and uses them to - # add a list of "label specific" attributes to the ContourSet - # object. These attributes are all of the form label* and names - # should be fairly self explanatory. + # Based on the input arguments, clabel() adds a list of "label + # specific" attributes to the ContourSet object. These attributes are + # all of the form label* and names should be fairly self explanatory. # - # Once these attributes are set, clabel passes control to the - # labels method (case of automatic label placement) or - # `BlockingContourLabeler` (case of manual label placement). + # Once these attributes are set, clabel passes control to the labels() + # method (for automatic label placement) or blocking_input_loop and + # _contour_labeler_event_handler (for manual label placement). + if fmt is None: + fmt = ticker.ScalarFormatter(useOffset=False) + fmt.create_dummy_axis() self.labelFmt = fmt self._use_clabeltext = use_clabeltext - # Detect if manual selection is desired and remove from argument list. self.labelManual = manual self.rightside_up = rightside_up - if zorder is None: - self._clabel_zorder = 2+self._contour_zorder - else: - self._clabel_zorder = zorder + self._clabel_zorder = 2 + self.get_zorder() if zorder is None else zorder if levels is None: levels = self.levels @@ -169,374 +178,293 @@ def clabel(self, levels=None, *, self.labelLevelList = levels self.labelIndiceList = indices - self.labelFontProps = font_manager.FontProperties() - self.labelFontProps.set_size(fontsize) - font_size_pts = self.labelFontProps.get_size_in_points() - self.labelFontSizeList = [font_size_pts] * len(levels) + self._label_font_props = font_manager.FontProperties(size=fontsize) if colors is None: self.labelMappable = self self.labelCValueList = np.take(self.cvalues, self.labelIndiceList) else: - cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList)) - self.labelCValueList = list(range(len(self.labelLevelList))) - self.labelMappable = cm.ScalarMappable(cmap=cmap, - norm=mcolors.NoNorm()) + # handling of explicit colors for labels: + # make labelCValueList contain integers [0, 1, 2, ...] and a cmap + # so that cmap(i) == colors[i] + num_levels = len(self.labelLevelList) + colors = cbook._resize_sequence(mcolors.to_rgba_array(colors), num_levels) + self.labelMappable = cm.ScalarMappable( + cmap=mcolors.ListedColormap(colors), norm=mcolors.NoNorm()) + self.labelCValueList = list(range(num_levels)) self.labelXYs = [] - if np.iterable(self.labelManual): - for x, y in self.labelManual: + if np.iterable(manual): + for x, y in manual: self.add_label_near(x, y, inline, inline_spacing) - elif self.labelManual: + elif manual: print('Select label locations manually using first mouse button.') print('End manual selection with second mouse button.') if not inline: print('Remove last label by clicking third mouse button.') - blocking_contour_labeler = BlockingContourLabeler(self) - blocking_contour_labeler(inline, inline_spacing) + mpl._blocking_input.blocking_input_loop( + self.axes.get_figure(root=True), + ["button_press_event", "key_press_event"], + timeout=-1, handler=functools.partial( + _contour_labeler_event_handler, + self, inline, inline_spacing)) else: self.labels(inline, inline_spacing) - self.labelTextsList = cbook.silent_list('text.Text', self.labelTexts) - return self.labelTextsList + return cbook.silent_list('text.Text', self.labelTexts) def print_label(self, linecontour, labelwidth): """Return whether a contour is long enough to hold a label.""" return (len(linecontour) > 10 * labelwidth - or (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any()) + or (len(linecontour) + and (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any())) def too_close(self, x, y, lw): - """Return *True* if a label is already near this location.""" + """Return whether a label is already near this location.""" thresh = (1.2 * lw) ** 2 return any((x - loc[0]) ** 2 + (y - loc[1]) ** 2 < thresh for loc in self.labelXYs) - def get_label_coords(self, distances, XX, YY, ysize, lw): - """ - Return x, y, and the index of a label location. - - Labels are plotted at a location with the smallest - deviation of the contour from a straight line - unless there is another label nearby, in which case - the next best place on the contour is picked up. - If all such candidates are rejected, the beginning - of the contour is chosen. - """ - hysize = int(ysize / 2) - adist = np.argsort(distances) - - for ind in adist: - x, y = XX[ind][hysize], YY[ind][hysize] - if self.too_close(x, y, lw): - continue - return x, y, ind - - ind = adist[0] - x, y = XX[ind][hysize], YY[ind][hysize] - return x, y, ind - - def get_label_width(self, lev, fmt, fsize): - """ - Return the width of the label in points. - """ - if not isinstance(lev, str): - lev = self.get_text(lev, fmt) - lev, ismath = text.Text()._preprocess_math(lev) - if ismath == 'TeX': - lw, _, _ = (texmanager.TexManager() - .get_text_width_height_descent(lev, fsize)) - elif ismath: - if not hasattr(self, '_mathtext_parser'): - self._mathtext_parser = mathtext.MathTextParser('bitmap') - img, _ = self._mathtext_parser.parse(lev, dpi=72, - prop=self.labelFontProps) - _, lw = np.shape(img) # at dpi=72, the units are PostScript points - else: - # width is much less than "font size" - lw = len(lev) * fsize * 0.6 - return lw - - def set_label_props(self, label, text, color): - """Set the label properties - color, fontsize, text.""" - label.set_text(text) - label.set_color(color) - label.set_fontproperties(self.labelFontProps) - label.set_clip_box(self.axes.bbox) + def _get_nth_label_width(self, nth): + """Return the width of the *nth* label, in pixels.""" + fig = self.axes.get_figure(root=False) + renderer = fig.get_figure(root=True)._get_renderer() + return (Text(0, 0, + self.get_text(self.labelLevelList[nth], self.labelFmt), + figure=fig, fontproperties=self._label_font_props) + .get_window_extent(renderer).width) def get_text(self, lev, fmt): """Get the text of the label.""" if isinstance(lev, str): return lev + elif isinstance(fmt, dict): + return fmt.get(lev, '%1.3f') + elif callable(getattr(fmt, "format_ticks", None)): + return fmt.format_ticks([*self.labelLevelList, lev])[-1] + elif callable(fmt): + return fmt(lev) else: - if isinstance(fmt, dict): - return fmt.get(lev, '%1.3f') - elif callable(fmt): - return fmt(lev) - else: - return fmt % lev + return fmt % lev def locate_label(self, linecontour, labelwidth): """ Find good place to draw a label (relatively flat part of the contour). """ - - # Number of contour points - nsize = len(linecontour) - if labelwidth > 1: - xsize = int(np.ceil(nsize / labelwidth)) - else: - xsize = 1 - if xsize == 1: - ysize = nsize - else: - ysize = int(labelwidth) - - XX = np.resize(linecontour[:, 0], (xsize, ysize)) - YY = np.resize(linecontour[:, 1], (xsize, ysize)) - # I might have fouled up the following: - yfirst = YY[:, :1] - ylast = YY[:, -1:] - xfirst = XX[:, :1] - xlast = XX[:, -1:] - s = (yfirst - YY) * (xlast - xfirst) - (xfirst - XX) * (ylast - yfirst) - L = np.hypot(xlast - xfirst, ylast - yfirst) + ctr_size = len(linecontour) + n_blocks = int(np.ceil(ctr_size / labelwidth)) if labelwidth > 1 else 1 + block_size = ctr_size if n_blocks == 1 else int(labelwidth) + # Split contour into blocks of length ``block_size``, filling the last + # block by cycling the contour start (per `np.resize` semantics). (Due + # to cycling, the index returned is taken modulo ctr_size.) + xx = np.resize(linecontour[:, 0], (n_blocks, block_size)) + yy = np.resize(linecontour[:, 1], (n_blocks, block_size)) + yfirst = yy[:, :1] + ylast = yy[:, -1:] + xfirst = xx[:, :1] + xlast = xx[:, -1:] + s = (yfirst - yy) * (xlast - xfirst) - (xfirst - xx) * (ylast - yfirst) + l = np.hypot(xlast - xfirst, ylast - yfirst) # Ignore warning that divide by zero throws, as this is a valid option with np.errstate(divide='ignore', invalid='ignore'): - dist = np.sum(np.abs(s) / L, axis=-1) - x, y, ind = self.get_label_coords(dist, XX, YY, ysize, labelwidth) - - # There must be a more efficient way... - lc = [tuple(l) for l in linecontour] - dind = lc.index((x, y)) - - return x, y, dind - - def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): + distances = (abs(s) / l).sum(axis=-1) + # Labels are drawn in the middle of the block (``hbsize``) where the + # contour is the closest (per ``distances``) to a straight line, but + # not `too_close()` to a preexisting label. + hbsize = block_size // 2 + adist = np.argsort(distances) + # If all candidates are `too_close()`, go back to the straightest part + # (``adist[0]``). + for idx in np.append(adist, adist[0]): + x, y = xx[idx, hbsize], yy[idx, hbsize] + if not self.too_close(x, y, labelwidth): + break + return x, y, (idx * block_size + hbsize) % ctr_size + + def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=5): """ - Calculate the appropriate label rotation given the linecontour - coordinates in screen units, the index of the label location and the - label width. + Prepare for insertion of a label at index *idx* of *path*. - If *lc* is not None or empty, also break contours and compute - inlining. + Parameters + ---------- + path : Path + The path where the label will be inserted, in data space. + idx : int + The vertex index after which the label will be inserted. + screen_pos : (float, float) + The position where the label will be inserted, in screen space. + lw : float + The label width, in screen space. + spacing : float + Extra spacing around the label, in screen space. - *spacing* is the empty space to leave around the label, in pixels. + Returns + ------- + path : Path + The path, broken so that the label can be drawn over it. + angle : float + The rotation of the label. - Both tasks are done together to avoid calculating path lengths - multiple times, which is relatively costly. + Notes + ----- + Both tasks are done together to avoid calculating path lengths multiple times, + which is relatively costly. - The method used here involves computing the path length along the - contour in pixel coordinates and then looking approximately (label - width / 2) away from central point to determine rotation and then to - break contour if desired. + The method used here involves computing the path length along the contour in + pixel coordinates and then looking (label width / 2) away from central point to + determine rotation and then to break contour if desired. The extra spacing is + taken into account when breaking the path, but not when computing the angle. """ - - if lc is None: - lc = [] - # Half the label width - hlw = lw / 2.0 - - # Check if closed and, if so, rotate contour so label is at edge - closed = _is_closed_polygon(slc) - if closed: - slc = np.concatenate([slc[ind:-1], slc[:ind + 1]]) - if len(lc): # Rotate lc also if not empty - lc = np.concatenate([lc[ind:-1], lc[:ind + 1]]) - ind = 0 - - # Calculate path lengths - pl = np.zeros(slc.shape[0], dtype=float) - dx = np.diff(slc, axis=0) - pl[1:] = np.cumsum(np.hypot(dx[:, 0], dx[:, 1])) - pl = pl - pl[ind] - - # Use linear interpolation to get points around label - xi = np.array([-hlw, hlw]) - if closed: # Look at end also for closed contours - dp = np.array([pl[-1], 0]) + xys = path.vertices + codes = path.codes + + # Insert a vertex at idx/pos (converting back to data space), if there isn't yet + # a vertex there. With infinite precision one could also always insert the + # extra vertex (it will get masked out by the label below anyways), but floating + # point inaccuracies (the point can have undergone a data->screen->data + # transform loop) can slightly shift the point and e.g. shift the angle computed + # below from exactly zero to nonzero. + pos = self.get_transform().inverted().transform(screen_pos) + if not np.allclose(pos, xys[idx]): + xys = np.insert(xys, idx, pos, axis=0) + codes = np.insert(codes, idx, Path.LINETO) + + # Find the connected component where the label will be inserted. Note that a + # path always starts with a MOVETO, and we consider there's an implicit + # MOVETO (closing the last path) at the end. + movetos = (codes == Path.MOVETO).nonzero()[0] + start = movetos[movetos <= idx][-1] + try: + stop = movetos[movetos > idx][0] + except IndexError: + stop = len(codes) + + # Restrict ourselves to the connected component. + cc_xys = xys[start:stop] + idx -= start + + # If the path is closed, rotate it s.t. it starts at the label. + is_closed_path = codes[stop - 1] == Path.CLOSEPOLY + if is_closed_path: + cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]]) + idx = 0 + + # Like np.interp, but additionally vectorized over fp. + def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T] + + # Use cumulative path lengths ("cpl") as curvilinear coordinate along contour. + screen_xys = self.get_transform().transform(cc_xys) + path_cpls = np.insert( + np.cumsum(np.hypot(*np.diff(screen_xys, axis=0).T)), 0, 0) + path_cpls -= path_cpls[idx] + + # Use linear interpolation to get end coordinates of label. + target_cpls = np.array([-lw/2, lw/2]) + if is_closed_path: # For closed paths, target from the other end. + target_cpls[0] += (path_cpls[-1] - path_cpls[0]) + (sx0, sx1), (sy0, sy1) = interp_vec(target_cpls, path_cpls, screen_xys) + angle = np.rad2deg(np.arctan2(sy1 - sy0, sx1 - sx0)) # Screen space. + if self.rightside_up: # Fix angle so text is never upside-down + angle = (angle + 90) % 180 - 90 + + target_cpls += [-spacing, +spacing] # Expand range by spacing. + + # Get indices near points of interest; use -1 as out of bounds marker. + i0, i1 = np.interp(target_cpls, path_cpls, range(len(path_cpls)), + left=-1, right=-1) + i0 = math.floor(i0) + i1 = math.ceil(i1) + (x0, x1), (y0, y1) = interp_vec(target_cpls, path_cpls, cc_xys) + + # Actually break contours (dropping zero-len parts). + new_xy_blocks = [] + new_code_blocks = [] + if is_closed_path: + if i0 != -1 and i1 != -1: + # This is probably wrong in the case that the entire contour would + # be discarded, but ensures that a valid path is returned and is + # consistent with behavior of mpl <3.8 + points = cc_xys[i1:i0+1] + new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]]) + nlines = len(points) + 1 + new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines]) else: - dp = np.zeros_like(xi) - - # Get angle of vector between the two ends of the label - must be - # calculated in pixel space for text rotation to work correctly. - (dx,), (dy,) = (np.diff(np.interp(dp + xi, pl, slc_col)) - for slc_col in slc.T) - rotation = np.rad2deg(np.arctan2(dy, dx)) - - if self.rightside_up: - # Fix angle so text is never upside-down - rotation = (rotation + 90) % 180 - 90 - - # Break contour if desired - nlc = [] - if len(lc): - # Expand range by spacing - xi = dp + xi + np.array([-spacing, spacing]) - - # Get (integer) indices near points of interest; use -1 as marker - # for out of bounds. - I = np.interp(xi, pl, np.arange(len(pl)), left=-1, right=-1) - I = [np.floor(I[0]).astype(int), np.ceil(I[1]).astype(int)] - if I[0] != -1: - xy1 = [np.interp(xi[0], pl, lc_col) for lc_col in lc.T] - if I[1] != -1: - xy2 = [np.interp(xi[1], pl, lc_col) for lc_col in lc.T] - - # Actually break contours - if closed: - # This will remove contour if shorter than label - if all(i != -1 for i in I): - nlc.append(np.row_stack([xy2, lc[I[1]:I[0]+1], xy1])) - else: - # These will remove pieces of contour if they have length zero - if I[0] != -1: - nlc.append(np.row_stack([lc[:I[0]+1], xy1])) - if I[1] != -1: - nlc.append(np.row_stack([xy2, lc[I[1]:]])) - - # The current implementation removes contours completely - # covered by labels. Uncomment line below to keep - # original contour if this is the preferred behavior. - # if not len(nlc): nlc = [ lc ] - - return rotation, nlc - - def _get_label_text(self, x, y, rotation): - dx, dy = self.axes.transData.inverted().transform((x, y)) - t = text.Text(dx, dy, rotation=rotation, - horizontalalignment='center', - verticalalignment='center', zorder=self._clabel_zorder) - return t - - def _get_label_clabeltext(self, x, y, rotation): - # x, y, rotation is given in pixel coordinate. Convert them to - # the data coordinate and create a label using ClabelText - # class. This way, the rotation of the clabel is along the - # contour line always. - transDataInv = self.axes.transData.inverted() - dx, dy = transDataInv.transform((x, y)) - drotation = transDataInv.transform_angles(np.array([rotation]), - np.array([[x, y]])) - t = ClabelText(dx, dy, rotation=drotation[0], - horizontalalignment='center', - verticalalignment='center', zorder=self._clabel_zorder) - - return t - - def _add_label(self, t, x, y, lev, cvalue): - color = self.labelMappable.to_rgba(cvalue, alpha=self.alpha) - - _text = self.get_text(lev, self.labelFmt) - self.set_label_props(t, _text, color) + if i0 != -1: + new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]]) + new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 1)]) + if i1 != -1: + new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:]]) + new_code_blocks.extend([ + [Path.MOVETO], [Path.LINETO] * (len(cc_xys) - i1)]) + + # Back to the full path. + xys = np.concatenate([xys[:start], *new_xy_blocks, xys[stop:]]) + codes = np.concatenate([codes[:start], *new_code_blocks, codes[stop:]]) + + return angle, Path(xys, codes) + + def add_label(self, x, y, rotation, lev, cvalue): + """Add a contour label, respecting whether *use_clabeltext* was set.""" + data_x, data_y = self.axes.transData.inverted().transform((x, y)) + t = Text( + data_x, data_y, + text=self.get_text(lev, self.labelFmt), + rotation=rotation, + horizontalalignment='center', verticalalignment='center', + zorder=self._clabel_zorder, + color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()), + fontproperties=self._label_font_props, + clip_box=self.axes.bbox) + if self._use_clabeltext: + data_rotation, = self.axes.transData.inverted().transform_angles( + [rotation], [[x, y]]) + t.set(rotation=data_rotation, transform_rotates_text=True) self.labelTexts.append(t) self.labelCValues.append(cvalue) self.labelXYs.append((x, y)) - # Add label to plot here - useful for manual mode label selection self.axes.add_artist(t) - def add_label(self, x, y, rotation, lev, cvalue): - """ - Add contour label using :class:`~matplotlib.text.Text` class. - """ - t = self._get_label_text(x, y, rotation) - self._add_label(t, x, y, lev, cvalue) - - def add_label_clabeltext(self, x, y, rotation, lev, cvalue): - """ - Add contour label using :class:`ClabelText` class. - """ - # x, y, rotation is given in pixel coordinate. Convert them to - # the data coordinate and create a label using ClabelText - # class. This way, the rotation of the clabel is along the - # contour line always. - t = self._get_label_clabeltext(x, y, rotation) - self._add_label(t, x, y, lev, cvalue) - def add_label_near(self, x, y, inline=True, inline_spacing=5, transform=None): """ - Add a label near the point (x, y). If transform is None - (default), (x, y) is in data coordinates; if transform is - False, (x, y) is in display coordinates; otherwise, the - specified transform will be used to translate (x, y) into - display coordinates. + Add a label near the point ``(x, y)``. Parameters ---------- x, y : float The approximate location of the label. - inline : bool, default: True If *True* remove the segment of the contour beneath the label. - inline_spacing : int, default: 5 Space in pixels to leave on each side of label when placing inline. This spacing will be exact for labels at locations where the contour is straight, less so for labels on curved contours. + transform : `.Transform` or `False`, default: ``self.axes.transData`` + A transform applied to ``(x, y)`` before labeling. The default + causes ``(x, y)`` to be interpreted as data coordinates. `False` + is a synonym for `.IdentityTransform`; i.e. ``(x, y)`` should be + interpreted as display coordinates. """ if transform is None: transform = self.axes.transData - if transform: x, y = transform.transform((x, y)) - # find the nearest contour _in screen units_ - conmin, segmin, imin, xmin, ymin = self.find_nearest_contour( - x, y, self.labelIndiceList)[:5] - - # The calc_label_rot_and_inline routine requires that (xmin, ymin) - # be a vertex in the path. So, if it isn't, add a vertex here - - # grab the paths from the collections - paths = self.collections[conmin].get_paths() - # grab the correct segment - active_path = paths[segmin] - # grab its vertices - lc = active_path.vertices - # sort out where the new vertex should be added data-units - xcmin = self.axes.transData.inverted().transform([xmin, ymin]) - # if there isn't a vertex close enough - if not np.allclose(xcmin, lc[imin]): - # insert new data into the vertex list - lc = np.row_stack([lc[:imin], xcmin, lc[imin:]]) - # replace the path with the new one - paths[segmin] = mpath.Path(lc) - - # Get index of nearest level in subset of levels used for labeling - lmin = self.labelIndiceList.index(conmin) - - # Coordinates of contour - paths = self.collections[conmin].get_paths() - lc = paths[segmin].vertices - - # In pixel/screen space - slc = self.axes.transData.transform(lc) - - # Get label width for rotating labels and breaking contours - lw = self.get_label_width(self.labelLevelList[lmin], - self.labelFmt, self.labelFontSizeList[lmin]) - # lw is in points. - lw *= self.axes.figure.dpi / 72 # scale to screen coordinates - # now lw in pixels - - # Figure out label rotation. - rotation, nlc = self.calc_label_rot_and_inline( - slc, imin, lw, lc if inline else None, inline_spacing) - - self.add_label(xmin, ymin, rotation, self.labelLevelList[lmin], - self.labelCValueList[lmin]) + idx_level_min, idx_vtx_min, proj = self._find_nearest_contour( + (x, y), self.labelIndiceList) + path = self._paths[idx_level_min] + level = self.labelIndiceList.index(idx_level_min) + label_width = self._get_nth_label_width(level) + rotation, path = self._split_path_and_get_label_rotation( + path, idx_vtx_min, proj, label_width, inline_spacing) + self.add_label(*proj, rotation, self.labelLevelList[idx_level_min], + self.labelCValueList[idx_level_min]) if inline: - # Remove old, not looping over paths so we can do this up front - paths.pop(segmin) - - # Add paths if not empty or single point - for n in nlc: - if len(n) > 1: - paths.append(mpath.Path(n)) + self._paths[idx_level_min] = path def pop_label(self, index=-1): """Defaults to removing last label, but any index can be supplied""" @@ -545,67 +473,36 @@ def pop_label(self, index=-1): t.remove() def labels(self, inline, inline_spacing): - - if self._use_clabeltext: - add_label = self.add_label_clabeltext - else: - add_label = self.add_label - - for icon, lev, fsize, cvalue in zip( - self.labelIndiceList, self.labelLevelList, - self.labelFontSizeList, self.labelCValueList): - - con = self.collections[icon] - trans = con.get_transform() - lw = self.get_label_width(lev, self.labelFmt, fsize) - lw *= self.axes.figure.dpi / 72 # scale to screen coordinates + for idx, (icon, lev, cvalue) in enumerate(zip( + self.labelIndiceList, + self.labelLevelList, + self.labelCValueList, + )): + trans = self.get_transform() + label_width = self._get_nth_label_width(idx) additions = [] - paths = con.get_paths() - for segNum, linepath in enumerate(paths): - lc = linepath.vertices # Line contour - slc0 = trans.transform(lc) # Line contour in screen coords - - # For closed polygons, add extra point to avoid division by - # zero in print_label and locate_label. Other than these - # functions, this is not necessary and should probably be - # eventually removed. - if _is_closed_polygon(lc): - slc = np.row_stack([slc0, slc0[1:2]]) - else: - slc = slc0 - + for subpath in self._paths[icon]._iter_connected_components(): + screen_xys = trans.transform(subpath.vertices) # Check if long enough for a label - if self.print_label(slc, lw): - x, y, ind = self.locate_label(slc, lw) - - rotation, new = self.calc_label_rot_and_inline( - slc0, ind, lw, lc if inline else None, inline_spacing) - - # Actually add the label - add_label(x, y, rotation, lev, cvalue) - - # If inline, add new contours - if inline: - for n in new: - # Add path if not empty or single point - if len(n) > 1: - additions.append(mpath.Path(n)) + if self.print_label(screen_xys, label_width): + x, y, idx = self.locate_label(screen_xys, label_width) + rotation, path = self._split_path_and_get_label_rotation( + subpath, idx, (x, y), + label_width, inline_spacing) + self.add_label(x, y, rotation, lev, cvalue) # Really add label. + if inline: # If inline, add new contours + additions.append(path) else: # If not adding label, keep old path - additions.append(linepath) - - # After looping over all segments on a contour, replace old paths - # by new ones if inlining. + additions.append(subpath) + # After looping over all segments on a contour, replace old path by new one + # if inlining. if inline: - paths[:] = additions + self._paths[icon] = Path.make_compound_path(*additions) - -def _is_closed_polygon(X): - """ - Return whether first and last object in a sequence are the same. These are - presumably coordinates on a polygonal curve, in which case this function - tests if that curve is closed. - """ - return np.allclose(X[0], X[-1], rtol=1e-10, atol=1e-13) + def remove(self): + super().remove() + for text in self.labelTexts: + text.remove() def _find_closest_point_on_path(xys, p): @@ -625,7 +522,7 @@ def _find_closest_point_on_path(xys, p): Projection of *p* onto *xys*. imin : (int, int) Consecutive indices of vertices of segment in *xys* where *proj* is. - Segments are considered as including their end-points; i.e if the + Segments are considered as including their end-points; i.e. if the closest point on the path is a node in *xys* with index *i*, this returns ``(i-1, i)``. For the special case where *xys* is a single point, this returns ``(0, 0)``. @@ -644,17 +541,9 @@ def _find_closest_point_on_path(xys, p): return (d2s[imin], projs[imin], (imin, imin+1)) -docstring.interpd.update(contour_set_attributes=r""" +_docstring.interpd.register(contour_set_attributes=r""" Attributes ---------- -ax : `~matplotlib.axes.Axes` - The Axes object in which the contours are drawn. - -collections : `.silent_list` of `.LineCollection`\s or `.PathCollection`\s - The `.Artist`\s representing the contour. This is a list of - `.LineCollection`\s for line contours and a list of `.PathCollection`\s - for filled contours. - levels : array The values of the contour levels. @@ -664,8 +553,8 @@ def _find_closest_point_on_path(xys, p): """) -@docstring.dedent_interpd -class ContourSet(cm.ScalarMappable, ContourLabeler): +@_docstring.interpd +class ContourSet(ContourLabeler, mcoll.Collection): """ Store a set of contour lines or filled regions. @@ -673,7 +562,7 @@ class ContourSet(cm.ScalarMappable, ContourLabeler): Parameters ---------- - ax : `~.axes.Axes` + ax : `~matplotlib.axes.Axes` levels : [level0, level1, ..., leveln] A list of floating point numbers indicating the contour levels. @@ -712,8 +601,8 @@ def __init__(self, ax, *args, levels=None, filled=False, linewidths=None, linestyles=None, hatches=(None,), alpha=None, origin=None, extent=None, cmap=None, colors=None, norm=None, vmin=None, vmax=None, - extend='neither', antialiased=None, nchunk=0, locator=None, - transform=None, + colorizer=None, extend='neither', antialiased=None, nchunk=0, + locator=None, transform=None, negative_linestyles=None, clip_path=None, **kwargs): """ Draw contour lines or filled regions, depending on @@ -725,7 +614,7 @@ def __init__(self, ax, *args, Parameters ---------- - ax : `~.axes.Axes` + ax : `~matplotlib.axes.Axes` The `~.axes.Axes` object to draw on. levels : [level0, level1, ..., leveln] @@ -759,26 +648,36 @@ def __init__(self, ax, *args, Keyword arguments are as described in the docstring of `~.Axes.contour`. """ + if antialiased is None and filled: + # Eliminate artifacts; we are not stroking the boundaries. + antialiased = False + # The default for line contours will be taken from the + # LineCollection default, which uses :rc:`lines.antialiased`. + super().__init__( + antialiaseds=antialiased, + alpha=alpha, + clip_path=clip_path, + transform=transform, + colorizer=colorizer, + ) self.axes = ax self.levels = levels self.filled = filled - self.linewidths = linewidths - self.linestyles = linestyles self.hatches = hatches - self.alpha = alpha self.origin = origin self.extent = extent self.colors = colors self.extend = extend - self.antialiased = antialiased - if self.antialiased is None and self.filled: - # Eliminate artifacts; we are not stroking the boundaries. - self.antialiased = False - # The default for line contours will be taken from the - # LineCollection default, which uses :rc:`lines.antialiased`. self.nchunk = nchunk self.locator = locator + + if colorizer: + self._set_colorizer_check_keywords(colorizer, cmap=cmap, + norm=norm, vmin=vmin, + vmax=vmax, colors=colors) + norm = colorizer.norm + cmap = colorizer.cmap if (isinstance(norm, mcolors.LogNorm) or isinstance(self.locator, ticker.LogLocator)): self.logscale = True @@ -787,7 +686,7 @@ def __init__(self, ax, *args, else: self.logscale = False - cbook._check_in_list([None, 'lower', 'upper', 'image'], origin=origin) + _api.check_in_list([None, 'lower', 'upper', 'image'], origin=origin) if self.extent is not None and len(self.extent) != 4: raise ValueError( "If given, 'extent' must be None or (x0, x1, y0, y1)") @@ -796,12 +695,21 @@ def __init__(self, ax, *args, if self.origin == 'image': self.origin = mpl.rcParams['image.origin'] - self._transform = transform + self._orig_linestyles = linestyles # Only kept for user access. + self.negative_linestyles = mpl._val_or_rc(negative_linestyles, + 'contour.negative_linestyle') kwargs = self._process_args(*args, **kwargs) self._process_levels() + self._extend_min = self.extend in ['min', 'both'] + self._extend_max = self.extend in ['max', 'both'] if self.colors is not None: + if mcolors.is_color_like(self.colors): + color_sequence = [self.colors] + else: + color_sequence = self.colors + ncolors = len(self.levels) if self.filled: ncolors -= 1 @@ -809,113 +717,96 @@ def __init__(self, ax, *args, # Handle the case where colors are given for the extended # parts of the contour. - extend_min = self.extend in ['min', 'both'] - extend_max = self.extend in ['max', 'both'] + use_set_under_over = False # if we are extending the lower end, and we've been given enough # colors then skip the first color in the resulting cmap. For the # extend_max case we don't need to worry about passing more colors # than ncolors as ListedColormap will clip. - total_levels = ncolors + int(extend_min) + int(extend_max) - if len(self.colors) == total_levels and (extend_min or extend_max): + total_levels = (ncolors + + int(self._extend_min) + + int(self._extend_max)) + if (len(color_sequence) == total_levels and + (self._extend_min or self._extend_max)): use_set_under_over = True - if extend_min: + if self._extend_min: i0 = 1 - cmap = mcolors.ListedColormap(self.colors[i0:None], N=ncolors) + cmap = mcolors.ListedColormap( + cbook._resize_sequence(color_sequence[i0:], ncolors)) if use_set_under_over: - if extend_min: - cmap.set_under(self.colors[0]) - if extend_max: - cmap.set_over(self.colors[-1]) - - self.collections = cbook.silent_list(None) + if self._extend_min: + cmap.set_under(color_sequence[0]) + if self._extend_max: + cmap.set_over(color_sequence[-1]) # label lists must be initialized here self.labelTexts = [] self.labelCValues = [] - kw = {'cmap': cmap} + self.set_cmap(cmap) if norm is not None: - kw['norm'] = norm - # sets self.cmap, norm if needed; - cm.ScalarMappable.__init__(self, **kw) - if vmin is not None: - self.norm.vmin = vmin - if vmax is not None: - self.norm.vmax = vmax + self.set_norm(norm) + with self.norm.callbacks.blocked(signal="changed"): + if vmin is not None: + self.norm.vmin = vmin + if vmax is not None: + self.norm.vmax = vmax + self.norm._changed() self._process_colors() - self.allsegs, self.allkinds = self._get_allsegs_and_allkinds() + if self._paths is None: + self._paths = self._make_paths_from_contour_generator() if self.filled: - if self.linewidths is not None: - cbook._warn_external('linewidths is ignored by contourf') + if linewidths is not None: + _api.warn_external('linewidths is ignored by contourf') # Lower and upper contour levels. lowers, uppers = self._get_lowers_and_uppers() - # Ensure allkinds can be zipped below. - if self.allkinds is None: - self.allkinds = [None] * len(self.allsegs) - # Default zorder taken from Collection - self._contour_zorder = kwargs.pop('zorder', 1) - - self.collections[:] = [ - mcoll.PathCollection( - self._make_paths(segs, kinds), - antialiaseds=(self.antialiased,), - edgecolors='none', - alpha=self.alpha, - transform=self.get_transform(), - zorder=self._contour_zorder) - for level, level_upper, segs, kinds - in zip(lowers, uppers, self.allsegs, self.allkinds)] + self.set( + edgecolor="none", + # Default zorder taken from Collection + zorder=kwargs.pop("zorder", 1), + rasterized=kwargs.pop("rasterized", False), + ) + else: - self.tlinewidths = tlinewidths = self._process_linewidths() - tlinestyles = self._process_linestyles() - aa = self.antialiased - if aa is not None: - aa = (self.antialiased,) - # Default zorder taken from LineCollection - self._contour_zorder = kwargs.pop('zorder', 2) - - self.collections[:] = [ - mcoll.LineCollection( - segs, - antialiaseds=aa, - linewidths=width, - linestyles=[lstyle], - alpha=self.alpha, - transform=self.get_transform(), - zorder=self._contour_zorder, - label='_nolegend_') - for level, width, lstyle, segs - in zip(self.levels, tlinewidths, tlinestyles, self.allsegs)] - - for col in self.collections: - self.axes.add_collection(col, autolim=False) - col.sticky_edges.x[:] = [self._mins[0], self._maxs[0]] - col.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] + self.set( + facecolor="none", + linewidths=self._process_linewidths(linewidths), + linestyle=self._process_linestyles(linestyles), + # Default zorder taken from LineCollection, which is higher + # than for filled contours so that lines are displayed on top. + zorder=kwargs.pop("zorder", 2), + label="_nolegend_", + ) + + self.axes.add_collection(self, autolim=False) + self.sticky_edges.x[:] = [self._mins[0], self._maxs[0]] + self.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] self.axes.update_datalim([self._mins, self._maxs]) self.axes.autoscale_view(tight=True) self.changed() # set the colors if kwargs: - s = ", ".join(map(repr, kwargs)) - cbook._warn_external('The following kwargs were not used by ' - 'contour: ' + s) - - @cbook.deprecated("3.3") - @property - def ax(self): - return self.axes + _api.warn_external( + 'The following kwargs were not used by contour: ' + + ", ".join(map(repr, kwargs)) + ) + + allsegs = property(lambda self: [ + [subp.vertices for subp in p._iter_connected_components()] + for p in self.get_paths()]) + allkinds = property(lambda self: [ + [subp.codes for subp in p._iter_connected_components()] + for p in self.get_paths()]) + alpha = property(lambda self: self.get_alpha()) + linestyles = property(lambda self: self._orig_linestyles) def get_transform(self): - """ - Return the :class:`~matplotlib.transforms.Transform` - instance used by this ContourSet. - """ + """Return the `.Transform` instance used by this ContourSet.""" if self._transform is None: self._transform = self.axes.transData elif (not isinstance(self._transform, mtransforms.Transform) @@ -943,54 +834,45 @@ def legend_elements(self, variable_name='x', str_format=str): ---------- variable_name : str The string used inside the inequality used on the labels. - str_format : function: float -> str Function used to format the numbers in the labels. Returns ------- - artists : List[`.Artist`] + artists : list[`.Artist`] A list of the artists. - - labels : List[str] + labels : list[str] A list of the labels. - """ artists = [] labels = [] if self.filled: lowers, uppers = self._get_lowers_and_uppers() - n_levels = len(self.collections) - - for i, (collection, lower, upper) in enumerate( - zip(self.collections, lowers, uppers)): - patch = mpatches.Rectangle( + n_levels = len(self._paths) + for idx in range(n_levels): + artists.append(mpatches.Rectangle( (0, 0), 1, 1, - facecolor=collection.get_facecolor()[0], - hatch=collection.get_hatch(), - alpha=collection.get_alpha()) - artists.append(patch) - - lower = str_format(lower) - upper = str_format(upper) - - if i == 0 and self.extend in ('min', 'both'): + facecolor=self.get_facecolor()[idx], + hatch=self.hatches[idx % len(self.hatches)], + )) + lower = str_format(lowers[idx]) + upper = str_format(uppers[idx]) + if idx == 0 and self.extend in ('min', 'both'): labels.append(fr'${variable_name} \leq {lower}s$') - elif i == n_levels - 1 and self.extend in ('max', 'both'): + elif idx == n_levels - 1 and self.extend in ('max', 'both'): labels.append(fr'${variable_name} > {upper}s$') else: labels.append(fr'${lower} < {variable_name} \leq {upper}$') else: - for collection, level in zip(self.collections, self.levels): - - patch = mcoll.LineCollection(None) - patch.update_from(collection) - - artists.append(patch) - # format the level for insertion into the labels - level = str_format(level) - labels.append(fr'${variable_name} = {level}$') + for idx, level in enumerate(self.levels): + artists.append(Line2D( + [], [], + color=self.get_edgecolor()[idx], + linewidth=self.get_linewidths()[idx], + linestyle=self.get_linestyles()[idx], + )) + labels.append(fr'${variable_name} = {str_format(level)}$') return artists, labels @@ -998,42 +880,58 @@ def _process_args(self, *args, **kwargs): """ Process *args* and *kwargs*; override in derived classes. - Must set self.levels, self.zmin and self.zmax, and update axes limits. + Must set self.levels, self.zmin and self.zmax, and update Axes limits. """ self.levels = args[0] - self.allsegs = args[1] - self.allkinds = args[2] if len(args) > 2 else None + allsegs = args[1] + allkinds = args[2] if len(args) > 2 else None self.zmax = np.max(self.levels) self.zmin = np.min(self.levels) + if allkinds is None: + allkinds = [[None] * len(segs) for segs in allsegs] + # Check lengths of levels and allsegs. if self.filled: - if len(self.allsegs) != len(self.levels) - 1: + if len(allsegs) != len(self.levels) - 1: raise ValueError('must be one less number of segments as ' 'levels') else: - if len(self.allsegs) != len(self.levels): + if len(allsegs) != len(self.levels): raise ValueError('must be same number of segments as levels') # Check length of allkinds. - if (self.allkinds is not None and - len(self.allkinds) != len(self.allsegs)): + if len(allkinds) != len(allsegs): raise ValueError('allkinds has different length to allsegs') # Determine x, y bounds and update axes data limits. - flatseglist = [s for seg in self.allsegs for s in seg] + flatseglist = [s for seg in allsegs for s in seg] points = np.concatenate(flatseglist, axis=0) self._mins = points.min(axis=0) self._maxs = points.max(axis=0) + # Each entry in (allsegs, allkinds) is a list of (segs, kinds): segs is a list + # of (N, 2) arrays of xy coordinates, kinds is a list of arrays of corresponding + # pathcodes. However, kinds can also be None; in which case all paths in that + # list are codeless (this case is normalized above). These lists are used to + # construct paths, which then get concatenated. + self._paths = [Path.make_compound_path(*map(Path, segs, kinds)) + for segs, kinds in zip(allsegs, allkinds)] + return kwargs - def _get_allsegs_and_allkinds(self): - """ - Override in derived classes to create and return allsegs and allkinds. - allkinds can be None. - """ - return self.allsegs, self.allkinds + def _make_paths_from_contour_generator(self): + """Compute ``paths`` using C extension.""" + if self._paths is not None: + return self._paths + cg = self._contour_generator + empty_path = Path(np.empty((0, 2))) + vertices_and_codes = ( + map(cg.create_filled_contour, *self._get_lowers_and_uppers()) + if self.filled else + map(cg.create_contour, self.levels)) + return [Path(np.concatenate(vs), np.concatenate(cs)) if len(vs) else empty_path + for vs, cs in vertices_and_codes] def _get_lowers_and_uppers(self): """ @@ -1050,38 +948,45 @@ def _get_lowers_and_uppers(self): uppers = self._levels[1:] return (lowers, uppers) - def _make_paths(self, segs, kinds): - if kinds is not None: - return [mpath.Path(seg, codes=kind) - for seg, kind in zip(segs, kinds)] - else: - return [mpath.Path(seg) for seg in segs] - def changed(self): - tcolors = [(tuple(rgba),) - for rgba in self.to_rgba(self.cvalues, alpha=self.alpha)] - self.tcolors = tcolors - hatches = self.hatches * len(tcolors) - for color, hatch, collection in zip(tcolors, hatches, - self.collections): - if self.filled: - collection.set_facecolor(color) - # update the collection's hatch (may be None) - collection.set_hatch(hatch) - else: - collection.set_color(color) - for label, cv in zip(self.labelTexts, self.labelCValues): - label.set_alpha(self.alpha) + if not hasattr(self, "cvalues"): + self._process_colors() # Sets cvalues. + # Force an autoscale immediately because self.to_rgba() calls + # autoscale_None() internally with the data passed to it, + # so if vmin/vmax are not set yet, this would override them with + # content from *cvalues* rather than levels like we want + self.norm.autoscale_None(self.levels) + self.set_array(self.cvalues) + self.update_scalarmappable() + alphas = np.broadcast_to(self.get_alpha(), len(self.cvalues)) + for label, cv, alpha in zip(self.labelTexts, self.labelCValues, alphas): + label.set_alpha(alpha) label.set_color(self.labelMappable.to_rgba(cv)) - # add label colors - cm.ScalarMappable.changed(self) + super().changed() - def _autolev(self, N): + def _ensure_locator_exists(self, N): """ - Select contour levels to span the data. + Set a locator on this ContourSet if it's not already set. - The target number of levels, *N*, is used only when the - scale is not log and default locator is used. + Parameters + ---------- + N : int or None + If *N* is an int, it is used as the target number of levels. + Otherwise when *N* is None, a reasonable default is chosen; + for logscales the LogLocator chooses, N=7 is the default + otherwise. + """ + if self.locator is None: + if self.logscale: + self.locator = ticker.LogLocator(numticks=N) + else: + if N is None: + N = 7 # Hard coded default + self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1) + + def _autolev(self): + """ + Select contour levels to span the data. We need two more levels for filled contours than for line contours, because for the latter we need to specify @@ -1090,12 +995,6 @@ def _autolev(self, N): one contour line, but two filled regions, and therefore three levels to provide boundaries for both regions. """ - if self.locator is None: - if self.logscale: - self.locator = ticker.LogLocator() - else: - self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1) - lev = self.locator.tick_values(self.zmin, self.zmax) try: @@ -1119,33 +1018,27 @@ def _autolev(self, N): return lev[i0:i1] - def _process_contour_level_args(self, args): + def _process_contour_level_args(self, args, z_dtype): """ Determine the contour levels and store in self.levels. """ - if self.levels is None: - if len(args) == 0: - levels_arg = 7 # Default, hard-wired. - else: + levels_arg = self.levels + if levels_arg is None: + if args: + # Set if levels manually provided levels_arg = args[0] - else: - levels_arg = self.levels - if isinstance(levels_arg, Integral): - self.levels = self._autolev(levels_arg) - else: - self.levels = np.asarray(levels_arg).astype(np.float64) + elif np.issubdtype(z_dtype, bool): + # Set default values for bool data types + levels_arg = [0, .5, 1] if self.filled else [.5] - if not self.filled: - inside = (self.levels > self.zmin) & (self.levels < self.zmax) - levels_in = self.levels[inside] - if len(levels_in) == 0: - self.levels = [self.zmin] - cbook._warn_external( - "No contour levels were found within the data range.") + if isinstance(levels_arg, Integral) or levels_arg is None: + self._ensure_locator_exists(levels_arg) + self.levels = self._autolev() + else: + self.levels = np.asarray(levels_arg, np.float64) if self.filled and len(self.levels) < 2: raise ValueError("Filled contours require at least 2 levels.") - if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0: raise ValueError("Contour levels must be increasing") @@ -1190,7 +1083,7 @@ def _process_colors(self): """ Color argument processing for contouring. - Note that we base the color mapping on the contour levels + Note that we base the colormapping on the contour levels and layers, not on the actual range of the Z values. This means we don't have to worry about bad values in Z, and we always have the full dynamic range available for the selected @@ -1224,45 +1117,34 @@ def _process_colors(self): self.set_norm(mcolors.NoNorm()) else: self.cvalues = self.layers - self.set_array(self.levels) - self.autoscale_None() + self.norm.autoscale_None(self.levels) + self.set_array(self.cvalues) + self.update_scalarmappable() if self.extend in ('both', 'max', 'min'): self.norm.clip = False - # self.tcolors are set by the "changed" method - - def _process_linewidths(self): - linewidths = self.linewidths + def _process_linewidths(self, linewidths): Nlev = len(self.levels) if linewidths is None: default_linewidth = mpl.rcParams['contour.linewidth'] if default_linewidth is None: default_linewidth = mpl.rcParams['lines.linewidth'] - tlinewidths = [(default_linewidth,)] * Nlev + return [default_linewidth] * Nlev + elif not np.iterable(linewidths): + return [linewidths] * Nlev else: - if not np.iterable(linewidths): - linewidths = [linewidths] * Nlev - else: - linewidths = list(linewidths) - if len(linewidths) < Nlev: - nreps = int(np.ceil(Nlev / len(linewidths))) - linewidths = linewidths * nreps - if len(linewidths) > Nlev: - linewidths = linewidths[:Nlev] - tlinewidths = [(w,) for w in linewidths] - return tlinewidths - - def _process_linestyles(self): - linestyles = self.linestyles + linewidths = list(linewidths) + return (linewidths * math.ceil(Nlev / len(linewidths)))[:Nlev] + + def _process_linestyles(self, linestyles): Nlev = len(self.levels) if linestyles is None: tlinestyles = ['solid'] * Nlev if self.monochrome: - neg_ls = mpl.rcParams['contour.negative_linestyle'] eps = - (self.zmax - self.zmin) * 1e-15 for i, lev in enumerate(self.levels): if lev < eps: - tlinestyles[i] = neg_ls + tlinestyles[i] = self.negative_linestyles else: if isinstance(linestyles, str): tlinestyles = [linestyles] * Nlev @@ -1277,25 +1159,68 @@ def _process_linestyles(self): raise ValueError("Unrecognized type for linestyles kwarg") return tlinestyles - def get_alpha(self): - """Return alpha to be applied to all ContourSet artists.""" - return self.alpha - - def set_alpha(self, alpha): + def _find_nearest_contour(self, xy, indices=None): """ - Set the alpha blending value for all ContourSet artists. - *alpha* must be between 0 (transparent) and 1 (opaque). + Find the point in the unfilled contour plot that is closest (in screen + space) to point *xy*. + + Parameters + ---------- + xy : tuple[float, float] + The reference point (in screen space). + indices : list of int or None, default: None + Indices of contour levels to consider. If None (the default), all levels + are considered. + + Returns + ------- + idx_level_min : int + The index of the contour level closest to *xy*. + idx_vtx_min : int + The index of the `.Path` segment closest to *xy* (at that level). + proj : (float, float) + The point in the contour plot closest to *xy*. """ - self.alpha = alpha - self.changed() + + # Convert each contour segment to pixel coordinates and then compare the given + # point to those coordinates for each contour. This is fast enough in normal + # cases, but speedups may be possible. + + if self.filled: + raise ValueError("Method does not support filled contours") + + if indices is None: + indices = range(len(self._paths)) + + d2min = np.inf + idx_level_min = idx_vtx_min = proj_min = None + + for idx_level in indices: + path = self._paths[idx_level] + idx_vtx_start = 0 + for subpath in path._iter_connected_components(): + if not len(subpath.vertices): + continue + lc = self.get_transform().transform(subpath.vertices) + d2, proj, leg = _find_closest_point_on_path(lc, xy) + if d2 < d2min: + d2min = d2 + idx_level_min = idx_level + idx_vtx_min = leg[1] + idx_vtx_start + proj_min = proj + idx_vtx_start += len(subpath) + + return idx_level_min, idx_vtx_min, proj_min def find_nearest_contour(self, x, y, indices=None, pixel=True): """ Find the point in the contour plot that is closest to ``(x, y)``. + This method does not support filled contours. + Parameters ---------- - x, y: float + x, y : float The reference point. indices : list of int or None, default: None Indices of contour levels to consider. If None (the default), all @@ -1307,63 +1232,63 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): Returns ------- - contour : `.Collection` - The contour that is closest to ``(x, y)``. - segment : int - The index of the `.Path` in *contour* that is closest to - ``(x, y)``. + path : int + The index of the path that is closest to ``(x, y)``. Each path corresponds + to one contour level. + subpath : int + The index within that closest path of the subpath that is closest to + ``(x, y)``. Each subpath corresponds to one unbroken contour line. index : int - The index of the path segment in *segment* that is closest to + The index of the vertices within that subpath that are closest to ``(x, y)``. xmin, ymin : float The point in the contour plot that is closest to ``(x, y)``. d2 : float The squared distance from ``(xmin, ymin)`` to ``(x, y)``. """ - - # This function uses a method that is probably quite - # inefficient based on converting each contour segment to - # pixel coordinates and then comparing the given point to - # those coordinates for each contour. This will probably be - # quite slow for complex contours, but for normal use it works - # sufficiently well that the time is not noticeable. - # Nonetheless, improvements could probably be made. - - if indices is None: - indices = list(range(len(self.levels))) - - d2min = np.inf - conmin = None - segmin = None - xmin = None - ymin = None - - point = np.array([x, y]) - - for icon in indices: - con = self.collections[icon] - trans = con.get_transform() - paths = con.get_paths() - - for segNum, linepath in enumerate(paths): - lc = linepath.vertices - # transfer all data points to screen coordinates if desired - if pixel: - lc = trans.transform(lc) - - d2, xc, leg = _find_closest_point_on_path(lc, point) - if d2 < d2min: - d2min = d2 - conmin = icon - segmin = segNum - imin = leg[1] - xmin = xc[0] - ymin = xc[1] - - return (conmin, segmin, imin, xmin, ymin, d2min) - - -@docstring.dedent_interpd + segment = index = d2 = None + + with ExitStack() as stack: + if not pixel: + # _find_nearest_contour works in pixel space. We want axes space, so + # effectively disable the transformation here by setting to identity. + stack.enter_context(self._cm_set( + transform=mtransforms.IdentityTransform())) + + i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices) + + if i_level is not None: + cc_cumlens = np.cumsum( + [*map(len, self._paths[i_level]._iter_connected_components())]) + segment = cc_cumlens.searchsorted(i_vtx, "right") + index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1] + d2 = (xmin-x)**2 + (ymin-y)**2 + + return (i_level, segment, index, xmin, ymin, d2) + + @artist.allow_rasterization + def draw(self, renderer): + paths = self._paths + n_paths = len(paths) + if not self.filled or all(hatch is None for hatch in self.hatches): + super().draw(renderer) + return + # In presence of hatching, draw contours one at a time. + edgecolors = self.get_edgecolors() + if edgecolors.size == 0: + edgecolors = ("none",) + for idx in range(n_paths): + with cbook._setattr_cm(self, _paths=[paths[idx]]), self._cm_set( + hatch=self.hatches[idx % len(self.hatches)], + array=[self.get_array()[idx]], + linewidths=[self.get_linewidths()[idx % len(self.get_linewidths())]], + linestyles=[self.get_linestyles()[idx % len(self.get_linestyles())]], + edgecolors=edgecolors[idx % len(edgecolors)], + ): + super().draw(renderer) + + +@_docstring.interpd class QuadContourSet(ContourSet): """ Create and store a set of contour lines or filled regions. @@ -1374,11 +1299,11 @@ class QuadContourSet(ContourSet): %(contour_set_attributes)s """ - def _process_args(self, *args, corner_mask=None, **kwargs): + def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs): """ Process args and kwargs. """ - if isinstance(args[0], QuadContourSet): + if args and isinstance(args[0], QuadContourSet): if self.levels is None: self.levels = args[0].levels self.zmin = args[0].zmin @@ -1387,21 +1312,30 @@ def _process_args(self, *args, corner_mask=None, **kwargs): contour_generator = args[0]._contour_generator self._mins = args[0]._mins self._maxs = args[0]._maxs + self._algorithm = args[0]._algorithm else: - import matplotlib._contour as _contour + import contourpy + + algorithm = mpl._val_or_rc(algorithm, 'contour.algorithm') + mpl.rcParams.validate["contour.algorithm"](algorithm) + self._algorithm = algorithm if corner_mask is None: - corner_mask = mpl.rcParams['contour.corner_mask'] + if self._algorithm == "mpl2005": + # mpl2005 does not support corner_mask=True so if not + # specifically requested then disable it. + corner_mask = False + else: + corner_mask = mpl.rcParams['contour.corner_mask'] self._corner_mask = corner_mask x, y, z = self._contour_args(args, kwargs) - _mask = ma.getmask(z) - if _mask is ma.nomask or not _mask.any(): - _mask = None - - contour_generator = _contour.QuadContourGenerator( - x, y, z.filled(), _mask, self._corner_mask, self.nchunk) + contour_generator = contourpy.contour_generator( + x, y, z, name=self._algorithm, corner_mask=self._corner_mask, + line_type=contourpy.LineType.SeparateCode, + fill_type=contourpy.FillType.OuterCode, + chunk_size=self.nchunk) t = self.get_transform() @@ -1422,65 +1356,43 @@ def _process_args(self, *args, corner_mask=None, **kwargs): return kwargs - def _get_allsegs_and_allkinds(self): - """Compute ``allsegs`` and ``allkinds`` using C extension.""" - allsegs = [] - if self.filled: - lowers, uppers = self._get_lowers_and_uppers() - allkinds = [] - for level, level_upper in zip(lowers, uppers): - vertices, kinds = \ - self._contour_generator.create_filled_contour( - level, level_upper) - allsegs.append(vertices) - allkinds.append(kinds) - else: - allkinds = None - for level in self.levels: - vertices = self._contour_generator.create_contour(level) - allsegs.append(vertices) - return allsegs, allkinds - def _contour_args(self, args, kwargs): if self.filled: fn = 'contourf' else: fn = 'contour' - Nargs = len(args) - if Nargs <= 2: - z = ma.asarray(args[0], dtype=np.float64) + nargs = len(args) + + if 0 < nargs <= 2: + z, *args = args + z = ma.asarray(z) x, y = self._initialize_x_y(z) - args = args[1:] - elif Nargs <= 4: - x, y, z = self._check_xyz(args[:3], kwargs) - args = args[3:] + elif 2 < nargs <= 4: + x, y, z_orig, *args = args + x, y, z = self._check_xyz(x, y, z_orig, kwargs) + else: - raise TypeError("Too many arguments to %s; see help(%s)" % - (fn, fn)) + raise _api.nargs_error(fn, takes="from 1 to 4", given=nargs) z = ma.masked_invalid(z, copy=False) - self.zmax = float(z.max()) - self.zmin = float(z.min()) + self.zmax = z.max().astype(float) + self.zmin = z.min().astype(float) if self.logscale and self.zmin <= 0: z = ma.masked_where(z <= 0, z) - cbook._warn_external('Log scale: values of z <= 0 have been ' - 'masked') - self.zmin = float(z.min()) - self._process_contour_level_args(args) + _api.warn_external('Log scale: values of z <= 0 have been masked') + self.zmin = z.min().astype(float) + self._process_contour_level_args(args, z.dtype) return (x, y, z) - def _check_xyz(self, args, kwargs): + def _check_xyz(self, x, y, z, kwargs): """ Check that the shapes of the input arrays match; if x and y are 1D, convert them to 2D using meshgrid. """ - x, y = args[:2] - kwargs = self.axes._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) - x = self.axes.convert_xunits(x) - y = self.axes.convert_yunits(y) + x, y = self.axes._process_unit_info([("x", x), ("y", y)], kwargs) x = np.asarray(x, dtype=np.float64) y = np.asarray(y, dtype=np.float64) - z = ma.asarray(args[2], dtype=np.float64) + z = ma.asarray(z) if z.ndim != 2: raise TypeError(f"Input z must be 2D, not {z.ndim}D") @@ -1555,213 +1467,256 @@ def _initialize_x_y(self, z): y = y[::-1] return np.meshgrid(x, y) - _contour_doc = """ - Plot contours. - Call signature:: +_docstring.interpd.register(contour_doc=""" +`.contour` and `.contourf` draw contour lines and filled contours, +respectively. Except as noted, function signatures and return values +are the same for both versions. - contour([X, Y,] Z, [levels], **kwargs) +Parameters +---------- +X, Y : array-like, optional + The coordinates of the values in *Z*. - `.contour` and `.contourf` draw contour lines and filled contours, - respectively. Except as noted, function signatures and return values - are the same for both versions. + *X* and *Y* must both be 2D with the same shape as *Z* (e.g. + created via `numpy.meshgrid`), or they must both be 1-D such + that ``len(X) == N`` is the number of columns in *Z* and + ``len(Y) == M`` is the number of rows in *Z*. - Parameters - ---------- - X, Y : array-like, optional - The coordinates of the values in *Z*. + *X* and *Y* must both be ordered monotonically. - *X* and *Y* must both be 2-D with the same shape as *Z* (e.g. - created via `numpy.meshgrid`), or they must both be 1-D such - that ``len(X) == M`` is the number of columns in *Z* and - ``len(Y) == N`` is the number of rows in *Z*. + If not given, they are assumed to be integer indices, i.e. + ``X = range(N)``, ``Y = range(M)``. - If not given, they are assumed to be integer indices, i.e. - ``X = range(M)``, ``Y = range(N)``. +Z : (M, N) array-like + The height values over which the contour is drawn. Color-mapping is + controlled by *cmap*, *norm*, *vmin*, and *vmax*. - Z : array-like(N, M) - The height values over which the contour is drawn. +levels : int or array-like, optional + Determines the number and positions of the contour lines / regions. - levels : int or array-like, optional - Determines the number and positions of the contour lines / regions. + If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries + to automatically choose no more than *n+1* "nice" contour levels + between minimum and maximum numeric values of *Z*. - If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries - to automatically choose no more than *n+1* "nice" contour levels - between *vmin* and *vmax*. + If array-like, draw contour lines at the specified levels. + The values must be in increasing order. - If array-like, draw contour lines at the specified levels. - The values must be in increasing order. +Returns +------- +`~.contour.QuadContourSet` - Returns - ------- - `~.contour.QuadContourSet` - - Other Parameters - ---------------- - corner_mask : bool, default: :rc:`contour.corner_mask` - Enable/disable corner masking, which only has an effect if *Z* is - a masked array. If ``False``, any quad touching a masked point is - masked out. If ``True``, only the triangular corners of quads - nearest those points are always masked out, other triangular - corners comprising three unmasked points are contoured as usual. - - colors : color string or sequence of colors, optional - The colors of the levels, i.e. the lines for `.contour` and the - areas for `.contourf`. - - The sequence is cycled for the levels in ascending order. If the - sequence is shorter than the number of levels, it's repeated. - - As a shortcut, single color strings may be used in place of - one-element lists, i.e. ``'red'`` instead of ``['red']`` to color - all levels with the same color. This shortcut does only work for - color strings, not for other ways of specifying colors. - - By default (value *None*), the colormap specified by *cmap* - will be used. - - alpha : float, default: 1 - The alpha blending value, between 0 (transparent) and 1 (opaque). - - cmap : str or `.Colormap`, default: :rc:`image.cmap` - A `.Colormap` instance or registered colormap name. The colormap - maps the level values to colors. - - If both *colors* and *cmap* are given, an error is raised. - - norm : `~matplotlib.colors.Normalize`, optional - If a colormap is used, the `.Normalize` instance scales the level - values to the canonical colormap range [0, 1] for mapping to - colors. If not given, the default linear scaling is used. - - vmin, vmax : float, optional - If not *None*, either or both of these values will be supplied to - the `.Normalize` instance, overriding the default color scaling - based on *levels*. - - origin : {*None*, 'upper', 'lower', 'image'}, default: None - Determines the orientation and exact position of *Z* by specifying - the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y* - are not given. - - - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner. - - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. - - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left - corner. - - 'image': Use the value from :rc:`image.origin`. - - extent : (x0, x1, y0, y1), optional - If *origin* is not *None*, then *extent* is interpreted as in - `.imshow`: it gives the outer pixel boundaries. In this case, the - position of Z[0, 0] is the center of the pixel, not a corner. If - *origin* is *None*, then (*x0*, *y0*) is the position of Z[0, 0], - and (*x1*, *y1*) is the position of Z[-1, -1]. - - This argument is ignored if *X* and *Y* are specified in the call - to contour. - - locator : ticker.Locator subclass, optional - The locator is used to determine the contour levels if they - are not given explicitly via *levels*. - Defaults to `~.ticker.MaxNLocator`. - - extend : {'neither', 'both', 'min', 'max'}, default: 'neither' - Determines the ``contourf``-coloring of values that are outside the - *levels* range. - - If 'neither', values outside the *levels* range are not colored. - If 'min', 'max' or 'both', color the values below, above or below - and above the *levels* range. - - Values below ``min(levels)`` and above ``max(levels)`` are mapped - to the under/over values of the `.Colormap`. Note that most - colormaps do not have dedicated colors for these by default, so - that the over and under values are the edge values of the colormap. - You may want to set these values explicitly using - `.Colormap.set_under` and `.Colormap.set_over`. - - .. note:: - - An existing `.QuadContourSet` does not get notified if - properties of its colormap are changed. Therefore, an explicit - call `.QuadContourSet.changed()` is needed after modifying the - colormap. The explicit call can be left out, if a colorbar is - assigned to the `.QuadContourSet` because it internally calls - `.QuadContourSet.changed()`. - - Example:: - - x = np.arange(1, 10) - y = x.reshape(-1, 1) - h = x * y - - cs = plt.contourf(h, levels=[10, 30, 50], - colors=['#808080', '#A0A0A0', '#C0C0C0'], extend='both') - cs.cmap.set_over('red') - cs.cmap.set_under('blue') - cs.changed() - - xunits, yunits : registered units, optional - Override axis units by specifying an instance of a - :class:`matplotlib.units.ConversionInterface`. +Other Parameters +---------------- +corner_mask : bool, default: :rc:`contour.corner_mask` + Enable/disable corner masking, which only has an effect if *Z* is + a masked array. If ``False``, any quad touching a masked point is + masked out. If ``True``, only the triangular corners of quads + nearest those points are always masked out, other triangular + corners comprising three unmasked points are contoured as usual. - antialiased : bool, optional - Enable antialiasing, overriding the defaults. For - filled contours, the default is *True*. For line contours, - it is taken from :rc:`lines.antialiased`. - - nchunk : int >= 0, optional - If 0, no subdivision of the domain. Specify a positive integer to - divide the domain into subdomains of *nchunk* by *nchunk* quads. - Chunking reduces the maximum length of polygons generated by the - contouring algorithm which reduces the rendering workload passed - on to the backend and also requires slightly less RAM. It can - however introduce rendering artifacts at chunk boundaries depending - on the backend, the *antialiased* flag and value of *alpha*. - - linewidths : float or array-like, default: :rc:`contour.linewidth` - *Only applies to* `.contour`. - - The line width of the contour lines. - - If a number, all levels will be plotted with this linewidth. - - If a sequence, the levels in ascending order will be plotted with - the linewidths in the order specified. - - If None, this falls back to :rc:`lines.linewidth`. - - linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional - *Only applies to* `.contour`. - - If *linestyles* is *None*, the default is 'solid' unless the lines - are monochrome. In that case, negative contours will take their - linestyle from :rc:`contour.negative_linestyle` setting. - - *linestyles* can also be an iterable of the above strings - specifying a set of linestyles to be used. If this - iterable is shorter than the number of contour levels - it will be repeated as necessary. - - hatches : List[str], optional - *Only applies to* `.contourf`. - - A list of cross hatch patterns to use on the filled areas. - If None, no hatching will be added to the contour. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. +colors : :mpltype:`color` or list of :mpltype:`color`, optional + The colors of the levels, i.e. the lines for `.contour` and the + areas for `.contourf`. - Notes - ----- - 1. `.contourf` differs from the MATLAB version in that it does not draw - the polygon edges. To draw edges, add line contours with calls to - `.contour`. + The sequence is cycled for the levels in ascending order. If the + sequence is shorter than the number of levels, it's repeated. - 2. `.contourf` fills intervals that are closed at the top; that is, for - boundaries *z1* and *z2*, the filled region is:: + As a shortcut, a single color may be used in place of one-element lists, i.e. + ``'red'`` instead of ``['red']`` to color all levels with the same color. - z1 < Z <= z2 + .. versionchanged:: 3.10 + Previously a single color had to be expressed as a string, but now any + valid color format may be passed. - except for the lowest interval, which is closed on both sides (i.e. - it includes the lowest value). - """ + By default (value *None*), the colormap specified by *cmap* + will be used. + +alpha : float, default: 1 + The alpha blending value, between 0 (transparent) and 1 (opaque). + +%(cmap_doc)s + + This parameter is ignored if *colors* is set. + +%(norm_doc)s + + This parameter is ignored if *colors* is set. + +%(vmin_vmax_doc)s + + If *vmin* or *vmax* are not given, the default color scaling is based on + *levels*. + + This parameter is ignored if *colors* is set. + +%(colorizer_doc)s + + This parameter is ignored if *colors* is set. + +origin : {*None*, 'upper', 'lower', 'image'}, default: None + Determines the orientation and exact position of *Z* by specifying + the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y* + are not given. + + - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner. + - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. + - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left + corner. + - 'image': Use the value from :rc:`image.origin`. + +extent : (x0, x1, y0, y1), optional + If *origin* is not *None*, then *extent* is interpreted as in + `.imshow`: it gives the outer pixel boundaries. In this case, the + position of Z[0, 0] is the center of the pixel, not a corner. If + *origin* is *None*, then (*x0*, *y0*) is the position of Z[0, 0], + and (*x1*, *y1*) is the position of Z[-1, -1]. + + This argument is ignored if *X* and *Y* are specified in the call + to contour. + +locator : ticker.Locator subclass, optional + The locator is used to determine the contour levels if they + are not given explicitly via *levels*. + Defaults to `~.ticker.MaxNLocator`. + +extend : {'neither', 'both', 'min', 'max'}, default: 'neither' + Determines the ``contourf``-coloring of values that are outside the + *levels* range. + + If 'neither', values outside the *levels* range are not colored. + If 'min', 'max' or 'both', color the values below, above or below + and above the *levels* range. + + Values below ``min(levels)`` and above ``max(levels)`` are mapped + to the under/over values of the `.Colormap`. Note that most + colormaps do not have dedicated colors for these by default, so + that the over and under values are the edge values of the colormap. + You may want to set these values explicitly using + `.Colormap.set_under` and `.Colormap.set_over`. + + .. note:: + + An existing `.QuadContourSet` does not get notified if + properties of its colormap are changed. Therefore, an explicit + call `~.ContourSet.changed()` is needed after modifying the + colormap. The explicit call can be left out, if a colorbar is + assigned to the `.QuadContourSet` because it internally calls + `~.ContourSet.changed()`. + + Example:: + + x = np.arange(1, 10) + y = x.reshape(-1, 1) + h = x * y + + cs = plt.contourf(h, levels=[10, 30, 50], + colors=['#808080', '#A0A0A0', '#C0C0C0'], extend='both') + cs.cmap.set_over('red') + cs.cmap.set_under('blue') + cs.changed() + +xunits, yunits : registered units, optional + Override axis units by specifying an instance of a + :class:`matplotlib.units.ConversionInterface`. + +antialiased : bool, optional + Enable antialiasing, overriding the defaults. For + filled contours, the default is *False*. For line contours, + it is taken from :rc:`lines.antialiased`. + +nchunk : int >= 0, optional + If 0, no subdivision of the domain. Specify a positive integer to + divide the domain into subdomains of *nchunk* by *nchunk* quads. + Chunking reduces the maximum length of polygons generated by the + contouring algorithm which reduces the rendering workload passed + on to the backend and also requires slightly less RAM. It can + however introduce rendering artifacts at chunk boundaries depending + on the backend, the *antialiased* flag and value of *alpha*. + +linewidths : float or array-like, default: :rc:`contour.linewidth` + *Only applies to* `.contour`. + + The line width of the contour lines. + + If a number, all levels will be plotted with this linewidth. + + If a sequence, the levels in ascending order will be plotted with + the linewidths in the order specified. + + If None, this falls back to :rc:`lines.linewidth`. + +linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional + *Only applies to* `.contour`. + + If *linestyles* is *None*, the default is 'solid' unless the lines are + monochrome. In that case, negative contours will instead take their + linestyle from the *negative_linestyles* argument. + + *linestyles* can also be an iterable of the above strings specifying a set + of linestyles to be used. If this iterable is shorter than the number of + contour levels it will be repeated as necessary. + +negative_linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, \ + optional + *Only applies to* `.contour`. + + If *linestyles* is *None* and the lines are monochrome, this argument + specifies the line style for negative contours. + + If *negative_linestyles* is *None*, the default is taken from + :rc:`contour.negative_linestyle`. + + *negative_linestyles* can also be an iterable of the above strings + specifying a set of linestyles to be used. If this iterable is shorter than + the number of contour levels it will be repeated as necessary. + +hatches : list[str], optional + *Only applies to* `.contourf`. + + A list of cross hatch patterns to use on the filled areas. + If None, no hatching will be added to the contour. + +algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional + Which contouring algorithm to use to calculate the contour lines and + polygons. The algorithms are implemented in + `ContourPy `_, consult the + `ContourPy documentation `_ for + further information. + + The default is taken from :rc:`contour.algorithm`. + +clip_path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath` + Set the clip path. See `~matplotlib.artist.Artist.set_clip_path`. + + .. versionadded:: 3.8 + +data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + +rasterized : bool, optional + *Only applies to* `.contourf`. + Rasterize the contour plot when drawing vector graphics. This can + speed up rendering and produce smaller files for large data sets. + See also :doc:`/gallery/misc/rasterization_demo`. + + +Notes +----- +1. `.contourf` differs from the MATLAB version in that it does not draw + the polygon edges. To draw edges, add line contours with calls to + `.contour`. + +2. `.contourf` fills intervals that are closed at the top; that is, for + boundaries *z1* and *z2*, the filled region is:: + + z1 < Z <= z2 + + except for the lowest interval, which is closed on both sides (i.e. + it includes the lowest value). + +3. `.contour` and `.contourf` use a `marching squares + `_ algorithm to + compute contour locations. More information can be found in + `ContourPy documentation `_. +""" % _docstring.interpd.params) diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi new file mode 100644 index 000000000000..2a89d6016170 --- /dev/null +++ b/lib/matplotlib/contour.pyi @@ -0,0 +1,140 @@ +import matplotlib.cm as cm +from matplotlib.artist import Artist +from matplotlib.axes import Axes +from matplotlib.collections import Collection +from matplotlib.colorizer import Colorizer, ColorizingArtist +from matplotlib.colors import Colormap, Normalize +from matplotlib.path import Path +from matplotlib.patches import Patch +from matplotlib.text import Text +from matplotlib.transforms import Transform, TransformedPatchPath, TransformedPath +from matplotlib.ticker import Locator, Formatter + +from numpy.typing import ArrayLike +import numpy as np +from collections.abc import Callable, Iterable, Sequence +from typing import Literal +from .typing import ColorType + + + +class ContourLabeler: + labelFmt: str | Formatter | Callable[[float], str] | dict[float, str] + labelManual: bool | Iterable[tuple[float, float]] + rightside_up: bool + labelLevelList: list[float] + labelIndiceList: list[int] + labelMappable: cm.ScalarMappable | ColorizingArtist + labelCValueList: list[ColorType] + labelXYs: list[tuple[float, float]] + def clabel( + self, + levels: ArrayLike | None = ..., + *, + fontsize: str | float | None = ..., + inline: bool = ..., + inline_spacing: float = ..., + fmt: str | Formatter | Callable[[float], str] | dict[float, str] | None = ..., + colors: ColorType | Sequence[ColorType] | None = ..., + use_clabeltext: bool = ..., + manual: bool | Iterable[tuple[float, float]] = ..., + rightside_up: bool = ..., + zorder: float | None = ... + ) -> list[Text]: ... + def print_label(self, linecontour: ArrayLike, labelwidth: float) -> bool: ... + def too_close(self, x: float, y: float, lw: float) -> bool: ... + def get_text( + self, + lev: float, + fmt: str | Formatter | Callable[[float], str] | dict[float, str], + ) -> str: ... + def locate_label( + self, linecontour: ArrayLike, labelwidth: float + ) -> tuple[float, float, float]: ... + def add_label( + self, x: float, y: float, rotation: float, lev: float, cvalue: ColorType + ) -> None: ... + def add_label_near( + self, + x: float, + y: float, + inline: bool = ..., + inline_spacing: int = ..., + transform: Transform | Literal[False] | None = ..., + ) -> None: ... + def pop_label(self, index: int = ...) -> None: ... + def labels(self, inline: bool, inline_spacing: int) -> None: ... + def remove(self) -> None: ... + +class ContourSet(ContourLabeler, Collection): + axes: Axes + levels: Iterable[float] + filled: bool + linewidths: float | ArrayLike | None + hatches: Iterable[str | None] + origin: Literal["upper", "lower", "image"] | None + extent: tuple[float, float, float, float] | None + colors: ColorType | Sequence[ColorType] + extend: Literal["neither", "both", "min", "max"] + nchunk: int + locator: Locator | None + logscale: bool + negative_linestyles: None | Literal[ + "solid", "dashed", "dashdot", "dotted" + ] | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]] + clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None + labelTexts: list[Text] + labelCValues: list[ColorType] + + @property + def allkinds(self) -> list[list[np.ndarray | None]]: ... + @property + def allsegs(self) -> list[list[np.ndarray]]: ... + @property + def alpha(self) -> float | None: ... + @property + def linestyles(self) -> ( + None | + Literal["solid", "dashed", "dashdot", "dotted"] | + Iterable[Literal["solid", "dashed", "dashdot", "dotted"]] + ): ... + + def __init__( + self, + ax: Axes, + *args, + levels: Iterable[float] | None = ..., + filled: bool = ..., + linewidths: float | ArrayLike | None = ..., + linestyles: Literal["solid", "dashed", "dashdot", "dotted"] + | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]] + | None = ..., + hatches: Iterable[str | None] = ..., + alpha: float | None = ..., + origin: Literal["upper", "lower", "image"] | None = ..., + extent: tuple[float, float, float, float] | None = ..., + cmap: str | Colormap | None = ..., + colors: ColorType | Sequence[ColorType] | None = ..., + norm: str | Normalize | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + colorizer: Colorizer | None = ..., + extend: Literal["neither", "both", "min", "max"] = ..., + antialiased: bool | None = ..., + nchunk: int = ..., + locator: Locator | None = ..., + transform: Transform | None = ..., + negative_linestyles: Literal["solid", "dashed", "dashdot", "dotted"] + | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]] + | None = ..., + clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None = ..., + **kwargs + ) -> None: ... + def legend_elements( + self, variable_name: str = ..., str_format: Callable[[float], str] = ... + ) -> tuple[list[Artist], list[str]]: ... + def find_nearest_contour( + self, x: float, y: float, indices: Iterable[int] | None = ..., pixel: bool = ... + ) -> tuple[int, int, int, float, float, float]: ... + +class QuadContourSet(ContourSet): ... diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index f2a3b7a68f50..511e1c6df6cc 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1,6 +1,28 @@ """ Matplotlib provides sophisticated date plotting capabilities, standing on the -shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`. +shoulders of python :mod:`datetime` and the add-on module dateutil_. + +By default, Matplotlib uses the units machinery described in +`~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64` +objects when plotted on an x- or y-axis. The user does not +need to do anything for dates to be formatted, but dates often have strict +formatting needs, so this module provides many tick locators and formatters. +A basic example using `numpy.datetime64` is:: + + import numpy as np + + times = np.arange(np.datetime64('2001-01-02'), + np.datetime64('2002-02-03'), np.timedelta64(75, 'm')) + y = np.random.randn(len(times)) + + fig, ax = plt.subplots() + ax.plot(times, y) + +.. seealso:: + + - :doc:`/gallery/text_labels_and_annotations/date` + - :doc:`/gallery/ticks/date_concise_formatter` + - :doc:`/gallery/ticks/date_demo_convert` .. _date-format: @@ -15,8 +37,8 @@ is achievable for (approximately) 70 years on either side of the epoch, and 20 microseconds for the rest of the allowable range of dates (year 0001 to 9999). The epoch can be changed at import time via `.dates.set_epoch` or -:rc:`dates.epoch` to other dates if necessary; see -:doc:`/gallery/ticks_and_spines/date_precision_and_epochs` for a discussion. +:rc:`date.epoch` to other dates if necessary; see +:doc:`/gallery/ticks/date_precision_and_epochs` for a discussion. .. note:: @@ -60,11 +82,12 @@ In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal() Out[1]: 732401 -All the Matplotlib date converters, tickers and formatters are timezone aware. -If no explicit timezone is provided, :rc:`timezone` is assumed. If you want to -use a custom time zone, pass a `datetime.tzinfo` instance with the tz keyword -argument to `num2date`, `~.Axes.plot_date`, and any custom date tickers or -locators you create. +All the Matplotlib date converters, locators and formatters are timezone aware. +If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a +string. If you want to use a different timezone, pass the *tz* keyword +argument of `num2date` to any date tick locators or formatters you create. This +can be either a `datetime.tzinfo` instance or a string with the timezone name +that can be parsed by `~dateutil.tz.gettz`. A wide range of specific and general purpose date tick locators and formatters are provided in this module. See @@ -76,23 +99,25 @@ .. _dateutil: https://dateutil.readthedocs.io -Date tickers ------------- +.. _date-locators: + +Date tick locators +------------------ -Most of the date tickers can locate single or multiple values. For example:: +Most of the date tick locators can locate single or multiple ticks. For example:: # import constants for the days of the week from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU - # tick on mondays every week + # tick on Mondays every week loc = WeekdayLocator(byweekday=MO, tz=tz) - # tick on mondays and saturdays + # tick on Mondays and Saturdays loc = WeekdayLocator(byweekday=(MO, SA)) In addition, most of the constructors take an interval argument:: - # tick on mondays every second week + # tick on Mondays every second week loc = WeekdayLocator(byweekday=MO, interval=2) The rrule locator allows completely general date ticking:: @@ -101,7 +126,7 @@ rule = rrulewrapper(YEARLY, byeaster=1, interval=5) loc = RRuleLocator(rule) -The available date tickers are: +The available date tick locators are: * `MicrosecondLocator`: Locate microseconds. @@ -119,17 +144,19 @@ * `YearLocator`: Locate years that are multiples of base. -* `RRuleLocator`: Locate using a `matplotlib.dates.rrulewrapper`. - `.rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` which - allow almost arbitrary date tick specifications. See :doc:`rrule example - `. +* `RRuleLocator`: Locate using a `rrulewrapper`. + `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` + which allow almost arbitrary date tick specifications. + See :doc:`rrule example `. * `AutoDateLocator`: On autoscale, this class picks the best `DateLocator` (e.g., `RRuleLocator`) to set the view limits and the tick locations. If called with ``interval_multiples=True`` it will make ticks line up with - sensible multiples of the tick intervals. E.g. if the interval is 4 hours, - it will pick hours 0, 4, 8, etc as ticks. This behaviour is not guaranteed - by default. + sensible multiples of the tick intervals. For example, if the interval is + 4 hours, it will pick hours 0, 4, 8, etc. as ticks. This behaviour is not + guaranteed by default. + +.. _date-formatters: Date formatters --------------- @@ -144,14 +171,11 @@ date information. This is most useful when used with the `AutoDateLocator`. * `DateFormatter`: use `~datetime.datetime.strftime` format strings. - -* `IndexDateFormatter`: date plots with implicit *x* indexing. """ import datetime import functools import logging -import math import re from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, @@ -162,48 +186,47 @@ import dateutil.tz import numpy as np -import matplotlib -import matplotlib.units as units -import matplotlib.cbook as cbook -import matplotlib.ticker as ticker +import matplotlib as mpl +from matplotlib import _api, cbook, ticker, units __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange', - 'epoch2num', 'num2epoch', 'mx2num', 'set_epoch', - 'get_epoch', 'DateFormatter', - 'ConciseDateFormatter', 'IndexDateFormatter', 'AutoDateFormatter', - 'DateLocator', 'RRuleLocator', 'AutoDateLocator', 'YearLocator', - 'MonthLocator', 'WeekdayLocator', + 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter', + 'AutoDateFormatter', 'DateLocator', 'RRuleLocator', + 'AutoDateLocator', 'YearLocator', 'MonthLocator', 'WeekdayLocator', 'DayLocator', 'HourLocator', 'MinuteLocator', 'SecondLocator', 'MicrosecondLocator', 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU', 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta', - 'DateConverter', 'ConciseDateConverter') + 'DateConverter', 'ConciseDateConverter', 'rrulewrapper') _log = logging.getLogger(__name__) UTC = datetime.timezone.utc -def _get_rc_timezone(): - """Retrieve the preferred timezone from the rcParams dictionary.""" - s = matplotlib.rcParams['timezone'] - if s == 'UTC': +def _get_tzinfo(tz=None): + """ + Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`. + If None, retrieve the preferred timezone from the rcParams dictionary. + """ + tz = mpl._val_or_rc(tz, 'timezone') + if tz == 'UTC': return UTC - return dateutil.tz.gettz(s) - - -""" -Time-related constants. -""" + if isinstance(tz, str): + tzinfo = dateutil.tz.gettz(tz) + if tzinfo is None: + raise ValueError(f"{tz} is not a valid timezone as parsed by" + " dateutil.tz.gettz.") + return tzinfo + if isinstance(tz, datetime.tzinfo): + return tz + raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.") + + +# Time-related constants. EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal()) # EPOCH_OFFSET is not used by matplotlib -JULIAN_OFFSET = 1721424.5 # Julian date at 0000-12-31 -# note that the Julian day epoch is achievable w/ -# np.datetime64('-4713-11-24T12:00:00'); datetime64 is proleptic -# Gregorian and BC has a one-year offset. So -# np.datetime64('0000-12-31') - np.datetime64('-4713-11-24T12:00') = 1721424.5 -# Ref: https://en.wikipedia.org/wiki/Julian_day MICROSECONDLY = SECONDLY + 1 HOURS_PER_DAY = 24. MIN_PER_HOUR = 60. @@ -244,17 +267,17 @@ def set_epoch(epoch): """ Set the epoch (origin for dates) for datetime calculations. - The default epoch is :rc:`dates.epoch` (by default 1970-01-01T00:00). + The default epoch is :rc:`date.epoch`. If microsecond accuracy is desired, the date being plotted needs to be within approximately 70 years of the epoch. Matplotlib internally represents dates as days since the epoch, so floating point dynamic - range needs to be within a factor fo 2^52. + range needs to be within a factor of 2^52. `~.dates.set_epoch` must be called before any dates are converted (i.e. near the import section) or a RuntimeError will be raised. - See also :doc:`/gallery/ticks_and_spines/date_precision_and_epochs`. + See also :doc:`/gallery/ticks/date_precision_and_epochs`. Parameters ---------- @@ -275,41 +298,21 @@ def get_epoch(): Returns ------- - epoch: str + epoch : str String for the epoch (parsable by `numpy.datetime64`). """ global _epoch - if _epoch is None: - _epoch = matplotlib.rcParams['date.epoch'] + _epoch = mpl._val_or_rc(_epoch, 'date.epoch') return _epoch -def _to_ordinalf(dt): - """ - Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float - days, preserving hours, minutes, seconds and microseconds. Return value - is a `float`. - """ - # Convert to UTC - tzi = getattr(dt, 'tzinfo', None) - if tzi is not None: - dt = dt.astimezone(UTC) - dt = dt.replace(tzinfo=None) - dt64 = np.datetime64(dt) - return _dt64_to_ordinalf(dt64) - - -# a version of _to_ordinalf that can operate on numpy arrays -_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) - - def _dt64_to_ordinalf(d): """ - Convert `numpy.datetime64` or an ndarray of those types to Gregorian - date as UTC float relative to the epoch (see `.get_epoch`). Roundoff - is float64 precision. Practically: microseconds for dates between - 290301 BC, 294241 AD, milliseconds for larger dates + Convert `numpy.datetime64` or an `numpy.ndarray` of those types to + Gregorian date as UTC float relative to the epoch (see `.get_epoch`). + Roundoff is float64 precision. Practically: microseconds for dates + between 290301 BC, 294241 AD, milliseconds for larger dates (see `numpy.datetime64`). """ @@ -324,11 +327,7 @@ def _dt64_to_ordinalf(d): NaT_int = np.datetime64('NaT').astype(np.int64) d_int = d.astype(np.int64) - try: - dt[d_int == NaT_int] = np.nan - except TypeError: - if d_int == NaT_int: - dt = np.nan + dt[d_int == NaT_int] = np.nan return dt @@ -343,8 +342,7 @@ def _from_ordinalf(x, tz=None): :rc:`timezone`. """ - if tz is None: - tz = _get_rc_timezone() + tz = _get_tzinfo(tz) dt = (np.datetime64(get_epoch()) + np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us')) @@ -374,8 +372,6 @@ def _from_ordinalf(x, tz=None): # a version of _from_ordinalf that can operate on numpy arrays _from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O") - - # a version of dateutil.parser.parse that can operate on numpy arrays _dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse) @@ -397,7 +393,9 @@ def datestr2num(d, default=None): return date2num(dt) else: if default is not None: - d = [dateutil.parser.parse(s, default=default) for s in d] + d = [date2num(dateutil.parser.parse(s, default=default)) + for s in d] + return np.asarray(d) d = np.asarray(d) if not d.size: return d @@ -425,68 +423,35 @@ def date2num(d): The Gregorian calendar is assumed; this is not universal practice. For details see the module docstring. """ - if hasattr(d, "values"): - # this unpacks pandas series or dataframes... - d = d.values - if not np.iterable(d): - if (isinstance(d, np.datetime64) or - (isinstance(d, np.ndarray) and - np.issubdtype(d.dtype, np.datetime64))): - return _dt64_to_ordinalf(d) - return _to_ordinalf(d) - - else: - d = np.asarray(d) - if np.issubdtype(d.dtype, np.datetime64): - return _dt64_to_ordinalf(d) - if not d.size: - return d - return _to_ordinalf_np_vectorized(d) + # Unpack in case of e.g. Pandas or xarray object + d = cbook._unpack_to_numpy(d) + # make an iterable, but save state to unpack later: + iterable = np.iterable(d) + if not iterable: + d = [d] -def julian2num(j): - """ - Convert a Julian date (or sequence) to a Matplotlib date (or sequence). + masked = np.ma.is_masked(d) + mask = np.ma.getmask(d) + d = np.asarray(d) - Parameters - ---------- - j : float or sequence of floats - Julian dates (days relative to 4713 BC Jan 1, 12:00:00 Julian - calendar or 4714 BC Nov 24, 12:00:00, proleptic Gregorian calendar). - - Returns - ------- - float or sequence of floats - Matplotlib dates (days relative to `.get_epoch`). - """ - ep = np.datetime64(get_epoch(), 'h').astype(float) / 24. - ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24. - # Julian offset defined above is relative to 0000-12-31, but we need - # relative to our current epoch: - dt = JULIAN_OFFSET - ep0 + ep - return np.subtract(j, dt) # Handles both scalar & nonscalar j. - - -def num2julian(n): - """ - Convert a Matplotlib date (or sequence) to a Julian date (or sequence). + # convert to datetime64 arrays, if not already: + if not np.issubdtype(d.dtype, np.datetime64): + # datetime arrays + if not d.size: + # deals with an empty array... + return d + tzi = getattr(d[0], 'tzinfo', None) + if tzi is not None: + # make datetime naive: + d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d] + d = np.asarray(d) + d = d.astype('datetime64[us]') - Parameters - ---------- - n : float or sequence of floats - Matplotlib dates (days relative to `.get_epoch`). + d = np.ma.masked_array(d, mask=mask) if masked else d + d = _dt64_to_ordinalf(d) - Returns - ------- - float or sequence of floats - Julian dates (days relative to 4713 BC Jan 1, 12:00:00). - """ - ep = np.datetime64(get_epoch(), 'h').astype(float) / 24. - ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24. - # Julian offset defined above is relative to 0000-12-31, but we need - # relative to our current epoch: - dt = JULIAN_OFFSET - ep0 + ep - return np.add(n, dt) # Handles both scalar & nonscalar j. + return d if iterable else d[0] def num2date(x, tz=None): @@ -499,8 +464,8 @@ def num2date(x, tz=None): Number of days (fraction part represents hours, minutes, seconds) since the epoch. See `.get_epoch` for the epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. - tz : str, optional - Timezone of *x* (defaults to :rc:`timezone`). + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`. Returns ------- @@ -512,12 +477,10 @@ def num2date(x, tz=None): Notes ----- - The addition of one here is a historical artifact. Also, note that the - Gregorian calendar is assumed; this is not universal practice. + The Gregorian calendar is assumed; this is not universal practice. For details, see the module docstring. """ - if tz is None: - tz = _get_rc_timezone() + tz = _get_tzinfo(tz) return _from_ordinalf_np_vectorized(x, tz).tolist() @@ -576,14 +539,29 @@ def drange(dstart, dend, delta): # ensure, that an half open interval will be generated [dstart, dend) if dinterval_end >= dend: - # if the endpoint is greater than dend, just subtract one delta + # if the endpoint is greater than or equal to dend, + # just subtract one delta dinterval_end -= delta num -= 1 f2 = date2num(dinterval_end) # new float-endpoint return np.linspace(f1, f2, num + 1) -## date tickers and formatters ### + +def _wrap_in_tex(text): + p = r'([a-zA-Z]+)' + ret_text = re.sub(p, r'}$\1$\\mathdefault{', text) + + # Braces ensure symbols are not spaced like binary operators. + ret_text = ret_text.replace('-', '{-}').replace(':', '{:}') + # To not concatenate space between numbers. + ret_text = ret_text.replace(' ', r'\;') + ret_text = '$\\mathdefault{' + ret_text + '}$' + ret_text = ret_text.replace('$\\mathdefault{}$', '') + return ret_text + + +## date tick locators and formatters ### class DateFormatter(ticker.Formatter): @@ -592,57 +570,28 @@ class DateFormatter(ticker.Formatter): `~datetime.datetime.strftime` format string. """ - @cbook.deprecated("3.3") - @property - def illegal_s(self): - return re.compile(r"((^|[^%])(%%)*%s)") - - def __init__(self, fmt, tz=None): + def __init__(self, fmt, tz=None, *, usetex=None): """ Parameters ---------- fmt : str `~datetime.datetime.strftime` format string - tz : `datetime.tzinfo`, default: :rc:`timezone` - Ticks timezone. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. + usetex : bool, default: :rc:`text.usetex` + To enable/disable the use of TeX's math mode for rendering the + results of the formatter. """ - if tz is None: - tz = _get_rc_timezone() + self.tz = _get_tzinfo(tz) self.fmt = fmt - self.tz = tz + self._usetex = mpl._val_or_rc(usetex, 'text.usetex') def __call__(self, x, pos=0): - return num2date(x, self.tz).strftime(self.fmt) + result = num2date(x, self.tz).strftime(self.fmt) + return _wrap_in_tex(result) if self._usetex else result def set_tzinfo(self, tz): - self.tz = tz - - -@cbook.deprecated("3.3") -class IndexDateFormatter(ticker.Formatter): - """Use with `.IndexLocator` to cycle format strings by index.""" - - def __init__(self, t, fmt, tz=None): - """ - Parameters - ---------- - t : list of float - A sequence of dates (floating point days). - fmt : str - A `~datetime.datetime.strftime` format string. - """ - if tz is None: - tz = _get_rc_timezone() - self.t = t - self.fmt = fmt - self.tz = tz - - def __call__(self, x, pos=0): - """Return the label for time *x* at position *pos*.""" - ind = int(round(x)) - if ind >= len(self.t) or ind <= 0: - return '' - return num2date(self.t[ind], self.tz).strftime(self.fmt) + self.tz = _get_tzinfo(tz) class ConciseDateFormatter(ticker.Formatter): @@ -659,8 +608,8 @@ class ConciseDateFormatter(ticker.Formatter): locator : `.ticker.Locator` Locator that this axis is using. - tz : str, optional - Passed to `.dates.date2num`. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone, passed to `.dates.num2date`. formats : list of 6 strings, optional Format strings for 6 levels of tick labelling: mostly years, @@ -685,9 +634,13 @@ class ConciseDateFormatter(ticker.Formatter): show_offset : bool, default: True Whether to show the offset or not. + usetex : bool, default: :rc:`text.usetex` + To enable/disable the use of TeX's math mode for rendering the results + of the formatter. + Examples -------- - See :doc:`/gallery/ticks_and_spines/date_concise_formatter` + See :doc:`/gallery/ticks/date_concise_formatter` .. plot:: @@ -713,7 +666,7 @@ class ConciseDateFormatter(ticker.Formatter): """ def __init__(self, locator, tz=None, formats=None, offset_formats=None, - zero_formats=None, show_offset=True): + zero_formats=None, show_offset=True, *, usetex=None): """ Autoformat the date labels. The default format is used to form an initial string, and then redundant elements are removed. @@ -756,7 +709,7 @@ def __init__(self, locator, tz=None, formats=None, offset_formats=None, if offset_formats: if len(offset_formats) != 6: - raise ValueError('offsetfmts argument must be a list of ' + raise ValueError('offset_formats argument must be a list of ' '6 format strings (or None)') self.offset_formats = offset_formats else: @@ -768,9 +721,11 @@ def __init__(self, locator, tz=None, formats=None, offset_formats=None, '%Y-%b-%d %H:%M'] self.offset_string = '' self.show_offset = show_offset + self._usetex = mpl._val_or_rc(usetex, 'text.usetex') def __call__(self, x, pos=None): - formatter = DateFormatter(self.defaultfmt, self._tz) + formatter = DateFormatter(self.defaultfmt, self._tz, + usetex=self._usetex) return formatter(x, pos=pos) def format_ticks(self, values): @@ -784,17 +739,22 @@ def format_ticks(self, values): # year, month, day etc. # fmt for most ticks at this level fmts = self.formats - # format beginnings of days, months, years, etc... + # format beginnings of days, months, years, etc. zerofmts = self.zero_formats # offset fmt are for the offset in the upper left of the # or lower right of the axis. offsetfmts = self.offset_formats + show_offset = self.show_offset # determine the level we will label at: # mostly 0: years, 1: months, 2: days, # 3: hours, 4: minutes, 5: seconds, 6: microseconds for level in range(5, -1, -1): - if len(np.unique(tickdate[:, level])) > 1: + unique = np.unique(tickdate[:, level]) + if len(unique) > 1: + # if 1 is included in unique, the year is shown in ticks + if level < 2 and np.any(unique == 1): + show_offset = False break elif level == 0: # all tickdate are the same, so only micros might be different @@ -834,11 +794,23 @@ def format_ticks(self, values): if '.' in labels[nn]: labels[nn] = labels[nn][:-trailing_zeros].rstrip('.') - if self.show_offset: + if show_offset: # set the offset string: - self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) + if (self._locator.axis and + self._locator.axis.__name__ in ('xaxis', 'yaxis') + and self._locator.axis.get_inverted()): + self.offset_string = tickdatetime[0].strftime(offsetfmts[level]) + else: + self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) + if self._usetex: + self.offset_string = _wrap_in_tex(self.offset_string) + else: + self.offset_string = '' - return labels + if self._usetex: + return [_wrap_in_tex(l) for l in labels] + else: + return labels def get_offset(self): return self.offset_string @@ -852,48 +824,48 @@ class AutoDateFormatter(ticker.Formatter): A `.Formatter` which attempts to figure out the best format to use. This is most useful when used with the `AutoDateLocator`. - The AutoDateFormatter has a scale dictionary that maps the scale - of the tick (the distance in days between one major tick) and a - format string. The default looks like this:: + `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the + interval in days between one major tick) to format strings; this dictionary + defaults to :: self.scaled = { - DAYS_PER_YEAR: rcParams['date.autoformat.year'], - DAYS_PER_MONTH: rcParams['date.autoformat.month'], - 1.0: rcParams['date.autoformat.day'], - 1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'], - 1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'], - 1. / (SEC_PER_DAY): rcParams['date.autoformat.second'], - 1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'], + DAYS_PER_YEAR: rcParams['date.autoformatter.year'], + DAYS_PER_MONTH: rcParams['date.autoformatter.month'], + 1: rcParams['date.autoformatter.day'], + 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'], + 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'], + 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'], + 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'], } - The algorithm picks the key in the dictionary that is >= the - current scale and uses that format string. You can customize this - dictionary by doing:: + The formatter uses the format string corresponding to the lowest key in + the dictionary that is greater or equal to the current scale. Dictionary + entries can be customized:: - >>> locator = AutoDateLocator() - >>> formatter = AutoDateFormatter(locator) - >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec - - A custom `.FuncFormatter` can also be used. The following example shows - how to use a custom format function to strip trailing zeros from decimal - seconds and adds the date to the first ticklabel:: - - >>> def my_format_function(x, pos=None): - ... x = matplotlib.dates.num2date(x) - ... if pos == 0: - ... fmt = '%D %H:%M:%S.%f' - ... else: - ... fmt = '%H:%M:%S.%f' - ... label = x.strftime(fmt) - ... label = label.rstrip("0") - ... label = label.rstrip(".") - ... return label - >>> from matplotlib.ticker import FuncFormatter - >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function) + locator = AutoDateLocator() + formatter = AutoDateFormatter(locator) + formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec + + Custom callables can also be used instead of format strings. The following + example shows how to use a custom format function to strip trailing zeros + from decimal seconds and adds the date to the first ticklabel:: + + def my_format_function(x, pos=None): + x = matplotlib.dates.num2date(x) + if pos == 0: + fmt = '%D %H:%M:%S.%f' + else: + fmt = '%H:%M:%S.%f' + label = x.strftime(fmt) + label = label.rstrip("0") + label = label.rstrip(".") + return label + + formatter.scaled[1/(24*60)] = my_format_function """ # This can be improved by providing some user-level direction on - # how to choose the best format (precedence, etc...) + # how to choose the best format (precedence, etc.). # Perhaps a 'struct' that has a field for each time-type where a # zero would indicate "don't show" and a number would indicate @@ -903,17 +875,35 @@ class AutoDateFormatter(ticker.Formatter): # Or more simply, perhaps just a format string for each # possibility... - def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'): + def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *, + usetex=None): """ - Autoformat the date labels. The default format is the one to use - if none of the values in ``self.scaled`` are greater than the unit - returned by ``locator._get_unit()``. + Autoformat the date labels. + + Parameters + ---------- + locator : `.ticker.Locator` + Locator that this axis is using. + + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. + + defaultfmt : str + The default format to use if none of the values in ``self.scaled`` + are greater than the unit returned by ``locator._get_unit()``. + + usetex : bool, default: :rc:`text.usetex` + To enable/disable the use of TeX's math mode for rendering the + results of the formatter. If any entries in ``self.scaled`` are set + as functions, then it is up to the customized function to enable or + disable TeX's math mode itself. """ self._locator = locator self._tz = tz self.defaultfmt = defaultfmt self._formatter = DateFormatter(self.defaultfmt, tz) - rcParams = matplotlib.rcParams + rcParams = mpl.rcParams + self._usetex = mpl._val_or_rc(usetex, 'text.usetex') self.scaled = { DAYS_PER_YEAR: rcParams['date.autoformatter.year'], DAYS_PER_MONTH: rcParams['date.autoformatter.month'], @@ -938,24 +928,40 @@ def __call__(self, x, pos=None): self.defaultfmt) if isinstance(fmt, str): - self._formatter = DateFormatter(fmt, self._tz) + self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex) result = self._formatter(x, pos) elif callable(fmt): result = fmt(x, pos) else: - raise TypeError('Unexpected type passed to {0!r}.'.format(self)) + raise TypeError(f'Unexpected type passed to {self!r}.') return result class rrulewrapper: + """ + A simple wrapper around a `dateutil.rrule` allowing flexible + date tick specifications. + """ def __init__(self, freq, tzinfo=None, **kwargs): + """ + Parameters + ---------- + freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY} + Tick frequency. These constants are defined in `dateutil.rrule`, + but they are accessible from `matplotlib.dates` as well. + tzinfo : `datetime.tzinfo`, optional + Time zone information. The default is None. + **kwargs + Additional keyword arguments are passed to the `dateutil.rrule`. + """ kwargs['freq'] = freq self._base_tzinfo = tzinfo self._update_rrule(**kwargs) def set(self, **kwargs): + """Set parameters for an existing wrapper.""" self._construct.update(kwargs) self._update_rrule(**self._construct) @@ -963,7 +969,7 @@ def set(self, **kwargs): def _update_rrule(self, **kwargs): tzinfo = self._base_tzinfo - # rrule does not play nicely with time zones - especially pytz time + # rrule does not play nicely with timezones - especially pytz time # zones, it's best to use naive zones and attach timezones once the # datetimes are returned if 'dtstart' in kwargs: @@ -1066,17 +1072,21 @@ def __init__(self, tz=None): """ Parameters ---------- - tz : `datetime.tzinfo` + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ - if tz is None: - tz = _get_rc_timezone() - self.tz = tz + self.tz = _get_tzinfo(tz) def set_tzinfo(self, tz): """ - Set time zone info. + Set timezone info. + + Parameters + ---------- + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ - self.tz = tz + self.tz = _get_tzinfo(tz) def datalim_to_dt(self): """Convert axis data interval to datetime objects.""" @@ -1112,9 +1122,9 @@ def nonsingular(self, vmin, vmax): if it is too close to being singular (i.e. a range of ~0). """ if not np.isfinite(vmin) or not np.isfinite(vmax): - # Except if there is no data, then use 2000-2010 as default. - return (date2num(datetime.date(2000, 1, 1)), - date2num(datetime.date(2010, 1, 1))) + # Except if there is no data, then use 1970 as default. + return (date2num(datetime.date(1970, 1, 1)), + date2num(datetime.date(1970, 1, 2))) if vmax < vmin: vmin, vmax = vmax, vmin unit = self._get_unit() @@ -1142,6 +1152,15 @@ def __call__(self): return self.tick_values(dmin, dmax) def tick_values(self, vmin, vmax): + start, stop = self._create_rrule(vmin, vmax) + dates = self.rule.between(start, stop, True) + if len(dates) == 0: + return date2num([vmin, vmax]) + return self.raise_if_exceeds(date2num(dates)) + + def _create_rrule(self, vmin, vmax): + # set appropriate rrule dtstart and until and return + # start and end delta = relativedelta(vmax, vmin) # We need to cap at the endpoints of valid datetime @@ -1161,10 +1180,7 @@ def tick_values(self, vmin, vmax): self.rule.set(dtstart=start, until=stop) - dates = self.rule.between(vmin, vmax, True) - if len(dates) == 0: - return date2num([vmin, vmax]) - return self.raise_if_exceeds(date2num(dates)) + return vmin, vmax def _get_unit(self): # docstring inherited @@ -1189,47 +1205,11 @@ def get_unit_generic(freq): return 1.0 / SEC_PER_DAY else: # error - return -1 # or should this just return '1'? + return -1 # or should this just return '1'? def _get_interval(self): return self.rule._rrule._interval - @cbook.deprecated("3.2") - def autoscale(self): - """ - Set the view limits to include the data range. - """ - dmin, dmax = self.datalim_to_dt() - delta = relativedelta(dmax, dmin) - - # We need to cap at the endpoints of valid datetime - try: - start = dmin - delta - except ValueError: - start = _from_ordinalf(1.0) - - try: - stop = dmax + delta - except ValueError: - # The magic number! - stop = _from_ordinalf(3652059.9999999) - - self.rule.set(dtstart=start, until=stop) - dmin, dmax = self.datalim_to_dt() - - vmin = self.rule.before(dmin, True) - if not vmin: - vmin = dmin - - vmax = self.rule.after(dmax, True) - if not vmax: - vmax = dmax - - vmin = date2num(vmin) - vmax = date2num(vmax) - - return self.nonsingular(vmin, vmax) - class AutoDateLocator(DateLocator): """ @@ -1276,8 +1256,8 @@ def __init__(self, tz=None, minticks=5, maxticks=None, """ Parameters ---------- - tz : `datetime.tzinfo` - Ticks timezone. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. minticks : int The minimum number of ticks desired; controls whether ticks occur yearly, monthly, etc. @@ -1296,7 +1276,7 @@ def __init__(self, tz=None, minticks=5, maxticks=None, the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done at 6 hour intervals. """ - super().__init__(tz) + super().__init__(tz=tz) self._freq = YEARLY self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY, SECONDLY, MICROSECONDLY] @@ -1347,9 +1327,9 @@ def nonsingular(self, vmin, vmax): # whatever is thrown at us, we can scale the unit. # But default nonsingular date plots at an ~4 year period. if not np.isfinite(vmin) or not np.isfinite(vmax): - # Except if there is no data, then use 2000-2010 as default. - return (date2num(datetime.date(2000, 1, 1)), - date2num(datetime.date(2010, 1, 1))) + # Except if there is no data, then use 1970 as default. + return (date2num(datetime.date(1970, 1, 1)), + date2num(datetime.date(1970, 1, 2))) if vmax < vmin: vmin, vmax = vmax, vmin if vmin == vmax: @@ -1363,12 +1343,6 @@ def _get_unit(self): else: return RRuleLocator.get_unit_generic(self._freq) - @cbook.deprecated("3.2") - def autoscale(self): - """Try to choose the view limits intelligently.""" - dmin, dmax = self.datalim_to_dt() - return self.get_locator(dmin, dmax).autoscale() - def get_locator(self, dmin, dmax): """Pick the best locator based on a distance.""" delta = relativedelta(dmax, dmin) @@ -1384,7 +1358,7 @@ def get_locator(self, dmin, dmax): # whenever possible. numYears = float(delta.years) numMonths = numYears * MONTHS_PER_YEAR + delta.months - numDays = tdelta.days # Avoids estimates of days/month, days/year + numDays = tdelta.days # Avoids estimates of days/month, days/year. numHours = numDays * HOURS_PER_DAY + delta.hours numMinutes = numHours * MIN_PER_HOUR + delta.minutes numSeconds = np.floor(tdelta.total_seconds()) @@ -1402,7 +1376,7 @@ def get_locator(self, dmin, dmax): # Loop over all the frequencies and try to find one that gives at # least a minticks tick positions. Once this is found, look for - # an interval from an list specific to that frequency that gives no + # an interval from a list specific to that frequency that gives no # more than maxticks tick positions. Also, set up some ranges # (bymonth, etc.) as appropriate to be passed to rrulewrapper. for i, (freq, num) in enumerate(zip(self._freqs, nums)): @@ -1421,7 +1395,7 @@ def get_locator(self, dmin, dmax): break else: if not (self.interval_multiples and freq == DAILY): - cbook._warn_external( + _api.warn_external( f"AutoDateLocator was unable to pick an appropriate " f"interval for this date range. It may be necessary " f"to add an interval value to the AutoDateLocator's " @@ -1456,25 +1430,21 @@ def get_locator(self, dmin, dmax): byhour=byhour, byminute=byminute, bysecond=bysecond) - locator = RRuleLocator(rrule, self.tz) + locator = RRuleLocator(rrule, tz=self.tz) else: locator = MicrosecondLocator(interval, tz=self.tz) if date2num(dmin) > 70 * 365 and interval < 1000: - cbook._warn_external( + _api.warn_external( 'Plotting microsecond time intervals for dates far from ' f'the epoch (time origin: {get_epoch()}) is not well-' 'supported. See matplotlib.dates.set_epoch to change the ' 'epoch.') locator.set_axis(self.axis) - - if self.axis is not None: - locator.set_view_interval(*self.axis.get_view_interval()) - locator.set_data_interval(*self.axis.get_data_interval()) return locator -class YearLocator(DateLocator): +class YearLocator(RRuleLocator): """ Make ticks on a given day of each year that is a multiple of base. @@ -1488,73 +1458,40 @@ class YearLocator(DateLocator): """ def __init__(self, base=1, month=1, day=1, tz=None): """ - Mark years that are multiple of base on a given month and day - (default jan 1). + Parameters + ---------- + base : int, default: 1 + Mark ticks every *base* years. + month : int, default: 1 + The month on which to place the ticks, starting from 1. Default is + January. + day : int, default: 1 + The day on which to place the ticks. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ - super().__init__(tz) + rule = rrulewrapper(YEARLY, interval=base, bymonth=month, + bymonthday=day, **self.hms0d) + super().__init__(rule, tz=tz) self.base = ticker._Edge_integer(base, 0) - self.replaced = {'month': month, - 'day': day, - 'hour': 0, - 'minute': 0, - 'second': 0, - } - if not hasattr(tz, 'localize'): - # if tz is pytz, we need to do this w/ the localize fcn, - # otherwise datetime.replace works fine... - self.replaced['tzinfo'] = tz - - def __call__(self): - # if no data have been set, this will tank with a ValueError - try: - dmin, dmax = self.viewlim_to_dt() - except ValueError: - return [] - return self.tick_values(dmin, dmax) + def _create_rrule(self, vmin, vmax): + # 'start' needs to be a multiple of the interval to create ticks on + # interval multiples when the tick frequency is YEARLY + ymin = max(self.base.le(vmin.year) * self.base.step, 1) + ymax = min(self.base.ge(vmax.year) * self.base.step, 9999) - def tick_values(self, vmin, vmax): - ymin = self.base.le(vmin.year) * self.base.step - ymax = self.base.ge(vmax.year) * self.base.step - - vmin = vmin.replace(year=ymin, **self.replaced) - if hasattr(self.tz, 'localize'): - # look after pytz - if not vmin.tzinfo: - vmin = self.tz.localize(vmin, is_dst=True) - - ticks = [vmin] - - while True: - dt = ticks[-1] - if dt.year >= ymax: - return date2num(ticks) - year = dt.year + self.base.step - dt = dt.replace(year=year, **self.replaced) - if hasattr(self.tz, 'localize'): - # look after pytz - if not dt.tzinfo: - dt = self.tz.localize(dt, is_dst=True) - - ticks.append(dt) - - @cbook.deprecated("3.2") - def autoscale(self): - """ - Set the view limits to include the data range. - """ - dmin, dmax = self.datalim_to_dt() + c = self.rule._construct + replace = {'year': ymin, + 'month': c.get('bymonth', 1), + 'day': c.get('bymonthday', 1), + 'hour': 0, 'minute': 0, 'second': 0} - ymin = self.base.le(dmin.year) - ymax = self.base.ge(dmax.year) - vmin = dmin.replace(year=ymin, **self.replaced) - vmin = vmin.astimezone(self.tz) - vmax = dmax.replace(year=ymax, **self.replaced) - vmax = vmax.astimezone(self.tz) + start = vmin.replace(**replace) + stop = start.replace(year=ymax) + self.rule.set(dtstart=start, until=stop) - vmin = date2num(vmin) - vmax = date2num(vmax) - return self.nonsingular(vmin, vmax) + return start, stop class MonthLocator(RRuleLocator): @@ -1563,23 +1500,25 @@ class MonthLocator(RRuleLocator): """ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): """ - Mark every month in *bymonth*; *bymonth* can be an int or - sequence. Default is ``range(1, 13)``, i.e. every month. - - *interval* is the interval between each iteration. For - example, if ``interval=2``, mark every second occurrence. + Parameters + ---------- + bymonth : int or list of int, default: all months + Ticks will be placed on every month in *bymonth*. Default is + ``range(1, 13)``, i.e. every month. + bymonthday : int, default: 1 + The day on which to place the ticks. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ if bymonth is None: bymonth = range(1, 13) - elif isinstance(bymonth, np.ndarray): - # This fixes a bug in dateutil <= 2.3 which prevents the use of - # numpy arrays in (among other things) the bymonthday, byweekday - # and bymonth parameters. - bymonth = [x.item() for x in bymonth.astype(int)] rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, interval=interval, **self.hms0d) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class WeekdayLocator(RRuleLocator): @@ -1589,25 +1528,24 @@ class WeekdayLocator(RRuleLocator): def __init__(self, byweekday=1, interval=1, tz=None): """ - Mark every weekday in *byweekday*; *byweekday* can be a number or - sequence. - - Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA, - SU, the constants from :mod:`dateutil.rrule`, which have been - imported into the :mod:`matplotlib.dates` namespace. - - *interval* specifies the number of weeks to skip. For example, - ``interval=2`` plots every second week. + Parameters + ---------- + byweekday : int or list of int, default: all days + Ticks will be placed on every weekday in *byweekday*. Default is + every day. + + Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA, + SU, the constants from :mod:`dateutil.rrule`, which have been + imported into the :mod:`matplotlib.dates` namespace. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ - if isinstance(byweekday, np.ndarray): - # This fixes a bug in dateutil <= 2.3 which prevents the use of - # numpy arrays in (among other things) the bymonthday, byweekday - # and bymonth parameters. - [x.item() for x in byweekday.astype(int)] - rule = rrulewrapper(DAILY, byweekday=byweekday, interval=interval, **self.hms0d) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class DayLocator(RRuleLocator): @@ -1617,23 +1555,25 @@ class DayLocator(RRuleLocator): """ def __init__(self, bymonthday=None, interval=1, tz=None): """ - Mark every day in *bymonthday*; *bymonthday* can be an int or sequence. - - Default is to tick every day of the month: ``bymonthday=range(1, 32)``. + Parameters + ---------- + bymonthday : int or list of int, default: all days + Ticks will be placed on every day in *bymonthday*. Default is + ``bymonthday=range(1, 32)``, i.e., every day of the month. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ if interval != int(interval) or interval < 1: raise ValueError("interval must be an integer greater than 0") if bymonthday is None: bymonthday = range(1, 32) - elif isinstance(bymonthday, np.ndarray): - # This fixes a bug in dateutil <= 2.3 which prevents the use of - # numpy arrays in (among other things) the bymonthday, byweekday - # and bymonth parameters. - bymonthday = [x.item() for x in bymonthday.astype(int)] rule = rrulewrapper(DAILY, bymonthday=bymonthday, interval=interval, **self.hms0d) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class HourLocator(RRuleLocator): @@ -1642,18 +1582,23 @@ class HourLocator(RRuleLocator): """ def __init__(self, byhour=None, interval=1, tz=None): """ - Mark every hour in *byhour*; *byhour* can be an int or sequence. - Default is to tick every hour: ``byhour=range(24)`` - - *interval* is the interval between each iteration. For - example, if ``interval=2``, mark every second occurrence. + Parameters + ---------- + byhour : int or list of int, default: all hours + Ticks will be placed on every hour in *byhour*. Default is + ``byhour=range(24)``, i.e., every hour. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ if byhour is None: byhour = range(24) rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval, byminute=0, bysecond=0) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class MinuteLocator(RRuleLocator): @@ -1662,18 +1607,23 @@ class MinuteLocator(RRuleLocator): """ def __init__(self, byminute=None, interval=1, tz=None): """ - Mark every minute in *byminute*; *byminute* can be an int or - sequence. Default is to tick every minute: ``byminute=range(60)`` - - *interval* is the interval between each iteration. For - example, if ``interval=2``, mark every second occurrence. + Parameters + ---------- + byminute : int or list of int, default: all minutes + Ticks will be placed on every minute in *byminute*. Default is + ``byminute=range(60)``, i.e., every minute. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ if byminute is None: byminute = range(60) rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval, bysecond=0) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class SecondLocator(RRuleLocator): @@ -1682,18 +1632,22 @@ class SecondLocator(RRuleLocator): """ def __init__(self, bysecond=None, interval=1, tz=None): """ - Mark every second in *bysecond*; *bysecond* can be an int or - sequence. Default is to tick every second: ``bysecond = range(60)`` - - *interval* is the interval between each iteration. For - example, if ``interval=2``, mark every second occurrence. - + Parameters + ---------- + bysecond : int or list of int, default: all seconds + Ticks will be placed on every second in *bysecond*. Default is + ``bysecond = range(60)``, i.e., every second. + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ if bysecond is None: bysecond = range(60) rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval) - super().__init__(rule, tz) + super().__init__(rule, tz=tz) class MicrosecondLocator(DateLocator): @@ -1715,31 +1669,27 @@ class MicrosecondLocator(DateLocator): If you really must use datetime.datetime() or similar and still need microsecond precision, change the time origin via `.dates.set_epoch` to something closer to the dates being plotted. - See :doc:`/gallery/ticks_and_spines/date_precision_and_epochs`. + See :doc:`/gallery/ticks/date_precision_and_epochs`. """ def __init__(self, interval=1, tz=None): """ - *interval* is the interval between each iteration. For - example, if ``interval=2``, mark every second microsecond. - + Parameters + ---------- + interval : int, default: 1 + The interval between each iteration. For example, if + ``interval=2``, mark every second occurrence. + tz : str or `~datetime.tzinfo`, default: :rc:`timezone` + Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. """ + super().__init__(tz=tz) self._interval = interval self._wrapped_locator = ticker.MultipleLocator(interval) - self.tz = tz def set_axis(self, axis): self._wrapped_locator.set_axis(axis) return super().set_axis(axis) - def set_view_interval(self, vmin, vmax): - self._wrapped_locator.set_view_interval(vmin, vmax) - return super().set_view_interval(vmin, vmax) - - def set_data_interval(self, vmin, vmax): - self._wrapped_locator.set_data_interval(vmin, vmax) - return super().set_data_interval(vmin, vmax) - def __call__(self): # if no data have been set, this will tank with a ValueError try: @@ -1771,112 +1721,12 @@ def _get_interval(self): return self._interval -def epoch2num(e): - """ - Convert UNIX time to days since Matplotlib epoch. - - Parameters - ---------- - e : list of floats - Time in seconds since 1970-01-01. - - Returns - ------- - `numpy.array` - Time in days since Matplotlib epoch (see `~.dates.get_epoch()`). - """ - - dt = (np.datetime64('1970-01-01T00:00:00', 's') - - np.datetime64(get_epoch(), 's')).astype(float) - - return (dt + np.asarray(e)) / SEC_PER_DAY - - -def num2epoch(d): - """ - Convert days since Matplotlib epoch to UNIX time. - - Parameters - ---------- - d : list of floats - Time in days since Matplotlib epoch (see `~.dates.get_epoch()`). - - Returns - ------- - `numpy.array` - Time in seconds since 1970-01-01. - """ - dt = (np.datetime64('1970-01-01T00:00:00', 's') - - np.datetime64(get_epoch(), 's')).astype(float) - - return np.asarray(d) * SEC_PER_DAY - dt - - -@cbook.deprecated("3.2") -def mx2num(mxdates): - """ - Convert mx :class:`datetime` instance (or sequence of mx - instances) to the new date format. - """ - scalar = False - if not np.iterable(mxdates): - scalar = True - mxdates = [mxdates] - ret = epoch2num([m.ticks() for m in mxdates]) - if scalar: - return ret[0] - else: - return ret - - -def date_ticker_factory(span, tz=None, numticks=5): - """ - Create a date locator with *numticks* (approx) and a date formatter - for *span* in days. Return value is (locator, formatter). - """ - - if span == 0: - span = 1 / HOURS_PER_DAY - - mins = span * MINUTES_PER_DAY - hrs = span * HOURS_PER_DAY - days = span - wks = span / DAYS_PER_WEEK - months = span / DAYS_PER_MONTH # Approx - years = span / DAYS_PER_YEAR # Approx - - if years > numticks: - locator = YearLocator(int(years / numticks), tz=tz) # define - fmt = '%Y' - elif months > numticks: - locator = MonthLocator(tz=tz) - fmt = '%b %Y' - elif wks > numticks: - locator = WeekdayLocator(tz=tz) - fmt = '%a, %b %d' - elif days > numticks: - locator = DayLocator(interval=math.ceil(days / numticks), tz=tz) - fmt = '%b %d' - elif hrs > numticks: - locator = HourLocator(interval=math.ceil(hrs / numticks), tz=tz) - fmt = '%H:%M\n%b %d' - elif mins > numticks: - locator = MinuteLocator(interval=math.ceil(mins / numticks), tz=tz) - fmt = '%H:%M:%S' - else: - locator = MinuteLocator(tz=tz) - fmt = '%H:%M:%S' - - formatter = DateFormatter(fmt, tz=tz) - return locator, formatter - - class DateConverter(units.ConversionInterface): """ Converter for `datetime.date` and `datetime.datetime` data, or for date/time data represented as it would be converted by `date2num`. - The 'unit' tag for such data is None or a tzinfo instance. + The 'unit' tag for such data is None or a `~datetime.tzinfo` instance. """ def __init__(self, *, interval_multiples=True): @@ -1887,7 +1737,7 @@ def axisinfo(self, unit, axis): """ Return the `~matplotlib.units.AxisInfo` for *unit*. - *unit* is a tzinfo instance or None. + *unit* is a `~datetime.tzinfo` instance or None. The *axis* argument is required but not used. """ tz = unit @@ -1895,8 +1745,8 @@ def axisinfo(self, unit, axis): majloc = AutoDateLocator(tz=tz, interval_multiples=self._interval_multiples) majfmt = AutoDateFormatter(majloc, tz=tz) - datemin = datetime.date(2000, 1, 1) - datemax = datetime.date(2010, 1, 1) + datemin = datetime.date(1970, 1, 1) + datemax = datetime.date(1970, 1, 2) return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', default_limits=(datemin, datemax)) @@ -1914,13 +1764,14 @@ def convert(value, unit, axis): @staticmethod def default_units(x, axis): """ - Return the tzinfo instance of *x* or of its first element, or None + Return the `~datetime.tzinfo` instance of *x* or of its first element, + or None """ if isinstance(x, np.ndarray): x = x.ravel() try: - x = cbook.safe_first_element(x) + x = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass @@ -1952,50 +1803,38 @@ def axisinfo(self, unit, axis): zero_formats=self._zero_formats, offset_formats=self._offset_formats, show_offset=self._show_offset) - datemin = datetime.date(2000, 1, 1) - datemax = datetime.date(2010, 1, 1) + datemin = datetime.date(1970, 1, 1) + datemax = datetime.date(1970, 1, 2) return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', default_limits=(datemin, datemax)) -class _rcParam_helper: +class _SwitchableDateConverter: """ - This helper class is so that we can set the converter for dates - via the validator for the rcParams `date.converter` and - `date.interval_multiples`. Never instatiated. + Helper converter-like object that generates and dispatches to + temporary ConciseDateConverter or DateConverter instances based on + :rc:`date.converter` and :rc:`date.interval_multiples`. """ - conv_st = 'auto' - int_mult = True - - @classmethod - def set_converter(cls, s): - """Called by validator for rcParams date.converter""" - if s not in ['concise', 'auto']: - raise ValueError('Converter must be one of "concise" or "auto"') - cls.conv_st = s - cls.register_converters() - - @classmethod - def set_int_mult(cls, b): - """Called by validator for rcParams date.interval_multiples""" - cls.int_mult = b - cls.register_converters() - - @classmethod - def register_converters(cls): - """ - Helper to register the date converters when rcParams `date.converter` - and `date.interval_multiples` are changed. Called by the helpers - above. - """ - if cls.conv_st == 'concise': - converter = ConciseDateConverter - else: - converter = DateConverter + @staticmethod + def _get_converter(): + converter_cls = { + "concise": ConciseDateConverter, "auto": DateConverter}[ + mpl.rcParams["date.converter"]] + interval_multiples = mpl.rcParams["date.interval_multiples"] + return converter_cls(interval_multiples=interval_multiples) + + def axisinfo(self, *args, **kwargs): + return self._get_converter().axisinfo(*args, **kwargs) + + def default_units(self, *args, **kwargs): + return self._get_converter().default_units(*args, **kwargs) + + def convert(self, *args, **kwargs): + return self._get_converter().convert(*args, **kwargs) + - interval_multiples = cls.int_mult - convert = converter(interval_multiples=interval_multiples) - units.registry[np.datetime64] = convert - units.registry[datetime.date] = convert - units.registry[datetime.datetime] = convert +units.registry[np.datetime64] = \ + units.registry[datetime.date] = \ + units.registry[datetime.datetime] = \ + _SwitchableDateConverter() diff --git a/lib/matplotlib/docstring.py b/lib/matplotlib/docstring.py deleted file mode 100644 index 4fb2d59d326c..000000000000 --- a/lib/matplotlib/docstring.py +++ /dev/null @@ -1,80 +0,0 @@ -import inspect - -from matplotlib import cbook - - -class Substitution: - """ - A decorator that performs %-substitution on an object's docstring. - - This decorator should be robust even if ``obj.__doc__`` is None (for - example, if -OO was passed to the interpreter). - - Usage: construct a docstring.Substitution with a sequence or dictionary - suitable for performing substitution; then decorate a suitable function - with the constructed object, e.g.:: - - sub_author_name = Substitution(author='Jason') - - @sub_author_name - def some_function(x): - "%(author)s wrote this function" - - # note that some_function.__doc__ is now "Jason wrote this function" - - One can also use positional arguments:: - - sub_first_last_names = Substitution('Edgar Allen', 'Poe') - - @sub_first_last_names - def some_function(x): - "%s %s wrote the Raven" - """ - def __init__(self, *args, **kwargs): - if args and kwargs: - raise TypeError("Only positional or keyword args are allowed") - self.params = args or kwargs - - def __call__(self, func): - if func.__doc__: - func.__doc__ %= self.params - return func - - def update(self, *args, **kwargs): - """ - Update ``self.params`` (which must be a dict) with the supplied args. - """ - self.params.update(*args, **kwargs) - - @classmethod - @cbook.deprecated("3.3", alternative="assign to the params attribute") - def from_params(cls, params): - """ - In the case where the params is a mutable sequence (list or - dictionary) and it may change before this class is called, one may - explicitly use a reference to the params rather than using *args or - **kwargs which will copy the values and not reference them. - """ - result = cls() - result.params = params - return result - - -def copy(source): - """Copy a docstring from another source function (if present).""" - def do_copy(target): - if source.__doc__: - target.__doc__ = source.__doc__ - return target - return do_copy - - -# Create a decorator that will house the various docstring snippets reused -# throughout Matplotlib. -interpd = Substitution() - - -def dedent_interpd(func): - """Dedent *func*'s docstring, then interpolate it with ``interpd``.""" - func.__doc__ = inspect.getdoc(func) - return interpd(func) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 48de9ab6c07f..9e8b6a5facf5 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -17,20 +17,21 @@ ... """ -from collections import namedtuple +import dataclasses import enum -from functools import lru_cache, partial, wraps import logging import os -from pathlib import Path import re import struct +import subprocess import sys -import textwrap +from collections import namedtuple +from functools import cache, lru_cache, partial, wraps +from pathlib import Path import numpy as np -from matplotlib import cbook, rcParams +from matplotlib import _api, cbook, font_manager _log = logging.getLogger(__name__) @@ -39,8 +40,8 @@ # are cached using lru_cache(). # Dvi is a bytecode format documented in -# http://mirrors.ctan.org/systems/knuth/dist/texware/dvitype.web -# http://texdoc.net/texmf-dist/doc/generic/knuth/texware/dvitype.pdf +# https://ctan.org/pkg/dvitype +# https://texdoc.org/serve/dvitype.pdf/0 # # The file consists of a preamble, some number of pages, a postamble, # and a finale. Different opcodes are allowed in different contexts, @@ -58,78 +59,103 @@ # The marks on a page consist of text and boxes. A page also has dimensions. Page = namedtuple('Page', 'text boxes height width descent') -Text = namedtuple('Text', 'x y font glyph width') Box = namedtuple('Box', 'x y height width') -# Opcode argument parsing -# -# Each of the following functions takes a Dvi object and delta, -# which is the difference between the opcode and the minimum opcode -# with the same meaning. Dvi opcodes often encode the number of -# argument bytes in this delta. - -def _arg_raw(dvi, delta): - """Return *delta* without reading anything more from the dvi file.""" - return delta - - -def _arg(nbytes, signed, dvi, _): - """ - Read *nbytes* bytes, returning the bytes interpreted as a signed integer - if *signed* is true, unsigned otherwise. - """ - return dvi._arg(nbytes, signed) - - -def _arg_slen(dvi, delta): - """ - Signed, length *delta* - - Read *delta* bytes, returning None if *delta* is zero, and the bytes - interpreted as a signed integer otherwise. +# Also a namedtuple, for backcompat. +class Text(namedtuple('Text', 'x y font glyph width')): """ - if delta == 0: - return None - return dvi._arg(delta, True) - - -def _arg_slen1(dvi, delta): + A glyph in the dvi file. + + The *x* and *y* attributes directly position the glyph. The *font*, + *glyph*, and *width* attributes are kept public for back-compatibility, + but users wanting to draw the glyph themselves are encouraged to instead + load the font specified by `font_path` at `font_size`, warp it with the + effects specified by `font_effects`, and load the glyph at the FreeType + glyph `index`. """ - Signed, length *delta*+1 - - Read *delta*+1 bytes, returning the bytes interpreted as signed. - """ - return dvi._arg(delta+1, True) + def _get_pdftexmap_entry(self): + return PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname] + + @property + def font_path(self): + """The `~pathlib.Path` to the font for this glyph.""" + psfont = self._get_pdftexmap_entry() + if psfont.filename is None: + raise ValueError("No usable font file found for {} ({}); " + "the font may lack a Type-1 version" + .format(psfont.psname.decode("ascii"), + psfont.texname.decode("ascii"))) + return Path(psfont.filename) + + @property + def font_size(self): + """The font size.""" + return self.font.size + + @property + def font_effects(self): + """ + The "font effects" dict for this glyph. -def _arg_ulen1(dvi, delta): - """ - Unsigned length *delta*+1 + This dict contains the values for this glyph of SlantFont and + ExtendFont (if any), read off :file:`pdftex.map`. + """ + return self._get_pdftexmap_entry().effects - Read *delta*+1 bytes, returning the bytes interpreted as unsigned. - """ - return dvi._arg(delta+1, False) + @property + def index(self): + """ + The FreeType index of this glyph (that can be passed to FT_Load_Glyph). + """ + # See DviFont._index_dvi_to_freetype for details on the index mapping. + return self.font._index_dvi_to_freetype(self.glyph) + @property # To be deprecated together with font_size, font_effects. + def glyph_name_or_index(self): + """ + Either the glyph name or the native charmap glyph index. -def _arg_olen1(dvi, delta): - """ - Optionally signed, length *delta*+1 + If :file:`pdftex.map` specifies an encoding for this glyph's font, that + is a mapping of glyph indices to Adobe glyph names; use it to convert + dvi indices to glyph names. Callers can then convert glyph names to + glyph indices (with FT_Get_Name_Index/get_name_index), and load the + glyph using FT_Load_Glyph/load_glyph. - Read *delta*+1 bytes, returning the bytes interpreted as - unsigned integer for 0<=*delta*<3 and signed if *delta*==3. - """ - return dvi._arg(delta + 1, delta == 3) + If :file:`pdftex.map` specifies no encoding, the indices directly map + to the font's "native" charmap; glyphs should directly load using + FT_Load_Char/load_char after selecting the native charmap. + """ + entry = self._get_pdftexmap_entry() + return (_parse_enc(entry.encoding)[self.glyph] + if entry.encoding is not None else self.glyph) -_arg_mapping = dict(raw=_arg_raw, - u1=partial(_arg, 1, False), - u4=partial(_arg, 4, False), - s4=partial(_arg, 4, True), - slen=_arg_slen, - olen1=_arg_olen1, - slen1=_arg_slen1, - ulen1=_arg_ulen1) +# Opcode argument parsing +# +# Each of the following functions takes a Dvi object and delta, which is the +# difference between the opcode and the minimum opcode with the same meaning. +# Dvi opcodes often encode the number of argument bytes in this delta. +_arg_mapping = dict( + # raw: Return delta as is. + raw=lambda dvi, delta: delta, + # u1: Read 1 byte as an unsigned number. + u1=lambda dvi, delta: dvi._read_arg(1, signed=False), + # u4: Read 4 bytes as an unsigned number. + u4=lambda dvi, delta: dvi._read_arg(4, signed=False), + # s4: Read 4 bytes as a signed number. + s4=lambda dvi, delta: dvi._read_arg(4, signed=True), + # slen: Read delta bytes as a signed number, or None if delta is None. + slen=lambda dvi, delta: dvi._read_arg(delta, signed=True) if delta else None, + # slen1: Read (delta + 1) bytes as a signed number. + slen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=True), + # ulen1: Read (delta + 1) bytes as an unsigned number. + ulen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=False), + # olen1: Read (delta + 1) bytes as an unsigned number if less than 4 bytes, + # as a signed number if 4 bytes. + olen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=(delta == 3)), +) def _dispatch(table, min, max=None, state=None, args=('raw',)): @@ -139,30 +165,30 @@ def _dispatch(table, min, max=None, state=None, args=('raw',)): matches *state* if not None, reads arguments from the file according to *args*. - *table* - the dispatch table to be filled in - - *min* - minimum opcode for calling this function - - *max* - maximum opcode for calling this function, None if only *min* is allowed - - *state* - state of the Dvi object in which these opcodes are allowed - - *args* - sequence of argument specifications: - - ``'raw'``: opcode minus minimum - ``'u1'``: read one unsigned byte - ``'u4'``: read four bytes, treat as an unsigned number - ``'s4'``: read four bytes, treat as a signed number - ``'slen'``: read (opcode - minimum) bytes, treat as signed - ``'slen1'``: read (opcode - minimum + 1) bytes, treat as signed - ``'ulen1'``: read (opcode - minimum + 1) bytes, treat as unsigned - ``'olen1'``: read (opcode - minimum + 1) bytes, treat as unsigned - if under four bytes, signed if four bytes + Parameters + ---------- + table : dict[int, callable] + The dispatch table to be filled in. + + min, max : int + Range of opcodes that calls the registered function; *max* defaults to + *min*. + + state : _dvistate, optional + State of the Dvi object in which these opcodes are allowed. + + args : list[str], default: ['raw'] + Sequence of argument specifications: + + - 'raw': opcode minus minimum + - 'u1': read one unsigned byte + - 'u4': read four bytes, treat as an unsigned number + - 's4': read four bytes, treat as a signed number + - 'slen': read (opcode - minimum) bytes, treat as signed + - 'slen1': read (opcode - minimum + 1) bytes, treat as signed + - 'ulen1': read (opcode - minimum + 1) bytes, treat as unsigned + - 'olen1': read (opcode - minimum + 1) bytes, treat as unsigned + if under four bytes, signed if four bytes """ def decorate(method): get_args = [_arg_mapping[x] for x in args] @@ -185,6 +211,7 @@ def wrapper(self, byte): class Dvi: """ A reader for a dvi ("device-independent") file, as produced by TeX. + The current implementation can only iterate through pages in order, and does not even attempt to verify the postamble. @@ -212,15 +239,7 @@ def __init__(self, filename, dpi): self.dpi = dpi self.fonts = {} self.state = _dvistate.pre - self.baseline = self._get_baseline(filename) - - def _get_baseline(self, filename): - if dict.__getitem__(rcParams, 'text.latex.preview'): - baseline = Path(filename).with_suffix(".baseline") - if baseline.exists(): - height, depth, width = baseline.read_bytes().split() - return float(depth) - return None + self._missing_font = None def __enter__(self): """Context manager enter method, does nothing.""" @@ -261,13 +280,14 @@ def _output(self): Output the text and boxes belonging to the most recent page. page = dvi._output() """ - minx, miny, maxx, maxy = np.inf, np.inf, -np.inf, -np.inf + minx = miny = np.inf + maxx = maxy = -np.inf maxy_pure = -np.inf for elt in self.text + self.boxes: if isinstance(elt, Box): x, y, h, w = elt - e = 0 # zero depth - else: # glyph + e = 0 # zero depth + else: # glyph x, y, font, g, w = elt h, e = font._height_depth_of(g) minx = min(minx, x) @@ -290,10 +310,7 @@ def _output(self): # convert from TeX's "scaled points" to dpi units d = self.dpi / (72.27 * 2**16) - if self.baseline is None: - descent = (maxy - maxy_pure) * d - else: - descent = self.baseline + descent = (maxy - maxy_pure) * d text = [Text((x-minx)*d, (maxy-y)*d - descent, f, g, w*d) for (x, y, f, g, w) in self.text] @@ -311,20 +328,38 @@ def _read(self): # Pages appear to start with the sequence # bop (begin of page) # xxx comment + # # if using chemformula # down # push - # down, down + # down + # # if using xcolor + # down # push # down (possibly multiple) # push <= here, v is the baseline position. # etc. # (dviasm is useful to explore this structure.) + # Thus, we use the vertical position at the first time the stack depth + # reaches 3, while at least three "downs" have been executed (excluding + # those popped out (corresponding to the chemformula preamble)), as the + # baseline (the "down" count is necessary to handle xcolor). + down_stack = [0] self._baseline_v = None while True: byte = self.file.read(1)[0] self._dtable[byte](self, byte) + if self._missing_font: + raise self._missing_font.to_exception() + name = self._dtable[byte].__name__ + if name == "_push": + down_stack.append(down_stack[-1]) + elif name == "_pop": + down_stack.pop() + elif name == "_down": + down_stack[-1] += 1 if (self._baseline_v is None - and len(getattr(self, "stack", [])) == 3): + and len(getattr(self, "stack", [])) == 3 + and down_stack[-1] >= 4): self._baseline_v = self.v if byte == 140: # end of page return True @@ -332,27 +367,25 @@ def _read(self): self.close() return False - def _arg(self, nbytes, signed=False): + def _read_arg(self, nbytes, signed=False): """ - Read and return an integer argument *nbytes* long. + Read and return a big-endian integer *nbytes* long. Signedness is determined by the *signed* keyword. """ - buf = self.file.read(nbytes) - value = buf[0] - if signed and value >= 0x80: - value = value - 0x100 - for b in buf[1:]: - value = 0x100*value + b - return value + return int.from_bytes(self.file.read(nbytes), "big", signed=signed) @_dispatch(min=0, max=127, state=_dvistate.inpage) def _set_char_immediate(self, char): self._put_char_real(char) + if isinstance(self.fonts[self.f], cbook._ExceptionInfo): + return self.h += self.fonts[self.f]._width_of(char) @_dispatch(min=128, max=131, state=_dvistate.inpage, args=('olen1',)) def _set_char(self, char): self._put_char_real(char) + if isinstance(self.fonts[self.f], cbook._ExceptionInfo): + return self.h += self.fonts[self.f]._width_of(char) @_dispatch(132, state=_dvistate.inpage, args=('s4', 's4')) @@ -366,20 +399,22 @@ def _put_char(self, char): def _put_char_real(self, char): font = self.fonts[self.f] - if font._vf is None: + if isinstance(font, cbook._ExceptionInfo): + self._missing_font = font + elif font._vf is None: self.text.append(Text(self.h, self.v, font, char, font._width_of(char))) else: scale = font._scale for x, y, f, g, w in font._vf[char].text: - newf = DviFont(scale=_mul2012(scale, f._scale), + newf = DviFont(scale=_mul1220(scale, f._scale), tfm=f._tfm, texname=f.texname, vf=f._vf) - self.text.append(Text(self.h + _mul2012(x, scale), - self.v + _mul2012(y, scale), + self.text.append(Text(self.h + _mul1220(x, scale), + self.v + _mul1220(y, scale), newf, g, newf._width_of(g))) - self.boxes.extend([Box(self.h + _mul2012(x, scale), - self.v + _mul2012(y, scale), - _mul2012(a, scale), _mul2012(b, scale)) + self.boxes.extend([Box(self.h + _mul1220(x, scale), + self.v + _mul1220(y, scale), + _mul1220(a, scale), _mul1220(b, scale)) for x, y, a, b in font._vf[char].boxes]) @_dispatch(137, state=_dvistate.inpage, args=('s4', 's4')) @@ -397,7 +432,7 @@ def _nop(self, _): @_dispatch(139, state=_dvistate.outer, args=('s4',)*11) def _bop(self, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, p): self.state = _dvistate.inpage - self.h, self.v, self.w, self.x, self.y, self.z = 0, 0, 0, 0, 0, 0 + self.h = self.v = self.w = self.x = self.y = self.z = 0 self.stack = [] self.text = [] # list of Text objects self.boxes = [] # list of Box objects @@ -470,21 +505,29 @@ def _fnt_def(self, k, c, s, d, a, l): def _fnt_def_real(self, k, c, s, d, a, l): n = self.file.read(a + l) fontname = n[-l:].decode('ascii') - tfm = _tfmfile(fontname) - if tfm is None: - raise FileNotFoundError("missing font metrics file: %s" % fontname) + try: + tfm = _tfmfile(fontname) + except FileNotFoundError as exc: + # Explicitly allow defining missing fonts for Vf support; we only + # register an error when trying to load a glyph from a missing font + # and throw that error in Dvi._read. For Vf, _finalize_packet + # checks whether a missing glyph has been used, and in that case + # skips the glyph definition. + self.fonts[k] = cbook._ExceptionInfo.from_exception(exc) + return if c != 0 and tfm.checksum != 0 and c != tfm.checksum: - raise ValueError('tfm checksum mismatch: %s' % n) - - vf = _vffile(fontname) - + raise ValueError(f'tfm checksum mismatch: {n}') + try: + vf = _vffile(fontname) + except FileNotFoundError: + vf = None self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf) @_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1')) def _pre(self, i, num, den, mag, k): self.file.read(k) # comment in the dvi file if i != 2: - raise ValueError("Unknown dvi format %d" % i) + raise ValueError(f"Unknown dvi format {i}") if num != 25400000 or den != 7227 * 2**16: raise ValueError("Nonstandard units in dvi file") # meaning: TeX always uses those exact values, so it @@ -531,78 +574,112 @@ class DviFont: tfm : Tfm TeX font metrics for this font texname : bytes - Name of the font as used internally by TeX and friends, as an - ASCII bytestring. This is usually very different from any external - font names, and :class:`dviread.PsfontsMap` can be used to find - the external name of the font. + Name of the font as used internally by TeX and friends, as an ASCII + bytestring. This is usually very different from any external font + names; `PsfontsMap` can be used to find the external name of the font. vf : Vf A TeX "virtual font" file, or None if this font is not virtual. Attributes ---------- texname : bytes + fname : str + Compatibility shim so that DviFont can be used with + ``_backend_pdf_ps.CharacterTracker``; not a real filename. size : float Size of the font in Adobe points, converted from the slightly smaller TeX points. - widths : list - Widths of glyphs in glyph-space units, typically 1/1000ths of - the point size. - """ - __slots__ = ('texname', 'size', 'widths', '_scale', '_vf', '_tfm') + __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm', '_encoding') def __init__(self, scale, tfm, texname, vf): - cbook._check_isinstance(bytes, texname=texname) + _api.check_isinstance(bytes, texname=texname) self._scale = scale self._tfm = tfm self.texname = texname self._vf = vf self.size = scale * (72.0 / (72.27 * 2**16)) - try: - nchars = max(tfm.width) + 1 - except ValueError: - nchars = 0 - self.widths = [(1000*tfm.width.get(char, 0)) >> 20 - for char in range(nchars)] + self._encoding = None + + widths = _api.deprecated("3.11")(property(lambda self: [ + (1000 * self._tfm.width.get(char, 0)) >> 20 + for char in range(max(self._tfm.width, default=-1) + 1)])) + + @property + def fname(self): + """A fake filename""" + return self.texname.decode('latin-1') + + def _get_fontmap(self, string): + """Get the mapping from characters to the font that includes them. + + Each value maps to self; there is no fallback mechanism for DviFont. + """ + return {char: self for char in string} def __eq__(self, other): - return (type(self) == type(other) + return (type(self) is type(other) and self.texname == other.texname and self.size == other.size) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): - return "<{}: {}>".format(type(self).__name__, self.texname) + return f"<{type(self).__name__}: {self.texname}>" def _width_of(self, char): """Width of char in dvi units.""" - width = self._tfm.width.get(char, None) - if width is not None: - return _mul2012(width, self._scale) - _log.debug('No width for char %d in font %s.', char, self.texname) - return 0 + metrics = self._tfm.get_metrics(char) + if metrics is None: + _log.debug('No width for char %d in font %s.', char, self.texname) + return 0 + return _mul1220(metrics.tex_width, self._scale) def _height_depth_of(self, char): """Height and depth of char in dvi units.""" - result = [] - for metric, name in ((self._tfm.height, "height"), - (self._tfm.depth, "depth")): - value = metric.get(char, None) - if value is None: - _log.debug('No %s for char %d in font %s', - name, char, self.texname) - result.append(0) - else: - result.append(_mul2012(value, self._scale)) + metrics = self._tfm.get_metrics(char) + if metrics is None: + _log.debug('No metrics for char %d in font %s', char, self.texname) + return [0, 0] + hd = [ + _mul1220(metrics.tex_height, self._scale), + _mul1220(metrics.tex_depth, self._scale), + ] # cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent # so that TeX aligns equations properly - # (https://tex.stackexchange.com/questions/526103/), + # (https://tex.stackexchange.com/q/526103/) # but we actually care about the rasterization depth to align # the dvipng-generated images. if re.match(br'^cmsy\d+$', self.texname) and char == 0: - result[-1] = 0 - return result + hd[-1] = 0 + return hd + + def _index_dvi_to_freetype(self, idx): + """Convert dvi glyph indices to FreeType ones.""" + # Glyphs indices stored in the dvi file map to FreeType glyph indices + # (i.e., which can be passed to FT_Load_Glyph) in various ways: + # - if pdftex.map specifies an ".enc" file for the font, that file maps + # dvi indices to Adobe glyph names, which can then be converted to + # FreeType glyph indices with FT_Get_Name_Index. + # - if no ".enc" file is specified, then the font must be a Type 1 + # font, and dvi indices directly index into the font's CharStrings + # vector. + # - (xetex & luatex, currently unsupported, can also declare "native + # fonts", for which dvi indices are equal to FreeType indices.) + if self._encoding is None: + psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname] + if psfont.filename is None: + raise ValueError("No usable font file found for {} ({}); " + "the font may lack a Type-1 version" + .format(psfont.psname.decode("ascii"), + psfont.texname.decode("ascii"))) + face = font_manager.get_font(psfont.filename) + if psfont.encoding: + self._encoding = [face.get_name_index(name) + for name in _parse_enc(psfont.encoding)] + else: + self._encoding = face._get_type1_encoding_vector() + return self._encoding[idx] class Vf(Dvi): @@ -618,7 +695,7 @@ class Vf(Dvi): The virtual font format is a derivative of dvi: http://mirrors.ctan.org/info/knuth/virtual-fonts This class reuses some of the machinery of `Dvi` - but replaces the `_read` loop and dispatch mechanism. + but replaces the `!_read` loop and dispatch mechanism. Examples -------- @@ -646,8 +723,8 @@ def _read(self): Read one page from the file. Return True if successful, False if there were no more pages. """ - packet_char, packet_ends = None, None - packet_len, packet_width = None, None + packet_char = packet_ends = None + packet_len = packet_width = None while True: byte = self.file.read(1)[0] # If we are in a packet, execute the dvi instructions @@ -655,83 +732,101 @@ def _read(self): byte_at = self.file.tell()-1 if byte_at == packet_ends: self._finalize_packet(packet_char, packet_width) - packet_len, packet_char, packet_width = None, None, None + packet_len = packet_char = packet_width = None # fall through to out-of-packet code elif byte_at > packet_ends: raise ValueError("Packet length mismatch in vf file") else: if byte in (139, 140) or byte >= 243: - raise ValueError( - "Inappropriate opcode %d in vf file" % byte) + raise ValueError(f"Inappropriate opcode {byte} in vf file") Dvi._dtable[byte](self, byte) continue # We are outside a packet if byte < 242: # a short packet (length given by byte) packet_len = byte - packet_char, packet_width = self._arg(1), self._arg(3) + packet_char = self._read_arg(1) + packet_width = self._read_arg(3) packet_ends = self._init_packet(byte) self.state = _dvistate.inpage elif byte == 242: # a long packet - packet_len, packet_char, packet_width = \ - [self._arg(x) for x in (4, 4, 4)] + packet_len = self._read_arg(4) + packet_char = self._read_arg(4) + packet_width = self._read_arg(4) self._init_packet(packet_len) elif 243 <= byte <= 246: - k = self._arg(byte - 242, byte == 246) - c, s, d, a, l = [self._arg(x) for x in (4, 4, 4, 1, 1)] + k = self._read_arg(byte - 242, byte == 246) + c = self._read_arg(4) + s = self._read_arg(4) + d = self._read_arg(4) + a = self._read_arg(1) + l = self._read_arg(1) self._fnt_def_real(k, c, s, d, a, l) if self._first_font is None: self._first_font = k elif byte == 247: # preamble - i, k = self._arg(1), self._arg(1) + i = self._read_arg(1) + k = self._read_arg(1) x = self.file.read(k) - cs, ds = self._arg(4), self._arg(4) + cs = self._read_arg(4) + ds = self._read_arg(4) self._pre(i, x, cs, ds) elif byte == 248: # postamble (just some number of 248s) break else: - raise ValueError("Unknown vf opcode %d" % byte) + raise ValueError(f"Unknown vf opcode {byte}") def _init_packet(self, pl): if self.state != _dvistate.outer: raise ValueError("Misplaced packet in vf file") - self.h, self.v, self.w, self.x, self.y, self.z = 0, 0, 0, 0, 0, 0 - self.stack, self.text, self.boxes = [], [], [] + self.h = self.v = self.w = self.x = self.y = self.z = 0 + self.stack = [] + self.text = [] + self.boxes = [] self.f = self._first_font + self._missing_font = None return self.file.tell() + pl def _finalize_packet(self, packet_char, packet_width): - self._chars[packet_char] = Page( - text=self.text, boxes=self.boxes, width=packet_width, - height=None, descent=None) + if not self._missing_font: # Otherwise we don't have full glyph definition. + self._chars[packet_char] = Page( + text=self.text, boxes=self.boxes, width=packet_width, + height=None, descent=None) self.state = _dvistate.outer def _pre(self, i, x, cs, ds): if self.state is not _dvistate.pre: raise ValueError("pre command in middle of vf file") if i != 202: - raise ValueError("Unknown vf format %d" % i) + raise ValueError(f"Unknown vf format {i}") if len(x): _log.debug('vf file comment: %s', x) self.state = _dvistate.outer # cs = checksum, ds = design size -def _fix2comp(num): - """Convert from two's complement to negative.""" - assert 0 <= num < 2**32 - if num & 2**31: - return num - 2**32 - else: - return num - - -def _mul2012(num1, num2): - """Multiply two numbers in 20.12 fixed point format.""" +def _mul1220(num1, num2): + """Multiply two numbers in 12.20 fixed point format.""" # Separated into a function because >> has surprising precedence return (num1*num2) >> 20 +@dataclasses.dataclass(frozen=True, kw_only=True) +class TexMetrics: + """ + Metrics of a glyph, with TeX semantics. + + TeX metrics have different semantics from FreeType metrics: tex_width + corresponds to FreeType's ``advance`` (i.e., including whitespace padding); + tex_height to ``bearingY`` (how much the glyph extends over the baseline); + tex_depth to ``height - bearingY`` (how much the glyph extends under the + baseline, as a positive number). + """ + tex_width: int + tex_height: int + tex_depth: int + + class Tfm: """ A TeX Font Metric file. @@ -747,41 +842,44 @@ class Tfm: checksum : int Used for verifying against the dvi file. design_size : int - Design size of the font (unknown units) - width, height, depth : dict - Dimensions of each character, need to be scaled by the factor - specified in the dvi file. These are dicts because indexing may - not start from 0. + Design size of the font (in 12.20 TeX points); unused because it is + overridden by the scale factor specified in the dvi file. """ - __slots__ = ('checksum', 'design_size', 'width', 'height', 'depth') def __init__(self, filename): _log.debug('opening tfm file %s', filename) with open(filename, 'rb') as file: header1 = file.read(24) - lh, bc, ec, nw, nh, nd = \ - struct.unpack('!6H', header1[2:14]) + lh, bc, ec, nw, nh, nd = struct.unpack('!6H', header1[2:14]) _log.debug('lh=%d, bc=%d, ec=%d, nw=%d, nh=%d, nd=%d', lh, bc, ec, nw, nh, nd) header2 = file.read(4*lh) - self.checksum, self.design_size = \ - struct.unpack('!2I', header2[:8]) + self.checksum, self.design_size = struct.unpack('!2I', header2[:8]) # there is also encoding information etc. char_info = file.read(4*(ec-bc+1)) - widths = file.read(4*nw) - heights = file.read(4*nh) - depths = file.read(4*nd) - - self.width, self.height, self.depth = {}, {}, {} - widths, heights, depths = \ - [struct.unpack('!%dI' % (len(x)/4), x) - for x in (widths, heights, depths)] + widths = struct.unpack(f'!{nw}i', file.read(4*nw)) + heights = struct.unpack(f'!{nh}i', file.read(4*nh)) + depths = struct.unpack(f'!{nd}i', file.read(4*nd)) + self._glyph_metrics = {} for idx, char in enumerate(range(bc, ec+1)): byte0 = char_info[4*idx] byte1 = char_info[4*idx+1] - self.width[char] = _fix2comp(widths[byte0]) - self.height[char] = _fix2comp(heights[byte1 >> 4]) - self.depth[char] = _fix2comp(depths[byte1 & 0xf]) + self._glyph_metrics[char] = TexMetrics( + tex_width=widths[byte0], + tex_height=heights[byte1 >> 4], + tex_depth=depths[byte1 & 0xf], + ) + + def get_metrics(self, idx): + """Return a glyph's TexMetrics, or None if unavailable.""" + return self._glyph_metrics.get(idx) + + width = _api.deprecated("3.11", alternative="get_metrics")( + property(lambda self: {c: m.tex_width for c, m in self._glyph_metrics})) + height = _api.deprecated("3.11", alternative="get_metrics")( + property(lambda self: {c: m.tex_height for c, m in self._glyph_metrics})) + depth = _api.deprecated("3.11", alternative="get_metrics")( + property(lambda self: {c: m.tex_depth for c, m in self._glyph_metrics})) PsFont = namedtuple('PsFont', 'texname psname effects encoding filename') @@ -831,197 +929,152 @@ class PsfontsMap: {'slant': 0.16700000000000001} >>> entry.filename """ - __slots__ = ('_font', '_filename') + __slots__ = ('_filename', '_unparsed', '_parsed') # Create a filename -> PsfontsMap cache, so that calling # `PsfontsMap(filename)` with the same filename a second time immediately # returns the same object. - @lru_cache() + @lru_cache def __new__(cls, filename): self = object.__new__(cls) - self._font = {} self._filename = os.fsdecode(filename) + # Some TeX distributions have enormous pdftex.map files which would + # take hundreds of milliseconds to parse, but it is easy enough to just + # store the unparsed lines (keyed by the first word, which is the + # texname) and parse them on-demand. with open(filename, 'rb') as file: - self._parse(file) + self._unparsed = {} + for line in file: + tfmname = line.split(b' ', 1)[0] + self._unparsed.setdefault(tfmname, []).append(line) + self._parsed = {} return self def __getitem__(self, texname): assert isinstance(texname, bytes) + if texname in self._unparsed: + for line in self._unparsed.pop(texname): + if self._parse_and_cache_line(line): + break try: - result = self._font[texname] + return self._parsed[texname] except KeyError: - fmt = ('A PostScript file for the font whose TeX name is "{0}" ' - 'could not be found in the file "{1}". The dviread module ' - 'can only handle fonts that have an associated PostScript ' - 'font file. ' - 'This problem can often be solved by installing ' - 'a suitable PostScript font package in your (TeX) ' - 'package manager.') - msg = fmt.format(texname.decode('ascii'), self._filename) - msg = textwrap.fill(msg, break_on_hyphens=False, - break_long_words=False) - _log.info(msg) - raise - fn, enc = result.filename, result.encoding - if fn is not None and not fn.startswith(b'/'): - fn = find_tex_file(fn) - if enc is not None and not enc.startswith(b'/'): - enc = find_tex_file(result.encoding) - return result._replace(filename=fn, encoding=enc) - - def _parse(self, file): - """ - Parse the font mapping file. + raise LookupError( + f"The font map {self._filename!r} is missing a PostScript font " + f"associated to TeX font {texname.decode('ascii')!r}; this problem can " + f"often be solved by installing a suitable PostScript font package in " + f"your TeX package manager") from None - The format is, AFAIK: texname fontname [effects and filenames] - Effects are PostScript snippets like ".177 SlantFont", - filenames begin with one or two less-than signs. A filename - ending in enc is an encoding file, other filenames are font - files. This can be overridden with a left bracket: <[foobar - indicates an encoding file named foobar. - - There is some difference between [^"]+ )" | # quoted encoding marked by [ - "< (?P [^"]+.enc)" | # quoted encoding, ends in .enc - "< [^"]+ )" | # quoted font file name - " (?P [^"]+ )" | # quoted effects or font name - <\[ (?P \S+ ) | # encoding marked by [ - < (?P \S+ .enc) | # encoding, ends in .enc - < \S+ ) | # font file name - (?P \S+ ) # effects or font name - )''') - effects_re = re.compile( - br'''(?x) (?P -?[0-9]*(?:\.[0-9]+)) \s* SlantFont - | (?P-?[0-9]*(?:\.[0-9]+)) \s* ExtendFont''') - - lines = (line.strip() - for line in file - if not empty_re.match(line)) - for line in lines: - effects, encoding, filename = b'', None, None - words = word_re.finditer(line) - - # The named groups are mutually exclusive and are - # referenced below at an estimated order of probability of - # occurrence based on looking at my copy of pdftex.map. - # The font names are probably unquoted: - w = next(words) - texname = w.group('eff2') or w.group('eff1') - w = next(words) - psname = w.group('eff2') or w.group('eff1') - - for w in words: - # Any effects are almost always quoted: - eff = w.group('eff1') or w.group('eff2') - if eff: - effects = eff - continue - # Encoding files usually have the .enc suffix - # and almost never need quoting: - enc = (w.group('enc4') or w.group('enc3') or - w.group('enc2') or w.group('enc1')) - if enc: - if encoding is not None: - _log.debug('Multiple encodings for %s = %s', - texname, psname) - encoding = enc - continue - # File names are probably unquoted: - filename = w.group('file2') or w.group('file1') - - effects_dict = {} - for match in effects_re.finditer(effects): - slant = match.group('slant') - if slant: - effects_dict['slant'] = float(slant) - else: - effects_dict['extend'] = float(match.group('extend')) + # https://tex.stackexchange.com/q/10826/ + + if not line or line.startswith((b" ", b"%", b"*", b";", b"#")): + return + tfmname = basename = special = encodingfile = fontfile = None + is_subsetted = is_t1 = is_truetype = False + matches = re.finditer(br'"([^"]*)(?:"|$)|(\S+)', line) + for match in matches: + quoted, unquoted = match.groups() + if unquoted: + if unquoted.startswith(b"<<"): # font + fontfile = unquoted[2:] + elif unquoted.startswith(b"<["): # encoding + encodingfile = unquoted[2:] + elif unquoted.startswith(b"<"): # font or encoding + word = ( + # foo + unquoted[1:] + # < by itself => read the next word + or next(filter(None, next(matches).groups()))) + if word.endswith(b".enc"): + encodingfile = word + else: + fontfile = word + is_subsetted = True + elif tfmname is None: + tfmname = unquoted + elif basename is None: + basename = unquoted + elif quoted: + special = quoted + effects = {} + if special: + words = reversed(special.split()) + for word in words: + if word == b"SlantFont": + effects["slant"] = float(next(words)) + elif word == b"ExtendFont": + effects["extend"] = float(next(words)) + + # Verify some properties of the line that would cause it to be ignored + # otherwise. + if fontfile is not None: + if fontfile.endswith((b".ttf", b".ttc")): + is_truetype = True + elif not fontfile.endswith(b".otf"): + is_t1 = True + elif basename is not None: + is_t1 = True + if is_truetype and is_subsetted and encodingfile is None: + return + if not is_t1 and ("slant" in effects or "extend" in effects): + return + if abs(effects.get("slant", 0)) > 1: + return + if abs(effects.get("extend", 0)) > 2: + return + + if basename is None: + basename = tfmname + if encodingfile is not None: + encodingfile = find_tex_file(encodingfile) + if fontfile is not None: + fontfile = find_tex_file(fontfile) + self._parsed[tfmname] = PsFont( + texname=tfmname, psname=basename, effects=effects, + encoding=encodingfile, filename=fontfile) + return True - self._font[texname] = PsFont( - texname=texname, psname=psname, effects=effects_dict, - encoding=encoding, filename=filename) - -@cbook.deprecated("3.3") -class Encoding: +def _parse_enc(path): r""" Parse a \*.enc file referenced from a psfonts.map style file. - The format this class understands is a very limited subset of PostScript. - - Usage (subject to change):: - - for name in Encoding(filename): - whatever(name) - - Parameters - ---------- - filename : str or path-like - - Attributes - ---------- - encoding : list - List of character names - """ - __slots__ = ('encoding',) - - def __init__(self, filename): - with open(filename, 'rb') as file: - _log.debug('Parsing TeX encoding %s', filename) - self.encoding = self._parse(file) - _log.debug('Result: %s', self.encoding) - - def __iter__(self): - yield from self.encoding - - @staticmethod - def _parse(file): - lines = (line.split(b'%', 1)[0].strip() for line in file) - data = b''.join(lines) - beginning = data.find(b'[') - if beginning < 0: - raise ValueError("Cannot locate beginning of encoding in {}" - .format(file)) - data = data[beginning:] - end = data.find(b']') - if end < 0: - raise ValueError("Cannot locate end of encoding in {}" - .format(file)) - data = data[:end] - return re.findall(br'/([^][{}<>\s]+)', data) - - -# Note: this function should ultimately replace the Encoding class, which -# appears to be mostly broken: because it uses b''.join(), there is no -# whitespace left between glyph names (only slashes) so the final re.findall -# returns a single string with all glyph names. However this does not appear -# to bother backend_pdf, so that needs to be investigated more. (The fixed -# version below is necessary for textpath/backend_svg, though.) -def _parse_enc(path): - r""" - Parses a \*.enc file referenced from a psfonts.map style file. - The format this class understands is a very limited subset of PostScript. + The format supported by this function is a tiny subset of PostScript. Parameters ---------- - path : os.PathLike + path : `os.PathLike` Returns ------- list - The nth entry of the list is the PostScript glyph name of the nth - glyph. + The nth list item is the PostScript glyph name of the nth glyph. """ no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii")) array = re.search(r"(?s)\[(.*)\]", no_comments).group(1) @@ -1029,67 +1082,93 @@ def _parse_enc(path): if all(line.startswith("/") for line in lines): return [line[1:] for line in lines] else: - raise ValueError( - "Failed to parse {} as Postscript encoding".format(path)) + raise ValueError(f"Failed to parse {path} as Postscript encoding") + + +class _LuatexKpsewhich: + @cache # A singleton. + def __new__(cls): + self = object.__new__(cls) + self._proc = self._new_proc() + return self + + def _new_proc(self): + return subprocess.Popen( + ["luatex", "--luaonly", + str(cbook._get_data_path("kpsewhich.lua"))], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + def search(self, filename): + if self._proc.poll() is not None: # Dead, restart it. + self._proc = self._new_proc() + self._proc.stdin.write(os.fsencode(filename) + b"\n") + self._proc.stdin.flush() + out = self._proc.stdout.readline().rstrip() + return None if out == b"nil" else os.fsdecode(out) -@lru_cache() -def find_tex_file(filename, format=None): +@lru_cache +def find_tex_file(filename): """ - Find a file in the texmf tree. + Find a file in the texmf tree using kpathsea_. - Calls :program:`kpsewhich` which is an interface to the kpathsea - library [1]_. Most existing TeX distributions on Unix-like systems use - kpathsea. It is also available as part of MikTeX, a popular - distribution on Windows. + The kpathsea library, provided by most existing TeX distributions, both + on Unix-like systems and on Windows (MikTeX), is invoked via a long-lived + luatex process if luatex is installed, or via kpsewhich otherwise. - *If the file is not found, an empty string is returned*. + .. _kpathsea: https://www.tug.org/kpathsea/ Parameters ---------- filename : str or path-like - format : str or bytes - Used as the value of the ``--format`` option to :program:`kpsewhich`. - Could be e.g. 'tfm' or 'vf' to limit the search to that type of files. - References - ---------- - .. [1] `Kpathsea documentation `_ - The library that :program:`kpsewhich` is part of. + Raises + ------ + FileNotFoundError + If the file is not found. """ # we expect these to always be ascii encoded, but use utf-8 # out of caution if isinstance(filename, bytes): filename = filename.decode('utf-8', errors='replace') - if isinstance(format, bytes): - format = format.decode('utf-8', errors='replace') - - if os.name == 'nt': - # On Windows only, kpathsea can use utf-8 for cmd args and output. - # The `command_line_encoding` environment variable is set to force it - # to always use utf-8 encoding. See Matplotlib issue #11848. - kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'}, - 'encoding': 'utf-8'} - else: # On POSIX, run through the equivalent of os.fsdecode(). - kwargs = {'encoding': sys.getfilesystemencoding(), - 'errors': 'surrogatescape'} - - cmd = ['kpsewhich'] - if format is not None: - cmd += ['--format=' + format] - cmd += [filename] + try: - result = cbook._check_and_log_subprocess(cmd, _log, **kwargs) - except RuntimeError: - return '' - return result.rstrip('\n') + lk = _LuatexKpsewhich() + except FileNotFoundError: + lk = None # Fallback to directly calling kpsewhich, as below. + + if lk: + path = lk.search(filename) + else: + if sys.platform == 'win32': + # On Windows only, kpathsea can use utf-8 for cmd args and output. + # The `command_line_encoding` environment variable is set to force + # it to always use utf-8 encoding. See Matplotlib issue #11848. + kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'}, + 'encoding': 'utf-8'} + else: # On POSIX, run through the equivalent of os.fsdecode(). + kwargs = {'encoding': sys.getfilesystemencoding(), + 'errors': 'surrogateescape'} + + try: + path = (cbook._check_and_log_subprocess(['kpsewhich', filename], + _log, **kwargs) + .rstrip('\n')) + except (FileNotFoundError, RuntimeError): + path = None + + if path: + return path + else: + raise FileNotFoundError( + f"Matplotlib's TeX implementation searched for a file named " + f"{filename!r} in your texmf tree, but could not find it") -@lru_cache() +@lru_cache def _fontfile(cls, suffix, texname): - filename = find_tex_file(texname + suffix) - return cls(filename) if filename else None + return cls(find_tex_file(texname + suffix)) _tfmfile = partial(_fontfile, Tfm, ".tfm") @@ -1097,24 +1176,40 @@ def _fontfile(cls, suffix, texname): if __name__ == '__main__': - from argparse import ArgumentParser import itertools + from argparse import ArgumentParser + + import fontTools.agl + + from matplotlib.ft2font import FT2Font parser = ArgumentParser() parser.add_argument("filename") parser.add_argument("dpi", nargs="?", type=float, default=None) args = parser.parse_args() + + def _print_fields(*args): + print(" ".join(map("{:>11}".format, args))) + with Dvi(args.filename, args.dpi) as dvi: fontmap = PsfontsMap(find_tex_file('pdftex.map')) for page in dvi: - print('=== new page ===') + print(f"=== NEW PAGE === " + f"(w: {page.width}, h: {page.height}, d: {page.descent})") + print("--- GLYPHS ---") for font, group in itertools.groupby( page.text, lambda text: text.font): - print('font', font.texname, 'scaled', font._scale / 2 ** 20) + psfont = fontmap[font.texname] + fontpath = psfont.filename + print(f"font: {font.texname.decode('latin-1')} " + f"(scale: {font._scale / 2 ** 20}) at {fontpath}") + face = FT2Font(fontpath) + _print_fields("x", "y", "glyph", "chr", "w") for text in group: - print(text.x, text.y, text.glyph, - chr(text.glyph) if chr(text.glyph).isprintable() - else ".", - text.width) - for x, y, w, h in page.boxes: - print(x, y, 'BOX', w, h) + glyph_str = fontTools.agl.toUnicode(face.get_glyph_name(text.index)) + _print_fields(text.x, text.y, text.glyph, glyph_str, text.width) + if page.boxes: + print("--- BOXES ---") + _print_fields("x", "y", "h", "w") + for box in page.boxes: + _print_fields(box.x, box.y, box.height, box.width) diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi new file mode 100644 index 000000000000..12a9215b5308 --- /dev/null +++ b/lib/matplotlib/dviread.pyi @@ -0,0 +1,107 @@ +import dataclasses +from pathlib import Path +import io +import os +from enum import Enum +from collections.abc import Generator + +from typing import NamedTuple +from typing_extensions import Self # < Py 3.11 + +class _dvistate(Enum): + pre = ... + outer = ... + inpage = ... + post_post = ... + finale = ... + +class Page(NamedTuple): + text: list[Text] + boxes: list[Box] + height: int + width: int + descent: int + +class Box(NamedTuple): + x: int + y: int + height: int + width: int + +class Text(NamedTuple): + x: int + y: int + font: DviFont + glyph: int + width: int + @property + def font_path(self) -> Path: ... + @property + def font_size(self) -> float: ... + @property + def font_effects(self) -> dict[str, float]: ... + @property + def index(self) -> int: ... # type: ignore[override] + @property + def glyph_name_or_index(self) -> int | str: ... + +class Dvi: + file: io.BufferedReader + dpi: float | None + fonts: dict[int, DviFont] + state: _dvistate + def __init__(self, filename: str | os.PathLike, dpi: float | None) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, etype, evalue, etrace) -> None: ... + def __iter__(self) -> Generator[Page, None, None]: ... + def close(self) -> None: ... + +class DviFont: + texname: bytes + size: float + def __init__( + self, scale: float, tfm: Tfm, texname: bytes, vf: Vf | None + ) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + @property + def widths(self) -> list[int]: ... + @property + def fname(self) -> str: ... + +class Vf(Dvi): + def __init__(self, filename: str | os.PathLike) -> None: ... + def __getitem__(self, code: int) -> Page: ... + +@dataclasses.dataclass(frozen=True, kw_only=True) +class TexMetrics: + tex_width: int + tex_height: int + tex_depth: int + # work around mypy not respecting kw_only=True in stub files + __match_args__ = () + +class Tfm: + checksum: int + design_size: int + def __init__(self, filename: str | os.PathLike) -> None: ... + def get_metrics(self, idx: int) -> TexMetrics | None: ... + @property + def width(self) -> dict[int, int]: ... + @property + def height(self) -> dict[int, int]: ... + @property + def depth(self) -> dict[int, int]: ... + +class PsFont(NamedTuple): + texname: bytes + psname: bytes + effects: dict[str, float] + encoding: None | bytes + filename: str + +class PsfontsMap: + def __new__(cls, filename: str | os.PathLike) -> Self: ... + def __getitem__(self, texname: bytes) -> PsFont: ... + +def find_tex_file(filename: str | os.PathLike) -> str: ... diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 99867b991e5f..c15da7597acd 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3,2146 +3,3279 @@ `Figure` Top level `~matplotlib.artist.Artist`, which holds all plot elements. + Many methods are implemented in `FigureBase`. -`SubplotParams` - Control the default spacing between subplots. +`SubFigure` + A logical figure inside a figure, usually added to a figure (or parent `SubFigure`) + with `Figure.add_subfigure` or `Figure.subfigures` methods. + +Figures are typically created using pyplot methods `~.pyplot.figure`, +`~.pyplot.subplots`, and `~.pyplot.subplot_mosaic`. + +.. plot:: + :include-source: + + fig, ax = plt.subplots(figsize=(2, 2), facecolor='lightskyblue', + layout='constrained') + fig.suptitle('Figure') + ax.set_title('Axes', loc='left', fontstyle='oblique', fontsize='medium') + +Some situations call for directly instantiating a `~.figure.Figure` class, +usually inside an application of some sort (see :ref:`user_interfaces` for a +list of examples) . More information about Figures can be found at +:ref:`figure-intro`. """ +from contextlib import ExitStack import inspect +import itertools +import functools import logging from numbers import Integral +import threading import numpy as np import matplotlib as mpl -from matplotlib import docstring, projections -from matplotlib import __version__ as _mpl_version - -import matplotlib.artist as martist +from matplotlib import _blocking_input, backend_bases, _docstring, projections from matplotlib.artist import ( Artist, allow_rasterization, _finalize_rasterization) from matplotlib.backend_bases import ( - FigureCanvasBase, NonGuiException, MouseButton) + DrawEvent, FigureCanvasBase, NonGuiException, MouseButton, _get_renderer) +import matplotlib._api as _api import matplotlib.cbook as cbook import matplotlib.colorbar as cbar import matplotlib.image as mimage -from matplotlib.axes import Axes, SubplotBase, subplot_class_factory -from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput -from matplotlib.gridspec import GridSpec +from matplotlib.axes import Axes +from matplotlib.gridspec import GridSpec, SubplotParams +from matplotlib.layout_engine import ( + ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine, + PlaceHolderLayoutEngine +) import matplotlib.legend as mlegend from matplotlib.patches import Rectangle from matplotlib.text import Text from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo, TransformedBbox) -import matplotlib._layoutgrid as layoutgrid _log = logging.getLogger(__name__) def _stale_figure_callback(self, val): - if self.figure: - self.figure.stale = val + if (fig := self.get_figure(root=False)) is not None: + fig.stale = val -class _AxesStack(cbook.Stack): +class _AxesStack: """ - Specialization of `.Stack`, to handle all tracking of `~.axes.Axes` in a - `.Figure`. - - This stack stores ``key, (ind, axes)`` pairs, where: + Helper class to track Axes in a figure. - * **key** is a hash of the args and kwargs used in generating the Axes. - * **ind** is a serial index tracking the order in which axes were added. - - AxesStack is a callable; calling it returns the current axes. - The `current_key_axes` method returns the current key and associated axes. + Axes are tracked both in the order in which they have been added + (``self._axes`` insertion/iteration order) and in the separate "gca" stack + (which is the index to which they map in the ``self._axes`` dict). """ def __init__(self): - super().__init__() - self._ind = 0 + self._axes = {} # Mapping of Axes to "gca" order. + self._counter = itertools.count() def as_list(self): - """ - Return a list of the Axes instances that have been added to the figure. - """ - ia_list = [a for k, a in self._elements] - ia_list.sort() - return [a for i, a in ia_list] - - def get(self, key): - """ - Return the Axes instance that was added with *key*. - If it is not present, return *None*. - """ - item = dict(self._elements).get(key) - if item is None: - return None - cbook.warn_deprecated( - "2.1", - message="Adding an axes using the same arguments as a previous " - "axes currently reuses the earlier instance. In a future " - "version, a new instance will always be created and returned. " - "Meanwhile, this warning can be suppressed, and the future " - "behavior ensured, by passing a unique label to each axes " - "instance.") - return item[1] - - def _entry_from_axes(self, e): - ind, k = {a: (ind, k) for k, (ind, a) in self._elements}[e] - return (k, (ind, e)) + """List the Axes that have been added to the figure.""" + return [*self._axes] # This relies on dict preserving order. def remove(self, a): - """Remove the axes from the stack.""" - super().remove(self._entry_from_axes(a)) + """Remove the Axes from the stack.""" + self._axes.pop(a) def bubble(self, a): - """ - Move the given axes, which must already exist in the - stack, to the top. - """ - return super().bubble(self._entry_from_axes(a)) + """Move an Axes, which must already exist in the stack, to the top.""" + if a not in self._axes: + raise ValueError("Axes has not been added yet") + self._axes[a] = next(self._counter) - def add(self, key, a): - """ - Add Axes *a*, with key *key*, to the stack, and return the stack. + def add(self, a): + """Add an Axes to the stack, ignoring it if already present.""" + if a not in self._axes: + self._axes[a] = next(self._counter) - If *key* is unhashable, replace it by a unique, arbitrary object. + def current(self): + """Return the active Axes, or None if the stack is empty.""" + return max(self._axes, key=self._axes.__getitem__, default=None) - If *a* is already on the stack, don't add it again, but - return *None*. - """ - # All the error checking may be unnecessary; but this method - # is called so seldom that the overhead is negligible. - cbook._check_isinstance(Axes, a=a) - try: - hash(key) - except TypeError: - key = object() - - a_existing = self.get(key) - if a_existing is not None: - super().remove((key, a_existing)) - cbook._warn_external( - "key {!r} already existed; Axes is being replaced".format(key)) - # I don't think the above should ever happen. - - if a in self: - return None - self._ind += 1 - return super().push((key, (self._ind, a))) + def __getstate__(self): + return { + **vars(self), + "_counter": max(self._axes.values(), default=0) + } - def current_key_axes(self): - """ - Return a tuple of ``(key, axes)`` for the active axes. + def __setstate__(self, state): + next_counter = state.pop('_counter') + vars(self).update(state) + self._counter = itertools.count(next_counter) - If no axes exists on the stack, then returns ``(None, None)``. - """ - if not len(self._elements): - return self._default, self._default - else: - key, (index, axes) = self._elements[self._pos] - return key, axes - def __call__(self): - return self.current_key_axes()[1] +class FigureBase(Artist): + """ + Base class for `.Figure` and `.SubFigure` containing the methods that add + artists to the figure or subfigure, create Axes, etc. + """ + def __init__(self, **kwargs): + super().__init__() + # remove the non-figure artist _axes property + # as it makes no sense for a figure to be _in_ an Axes + # this is used by the property methods in the artist base class + # which are over-ridden in this class + del self._axes + + self._suptitle = None + self._supxlabel = None + self._supylabel = None + + # groupers to keep track of x, y labels and title we want to align. + # see self.align_xlabels, self.align_ylabels, + # self.align_titles, and axis._get_tick_boxes_siblings + self._align_label_groups = { + "x": cbook.Grouper(), + "y": cbook.Grouper(), + "title": cbook.Grouper() + } - def __contains__(self, a): - return a in self.as_list() + self._localaxes = [] # track all Axes + self.artists = [] + self.lines = [] + self.patches = [] + self.texts = [] + self.images = [] + self.legends = [] + self.subfigs = [] + self.stale = True + self.suppressComposite = None + self.set(**kwargs) + def _get_draw_artists(self, renderer): + """Also runs apply_aspect""" + artists = self.get_children() -@cbook.deprecated("3.2") -class AxesStack(_AxesStack): - pass + artists.remove(self.patch) + artists = sorted( + (artist for artist in artists if not artist.get_animated()), + key=lambda artist: artist.get_zorder()) + for ax in self._localaxes: + locator = ax.get_axes_locator() + ax.apply_aspect(locator(ax, renderer) if locator else None) + for child in ax.get_children(): + if hasattr(child, 'apply_aspect'): + locator = child.get_axes_locator() + child.apply_aspect( + locator(child, renderer) if locator else None) + return artists -class SubplotParams: - """ - A class to hold the parameters for a subplot. - """ - def __init__(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): + def autofmt_xdate( + self, bottom=0.2, rotation=30, ha='right', which='major'): """ - Defaults are given by :rc:`figure.subplot.[name]`. + Date ticklabels often overlap, so it is useful to rotate them + and right align them. Also, a common use case is a number of + subplots with shared x-axis where the x-axis is date data. The + ticklabels are often long, and it helps to rotate them on the + bottom subplot and turn them off on other subplots, as well as + turn off xlabels. Parameters ---------- - left : float - The position of the left edge of the subplots, - as a fraction of the figure width. - right : float - The position of the right edge of the subplots, - as a fraction of the figure width. - bottom : float - The position of the bottom edge of the subplots, - as a fraction of the figure height. - top : float - The position of the top edge of the subplots, - as a fraction of the figure height. - wspace : float - The width of the padding between subplots, - as a fraction of the average axes width. - hspace : float - The height of the padding between subplots, - as a fraction of the average axes height. - """ - self.validate = True - for key in ["left", "bottom", "right", "top", "wspace", "hspace"]: - setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"]) - self.update(left, bottom, right, top, wspace, hspace) - - def update(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): - """ - Update the dimensions of the passed parameters. *None* means unchanged. - """ - if self.validate: - if ((left if left is not None else self.left) - >= (right if right is not None else self.right)): - raise ValueError('left cannot be >= right') - if ((bottom if bottom is not None else self.bottom) - >= (top if top is not None else self.top)): - raise ValueError('bottom cannot be >= top') - if left is not None: - self.left = left - if right is not None: - self.right = right - if bottom is not None: - self.bottom = bottom - if top is not None: - self.top = top - if wspace is not None: - self.wspace = wspace - if hspace is not None: - self.hspace = hspace - - -class Figure(Artist): - """ - The top level container for all the plot elements. - - The Figure instance supports callbacks through a *callbacks* attribute - which is a `.CallbackRegistry` instance. The events you can connect to - are 'dpi_changed', and the callback will be called with ``func(fig)`` where - fig is the `Figure` instance. - - Attributes - ---------- - patch - The `.Rectangle` instance representing the figure background patch. - - suppressComposite - For multiple figure images, the figure will make composite images - depending on the renderer option_image_nocomposite function. If - *suppressComposite* is a boolean, this will override the renderer. - """ + bottom : float, default: 0.2 + The bottom of the subplots for `subplots_adjust`. + rotation : float, default: 30 degrees + The rotation angle of the xtick labels in degrees. + ha : {'left', 'center', 'right'}, default: 'right' + The horizontal alignment of the xticklabels. + which : {'major', 'minor', 'both'}, default: 'major' + Selects which ticklabels to rotate. + """ + _api.check_in_list(['major', 'minor', 'both'], which=which) + axes = [ax for ax in self.axes if ax._label != ''] + allsubplots = all(ax.get_subplotspec() for ax in axes) + if len(axes) == 1: + for label in self.axes[0].get_xticklabels(which=which): + label.set_ha(ha) + label.set_rotation(rotation) + else: + if allsubplots: + for ax in axes: + if ax.get_subplotspec().is_last_row(): + for label in ax.get_xticklabels(which=which): + label.set_ha(ha) + label.set_rotation(rotation) + else: + for label in ax.get_xticklabels(which=which): + label.set_visible(False) + ax.set_xlabel('') - def __str__(self): - return "Figure(%gx%g)" % tuple(self.bbox.size) + engine = self.get_layout_engine() + if allsubplots and (engine is None or engine.adjust_compatible): + self.subplots_adjust(bottom=bottom) + self.stale = True - def __repr__(self): - return "<{clsname} size {h:g}x{w:g} with {naxes} Axes>".format( - clsname=self.__class__.__name__, - h=self.bbox.size[0], w=self.bbox.size[1], - naxes=len(self.axes), - ) + def get_children(self): + """Get a list of artists contained in the figure.""" + return [self.patch, + *self.artists, + *self._localaxes, + *self.lines, + *self.patches, + *self.texts, + *self.images, + *self.legends, + *self.subfigs] - def __init__(self, - figsize=None, - dpi=None, - facecolor=None, - edgecolor=None, - linewidth=0.0, - frameon=None, - subplotpars=None, # rc figure.subplot.* - tight_layout=None, # rc figure.autolayout - constrained_layout=None, # rc figure.constrained_layout.use - ): + def get_figure(self, root=None): """ + Return the `.Figure` or `.SubFigure` instance the (Sub)Figure belongs to. + Parameters ---------- - figsize : 2-tuple of floats, default: :rc:`figure.figsize` - Figure dimension ``(width, height)`` in inches. - - dpi : float, default: :rc:`figure.dpi` - Dots per inch. - - facecolor : default: :rc:`figure.facecolor` - The figure patch facecolor. + root : bool, default=True + If False, return the (Sub)Figure this artist is on. If True, + return the root Figure for a nested tree of SubFigures. + + .. deprecated:: 3.10 + + From version 3.12 *root* will default to False. + """ + if self._root_figure is self: + # Top level Figure + return self + + if self._parent is self._root_figure: + # Return early to prevent the deprecation warning when *root* does not + # matter + return self._parent + + if root is None: + # When deprecation expires, consider removing the docstring and just + # inheriting the one from Artist. + message = ('From Matplotlib 3.12 SubFigure.get_figure will by default ' + 'return the direct parent figure, which may be a SubFigure. ' + 'To suppress this warning, pass the root parameter. Pass ' + '`True` to maintain the old behavior and `False` to opt-in to ' + 'the future behavior.') + _api.warn_deprecated('3.10', message=message) + root = True + + if root: + return self._root_figure + + return self._parent + + def set_figure(self, fig): + """ + .. deprecated:: 3.10 + Currently this method will raise an exception if *fig* is anything other + than the root `.Figure` this (Sub)Figure is on. In future it will always + raise an exception. + """ + no_switch = ("The parent and root figures of a (Sub)Figure are set at " + "instantiation and cannot be changed.") + if fig is self._root_figure: + _api.warn_deprecated( + "3.10", + message=(f"{no_switch} From Matplotlib 3.12 this operation will raise " + "an exception.")) + return - edgecolor : default: :rc:`figure.edgecolor` - The figure patch edge color. + raise ValueError(no_switch) - linewidth : float - The linewidth of the frame (i.e. the edge linewidth of the figure - patch). + figure = property(functools.partial(get_figure, root=True), set_figure, + doc=("The root `Figure`. To get the parent of a `SubFigure`, " + "use the `get_figure` method.")) - frameon : bool, default: :rc:`figure.frameon` - If ``False``, suppress drawing the figure background patch. + def contains(self, mouseevent): + """ + Test whether the mouse event occurred on the figure. - subplotpars : `SubplotParams` - Subplot parameters. If not given, the default subplot - parameters :rc:`figure.subplot.*` are used. + Returns + ------- + bool, {} + """ + if self._different_canvas(mouseevent): + return False, {} + inside = self.bbox.contains(mouseevent.x, mouseevent.y) + return inside, {} - tight_layout : bool or dict, default: :rc:`figure.autolayout` - If ``False`` use *subplotpars*. If ``True`` adjust subplot - parameters using `.tight_layout` with default padding. - When providing a dict containing the keys ``pad``, ``w_pad``, - ``h_pad``, and ``rect``, the default `.tight_layout` paddings - will be overridden. + def get_window_extent(self, renderer=None): + # docstring inherited + return self.bbox - constrained_layout : bool, default: :rc:`figure.constrained_layout.use` - If ``True`` use constrained layout to adjust positioning of plot - elements. Like ``tight_layout``, but designed to be more - flexible. See - :doc:`/tutorials/intermediate/constrainedlayout_guide` - for examples. (Note: does not work with `add_subplot` or - `~.pyplot.subplot2grid`.) + def _suplabels(self, t, info, **kwargs): """ - super().__init__() - # remove the non-figure artist _axes property - # as it makes no sense for a figure to be _in_ an axes - # this is used by the property methods in the artist base class - # which are over-ridden in this class - del self._axes - self.callbacks = cbook.CallbackRegistry() - - if figsize is None: - figsize = mpl.rcParams['figure.figsize'] - if dpi is None: - dpi = mpl.rcParams['figure.dpi'] - if facecolor is None: - facecolor = mpl.rcParams['figure.facecolor'] - if edgecolor is None: - edgecolor = mpl.rcParams['figure.edgecolor'] - if frameon is None: - frameon = mpl.rcParams['figure.frameon'] + Add a centered %(name)s to the figure. - if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): - raise ValueError('figure size must be positive finite not ' - f'{figsize}') - self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) + Parameters + ---------- + t : str + The %(name)s text. + x : float, default: %(x0)s + The x location of the text in figure coordinates. + y : float, default: %(y0)s + The y location of the text in figure coordinates. + horizontalalignment, ha : {'center', 'left', 'right'}, default: %(ha)s + The horizontal alignment of the text relative to (*x*, *y*). + verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \ +default: %(va)s + The vertical alignment of the text relative to (*x*, *y*). + fontsize, size : default: :rc:`figure.%(rc)ssize` + The font size of the text. See `.Text.set_size` for possible + values. + fontweight, weight : default: :rc:`figure.%(rc)sweight` + The font weight of the text. See `.Text.set_weight` for possible + values. - self.dpi_scale_trans = Affine2D().scale(dpi) - # do not use property as it will trigger - self._dpi = dpi - self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) + Returns + ------- + text + The `.Text` instance of the %(name)s. - self.transFigure = BboxTransformTo(self.bbox) + Other Parameters + ---------------- + fontproperties : None or dict, optional + A dict of font properties. If *fontproperties* is given the + default values for font size and weight are taken from the + `.FontProperties` defaults. :rc:`figure.%(rc)ssize` and + :rc:`figure.%(rc)sweight` are ignored in this case. - self.patch = Rectangle( - xy=(0, 0), width=1, height=1, visible=frameon, - facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, - # Don't let the figure patch influence bbox calculation. - in_layout=False) - self._set_artist_props(self.patch) - self.patch.set_antialiased(False) + **kwargs + Additional kwargs are `matplotlib.text.Text` properties. + """ - FigureCanvasBase(self) # Set self.canvas. - self._suptitle = None + x = kwargs.pop('x', None) + y = kwargs.pop('y', None) + if info['name'] in ['_supxlabel', '_suptitle']: + autopos = y is None + elif info['name'] == '_supylabel': + autopos = x is None + if x is None: + x = info['x0'] + if y is None: + y = info['y0'] - if subplotpars is None: - subplotpars = SubplotParams() + kwargs = cbook.normalize_kwargs(kwargs, Text) + kwargs.setdefault('horizontalalignment', info['ha']) + kwargs.setdefault('verticalalignment', info['va']) + kwargs.setdefault('rotation', info['rotation']) - self.subplotpars = subplotpars + if 'fontproperties' not in kwargs: + kwargs.setdefault('fontsize', mpl.rcParams[info['size']]) + kwargs.setdefault('fontweight', mpl.rcParams[info['weight']]) + + suplab = getattr(self, info['name']) + if suplab is not None: + suplab.set_text(t) + suplab.set_position((x, y)) + suplab.set(**kwargs) + else: + suplab = self.text(x, y, t, **kwargs) + setattr(self, info['name'], suplab) + suplab._autopos = autopos + self.stale = True + return suplab - # constrained_layout: - self._layoutgrid = None - self._constrained = False + @_docstring.Substitution(x0=0.5, y0=0.98, name='super title', ha='center', + va='top', rc='title') + @_docstring.copy(_suplabels) + def suptitle(self, t, **kwargs): + # docstring from _suplabels... + info = {'name': '_suptitle', 'x0': 0.5, 'y0': 0.98, + 'ha': 'center', 'va': 'top', 'rotation': 0, + 'size': 'figure.titlesize', 'weight': 'figure.titleweight'} + return self._suplabels(t, info, **kwargs) + + def get_suptitle(self): + """Return the suptitle as string or an empty string if not set.""" + text_obj = self._suptitle + return "" if text_obj is None else text_obj.get_text() + + @_docstring.Substitution(x0=0.5, y0=0.01, name='super xlabel', ha='center', + va='bottom', rc='label') + @_docstring.copy(_suplabels) + def supxlabel(self, t, **kwargs): + # docstring from _suplabels... + info = {'name': '_supxlabel', 'x0': 0.5, 'y0': 0.01, + 'ha': 'center', 'va': 'bottom', 'rotation': 0, + 'size': 'figure.labelsize', 'weight': 'figure.labelweight'} + return self._suplabels(t, info, **kwargs) + + def get_supxlabel(self): + """Return the supxlabel as string or an empty string if not set.""" + text_obj = self._supxlabel + return "" if text_obj is None else text_obj.get_text() + + @_docstring.Substitution(x0=0.02, y0=0.5, name='super ylabel', ha='left', + va='center', rc='label') + @_docstring.copy(_suplabels) + def supylabel(self, t, **kwargs): + # docstring from _suplabels... + info = {'name': '_supylabel', 'x0': 0.02, 'y0': 0.5, + 'ha': 'left', 'va': 'center', 'rotation': 'vertical', + 'rotation_mode': 'anchor', 'size': 'figure.labelsize', + 'weight': 'figure.labelweight'} + return self._suplabels(t, info, **kwargs) + + def get_supylabel(self): + """Return the supylabel as string or an empty string if not set.""" + text_obj = self._supylabel + return "" if text_obj is None else text_obj.get_text() - self.set_tight_layout(tight_layout) + def get_edgecolor(self): + """Get the edge color of the Figure rectangle.""" + return self.patch.get_edgecolor() - self._axstack = _AxesStack() # track all figure axes and current axes - self.clf() - self._cachedRenderer = None + def get_facecolor(self): + """Get the face color of the Figure rectangle.""" + return self.patch.get_facecolor() - self.set_constrained_layout(constrained_layout) - # stub for subpanels: - self.panels = [] - self.transPanel = self.transFigure + def get_frameon(self): + """ + Return the figure's background patch visibility, i.e. + whether the figure background will be drawn. Equivalent to + ``Figure.patch.get_visible()``. + """ + return self.patch.get_visible() - # groupers to keep track of x and y labels we want to align. - # see self.align_xlabels and self.align_ylabels and - # axis._get_tick_boxes_siblings - self._align_xlabel_grp = cbook.Grouper() - self._align_ylabel_grp = cbook.Grouper() + def set_linewidth(self, linewidth): + """ + Set the line width of the Figure rectangle. - # list of child gridspecs for this figure - self._gridspecs = [] + Parameters + ---------- + linewidth : number + """ + self.patch.set_linewidth(linewidth) - # TODO: I'd like to dynamically add the _repr_html_ method - # to the figure in the right context, but then IPython doesn't - # use it, for some reason. + def get_linewidth(self): + """ + Get the line width of the Figure rectangle. + """ + return self.patch.get_linewidth() - def _repr_html_(self): - # We can't use "isinstance" here, because then we'd end up importing - # webagg unconditionally. - if 'WebAgg' in type(self.canvas).__name__: - from matplotlib.backends import backend_webagg - return backend_webagg.ipython_inline_display(self) + def set_edgecolor(self, color): + """ + Set the edge color of the Figure rectangle. - def show(self, warn=True): + Parameters + ---------- + color : :mpltype:`color` """ - If using a GUI backend with pyplot, display the figure window. + self.patch.set_edgecolor(color) - If the figure was not created using `~.pyplot.figure`, it will lack - a `~.backend_bases.FigureManagerBase`, and this method will raise an - AttributeError. - - .. warning:: - - This does not manage an GUI event loop. Consequently, the figure - may only be shown briefly or not shown at all if you or your - environment are not managing an event loop. - - Proper use cases for `.Figure.show` include running this from a - GUI application or an IPython shell. - - If you're running a pure python shell or executing a non-GUI - python script, you should use `matplotlib.pyplot.show` instead, - which takes care of managing the event loop for you. + def set_facecolor(self, color): + """ + Set the face color of the Figure rectangle. Parameters ---------- - warn : bool, default: True - If ``True`` and we are not running headless (i.e. on Linux with an - unset DISPLAY), issue warning when called on a non-GUI backend. + color : :mpltype:`color` """ - if self.canvas.manager is None: - raise AttributeError( - "Figure.show works only for figures managed by pyplot, " - "normally created by pyplot.figure()") - try: - self.canvas.manager.show() - except NonGuiException as exc: - cbook._warn_external(str(exc)) + self.patch.set_facecolor(color) - def get_axes(self): + def set_frameon(self, b): """ - Return a list of axes in the Figure. You can access and modify the - axes in the Figure through this list. - - Do not modify the list itself. Instead, use `~Figure.add_axes`, - `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. + Set the figure's background patch visibility, i.e. + whether the figure background will be drawn. Equivalent to + ``Figure.patch.set_visible()``. - Note: This is equivalent to the property `~.Figure.axes`. + Parameters + ---------- + b : bool """ - return self._axstack.as_list() + self.patch.set_visible(b) + self.stale = True - axes = property(get_axes, doc=""" - List of axes in the Figure. You can access and modify the axes in the - Figure through this list. + frameon = property(get_frameon, set_frameon) - Do not modify the list itself. Instead, use "`~Figure.add_axes`, - `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. - """) + def add_artist(self, artist, clip=False): + """ + Add an `.Artist` to the figure. - def _get_dpi(self): - return self._dpi + Usually artists are added to `~.axes.Axes` objects using + `.Axes.add_artist`; this method can be used in the rare cases where + one needs to add artists directly to the figure instead. - def _set_dpi(self, dpi, forward=True): - """ Parameters ---------- - dpi : float + artist : `~matplotlib.artist.Artist` + The artist to add to the figure. If the added artist has no + transform previously set, its transform will be set to + ``figure.transSubfigure``. + clip : bool, default: False + Whether the added artist should be clipped by the figure patch. - forward : bool - Passed on to `~.Figure.set_size_inches` + Returns + ------- + `~matplotlib.artist.Artist` + The added artist. """ - if dpi == self._dpi: - # We don't want to cause undue events in backends. - return - self._dpi = dpi - self.dpi_scale_trans.clear().scale(dpi) - w, h = self.get_size_inches() - self.set_size_inches(w, h, forward=forward) - self.callbacks.process('dpi_changed', self) + artist.set_figure(self) + self.artists.append(artist) + artist._remove_method = self.artists.remove - dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.") + if not artist.is_transform_set(): + artist.set_transform(self.transSubfigure) - def get_tight_layout(self): - """Return whether `.tight_layout` is called when drawing.""" - return self._tight + if clip and artist.get_clip_path() is None: + artist.set_clip_path(self.patch) - def set_tight_layout(self, tight): + self.stale = True + return artist + + @_docstring.interpd + def add_axes(self, *args, **kwargs): """ - Set whether and how `.tight_layout` is called when drawing. + Add an `~.axes.Axes` to the figure. + + Call signatures:: + + add_axes(rect, projection=None, polar=False, **kwargs) + add_axes(ax) Parameters ---------- - tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None - If a bool, sets whether to call `.tight_layout` upon drawing. - If ``None``, use the ``figure.autolayout`` rcparam instead. - If a dict, pass it as kwargs to `.tight_layout`, overriding the - default paddings. - """ - if tight is None: - tight = mpl.rcParams['figure.autolayout'] - self._tight = bool(tight) - self._tight_parameters = tight if isinstance(tight, dict) else {} - self.stale = True + rect : tuple (left, bottom, width, height) + The dimensions (left, bottom, width, height) of the new + `~.axes.Axes`. All quantities are in fractions of figure width and + height. - def get_constrained_layout(self): - """ - Return whether constrained layout is being used. + projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ +'polar', 'rectilinear', str}, optional + The projection type of the `~.axes.Axes`. *str* is the name of + a custom projection, see `~matplotlib.projections`. The default + None results in a 'rectilinear' projection. - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. - """ - return self._constrained + polar : bool, default: False + If True, equivalent to projection='polar'. - def set_constrained_layout(self, constrained): - """ - Set whether ``constrained_layout`` is used upon drawing. If None, - :rc:`figure.constrained_layout.use` value will be used. + axes_class : subclass type of `~.axes.Axes`, optional + The `.axes.Axes` subclass that is instantiated. This parameter + is incompatible with *projection* and *polar*. See + :ref:`axisartist_users-guide-index` for examples. - When providing a dict containing the keys `w_pad`, `h_pad` - the default ``constrained_layout`` paddings will be - overridden. These pads are in inches and default to 3.0/72.0. - ``w_pad`` is the width padding and ``h_pad`` is the height padding. + sharex, sharey : `~matplotlib.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. + The axis will have the same limits, ticks, and scale as the axis + of the shared Axes. - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + label : str + A label for the returned Axes. - Parameters - ---------- - constrained : bool or dict or None - """ - self._constrained_layout_pads = dict() - self._constrained_layout_pads['w_pad'] = None - self._constrained_layout_pads['h_pad'] = None - self._constrained_layout_pads['wspace'] = None - self._constrained_layout_pads['hspace'] = None - if constrained is None: - constrained = mpl.rcParams['figure.constrained_layout.use'] - self._constrained = bool(constrained) - if isinstance(constrained, dict): - self.set_constrained_layout_pads(**constrained) - else: - self.set_constrained_layout_pads() + Returns + ------- + `~.axes.Axes`, or a subclass of `~.axes.Axes` + The returned Axes class depends on the projection used. It is + `~.axes.Axes` if rectilinear projection is used and + `.projections.polar.PolarAxes` if polar projection is used. - self.init_layoutgrid() + Other Parameters + ---------------- + **kwargs + This method also takes the keyword arguments for + the returned Axes class. The keyword arguments for the + rectilinear Axes class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used, see the actual Axes + class. - self.stale = True + %(Axes:kwdoc)s - def set_constrained_layout_pads(self, **kwargs): - """ - Set padding for ``constrained_layout``. Note the kwargs can be passed - as a dictionary ``fig.set_constrained_layout(**paddict)``. + Notes + ----- + In rare circumstances, `.add_axes` may be called with a single + argument, an Axes instance already created in the present figure but + not in the figure's list of Axes. - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + See Also + -------- + .Figure.add_subplot + .pyplot.subplot + .pyplot.axes + .Figure.subplots + .pyplot.subplots - Parameters - ---------- - w_pad : float - Width padding in inches. This is the pad around axes - and is meant to make sure there is enough room for fonts to - look good. Defaults to 3 pts = 0.04167 inches + Examples + -------- + Some simple examples:: - h_pad : float - Height padding in inches. Defaults to 3 pts. + rect = l, b, w, h + fig = plt.figure() + fig.add_axes(rect) + fig.add_axes(rect, frameon=False, facecolor='g') + fig.add_axes(rect, polar=True) + ax = fig.add_axes(rect, projection='polar') + fig.delaxes(ax) + fig.add_axes(ax) + """ - wspace : float - Width padding between subplots, expressed as a fraction of the - subplot width. The total padding ends up being w_pad + wspace. + if not len(args) and 'rect' not in kwargs: + raise TypeError("add_axes() missing 1 required positional argument: 'rect'") + elif 'rect' in kwargs: + if len(args): + raise TypeError("add_axes() got multiple values for argument 'rect'") + args = (kwargs.pop('rect'), ) + if len(args) != 1: + raise _api.nargs_error("add_axes", 1, len(args)) - hspace : float - Height padding between subplots, expressed as a fraction of the - subplot width. The total padding ends up being h_pad + hspace. + if isinstance(args[0], Axes): + a, = args + key = a._projection_init + if a.get_figure(root=False) is not self: + raise ValueError( + "The Axes must have been created in the present figure") + else: + rect, = args + if not np.isfinite(rect).all(): + raise ValueError(f'all entries in rect must be finite not {rect}') + projection_class, pkw = self._process_projection_requirements(**kwargs) - """ + # create the new Axes using the Axes class given + a = projection_class(self, rect, **pkw) + key = (projection_class, pkw) - todo = ['w_pad', 'h_pad', 'wspace', 'hspace'] - for td in todo: - if td in kwargs and kwargs[td] is not None: - self._constrained_layout_pads[td] = kwargs[td] - else: - self._constrained_layout_pads[td] = ( - mpl.rcParams['figure.constrained_layout.' + td]) + return self._add_axes_internal(a, key) - def get_constrained_layout_pads(self, relative=False): + @_docstring.interpd + def add_subplot(self, *args, **kwargs): """ - Get padding for ``constrained_layout``. + Add an `~.axes.Axes` to the figure as part of a subplot arrangement. - Returns a list of ``w_pad, h_pad`` in inches and - ``wspace`` and ``hspace`` as fractions of the subplot. + Call signatures:: - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + add_subplot(nrows, ncols, index, **kwargs) + add_subplot(pos, **kwargs) + add_subplot(ax) + add_subplot() Parameters ---------- - relative : bool - If `True`, then convert from inches to figure relative. - """ - w_pad = self._constrained_layout_pads['w_pad'] - h_pad = self._constrained_layout_pads['h_pad'] - wspace = self._constrained_layout_pads['wspace'] - hspace = self._constrained_layout_pads['hspace'] + *args : int, (int, int, *index*), or `.SubplotSpec`, default: (1, 1, 1) + The position of the subplot described by one of - if relative and (w_pad is not None or h_pad is not None): - renderer0 = layoutgrid.get_renderer(self) - dpi = renderer0.dpi - w_pad = w_pad * dpi / renderer0.width - h_pad = h_pad * dpi / renderer0.height + - Three integers (*nrows*, *ncols*, *index*). The subplot will + take the *index* position on a grid with *nrows* rows and + *ncols* columns. *index* starts at 1 in the upper left corner + and increases to the right. *index* can also be a two-tuple + specifying the (*first*, *last*) indices (1-based, and including + *last*) of the subplot, e.g., ``fig.add_subplot(3, 1, (1, 2))`` + makes a subplot that spans the upper 2/3 of the figure. + - A 3-digit integer. The digits are interpreted as if given + separately as three single-digit integers, i.e. + ``fig.add_subplot(235)`` is the same as + ``fig.add_subplot(2, 3, 5)``. Note that this can only be used + if there are no more than 9 subplots. + - A `.SubplotSpec`. - return w_pad, h_pad, wspace, hspace + In rare circumstances, `.add_subplot` may be called with a single + argument, a subplot Axes instance already created in the + present figure but not in the figure's list of Axes. - def autofmt_xdate( - self, bottom=0.2, rotation=30, ha='right', which='major'): - """ - Date ticklabels often overlap, so it is useful to rotate them - and right align them. Also, a common use case is a number of - subplots with shared xaxes where the x-axis is date data. The - ticklabels are often long, and it helps to rotate them on the - bottom subplot and turn them off on other subplots, as well as - turn off xlabels. + projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ +'polar', 'rectilinear', str}, optional + The projection type of the subplot (`~.axes.Axes`). *str* is the + name of a custom projection, see `~matplotlib.projections`. The + default None results in a 'rectilinear' projection. - Parameters - ---------- - bottom : float, default: 0.2 - The bottom of the subplots for `subplots_adjust`. - rotation : float, default: 30 degrees - The rotation angle of the xtick labels in degrees. - ha : {'left', 'center', 'right'}, default: 'right' - The horizontal alignment of the xticklabels. - which : {'major', 'minor', 'both'}, default: 'major' - Selects which ticklabels to rotate. - """ - if which is None: - cbook.warn_deprecated( - "3.3", message="Support for passing which=None to mean " - "which='major' is deprecated since %(since)s and will be " - "removed %(removal)s.") - allsubplots = all(hasattr(ax, 'is_last_row') for ax in self.axes) - if len(self.axes) == 1: - for label in self.axes[0].get_xticklabels(which=which): - label.set_ha(ha) - label.set_rotation(rotation) - else: - if allsubplots: - for ax in self.get_axes(): - if ax.is_last_row(): - for label in ax.get_xticklabels(which=which): - label.set_ha(ha) - label.set_rotation(rotation) - else: - for label in ax.get_xticklabels(which=which): - label.set_visible(False) - ax.set_xlabel('') + polar : bool, default: False + If True, equivalent to projection='polar'. - if allsubplots: - self.subplots_adjust(bottom=bottom) - self.stale = True + axes_class : subclass type of `~.axes.Axes`, optional + The `.axes.Axes` subclass that is instantiated. This parameter + is incompatible with *projection* and *polar*. See + :ref:`axisartist_users-guide-index` for examples. - def get_children(self): - """Get a list of artists contained in the figure.""" - return [self.patch, - *self.artists, - *self.axes, - *self.lines, - *self.patches, - *self.texts, - *self.images, - *self.legends] + sharex, sharey : `~matplotlib.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. + The axis will have the same limits, ticks, and scale as the axis + of the shared Axes. - def contains(self, mouseevent): - """ - Test whether the mouse event occurred on the figure. + label : str + A label for the returned Axes. Returns ------- - bool, {} - """ - inside, info = self._default_contains(mouseevent, figure=self) - if inside is not None: - return inside, info - inside = self.bbox.contains(mouseevent.x, mouseevent.y) - return inside, {} + `~.axes.Axes` - def get_window_extent(self, *args, **kwargs): - """ - Return the figure bounding box in display space. Arguments are ignored. - """ - return self.bbox - - def suptitle(self, t, **kwargs): - """ - Add a centered title to the figure. - - Parameters - ---------- - t : str - The title text. - - x : float, default 0.5 - The x location of the text in figure coordinates. - - y : float, default 0.98 - The y location of the text in figure coordinates. - - horizontalalignment, ha : {'center', 'left', right'}, default: 'center' - The horizontal alignment of the text relative to (*x*, *y*). - - verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \ -default: 'top' - The vertical alignment of the text relative to (*x*, *y*). - - fontsize, size : default: :rc:`figure.titlesize` - The font size of the text. See `.Text.set_size` for possible - values. - - fontweight, weight : default: :rc:`figure.titleweight` - The font weight of the text. See `.Text.set_weight` for possible - values. - - Returns - ------- - `.Text` - The instance of the title. + The Axes of the subplot. The returned Axes can actually be an + instance of a subclass, such as `.projections.polar.PolarAxes` for + polar projections. Other Parameters ---------------- - fontproperties : None or dict, optional - A dict of font properties. If *fontproperties* is given the - default values for font size and weight are taken from the - `.FontProperties` defaults. :rc:`figure.titlesize` and - :rc:`figure.titleweight` are ignored in this case. - **kwargs - Additional kwargs are `matplotlib.text.Text` properties. + This method also takes the keyword arguments for the returned Axes + base class; except for the *figure* argument. The keyword arguments + for the rectilinear base class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used. + + %(Axes:kwdoc)s + + See Also + -------- + .Figure.add_axes + .pyplot.subplot + .pyplot.axes + .Figure.subplots + .pyplot.subplots Examples -------- - >>> fig.suptitle('This is the figure title', fontsize=12) - """ - manual_position = ('x' in kwargs or 'y' in kwargs) + :: + + fig = plt.figure() - x = kwargs.pop('x', 0.5) - y = kwargs.pop('y', 0.98) + fig.add_subplot(231) + ax1 = fig.add_subplot(2, 3, 1) # equivalent but more general - if 'horizontalalignment' not in kwargs and 'ha' not in kwargs: - kwargs['horizontalalignment'] = 'center' - if 'verticalalignment' not in kwargs and 'va' not in kwargs: - kwargs['verticalalignment'] = 'top' + fig.add_subplot(232, frameon=False) # subplot with no frame + fig.add_subplot(233, projection='polar') # polar subplot + fig.add_subplot(234, sharex=ax1) # subplot sharing x-axis with ax1 + fig.add_subplot(235, facecolor="red") # red subplot - if 'fontproperties' not in kwargs: - if 'fontsize' not in kwargs and 'size' not in kwargs: - kwargs['size'] = mpl.rcParams['figure.titlesize'] - if 'fontweight' not in kwargs and 'weight' not in kwargs: - kwargs['weight'] = mpl.rcParams['figure.titleweight'] - - sup = self.text(x, y, t, **kwargs) - if self._suptitle is not None: - self._suptitle.set_text(t) - self._suptitle.set_position((x, y)) - self._suptitle.update_from(sup) - sup.remove() + ax1.remove() # delete ax1 from the figure + fig.add_subplot(ax1) # add ax1 back to the figure + """ + if 'figure' in kwargs: + # Axes itself allows for a 'figure' kwarg, but since we want to + # bind the created Axes to self, it is not allowed here. + raise _api.kwarg_error("add_subplot", "figure") + + if (len(args) == 1 + and isinstance(args[0], mpl.axes._base._AxesBase) + and args[0].get_subplotspec()): + ax = args[0] + key = ax._projection_init + if ax.get_figure(root=False) is not self: + raise ValueError("The Axes must have been created in " + "the present figure") else: - self._suptitle = sup - if manual_position: - self._suptitle.set_in_layout(False) + if not args: + args = (1, 1, 1) + # Normalize correct ijk values to (i, j, k) here so that + # add_subplot(211) == add_subplot(2, 1, 1). Invalid values will + # trigger errors later (via SubplotSpec._from_subplot_args). + if (len(args) == 1 and isinstance(args[0], Integral) + and 100 <= args[0] <= 999): + args = tuple(map(int, str(args[0]))) + projection_class, pkw = self._process_projection_requirements(**kwargs) + ax = projection_class(self, *args, **pkw) + key = (projection_class, pkw) + return self._add_axes_internal(ax, key) + + def _add_axes_internal(self, ax, key): + """Private helper for `add_axes` and `add_subplot`.""" + self._axstack.add(ax) + if ax not in self._localaxes: + self._localaxes.append(ax) + self.sca(ax) + ax._remove_method = self.delaxes + # this is to support plt.subplot's re-selection logic + ax._projection_init = key self.stale = True - return self._suptitle + ax.stale_callback = _stale_figure_callback + return ax - def set_canvas(self, canvas): + def subplots(self, nrows=1, ncols=1, *, sharex=False, sharey=False, + squeeze=True, width_ratios=None, height_ratios=None, + subplot_kw=None, gridspec_kw=None): """ - Set the canvas that contains the figure + Add a set of subplots to this figure. + + This utility wrapper makes it convenient to create common layouts of + subplots in a single call. Parameters ---------- - canvas : FigureCanvas - """ - self.canvas = canvas + nrows, ncols : int, default: 1 + Number of rows/columns of the subplot grid. - def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, - vmin=None, vmax=None, origin=None, resize=False, **kwargs): - """ - Add a non-resampled image to the figure. + sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False + Controls sharing of x-axis (*sharex*) or y-axis (*sharey*): - The image is attached to the lower or upper left corner depending on - *origin*. + - True or 'all': x- or y-axis will be shared among all subplots. + - False or 'none': each subplot x- or y-axis will be independent. + - 'row': each subplot row will share an x- or y-axis. + - 'col': each subplot column will share an x- or y-axis. - Parameters - ---------- - X - The image data. This is an array of one of the following shapes: + When subplots have a shared x-axis along a column, only the x tick + labels of the bottom subplot are created. Similarly, when subplots + have a shared y-axis along a row, only the y tick labels of the + first column subplot are created. To later turn other subplots' + ticklabels on, use `~matplotlib.axes.Axes.tick_params`. - - MxN: luminance (grayscale) values - - MxNx3: RGB values - - MxNx4: RGBA values + When subplots have a shared axis that has units, calling + `.Axis.set_units` will update each axis with the new units. - xo, yo : int - The *x*/*y* image offset in pixels. + Note that it is not possible to unshare axes. - alpha : None or float - The alpha blending value. + squeeze : bool, default: True + - If True, extra dimensions are squeezed out from the returned + array of Axes: - norm : `matplotlib.colors.Normalize` - A `.Normalize` instance to map the luminance to the - interval [0, 1]. + - if only one subplot is constructed (nrows=ncols=1), the + resulting single Axes object is returned as a scalar. + - for Nx1 or 1xM subplots, the returned object is a 1D numpy + object array of Axes objects. + - for NxM, subplots with N>1 and M>1 are returned as a 2D array. - cmap : str or `matplotlib.colors.Colormap`, default: :rc:`image.cmap` - The colormap to use. + - If False, no squeezing at all is done: the returned Axes object + is always a 2D array containing Axes instances, even if it ends + up being 1x1. - vmin, vmax : float - If *norm* is not given, these values set the data limits for the - colormap. + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. Equivalent + to ``gridspec_kw={'width_ratios': [...]}``. - origin : {'upper', 'lower'}, default: :rc:`image.origin` - Indicates where the [0, 0] index of the array is in the upper left - or lower left corner of the axes. + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. Equivalent + to ``gridspec_kw={'height_ratios': [...]}``. - resize : bool - If *True*, resize the figure to match the given image size. + subplot_kw : dict, optional + Dict with keywords passed to the `.Figure.add_subplot` call used to + create each subplot. + + gridspec_kw : dict, optional + Dict with keywords passed to the + `~matplotlib.gridspec.GridSpec` constructor used to create + the grid the subplots are placed on. Returns ------- - `matplotlib.image.FigureImage` - - Other Parameters - ---------------- - **kwargs - Additional kwargs are `.Artist` kwargs passed on to `.FigureImage`. + `~.axes.Axes` or array of Axes + Either a single `~matplotlib.axes.Axes` object or an array of Axes + objects if more than one subplot was created. The dimensions of the + resulting array can be controlled with the *squeeze* keyword, see + above. - Notes - ----- - figimage complements the axes image (`~matplotlib.axes.Axes.imshow`) - which will be resampled to fit the current axes. If you want - a resampled image to fill the entire figure, you can define an - `~matplotlib.axes.Axes` with extent [0, 0, 1, 1]. + See Also + -------- + .pyplot.subplots + .Figure.add_subplot + .pyplot.subplot Examples -------- :: - f = plt.figure() - nx = int(f.get_figwidth() * f.dpi) - ny = int(f.get_figheight() * f.dpi) - data = np.random.random((ny, nx)) - f.figimage(data) - plt.show() - """ - if resize: - dpi = self.get_dpi() - figsize = [x / dpi for x in (X.shape[1], X.shape[0])] - self.set_size_inches(figsize, forward=True) + # First create some toy data: + x = np.linspace(0, 2*np.pi, 400) + y = np.sin(x**2) - im = mimage.FigureImage(self, cmap, norm, xo, yo, origin, **kwargs) - im.stale_callback = _stale_figure_callback + # Create a figure + fig = plt.figure() - im.set_array(X) - im.set_alpha(alpha) - if norm is None: - im.set_clim(vmin, vmax) - self.images.append(im) - im._remove_method = self.images.remove - self.stale = True - return im + # Create a subplot + ax = fig.subplots() + ax.plot(x, y) + ax.set_title('Simple plot') - def set_size_inches(self, w, h=None, forward=True): + # Create two subplots and unpack the output array immediately + ax1, ax2 = fig.subplots(1, 2, sharey=True) + ax1.plot(x, y) + ax1.set_title('Sharing Y axis') + ax2.scatter(x, y) + + # Create four polar Axes and access them through the returned array + axes = fig.subplots(2, 2, subplot_kw=dict(projection='polar')) + axes[0, 0].plot(x, y) + axes[1, 1].scatter(x, y) + + # Share an X-axis with each column of subplots + fig.subplots(2, 2, sharex='col') + + # Share a Y-axis with each row of subplots + fig.subplots(2, 2, sharey='row') + + # Share both X- and Y-axes with all subplots + fig.subplots(2, 2, sharex='all', sharey='all') + + # Note that this is the same as + fig.subplots(2, 2, sharex=True, sharey=True) """ - Set the figure size in inches. + gridspec_kw = dict(gridspec_kw or {}) + if height_ratios is not None: + if 'height_ratios' in gridspec_kw: + raise ValueError("'height_ratios' must not be defined both as " + "parameter and as key in 'gridspec_kw'") + gridspec_kw['height_ratios'] = height_ratios + if width_ratios is not None: + if 'width_ratios' in gridspec_kw: + raise ValueError("'width_ratios' must not be defined both as " + "parameter and as key in 'gridspec_kw'") + gridspec_kw['width_ratios'] = width_ratios + + gs = self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw) + axs = gs.subplots(sharex=sharex, sharey=sharey, squeeze=squeeze, + subplot_kw=subplot_kw) + return axs - Call signatures:: + def delaxes(self, ax): + """ + Remove the `~.axes.Axes` *ax* from the figure; update the current Axes. + """ + self._remove_axes(ax, owners=[self._axstack, self._localaxes]) - fig.set_size_inches(w, h) # OR - fig.set_size_inches((w, h)) + def _remove_axes(self, ax, owners): + """ + Common helper for removal of standard Axes (via delaxes) and of child Axes. Parameters ---------- - w : (float, float) or float - Width and height in inches (if height not specified as a separate - argument) or width. - h : float - Height in inches. - forward : bool, default: True - If ``True``, the canvas size is automatically updated, e.g., - you can resize the figure window from the shell. + ax : `~.AxesBase` + The Axes to remove. + owners + List of objects (list or _AxesStack) "owning" the Axes, from which the Axes + will be remove()d. + """ + for owner in owners: + owner.remove(ax) - See Also - -------- - matplotlib.figure.Figure.get_size_inches - matplotlib.figure.Figure.set_figwidth - matplotlib.figure.Figure.set_figheight + self._axobservers.process("_axes_change_event", self) + self.stale = True + self._root_figure.canvas.release_mouse(ax) + + for name in ax._axis_names: # Break link between any shared Axes + grouper = ax._shared_axes[name] + siblings = [other for other in grouper.get_siblings(ax) if other is not ax] + if not siblings: # Axes was not shared along this axis; we're done. + continue + grouper.remove(ax) + # Formatters and locators may previously have been associated with the now + # removed axis. Update them to point to an axis still there (we can pick + # any of them, and use the first sibling). + remaining_axis = siblings[0]._axis_map[name] + remaining_axis.get_major_formatter().set_axis(remaining_axis) + remaining_axis.get_major_locator().set_axis(remaining_axis) + remaining_axis.get_minor_formatter().set_axis(remaining_axis) + remaining_axis.get_minor_locator().set_axis(remaining_axis) + + ax._twinned_axes.remove(ax) # Break link between any twinned Axes. - Notes - ----- - To transform from pixels to inches divide by `Figure.dpi`. + def clear(self, keep_observers=False): """ - if h is None: # Got called with a single pair as argument. - w, h = w - size = np.array([w, h]) - if not np.isfinite(size).all() or (size < 0).any(): - raise ValueError(f'figure size must be positive finite not {size}') - self.bbox_inches.p1 = size - if forward: - canvas = getattr(self, 'canvas') - if canvas is not None: - dpi_ratio = getattr(canvas, '_dpi_ratio', 1) - manager = getattr(canvas, 'manager', None) - if manager is not None: - manager.resize(*(size * self.dpi / dpi_ratio).astype(int)) - self.stale = True + Clear the figure. - def get_size_inches(self): + Parameters + ---------- + keep_observers : bool, default: False + Set *keep_observers* to True if, for example, + a gui widget is tracking the Axes in the figure. """ - Return the current size of the figure in inches. + self.suppressComposite = None - Returns - ------- - ndarray - The size (width, height) of the figure in inches. + # first clear the Axes in any subfigures + for subfig in self.subfigs: + subfig.clear(keep_observers=keep_observers) + self.subfigs = [] - See Also - -------- - matplotlib.figure.Figure.set_size_inches - matplotlib.figure.Figure.get_figwidth - matplotlib.figure.Figure.get_figheight + for ax in tuple(self.axes): # Iterate over the copy. + ax.clear() + self.delaxes(ax) # Remove ax from self._axstack. - Notes - ----- - The size in pixels can be obtained by multiplying with `Figure.dpi`. + self.artists = [] + self.lines = [] + self.patches = [] + self.texts = [] + self.images = [] + self.legends = [] + self.subplotpars.reset() + if not keep_observers: + self._axobservers = cbook.CallbackRegistry() + self._suptitle = None + self._supxlabel = None + self._supylabel = None + + self.stale = True + + # synonym for `clear`. + def clf(self, keep_observers=False): """ - return np.array(self.bbox_inches.p1) + [*Discouraged*] Alias for the `clear()` method. - def get_edgecolor(self): - """Get the edge color of the Figure rectangle.""" - return self.patch.get_edgecolor() + .. admonition:: Discouraged - def get_facecolor(self): - """Get the face color of the Figure rectangle.""" - return self.patch.get_facecolor() + The use of ``clf()`` is discouraged. Use ``clear()`` instead. - def get_figwidth(self): - """Return the figure width in inches.""" - return self.bbox_inches.width + Parameters + ---------- + keep_observers : bool, default: False + Set *keep_observers* to True if, for example, + a gui widget is tracking the Axes in the figure. + """ + return self.clear(keep_observers=keep_observers) + + # Note: the docstring below is modified with replace for the pyplot + # version of this function because the method name differs (plt.figlegend) + # the replacements are: + # " legend(" -> " figlegend(" for the signatures + # "fig.legend(" -> "plt.figlegend" for the code examples + # "ax.plot" -> "plt.plot" for consistency in using pyplot when able + @_docstring.interpd + def legend(self, *args, **kwargs): + """ + Place a legend on the figure. - def get_figheight(self): - """Return the figure height in inches.""" - return self.bbox_inches.height + Call signatures:: - def get_dpi(self): - """Return the resolution in dots per inch as a float.""" - return self.dpi + legend() + legend(handles, labels) + legend(handles=handles) + legend(labels) - def get_frameon(self): - """ - Return the figure's background patch visibility, i.e. - whether the figure background will be drawn. Equivalent to - ``Figure.patch.get_visible()``. - """ - return self.patch.get_visible() + The call signatures correspond to the following different ways to use + this method: - def set_edgecolor(self, color): - """ - Set the edge color of the Figure rectangle. + **1. Automatic detection of elements to be shown in the legend** - Parameters - ---------- - color : color - """ - self.patch.set_edgecolor(color) + The elements to be added to the legend are automatically determined, + when you do not pass in any extra arguments. - def set_facecolor(self, color): - """ - Set the face color of the Figure rectangle. + In this case, the labels are taken from the artist. You can specify + them either at artist creation or by calling the + :meth:`~.Artist.set_label` method on the artist:: - Parameters - ---------- - color : color - """ - self.patch.set_facecolor(color) + ax.plot([1, 2, 3], label='Inline label') + fig.legend() - def set_dpi(self, val): - """ - Set the resolution of the figure in dots-per-inch. + or:: - Parameters - ---------- - val : float - """ - self.dpi = val - self.stale = True + line, = ax.plot([1, 2, 3]) + line.set_label('Label via method') + fig.legend() + + Specific lines can be excluded from the automatic legend element + selection by defining a label starting with an underscore. + This is default for all artists, so calling `.Figure.legend` without + any arguments and without setting the labels manually will result in + no legend being drawn. + + + **2. Explicitly listing the artists and labels in the legend** + + For full control of which artists have a legend entry, it is possible + to pass an iterable of legend artists followed by an iterable of + legend labels respectively:: + + fig.legend([line1, line2, line3], ['label1', 'label2', 'label3']) + + + **3. Explicitly listing the artists in the legend** + + This is similar to 2, but the labels are taken from the artists' + label properties. Example:: + + line1, = ax1.plot([1, 2, 3], label='label1') + line2, = ax2.plot([1, 2, 3], label='label2') + fig.legend(handles=[line1, line2]) + + + **4. Labeling existing plot elements** + + .. admonition:: Discouraged + + This call signature is discouraged, because the relation between + plot elements and labels is only implicit by their order and can + easily be mixed up. + + To make a legend for all artists on all Axes, call this function with + an iterable of strings, one for each legend item. For example:: + + fig, (ax1, ax2) = plt.subplots(1, 2) + ax1.plot([1, 3, 5], color='blue') + ax2.plot([2, 4, 6], color='red') + fig.legend(['the blues', 'the reds']) - def set_figwidth(self, val, forward=True): - """ - Set the width of the figure in inches. Parameters ---------- - val : float - forward : bool - See `set_size_inches`. + handles : list of `.Artist`, optional + A list of Artists (lines, patches) to be added to the legend. + Use this together with *labels*, if you need full control on what + is shown in the legend and the automatic mechanism described above + is not sufficient. + + The length of handles and labels should be the same in this + case. If they are not, they are truncated to the smaller length. + + labels : list of str, optional + A list of labels to show next to the artists. + Use this together with *handles*, if you need full control on what + is shown in the legend and the automatic mechanism described above + is not sufficient. + + Returns + ------- + `~matplotlib.legend.Legend` + + Other Parameters + ---------------- + %(_legend_kw_figure)s See Also -------- - matplotlib.figure.Figure.set_figheight - matplotlib.figure.Figure.set_size_inches + .Axes.legend + + Notes + ----- + Some artists are not supported by this function. See + :ref:`legend_guide` for details. """ - self.set_size_inches(val, self.get_figheight(), forward=forward) - def set_figheight(self, val, forward=True): + handles, labels, kwargs = mlegend._parse_legend_args(self.axes, *args, **kwargs) + # explicitly set the bbox transform if the user hasn't. + kwargs.setdefault("bbox_transform", self.transSubfigure) + l = mlegend.Legend(self, handles, labels, **kwargs) + self.legends.append(l) + l._remove_method = self.legends.remove + self.stale = True + return l + + @_docstring.interpd + def text(self, x, y, s, fontdict=None, **kwargs): """ - Set the height of the figure in inches. + Add text to figure. Parameters ---------- - val : float - forward : bool - See `set_size_inches`. + x, y : float + The position to place the text. By default, this is in figure + coordinates, floats in [0, 1]. The coordinate system can be changed + using the *transform* keyword. + + s : str + The text string. + + fontdict : dict, optional + A dictionary to override the default text properties. If not given, + the defaults are determined by :rc:`font.*`. Properties passed as + *kwargs* override the corresponding ones given in *fontdict*. + + Returns + ------- + `~.text.Text` + + Other Parameters + ---------------- + **kwargs : `~matplotlib.text.Text` properties + Other miscellaneous text parameters. + + %(Text:kwdoc)s See Also -------- - matplotlib.figure.Figure.set_figwidth - matplotlib.figure.Figure.set_size_inches + .Axes.text + .pyplot.text """ - self.set_size_inches(self.get_figwidth(), val, forward=forward) + effective_kwargs = { + 'transform': self.transSubfigure, + **(fontdict if fontdict is not None else {}), + **kwargs, + } + text = Text(x=x, y=y, text=s, **effective_kwargs) + text.set_figure(self) + text.stale_callback = _stale_figure_callback - def set_frameon(self, b): + self.texts.append(text) + text._remove_method = self.texts.remove + self.stale = True + return text + + @_docstring.interpd + def colorbar( + self, mappable, cax=None, ax=None, use_gridspec=True, **kwargs): """ - Set the figure's background patch visibility, i.e. - whether the figure background will be drawn. Equivalent to - ``Figure.patch.set_visible()``. + Add a colorbar to a plot. Parameters ---------- - b : bool - """ - self.patch.set_visible(b) - self.stale = True + mappable + The `matplotlib.colorizer.ColorizingArtist` (i.e., `.AxesImage`, + `.ContourSet`, etc.) described by this colorbar. This argument is + mandatory for the `.Figure.colorbar` method but optional for the + `.pyplot.colorbar` function, which sets the default to the current + image. - frameon = property(get_frameon, set_frameon) + Note that one can create a `.colorizer.ColorizingArtist` "on-the-fly" + to generate colorbars not attached to a previously drawn artist, e.g. + :: - def add_artist(self, artist, clip=False): - """ - Add an `.Artist` to the figure. + cr = colorizer.Colorizer(norm=norm, cmap=cmap) + fig.colorbar(colorizer.ColorizingArtist(cr), ax=ax) - Usually artists are added to axes objects using `.Axes.add_artist`; - this method can be used in the rare cases where one needs to add - artists directly to the figure instead. + cax : `~matplotlib.axes.Axes`, optional + Axes into which the colorbar will be drawn. If `None`, then a new + Axes is created and the space for it will be stolen from the Axes(s) + specified in *ax*. - Parameters - ---------- - artist : `~matplotlib.artist.Artist` - The artist to add to the figure. If the added artist has no - transform previously set, its transform will be set to - ``figure.transFigure``. - clip : bool, default: False - Whether the added artist should be clipped by the figure patch. + ax : `~matplotlib.axes.Axes` or iterable or `numpy.ndarray` of Axes, optional + The one or more parent Axes from which space for a new colorbar Axes + will be stolen. This parameter is only used if *cax* is not set. + + Defaults to the Axes that contains the mappable used to create the + colorbar. + + use_gridspec : bool, optional + If *cax* is ``None``, a new *cax* is created as an instance of + Axes. If *ax* is positioned with a subplotspec and *use_gridspec* + is ``True``, then *cax* is also positioned with a subplotspec. Returns ------- - `~matplotlib.artist.Artist` - The added artist. + colorbar : `~matplotlib.colorbar.Colorbar` + + Other Parameters + ---------------- + %(_make_axes_kw_doc)s + %(_colormap_kw_doc)s + + Notes + ----- + If *mappable* is a `~.contour.ContourSet`, its *extend* kwarg is + included automatically. + + The *shrink* kwarg provides a simple way to scale the colorbar with + respect to the Axes. Note that if *cax* is specified, it determines the + size of the colorbar, and *shrink* and *aspect* are ignored. + + For more precise control, you can manually specify the positions of the + axes objects in which the mappable and the colorbar are drawn. In this + case, do not use any of the Axes properties kwargs. + + It is known that some vector graphics viewers (svg and pdf) render + white gaps between segments of the colorbar. This is due to bugs in + the viewers, not Matplotlib. As a workaround, the colorbar can be + rendered with overlapping segments:: + + cbar = colorbar() + cbar.solids.set_edgecolor("face") + draw() + + However, this has negative consequences in other circumstances, e.g. + with semi-transparent images (alpha < 1) and colorbar extensions; + therefore, this workaround is not used by default (see issue #1188). + """ - artist.set_figure(self) - self.artists.append(artist) - artist._remove_method = self.artists.remove - if not artist.is_transform_set(): - artist.set_transform(self.transFigure) + if ax is None: + ax = getattr(mappable, "axes", None) - if clip: - artist.set_clip_path(self.patch) + if cax is None: + if ax is None: + raise ValueError( + 'Unable to determine Axes to steal space for Colorbar. ' + 'Either provide the *cax* argument to use as the Axes for ' + 'the Colorbar, provide the *ax* argument to steal space ' + 'from it, or add *mappable* to an Axes.') + fig = ( # Figure of first Axes; logic copied from make_axes. + [*ax.flat] if isinstance(ax, np.ndarray) + else [*ax] if np.iterable(ax) + else [ax])[0].get_figure(root=False) + current_ax = fig.gca() + if (fig.get_layout_engine() is not None and + not fig.get_layout_engine().colorbar_gridspec): + use_gridspec = False + if (use_gridspec + and isinstance(ax, mpl.axes._base._AxesBase) + and ax.get_subplotspec()): + cax, kwargs = cbar.make_axes_gridspec(ax, **kwargs) + else: + cax, kwargs = cbar.make_axes(ax, **kwargs) + # make_axes calls add_{axes,subplot} which changes gca; undo that. + fig.sca(current_ax) + cax.grid(visible=False, which='both', axis='both') + + if (hasattr(mappable, "get_figure") and + (mappable_host_fig := mappable.get_figure(root=True)) is not None): + # Warn in case of mismatch + if mappable_host_fig is not self._root_figure: + _api.warn_external( + f'Adding colorbar to a different Figure ' + f'{repr(mappable_host_fig)} than ' + f'{repr(self._root_figure)} which ' + f'fig.colorbar is called on.') + + NON_COLORBAR_KEYS = [ # remove kws that cannot be passed to Colorbar + 'fraction', 'pad', 'shrink', 'aspect', 'anchor', 'panchor'] + cb = cbar.Colorbar(cax, mappable, **{ + k: v for k, v in kwargs.items() if k not in NON_COLORBAR_KEYS}) + cax.get_figure(root=False).stale = True + return cb + + def subplots_adjust(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Adjust the subplot layout parameters. + + Unset parameters are left unmodified; initial values are given by + :rc:`figure.subplot.[name]`. + + .. plot:: _embedded_plots/figure_subplots_adjust.py + + Parameters + ---------- + left : float, optional + The position of the left edge of the subplots, + as a fraction of the figure width. + right : float, optional + The position of the right edge of the subplots, + as a fraction of the figure width. + bottom : float, optional + The position of the bottom edge of the subplots, + as a fraction of the figure height. + top : float, optional + The position of the top edge of the subplots, + as a fraction of the figure height. + wspace : float, optional + The width of the padding between subplots, + as a fraction of the average Axes width. + hspace : float, optional + The height of the padding between subplots, + as a fraction of the average Axes height. + """ + if (self.get_layout_engine() is not None and + not self.get_layout_engine().adjust_compatible): + _api.warn_external( + "This figure was using a layout engine that is " + "incompatible with subplots_adjust and/or tight_layout; " + "not calling subplots_adjust.") + return + self.subplotpars.update(left, bottom, right, top, wspace, hspace) + for ax in self.axes: + if ax.get_subplotspec() is not None: + ax._set_position(ax.get_subplotspec().get_position(self)) + self.stale = True + + def align_xlabels(self, axs=None): + """ + Align the xlabels of subplots in the same subplot row if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. + + If a label is on the bottom, it is aligned with labels on Axes that + also have their label on the bottom and that have the same + bottom-most subplot row. If the label is on the top, + it is aligned with labels on Axes with the same top-most row. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or `~numpy.ndarray`) `~matplotlib.axes.Axes` + to align the xlabels. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_titles + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that all Axes in ``axs`` are from the same `.GridSpec`, + so that their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with rotated xtick labels:: + + fig, axs = plt.subplots(1, 2) + axs[0].tick_params(axis='x', rotation=55) + axs[0].set_xlabel('XLabel 0') + axs[1].set_xlabel('XLabel 1') + fig.align_xlabels() + """ + if axs is None: + axs = self.axes + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] + for ax in axs: + _log.debug(' Working on: %s', ax.get_xlabel()) + rowspan = ax.get_subplotspec().rowspan + pos = ax.xaxis.get_label_position() # top or bottom + # Search through other Axes for label positions that are same as + # this one and that share the appropriate row number. + # Add to a grouper associated with each Axes of siblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. + for axc in axs: + if axc.xaxis.get_label_position() == pos: + rowspanc = axc.get_subplotspec().rowspan + if (pos == 'top' and rowspan.start == rowspanc.start or + pos == 'bottom' and rowspan.stop == rowspanc.stop): + # grouper for groups of xlabels to align + self._align_label_groups['x'].join(ax, axc) + + def align_ylabels(self, axs=None): + """ + Align the ylabels of subplots in the same subplot column if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. + + If a label is on the left, it is aligned with labels on Axes that + also have their label on the left and that have the same + left-most subplot column. If the label is on the right, + it is aligned with labels on Axes with the same right-most column. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list (or `~numpy.ndarray`) of `~matplotlib.axes.Axes` + to align the ylabels. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_titles + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that all Axes in ``axs`` are from the same `.GridSpec`, + so that their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with large yticks labels:: + + fig, axs = plt.subplots(2, 1) + axs[0].plot(np.arange(0, 1000, 50)) + axs[0].set_ylabel('YLabel 0') + axs[1].set_ylabel('YLabel 1') + fig.align_ylabels() + """ + if axs is None: + axs = self.axes + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] + for ax in axs: + _log.debug(' Working on: %s', ax.get_ylabel()) + colspan = ax.get_subplotspec().colspan + pos = ax.yaxis.get_label_position() # left or right + # Search through other Axes for label positions that are same as + # this one and that share the appropriate column number. + # Add to a list associated with each Axes of siblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. + for axc in axs: + if axc.yaxis.get_label_position() == pos: + colspanc = axc.get_subplotspec().colspan + if (pos == 'left' and colspan.start == colspanc.start or + pos == 'right' and colspan.stop == colspanc.stop): + # grouper for groups of ylabels to align + self._align_label_groups['y'].join(ax, axc) + + def align_titles(self, axs=None): + """ + Align the titles of subplots in the same subplot row if title + alignment is being done automatically (i.e. the title position is + not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or ndarray) `~matplotlib.axes.Axes` + to align the titles. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that all Axes in ``axs`` are from the same `.GridSpec`, + so that their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with titles:: + + fig, axs = plt.subplots(1, 2) + axs[0].set_aspect('equal') + axs[0].set_title('Title 0') + axs[1].set_title('Title 1') + fig.align_titles() + """ + if axs is None: + axs = self.axes + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] + for ax in axs: + _log.debug(' Working on: %s', ax.get_title()) + rowspan = ax.get_subplotspec().rowspan + for axc in axs: + rowspanc = axc.get_subplotspec().rowspan + if (rowspan.start == rowspanc.start): + self._align_label_groups['title'].join(ax, axc) + + def align_labels(self, axs=None): + """ + Align the xlabels and ylabels of subplots with the same subplots + row or column (respectively) if label alignment is being + done automatically (i.e. the label position is not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list (or `~numpy.ndarray`) of `~matplotlib.axes.Axes` + to align the labels. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_titles + + Notes + ----- + This assumes that all Axes in ``axs`` are from the same `.GridSpec`, + so that their `.SubplotSpec` positions correspond to figure positions. + """ + self.align_xlabels(axs=axs) + self.align_ylabels(axs=axs) + + def add_gridspec(self, nrows=1, ncols=1, **kwargs): + """ + Low-level API for creating a `.GridSpec` that has this figure as a parent. + + This is a low-level API, allowing you to create a gridspec and + subsequently add subplots based on the gridspec. Most users do + not need that freedom and should use the higher-level methods + `~.Figure.subplots` or `~.Figure.subplot_mosaic`. + + Parameters + ---------- + nrows : int, default: 1 + Number of rows in grid. + + ncols : int, default: 1 + Number of columns in grid. + + Returns + ------- + `.GridSpec` + + Other Parameters + ---------------- + **kwargs + Keyword arguments are passed to `.GridSpec`. + + See Also + -------- + matplotlib.pyplot.subplots + + Examples + -------- + Adding a subplot that spans two rows:: + + fig = plt.figure() + gs = fig.add_gridspec(2, 2) + ax1 = fig.add_subplot(gs[0, 0]) + ax2 = fig.add_subplot(gs[1, 0]) + # spans two rows: + ax3 = fig.add_subplot(gs[:, 1]) + + """ + + _ = kwargs.pop('figure', None) # pop in case user has added this... + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs) + return gs + + def subfigures(self, nrows=1, ncols=1, squeeze=True, + wspace=None, hspace=None, + width_ratios=None, height_ratios=None, + **kwargs): + """ + Add a set of subfigures to this figure or subfigure. + + A subfigure has the same artist methods as a figure, and is logically + the same as a figure, but cannot print itself. + See :doc:`/gallery/subplots_axes_and_figures/subfigures`. + + .. versionchanged:: 3.10 + subfigures are now added in row-major order. + + Parameters + ---------- + nrows, ncols : int, default: 1 + Number of rows/columns of the subfigure grid. + + squeeze : bool, default: True + If True, extra dimensions are squeezed out from the returned + array of subfigures. + + wspace, hspace : float, default: None + The amount of width/height reserved for space between subfigures, + expressed as a fraction of the average subfigure width/height. + If not given, the values will be inferred from rcParams if using + constrained layout (see `~.ConstrainedLayoutEngine`), or zero if + not using a layout engine. + + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. + + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. + """ + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, + wspace=wspace, hspace=hspace, + width_ratios=width_ratios, + height_ratios=height_ratios, + left=0, right=1, bottom=0, top=1) + + sfarr = np.empty((nrows, ncols), dtype=object) + for i in range(nrows): + for j in range(ncols): + sfarr[i, j] = self.add_subfigure(gs[i, j], **kwargs) + + if self.get_layout_engine() is None and (wspace is not None or + hspace is not None): + # Gridspec wspace and hspace is ignored on subfigure instantiation, + # and no space is left. So need to account for it here if required. + bottoms, tops, lefts, rights = gs.get_grid_positions(self) + for sfrow, bottom, top in zip(sfarr, bottoms, tops): + for sf, left, right in zip(sfrow, lefts, rights): + bbox = Bbox.from_extents(left, bottom, right, top) + sf._redo_transform_rel_fig(bbox=bbox) + + if squeeze: + # Discarding unneeded dimensions that equal 1. If we only have one + # subfigure, just return it instead of a 1-element array. + return sfarr.item() if sfarr.size == 1 else sfarr.squeeze() + else: + # Returned axis array will be always 2-d, even if nrows=ncols=1. + return sfarr + + def add_subfigure(self, subplotspec, **kwargs): + """ + Add a `.SubFigure` to the figure as part of a subplot arrangement. + + Parameters + ---------- + subplotspec : `.gridspec.SubplotSpec` + Defines the region in a parent gridspec where the subfigure will + be placed. + + Returns + ------- + `.SubFigure` + + Other Parameters + ---------------- + **kwargs + Are passed to the `.SubFigure` object. + + See Also + -------- + .Figure.subfigures + """ + sf = SubFigure(self, subplotspec, **kwargs) + self.subfigs += [sf] + sf._remove_method = self.subfigs.remove + sf.stale_callback = _stale_figure_callback + self.stale = True + return sf + + def sca(self, a): + """Set the current Axes to be *a* and return *a*.""" + self._axstack.bubble(a) + self._axobservers.process("_axes_change_event", self) + return a + + def gca(self): + """ + Get the current Axes. + + If there is currently no Axes on this Figure, a new one is created + using `.Figure.add_subplot`. (To test whether there is currently an + Axes on a Figure, check whether ``figure.axes`` is empty. To test + whether there is currently a Figure on the pyplot figure stack, check + whether `.pyplot.get_fignums()` is empty.) + """ + ax = self._axstack.current() + return ax if ax is not None else self.add_subplot() + + def _gci(self): + # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere. + """ + Get the current colorable artist. + + Specifically, returns the current `.ScalarMappable` instance (`.Image` + created by `imshow` or `figimage`, `.Collection` created by `pcolor` or + `scatter`, etc.), or *None* if no such instance has been defined. + + The current image is an attribute of the current Axes, or the nearest + earlier Axes in the current figure that contains an image. + + Notes + ----- + Historically, the only colorable artists were images; hence the name + ``gci`` (get current image). + """ + # Look first for an image in the current Axes. + ax = self._axstack.current() + if ax is None: + return None + im = ax._gci() + if im is not None: + return im + # If there is no image in the current Axes, search for + # one in a previously created Axes. Whether this makes + # sense is debatable, but it is the documented behavior. + for ax in reversed(self.axes): + im = ax._gci() + if im is not None: + return im + return None + + def _process_projection_requirements(self, *, axes_class=None, polar=False, + projection=None, **kwargs): + """ + Handle the args/kwargs to add_axes/add_subplot/gca, returning:: + + (axes_proj_class, proj_class_kwargs) + + which can be used for new Axes initialization/identification. + """ + if axes_class is not None: + if polar or projection is not None: + raise ValueError( + "Cannot combine 'axes_class' and 'projection' or 'polar'") + projection_class = axes_class + else: + + if polar: + if projection is not None and projection != 'polar': + raise ValueError( + f"polar={polar}, yet projection={projection!r}. " + "Only one of these arguments should be supplied." + ) + projection = 'polar' + + if isinstance(projection, str) or projection is None: + projection_class = projections.get_projection_class(projection) + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + raise TypeError( + f"projection must be a string, None or implement a " + f"_as_mpl_axes method, not {projection!r}") + return projection_class, kwargs + + def get_default_bbox_extra_artists(self): + """ + Return a list of Artists typically used in `.Figure.get_tightbbox`. + """ + bbox_artists = [artist for artist in self.get_children() + if (artist.get_visible() and artist.get_in_layout())] + for ax in self.axes: + if ax.get_visible(): + bbox_artists.extend(ax.get_default_bbox_extra_artists()) + return bbox_artists + + def get_tightbbox(self, renderer=None, *, bbox_extra_artists=None): + """ + Return a (tight) bounding box of the figure *in inches*. + + Note that `.FigureBase` differs from all other artists, which return + their `.Bbox` in pixels. + + Artists that have ``artist.set_in_layout(False)`` are not included + in the bbox. + + Parameters + ---------- + renderer : `.RendererBase` subclass + Renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + bbox_extra_artists : list of `.Artist` or ``None`` + List of artists to include in the tight bounding box. If + ``None`` (default), then all artist children of each Axes are + included in the tight bounding box. + + Returns + ------- + `.BboxBase` + containing the bounding box (in figure inches). + """ + + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + + bb = [] + if bbox_extra_artists is None: + artists = [artist for artist in self.get_children() + if (artist not in self.axes and artist.get_visible() + and artist.get_in_layout())] + else: + artists = bbox_extra_artists + + for a in artists: + bbox = a.get_tightbbox(renderer) + if bbox is not None: + bb.append(bbox) + + for ax in self.axes: + if ax.get_visible(): + # some Axes don't take the bbox_extra_artists kwarg so we + # need this conditional.... + try: + bbox = ax.get_tightbbox( + renderer, bbox_extra_artists=bbox_extra_artists) + except TypeError: + bbox = ax.get_tightbbox(renderer) + bb.append(bbox) + bb = [b for b in bb + if (np.isfinite(b.width) and np.isfinite(b.height) + and (b.width != 0 or b.height != 0))] + + isfigure = hasattr(self, 'bbox_inches') + if len(bb) == 0: + if isfigure: + return self.bbox_inches + else: + # subfigures do not have bbox_inches, but do have a bbox + bb = [self.bbox] + + _bbox = Bbox.union(bb) + + if isfigure: + # transform from pixels to inches... + _bbox = TransformedBbox(_bbox, self.dpi_scale_trans.inverted()) + + return _bbox + + @staticmethod + def _norm_per_subplot_kw(per_subplot_kw): + expanded = {} + for k, v in per_subplot_kw.items(): + if isinstance(k, tuple): + for sub_key in k: + if sub_key in expanded: + raise ValueError(f'The key {sub_key!r} appears multiple times.') + expanded[sub_key] = v + else: + if k in expanded: + raise ValueError(f'The key {k!r} appears multiple times.') + expanded[k] = v + return expanded + + @staticmethod + def _normalize_grid_string(layout): + if '\n' not in layout: + # single-line string + return [list(ln) for ln in layout.split(';')] + else: + # multi-line string + layout = inspect.cleandoc(layout) + return [list(ln) for ln in layout.strip('\n').split('\n')] + + def subplot_mosaic(self, mosaic, *, sharex=False, sharey=False, + width_ratios=None, height_ratios=None, + empty_sentinel='.', + subplot_kw=None, per_subplot_kw=None, gridspec_kw=None): + """ + Build a layout of Axes based on ASCII art or nested lists. + + This is a helper function to build complex GridSpec layouts visually. + + See :ref:`mosaic` + for an example and full API documentation + + Parameters + ---------- + mosaic : list of list of {hashable or nested} or str + + A visual layout of how you want your Axes to be arranged + labeled as strings. For example :: + + x = [['A panel', 'A panel', 'edge'], + ['C panel', '.', 'edge']] + + produces 4 Axes: + + - 'A panel' which is 1 row high and spans the first two columns + - 'edge' which is 2 rows high and is on the right edge + - 'C panel' which in 1 row and 1 column wide in the bottom left + - a blank space 1 row and 1 column wide in the bottom center + + Any of the entries in the layout can be a list of lists + of the same form to create nested layouts. + + If input is a str, then it can either be a multi-line string of + the form :: + + ''' + AAE + C.E + ''' + + where each character is a column and each line is a row. Or it + can be a single-line string where rows are separated by ``;``:: + + 'AB;CC' + + The string notation allows only single character Axes labels and + does not support nesting but is very terse. + + The Axes identifiers may be `str` or a non-iterable hashable + object (e.g. `tuple` s may not be used). + + sharex, sharey : bool, default: False + If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared + among all subplots. In that case, tick label visibility and axis + units behave as for `subplots`. If False, each subplot's x- or + y-axis will be independent. + + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. Equivalent + to ``gridspec_kw={'width_ratios': [...]}``. In the case of nested + layouts, this argument applies only to the outer layout. + + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. Equivalent + to ``gridspec_kw={'height_ratios': [...]}``. In the case of nested + layouts, this argument applies only to the outer layout. + + subplot_kw : dict, optional + Dictionary with keywords passed to the `.Figure.add_subplot` call + used to create each subplot. These values may be overridden by + values in *per_subplot_kw*. + + per_subplot_kw : dict, optional + A dictionary mapping the Axes identifiers or tuples of identifiers + to a dictionary of keyword arguments to be passed to the + `.Figure.add_subplot` call used to create each subplot. The values + in these dictionaries have precedence over the values in + *subplot_kw*. + + If *mosaic* is a string, and thus all keys are single characters, + it is possible to use a single string instead of a tuple as keys; + i.e. ``"AB"`` is equivalent to ``("A", "B")``. + + .. versionadded:: 3.7 + + gridspec_kw : dict, optional + Dictionary with keywords passed to the `.GridSpec` constructor used + to create the grid the subplots are placed on. In the case of + nested layouts, this argument applies only to the outer layout. + For more complex layouts, users should use `.Figure.subfigures` + to create the nesting. + + empty_sentinel : object, optional + Entry in the layout to mean "leave this space empty". Defaults + to ``'.'``. Note, if *layout* is a string, it is processed via + `inspect.cleandoc` to remove leading white space, which may + interfere with using white-space as the empty sentinel. + + Returns + ------- + dict[label, Axes] + A dictionary mapping the labels to the Axes objects. The order of + the Axes is left-to-right and top-to-bottom of their position in the + total layout. + + """ + subplot_kw = subplot_kw or {} + gridspec_kw = dict(gridspec_kw or {}) + per_subplot_kw = per_subplot_kw or {} + + if height_ratios is not None: + if 'height_ratios' in gridspec_kw: + raise ValueError("'height_ratios' must not be defined both as " + "parameter and as key in 'gridspec_kw'") + gridspec_kw['height_ratios'] = height_ratios + if width_ratios is not None: + if 'width_ratios' in gridspec_kw: + raise ValueError("'width_ratios' must not be defined both as " + "parameter and as key in 'gridspec_kw'") + gridspec_kw['width_ratios'] = width_ratios + + # special-case string input + if isinstance(mosaic, str): + mosaic = self._normalize_grid_string(mosaic) + per_subplot_kw = { + tuple(k): v for k, v in per_subplot_kw.items() + } + + per_subplot_kw = self._norm_per_subplot_kw(per_subplot_kw) + + # Only accept strict bools to allow a possible future API expansion. + _api.check_isinstance(bool, sharex=sharex, sharey=sharey) + + def _make_array(inp): + """ + Convert input into 2D array + + We need to have this internal function rather than + ``np.asarray(..., dtype=object)`` so that a list of lists + of lists does not get converted to an array of dimension > 2. + + Returns + ------- + 2D object array + """ + r0, *rest = inp + if isinstance(r0, str): + raise ValueError('List mosaic specification must be 2D') + for j, r in enumerate(rest, start=1): + if isinstance(r, str): + raise ValueError('List mosaic specification must be 2D') + if len(r0) != len(r): + raise ValueError( + "All of the rows must be the same length, however " + f"the first row ({r0!r}) has length {len(r0)} " + f"and row {j} ({r!r}) has length {len(r)}." + ) + out = np.zeros((len(inp), len(r0)), dtype=object) + for j, r in enumerate(inp): + for k, v in enumerate(r): + out[j, k] = v + return out + + def _identify_keys_and_nested(mosaic): + """ + Given a 2D object array, identify unique IDs and nested mosaics + + Parameters + ---------- + mosaic : 2D object array + + Returns + ------- + unique_ids : tuple + The unique non-sub mosaic entries in this mosaic + nested : dict[tuple[int, int], 2D object array] + """ + # make sure we preserve the user supplied order + unique_ids = cbook._OrderedSet() + nested = {} + for j, row in enumerate(mosaic): + for k, v in enumerate(row): + if v == empty_sentinel: + continue + elif not cbook.is_scalar_or_string(v): + nested[(j, k)] = _make_array(v) + else: + unique_ids.add(v) + + return tuple(unique_ids), nested + + def _do_layout(gs, mosaic, unique_ids, nested): + """ + Recursively do the mosaic. + + Parameters + ---------- + gs : GridSpec + mosaic : 2D object array + The input converted to a 2D array for this level. + unique_ids : tuple + The identified scalar labels at this level of nesting. + nested : dict[tuple[int, int]], 2D object array + The identified nested mosaics, if any. + + Returns + ------- + dict[label, Axes] + A flat dict of all of the Axes created. + """ + output = dict() - self.stale = True - return artist + # we need to merge together the Axes at this level and the Axes + # in the (recursively) nested sub-mosaics so that we can add + # them to the figure in the "natural" order if you were to + # ravel in c-order all of the Axes that will be created + # + # This will stash the upper left index of each object (axes or + # nested mosaic) at this level + this_level = dict() - def _make_key(self, *args, **kwargs): - """Make a hashable key out of args and kwargs.""" - - def fixitems(items): - # items may have arrays and lists in them, so convert them - # to tuples for the key - ret = [] - for k, v in items: - # some objects can define __getitem__ without being - # iterable and in those cases the conversion to tuples - # will fail. So instead of using the np.iterable(v) function - # we simply try and convert to a tuple, and proceed if not. - try: - v = tuple(v) - except Exception: - pass - ret.append((k, v)) - return tuple(ret) - - def fixlist(args): - ret = [] - for a in args: - if np.iterable(a): - a = tuple(a) - ret.append(a) - return tuple(ret) - - key = fixlist(args), fixitems(kwargs.items()) - return key - - def _process_projection_requirements( - self, *args, polar=False, projection=None, **kwargs): - """ - Handle the args/kwargs to add_axes/add_subplot/gca, returning:: + # go through the unique keys, + for name in unique_ids: + # sort out where each axes starts/ends + index = np.argwhere(mosaic == name) + start_row, start_col = np.min(index, axis=0) + end_row, end_col = np.max(index, axis=0) + 1 + # and construct the slice object + slc = (slice(start_row, end_row), slice(start_col, end_col)) + # some light error checking + if (mosaic[slc] != name).any(): + raise ValueError( + f"While trying to layout\n{mosaic!r}\n" + f"we found that the label {name!r} specifies a " + "non-rectangular or non-contiguous area.") + # and stash this slice for later + this_level[(start_row, start_col)] = (name, slc, 'axes') + + # do the same thing for the nested mosaics (simpler because these + # cannot be spans yet!) + for (j, k), nested_mosaic in nested.items(): + this_level[(j, k)] = (None, nested_mosaic, 'nested') + + # now go through the things in this level and add them + # in order left-to-right top-to-bottom + for key in sorted(this_level): + name, arg, method = this_level[key] + # we are doing some hokey function dispatch here based + # on the 'method' string stashed above to sort out if this + # element is an Axes or a nested mosaic. + if method == 'axes': + slc = arg + # add a single Axes + if name in output: + raise ValueError(f"There are duplicate keys {name} " + f"in the layout\n{mosaic!r}") + ax = self.add_subplot( + gs[slc], **{ + 'label': str(name), + **subplot_kw, + **per_subplot_kw.get(name, {}) + } + ) + output[name] = ax + elif method == 'nested': + nested_mosaic = arg + j, k = key + # recursively add the nested mosaic + rows, cols = nested_mosaic.shape + nested_output = _do_layout( + gs[j, k].subgridspec(rows, cols), + nested_mosaic, + *_identify_keys_and_nested(nested_mosaic) + ) + overlap = set(output) & set(nested_output) + if overlap: + raise ValueError( + f"There are duplicate keys {overlap} " + f"between the outer layout\n{mosaic!r}\n" + f"and the nested layout\n{nested_mosaic}" + ) + output.update(nested_output) + else: + raise RuntimeError("This should never happen") + return output - (axes_proj_class, proj_class_kwargs, proj_stack_key) + mosaic = _make_array(mosaic) + rows, cols = mosaic.shape + gs = self.add_gridspec(rows, cols, **gridspec_kw) + ret = _do_layout(gs, mosaic, *_identify_keys_and_nested(mosaic)) + ax0 = next(iter(ret.values())) + for ax in ret.values(): + if sharex: + ax.sharex(ax0) + ax._label_outer_xaxis(skip_non_rectangular_axes=True) + if sharey: + ax.sharey(ax0) + ax._label_outer_yaxis(skip_non_rectangular_axes=True) + if extra := set(per_subplot_kw) - set(ret): + raise ValueError( + f"The keys {extra} are in *per_subplot_kw* " + "but not in the mosaic." + ) + return ret - which can be used for new axes initialization/identification. - """ - if polar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) - projection = 'polar' - - if isinstance(projection, str) or projection is None: - projection_class = projections.get_projection_class(projection) - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - else: - raise TypeError('projection must be a string, None or implement a ' - '_as_mpl_axes method. Got %r' % projection) + def _set_artist_props(self, a): + if a != self: + a.set_figure(self) + a.stale_callback = _stale_figure_callback + a.set_transform(self.transSubfigure) - # Make the key without projection kwargs, this is used as a unique - # lookup for axes instances - key = self._make_key(*args, **kwargs) - return projection_class, kwargs, key +@_docstring.interpd +class SubFigure(FigureBase): + """ + Logical figure that can be placed inside a figure. - @docstring.dedent_interpd - def add_axes(self, *args, **kwargs): - """ - Add an axes to the figure. + See :ref:`figure-api-subfigure` for an index of methods on this class. + Typically instantiated using `.Figure.add_subfigure` or + `.SubFigure.add_subfigure`, or `.SubFigure.subfigures`. A subfigure has + the same methods as a figure except for those particularly tied to the size + or dpi of the figure, and is confined to a prescribed region of the figure. + For example the following puts two subfigures side-by-side:: - Call signatures:: + fig = plt.figure() + sfigs = fig.subfigures(1, 2) + axsL = sfigs[0].subplots(1, 2) + axsR = sfigs[1].subplots(2, 1) - add_axes(rect, projection=None, polar=False, **kwargs) - add_axes(ax) + See :doc:`/gallery/subplots_axes_and_figures/subfigures` + """ + def __init__(self, parent, subplotspec, *, + facecolor=None, + edgecolor=None, + linewidth=0.0, + frameon=None, + **kwargs): + """ Parameters ---------- - rect : sequence of float - The dimensions [left, bottom, width, height] of the new axes. All - quantities are in fractions of figure width and height. + parent : `.Figure` or `.SubFigure` + Figure or subfigure that contains the SubFigure. SubFigures + can be nested. - projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ -'polar', 'rectilinear', str}, optional - The projection type of the `~.axes.Axes`. *str* is the name of - a custom projection, see `~matplotlib.projections`. The default - None results in a 'rectilinear' projection. + subplotspec : `.gridspec.SubplotSpec` + Defines the region in a parent gridspec where the subfigure will + be placed. - polar : bool, default: False - If True, equivalent to projection='polar'. + facecolor : default: ``"none"`` + The figure patch face color; transparent by default. - sharex, sharey : `~.axes.Axes`, optional - Share the x or y `~matplotlib.axis` with sharex and/or sharey. - The axis will have the same limits, ticks, and scale as the axis - of the shared axes. + edgecolor : default: :rc:`figure.edgecolor` + The figure patch edge color. - label : str - A label for the returned axes. + linewidth : float + The linewidth of the frame (i.e. the edge linewidth of the figure + patch). - Returns - ------- - `~.axes.Axes`, or a subclass of `~.axes.Axes` - The returned axes class depends on the projection used. It is - `~.axes.Axes` if rectilinear projection is used and - `.projections.polar.PolarAxes` if polar projection is used. + frameon : bool, default: :rc:`figure.frameon` + If ``False``, suppress drawing the figure background patch. Other Parameters ---------------- - **kwargs - This method also takes the keyword arguments for - the returned axes class. The keyword arguments for the - rectilinear axes class `~.axes.Axes` can be found in - the following table but there might also be other keyword - arguments if another projection is used, see the actual axes - class. + **kwargs : `.SubFigure` properties, optional - %(Axes)s + %(SubFigure:kwdoc)s + """ + super().__init__(**kwargs) + if facecolor is None: + facecolor = "none" + edgecolor = mpl._val_or_rc(edgecolor, 'figure.edgecolor') + frameon = mpl._val_or_rc(frameon, 'figure.frameon') + + self._subplotspec = subplotspec + self._parent = parent + self._root_figure = parent._root_figure + + # subfigures use the parent axstack + self._axstack = parent._axstack + self.subplotpars = parent.subplotpars + self.dpi_scale_trans = parent.dpi_scale_trans + self._axobservers = parent._axobservers + self.transFigure = parent.transFigure + self.bbox_relative = Bbox.null() + self._redo_transform_rel_fig() + self.figbbox = self._parent.figbbox + self.bbox = TransformedBbox(self.bbox_relative, + self._parent.transSubfigure) + self.transSubfigure = BboxTransformTo(self.bbox) - Notes - ----- - If the figure already has an axes with key (*args*, - *kwargs*) then it will simply make that axes current and - return it. This behavior is deprecated. Meanwhile, if you do - not want this behavior (i.e., you want to force the creation of a - new axes), you must use a unique set of args and kwargs. The axes - *label* attribute has been exposed for this purpose: if you want - two axes that are otherwise identical to be added to the figure, - make sure you give them unique labels. + self.patch = Rectangle( + xy=(0, 0), width=1, height=1, visible=frameon, + facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, + # Don't let the figure patch influence bbox calculation. + in_layout=False, transform=self.transSubfigure) + self._set_artist_props(self.patch) + self.patch.set_antialiased(False) - In rare circumstances, `.add_axes` may be called with a single - argument, a axes instance already created in the present figure but - not in the figure's list of axes. + @property + def canvas(self): + return self._parent.canvas - See Also - -------- - .Figure.add_subplot - .pyplot.subplot - .pyplot.axes - .Figure.subplots - .pyplot.subplots + @property + def dpi(self): + return self._parent.dpi - Examples - -------- - Some simple examples:: + @dpi.setter + def dpi(self, value): + self._parent.dpi = value - rect = l, b, w, h - fig = plt.figure() - fig.add_axes(rect, label=label1) - fig.add_axes(rect, label=label2) - fig.add_axes(rect, frameon=False, facecolor='g') - fig.add_axes(rect, polar=True) - ax = fig.add_axes(rect, projection='polar') - fig.delaxes(ax) - fig.add_axes(ax) + def get_dpi(self): """ + Return the resolution of the parent figure in dots-per-inch as a float. + """ + return self._parent.dpi - if not len(args) and 'rect' not in kwargs: - cbook.warn_deprecated( - "3.3", - message="Calling add_axes() without argument is " - "deprecated since %(since)s and will be removed %(removal)s. " - "You may want to use add_subplot() instead.") - return - elif 'rect' in kwargs: - if len(args): - raise TypeError( - "add_axes() got multiple values for argument 'rect'") - args = (kwargs.pop('rect'), ) + def set_dpi(self, val): + """ + Set the resolution of parent figure in dots-per-inch. - # shortcut the projection "key" modifications later on, if an axes - # with the exact args/kwargs exists, return it immediately. - key = self._make_key(*args, **kwargs) - ax = self._axstack.get(key) - if ax is not None: - self.sca(ax) - return ax + Parameters + ---------- + val : float + """ + self._parent.dpi = val + self.stale = True - if isinstance(args[0], Axes): - a = args[0] - if a.get_figure() is not self: - raise ValueError( - "The Axes must have been created in the present figure") - else: - rect = args[0] - if not np.isfinite(rect).all(): - raise ValueError('all entries in rect must be finite ' - 'not {}'.format(rect)) - projection_class, kwargs, key = \ - self._process_projection_requirements(*args, **kwargs) + def _get_renderer(self): + return self._parent._get_renderer() - # check that an axes of this type doesn't already exist, if it - # does, set it as active and return it - ax = self._axstack.get(key) - if isinstance(ax, projection_class): - self.sca(ax) - return ax + def _redo_transform_rel_fig(self, bbox=None): + """ + Make the transSubfigure bbox relative to Figure transform. - # create the new axes using the axes class given - a = projection_class(self, rect, **kwargs) + Parameters + ---------- + bbox : bbox or None + If not None, then the bbox is used for relative bounding box. + Otherwise, it is calculated from the subplotspec. + """ + if bbox is not None: + self.bbox_relative.p0 = bbox.p0 + self.bbox_relative.p1 = bbox.p1 + return + # need to figure out *where* this subplotspec is. + gs = self._subplotspec.get_gridspec() + wr = np.asarray(gs.get_width_ratios()) + hr = np.asarray(gs.get_height_ratios()) + dx = wr[self._subplotspec.colspan].sum() / wr.sum() + dy = hr[self._subplotspec.rowspan].sum() / hr.sum() + x0 = wr[:self._subplotspec.colspan.start].sum() / wr.sum() + y0 = 1 - hr[:self._subplotspec.rowspan.stop].sum() / hr.sum() + self.bbox_relative.p0 = (x0, y0) + self.bbox_relative.p1 = (x0 + dx, y0 + dy) - return self._add_axes_internal(key, a) + def get_constrained_layout(self): + """ + Return whether constrained layout is being used. - @docstring.dedent_interpd - def add_subplot(self, *args, **kwargs): + See :ref:`constrainedlayout_guide`. """ - Add an `~.axes.Axes` to the figure as part of a subplot arrangement. + return self._parent.get_constrained_layout() - Call signatures:: + def get_constrained_layout_pads(self, relative=False): + """ + Get padding for ``constrained_layout``. - add_subplot(nrows, ncols, index, **kwargs) - add_subplot(pos, **kwargs) - add_subplot(ax) - add_subplot() + Returns a list of ``w_pad, h_pad`` in inches and + ``wspace`` and ``hspace`` as fractions of the subplot. + + See :ref:`constrainedlayout_guide`. Parameters ---------- - *args : int, (int, int, *index*), or `.SubplotSpec`, default: (1, 1, 1) - The position of the subplot described by one of + relative : bool + If `True`, then convert from inches to figure relative. + """ + return self._parent.get_constrained_layout_pads(relative=relative) - - Three integers (*nrows*, *ncols*, *index*). The subplot will - take the *index* position on a grid with *nrows* rows and - *ncols* columns. *index* starts at 1 in the upper left corner - and increases to the right. *index* can also be a two-tuple - specifying the (*first*, *last*) indices (1-based, and including - *last*) of the subplot, e.g., ``fig.add_subplot(3, 1, (1, 2))`` - makes a subplot that spans the upper 2/3 of the figure. - - A 3-digit integer. The digits are interpreted as if given - separately as three single-digit integers, i.e. - ``fig.add_subplot(235)`` is the same as - ``fig.add_subplot(2, 3, 5)``. Note that this can only be used - if there are no more than 9 subplots. - - A `.SubplotSpec`. + def get_layout_engine(self): + return self._parent.get_layout_engine() - In rare circumstances, `.add_subplot` may be called with a single - argument, a subplot axes instance already created in the - present figure but not in the figure's list of axes. + @property + def axes(self): + """ + List of Axes in the SubFigure. You can access and modify the Axes + in the SubFigure through this list. - projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ -'polar', 'rectilinear', str}, optional - The projection type of the subplot (`~.axes.Axes`). *str* is the - name of a custom projection, see `~matplotlib.projections`. The - default None results in a 'rectilinear' projection. + Modifying this list has no effect. Instead, use `~.SubFigure.add_axes`, + `~.SubFigure.add_subplot` or `~.SubFigure.delaxes` to add or remove an + Axes. - polar : bool, default: False - If True, equivalent to projection='polar'. + Note: The `.SubFigure.axes` property and `~.SubFigure.get_axes` method + are equivalent. + """ + return self._localaxes[:] - sharex, sharey : `~.axes.Axes`, optional - Share the x or y `~matplotlib.axis` with sharex and/or sharey. - The axis will have the same limits, ticks, and scale as the axis - of the shared axes. + get_axes = axes.fget - label : str - A label for the returned axes. + def draw(self, renderer): + # docstring inherited - Returns - ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + # draw the figure bounding box, perhaps none for white figure + if not self.get_visible(): + return - The axes of the subplot. The returned axes base class depends on - the projection used. It is `~.axes.Axes` if rectilinear projection - is used and `.projections.polar.PolarAxes` if polar projection - is used. The returned axes is then a subplot subclass of the - base class. + artists = self._get_draw_artists(renderer) - Other Parameters - ---------------- - **kwargs - This method also takes the keyword arguments for the returned axes - base class; except for the *figure* argument. The keyword arguments - for the rectilinear base class `~.axes.Axes` can be found in - the following table but there might also be other keyword - arguments if another projection is used. + try: + renderer.open_group('subfigure', gid=self.get_gid()) + self.patch.draw(renderer) + mimage._draw_list_compositing_images( + renderer, self, artists, self.get_figure(root=True).suppressComposite) + renderer.close_group('subfigure') - %(Axes)s + finally: + self.stale = False - Notes - ----- - If the figure already has a subplot with key (*args*, - *kwargs*) then it will simply make that subplot current and - return it. This behavior is deprecated. Meanwhile, if you do - not want this behavior (i.e., you want to force the creation of a - new subplot), you must use a unique set of args and kwargs. The axes - *label* attribute has been exposed for this purpose: if you want - two subplots that are otherwise identical to be added to the figure, - make sure you give them unique labels. - See Also - -------- - .Figure.add_axes - .pyplot.subplot - .pyplot.axes - .Figure.subplots - .pyplot.subplots +@_docstring.interpd +class Figure(FigureBase): + """ + The top level container for all the plot elements. - Examples - -------- - :: + See `matplotlib.figure` for an index of class methods. - fig = plt.figure() + Attributes + ---------- + patch + The `.Rectangle` instance representing the figure background patch. - fig.add_subplot(231) - ax1 = fig.add_subplot(2, 3, 1) # equivalent but more general + suppressComposite + For multiple images, the figure will make composite images + depending on the renderer option_image_nocomposite function. If + *suppressComposite* is a boolean, this will override the renderer. + """ - fig.add_subplot(232, frameon=False) # subplot with no frame - fig.add_subplot(233, projection='polar') # polar subplot - fig.add_subplot(234, sharex=ax1) # subplot sharing x-axis with ax1 - fig.add_subplot(235, facecolor="red") # red subplot + # we want to cache the fonts and mathtext at a global level so that when + # multiple figures are created we can reuse them. This helps with a bug on + # windows where the creation of too many figures leads to too many open + # file handles and improves the performance of parsing mathtext. However, + # these global caches are not thread safe. The solution here is to let the + # Figure acquire a shared lock at the start of the draw, and release it when it + # is done. This allows multiple renderers to share the cached fonts and + # parsed text, but only one figure can draw at a time and so the font cache + # and mathtext cache are used by only one renderer at a time. - ax1.remove() # delete ax1 from the figure - fig.add_subplot(ax1) # add ax1 back to the figure + _render_lock = threading.RLock() + + def __str__(self): + return "Figure(%gx%g)" % tuple(self.bbox.size) + + def __repr__(self): + return "<{clsname} size {h:g}x{w:g} with {naxes} Axes>".format( + clsname=self.__class__.__name__, + h=self.bbox.size[0], w=self.bbox.size[1], + naxes=len(self.axes), + ) + + def __init__(self, + figsize=None, + dpi=None, + *, + facecolor=None, + edgecolor=None, + linewidth=0.0, + frameon=None, + subplotpars=None, # rc figure.subplot.* + tight_layout=None, # rc figure.autolayout + constrained_layout=None, # rc figure.constrained_layout.use + layout=None, + **kwargs + ): """ - if 'figure' in kwargs: - # Axes itself allows for a 'figure' kwarg, but since we want to - # bind the created Axes to self, it is not allowed here. - raise TypeError( - "add_subplot() got an unexpected keyword argument 'figure'") + Parameters + ---------- + figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize` + The figure dimensions. This can be + + - a tuple ``(width, height, unit)``, where *unit* is one of "in" (inch), + "cm" (centimenter), "px" (pixel). + - a tuple ``(width, height)``, which is interpreted in inches, i.e. as + ``(width, height, "in")``. + + dpi : float, default: :rc:`figure.dpi` + Dots per inch. + + facecolor : default: :rc:`figure.facecolor` + The figure patch facecolor. + + edgecolor : default: :rc:`figure.edgecolor` + The figure patch edge color. + + linewidth : float + The linewidth of the frame (i.e. the edge linewidth of the figure + patch). - if len(args) == 1 and isinstance(args[0], SubplotBase): - ax = args[0] - if ax.get_figure() is not self: - raise ValueError("The Subplot must have been created in " - "the present figure") - # make a key for the subplot (which includes the axes object id - # in the hash) - key = self._make_key(*args, **kwargs) + frameon : bool, default: :rc:`figure.frameon` + If ``False``, suppress drawing the figure background patch. - else: - if not args: - args = (1, 1, 1) - # Normalize correct ijk values to (i, j, k) here so that - # add_subplot(111) == add_subplot(1, 1, 1). Invalid values will - # trigger errors later (via SubplotSpec._from_subplot_args). - if (len(args) == 1 and isinstance(args[0], Integral) - and 100 <= args[0] <= 999): - args = tuple(map(int, str(args[0]))) - projection_class, kwargs, key = \ - self._process_projection_requirements(*args, **kwargs) - ax = self._axstack.get(key) # search axes with this key in stack - if ax is not None: - if isinstance(ax, projection_class): - # the axes already existed, so set it as active & return - self.sca(ax) - return ax - else: - # Undocumented convenience behavior: - # subplot(111); subplot(111, projection='polar') - # will replace the first with the second. - # Without this, add_subplot would be simpler and - # more similar to add_axes. - self._axstack.remove(ax) - ax = subplot_class_factory(projection_class)(self, *args, **kwargs) + subplotpars : `~matplotlib.gridspec.SubplotParams` + Subplot parameters. If not given, the default subplot + parameters :rc:`figure.subplot.*` are used. - return self._add_axes_internal(key, ax) + tight_layout : bool or dict, default: :rc:`figure.autolayout` + Whether to use the tight layout mechanism. See `.set_tight_layout`. - def _add_axes_internal(self, key, ax): - """Private helper for `add_axes` and `add_subplot`.""" - #self._localaxes += [ax] - self._axstack.add(key, ax) - self.sca(ax) - ax._remove_method = self.delaxes - self.stale = True - ax.stale_callback = _stale_figure_callback - return ax + .. admonition:: Discouraged - @cbook._make_keyword_only("3.3", "sharex") - def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, - squeeze=True, subplot_kw=None, gridspec_kw=None): - """ - Add a set of subplots to this figure. + The use of this parameter is discouraged. Please use + ``layout='tight'`` instead for the common case of + ``tight_layout=True`` and use `.set_tight_layout` otherwise. - This utility wrapper makes it convenient to create common layouts of - subplots in a single call. + constrained_layout : bool, default: :rc:`figure.constrained_layout.use` + This is equal to ``layout='constrained'``. - Parameters - ---------- - nrows, ncols : int, default: 1 - Number of rows/columns of the subplot grid. + .. admonition:: Discouraged - sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False - Controls sharing of properties among x (*sharex*) or y (*sharey*) - axes: + The use of this parameter is discouraged. Please use + ``layout='constrained'`` instead. - - True or 'all': x- or y-axis will be shared among all subplots. - - False or 'none': each subplot x- or y-axis will be independent. - - 'row': each subplot row will share an x- or y-axis. - - 'col': each subplot column will share an x- or y-axis. + layout : {'constrained', 'compressed', 'tight', 'none', `.LayoutEngine`, \ +None}, default: None + The layout mechanism for positioning of plot elements to avoid + overlapping Axes decorations (labels, ticks, etc). Note that + layout managers can have significant performance penalties. - When subplots have a shared x-axis along a column, only the x tick - labels of the bottom subplot are created. Similarly, when subplots - have a shared y-axis along a row, only the y tick labels of the - first column subplot are created. To later turn other subplots' - ticklabels on, use `~matplotlib.axes.Axes.tick_params`. + - 'constrained': The constrained layout solver adjusts Axes sizes + to avoid overlapping Axes decorations. Can handle complex plot + layouts and colorbars, and is thus recommended. - squeeze : bool, default: True - - If True, extra dimensions are squeezed out from the returned - array of Axes: + See :ref:`constrainedlayout_guide` for examples. - - if only one subplot is constructed (nrows=ncols=1), the - resulting single Axes object is returned as a scalar. - - for Nx1 or 1xM subplots, the returned object is a 1D numpy - object array of Axes objects. - - for NxM, subplots with N>1 and M>1 are returned as a 2D array. + - 'compressed': uses the same algorithm as 'constrained', but + removes extra space between fixed-aspect-ratio Axes. Best for + simple grids of Axes. - - If False, no squeezing at all is done: the returned Axes object - is always a 2D array containing Axes instances, even if it ends - up being 1x1. + - 'tight': Use the tight layout mechanism. This is a relatively + simple algorithm that adjusts the subplot parameters so that + decorations do not overlap. - subplot_kw : dict, optional - Dict with keywords passed to the `.Figure.add_subplot` call used to - create each subplot. + See :ref:`tight_layout_guide` for examples. - gridspec_kw : dict, optional - Dict with keywords passed to the - `~matplotlib.gridspec.GridSpec` constructor used to create - the grid the subplots are placed on. + - 'none': Do not use a layout engine. - Returns - ------- - `~.axes.Axes` or array of Axes - Either a single `~matplotlib.axes.Axes` object or an array of Axes - objects if more than one subplot was created. The dimensions of the - resulting array can be controlled with the *squeeze* keyword, see - above. + - A `.LayoutEngine` instance. Builtin layout classes are + `.ConstrainedLayoutEngine` and `.TightLayoutEngine`, more easily + accessible by 'constrained' and 'tight'. Passing an instance + allows third parties to provide their own layout engine. - See Also - -------- - .pyplot.subplots - .Figure.add_subplot - .pyplot.subplot + If not given, fall back to using the parameters *tight_layout* and + *constrained_layout*, including their config defaults + :rc:`figure.autolayout` and :rc:`figure.constrained_layout.use`. - Examples - -------- - :: + Other Parameters + ---------------- + **kwargs : `.Figure` properties, optional + + %(Figure:kwdoc)s + """ + super().__init__(**kwargs) + self._root_figure = self + self._layout_engine = None + + if layout is not None: + if (tight_layout is not None): + _api.warn_external( + "The Figure parameters 'layout' and 'tight_layout' cannot " + "be used together. Please use 'layout' only.") + if (constrained_layout is not None): + _api.warn_external( + "The Figure parameters 'layout' and 'constrained_layout' " + "cannot be used together. Please use 'layout' only.") + self.set_layout_engine(layout=layout) + elif tight_layout is not None: + if constrained_layout is not None: + _api.warn_external( + "The Figure parameters 'tight_layout' and " + "'constrained_layout' cannot be used together. Please use " + "'layout' parameter") + self.set_layout_engine(layout='tight') + if isinstance(tight_layout, dict): + self.get_layout_engine().set(**tight_layout) + elif constrained_layout is not None: + if isinstance(constrained_layout, dict): + self.set_layout_engine(layout='constrained') + self.get_layout_engine().set(**constrained_layout) + elif constrained_layout: + self.set_layout_engine(layout='constrained') - # First create some toy data: - x = np.linspace(0, 2*np.pi, 400) - y = np.sin(x**2) + else: + # everything is None, so use default: + self.set_layout_engine(layout=layout) + + # Callbacks traditionally associated with the canvas (and exposed with + # a proxy property), but that actually need to be on the figure for + # pickling. + self._canvas_callbacks = cbook.CallbackRegistry( + signals=FigureCanvasBase.events) + connect = self._canvas_callbacks._connect_picklable + self._mouse_key_ids = [ + connect('key_press_event', backend_bases._key_handler), + connect('key_release_event', backend_bases._key_handler), + connect('key_release_event', backend_bases._key_handler), + connect('button_press_event', backend_bases._mouse_handler), + connect('button_release_event', backend_bases._mouse_handler), + connect('scroll_event', backend_bases._mouse_handler), + connect('motion_notify_event', backend_bases._mouse_handler), + ] + self._button_pick_id = connect('button_press_event', self.pick) + self._scroll_pick_id = connect('scroll_event', self.pick) + + figsize = mpl._val_or_rc(figsize, 'figure.figsize') + dpi = mpl._val_or_rc(dpi, 'figure.dpi') + facecolor = mpl._val_or_rc(facecolor, 'figure.facecolor') + edgecolor = mpl._val_or_rc(edgecolor, 'figure.edgecolor') + frameon = mpl._val_or_rc(frameon, 'figure.frameon') + + figsize = _parse_figsize(figsize, dpi) - # Create a figure - plt.figure() + if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): + raise ValueError('figure size must be positive finite not ' + f'{figsize}') + self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) - # Create a subplot - ax = fig.subplots() - ax.plot(x, y) - ax.set_title('Simple plot') + self.dpi_scale_trans = Affine2D().scale(dpi) + # do not use property as it will trigger + self._dpi = dpi + self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) + self.figbbox = self.bbox + self.transFigure = BboxTransformTo(self.bbox) + self.transSubfigure = self.transFigure - # Create two subplots and unpack the output array immediately - ax1, ax2 = fig.subplots(1, 2, sharey=True) - ax1.plot(x, y) - ax1.set_title('Sharing Y axis') - ax2.scatter(x, y) + self.patch = Rectangle( + xy=(0, 0), width=1, height=1, visible=frameon, + facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, + # Don't let the figure patch influence bbox calculation. + in_layout=False) + self._set_artist_props(self.patch) + self.patch.set_antialiased(False) - # Create four polar axes and access them through the returned array - axes = fig.subplots(2, 2, subplot_kw=dict(polar=True)) - axes[0, 0].plot(x, y) - axes[1, 1].scatter(x, y) + FigureCanvasBase(self) # Set self.canvas. - # Share a X axis with each column of subplots - fig.subplots(2, 2, sharex='col') + if subplotpars is None: + subplotpars = SubplotParams() - # Share a Y axis with each row of subplots - fig.subplots(2, 2, sharey='row') + self.subplotpars = subplotpars - # Share both X and Y axes with all subplots - fig.subplots(2, 2, sharex='all', sharey='all') + self._axstack = _AxesStack() # track all figure Axes and current Axes + self.clear() - # Note that this is the same as - fig.subplots(2, 2, sharex=True, sharey=True) - """ - if gridspec_kw is None: - gridspec_kw = {} - return (self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw) - .subplots(sharex=sharex, sharey=sharey, squeeze=squeeze, - subplot_kw=subplot_kw)) + def pick(self, mouseevent): + if not self.canvas.widgetlock.locked(): + super().pick(mouseevent) - @staticmethod - def _normalize_grid_string(layout): - layout = inspect.cleandoc(layout) - return [list(ln) for ln in layout.strip('\n').split('\n')] + def _check_layout_engines_compat(self, old, new): + """ + Helper for set_layout engine - def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, - empty_sentinel='.'): + If the figure has used the old engine and added a colorbar then the + value of colorbar_gridspec must be the same on the new engine. """ - Build a layout of Axes based on ASCII art or nested lists. + if old is None or new is None: + return True + if old.colorbar_gridspec == new.colorbar_gridspec: + return True + # colorbar layout different, so check if any colorbars are on the + # figure... + for ax in self.axes: + if hasattr(ax, '_colorbar'): + # colorbars list themselves as a colorbar. + return False + return True - This is a helper function to build complex GridSpec layouts visually. + def set_layout_engine(self, layout=None, **kwargs): + """ + Set the layout engine for this figure. - .. note :: + Parameters + ---------- + layout : {'constrained', 'compressed', 'tight', 'none', `.LayoutEngine`, None} - This API is provisional and may be revised in the future based on - early user feedback. + - 'constrained' will use `~.ConstrainedLayoutEngine` + - 'compressed' will also use `~.ConstrainedLayoutEngine`, but with + a correction that attempts to make a good layout for fixed-aspect + ratio Axes. + - 'tight' uses `~.TightLayoutEngine` + - 'none' removes layout engine. + If a `.LayoutEngine` instance, that instance will be used. - Parameters - ---------- - layout : list of list of {hashable or nested} or str + If `None`, the behavior is controlled by :rc:`figure.autolayout` + (which if `True` behaves as if 'tight' was passed) and + :rc:`figure.constrained_layout.use` (which if `True` behaves as if + 'constrained' was passed). If both are `True`, + :rc:`figure.autolayout` takes priority. - A visual layout of how you want your Axes to be arranged - labeled as strings. For example :: + Users and libraries can define their own layout engines and pass + the instance directly as well. - x = [['A panel', 'A panel', 'edge'], - ['C panel', '.', 'edge']] + **kwargs + The keyword arguments are passed to the layout engine to set things + like padding and margin sizes. Only used if *layout* is a string. - Produces 4 axes: + """ + if layout is None: + if mpl.rcParams['figure.autolayout']: + layout = 'tight' + elif mpl.rcParams['figure.constrained_layout.use']: + layout = 'constrained' + else: + self._layout_engine = None + return + if layout == 'tight': + new_layout_engine = TightLayoutEngine(**kwargs) + elif layout == 'constrained': + new_layout_engine = ConstrainedLayoutEngine(**kwargs) + elif layout == 'compressed': + new_layout_engine = ConstrainedLayoutEngine(compress=True, + **kwargs) + elif layout == 'none': + if self._layout_engine is not None: + new_layout_engine = PlaceHolderLayoutEngine( + self._layout_engine.adjust_compatible, + self._layout_engine.colorbar_gridspec + ) + else: + new_layout_engine = None + elif isinstance(layout, LayoutEngine): + new_layout_engine = layout + else: + raise ValueError(f"Invalid value for 'layout': {layout!r}") - - 'A panel' which is 1 row high and spans the first two columns - - 'edge' which is 2 rows high and is on the right edge - - 'C panel' which in 1 row and 1 column wide in the bottom left - - a blank space 1 row and 1 column wide in the bottom center + if self._check_layout_engines_compat(self._layout_engine, + new_layout_engine): + self._layout_engine = new_layout_engine + else: + raise RuntimeError('Colorbar layout of new layout engine not ' + 'compatible with old engine, and a colorbar ' + 'has been created. Engine not changed.') - Any of the entries in the layout can be a list of lists - of the same form to create nested layouts. + def get_layout_engine(self): + return self._layout_engine - If input is a str, then it must be of the form :: + # TODO: I'd like to dynamically add the _repr_html_ method + # to the figure in the right context, but then IPython doesn't + # use it, for some reason. - ''' - AAE - C.E - ''' + def _repr_html_(self): + # We can't use "isinstance" here, because then we'd end up importing + # webagg unconditionally. + if 'WebAgg' in type(self.canvas).__name__: + from matplotlib.backends import backend_webagg + return backend_webagg.ipython_inline_display(self) - where each character is a column and each line is a row. - This only allows only single character Axes labels and does - not allow nesting but is very terse. + def show(self, warn=True): + """ + If using a GUI backend with pyplot, display the figure window. - subplot_kw : dict, optional - Dictionary with keywords passed to the `.Figure.add_subplot` call - used to create each subplot. + If the figure was not created using `~.pyplot.figure`, it will lack + a `~.backend_bases.FigureManagerBase`, and this method will raise an + AttributeError. - gridspec_kw : dict, optional - Dictionary with keywords passed to the `.GridSpec` constructor used - to create the grid the subplots are placed on. + .. warning:: - empty_sentinel : object, optional - Entry in the layout to mean "leave this space empty". Defaults - to ``'.'``. Note, if *layout* is a string, it is processed via - `inspect.cleandoc` to remove leading white space, which may - interfere with using white-space as the empty sentinel. + This does not manage an GUI event loop. Consequently, the figure + may only be shown briefly or not shown at all if you or your + environment are not managing an event loop. - Returns - ------- - dict[label, Axes] - A dictionary mapping the labels to the Axes objects. + Use cases for `.Figure.show` include running this from a GUI + application (where there is persistently an event loop running) or + from a shell, like IPython, that install an input hook to allow the + interactive shell to accept input while the figure is also being + shown and interactive. Some, but not all, GUI toolkits will + register an input hook on import. See :ref:`cp_integration` for + more details. - """ - subplot_kw = subplot_kw or {} - gridspec_kw = gridspec_kw or {} - # special-case string input - if isinstance(layout, str): - layout = self._normalize_grid_string(layout) + If you're in a shell without input hook integration or executing a + python script, you should use `matplotlib.pyplot.show` with + ``block=True`` instead, which takes care of starting and running + the event loop for you. - def _make_array(inp): - """ - Convert input into 2D array + Parameters + ---------- + warn : bool, default: True + If ``True`` and we are not running headless (i.e. on Linux with an + unset DISPLAY), issue warning when called on a non-GUI backend. - We need to have this internal function rather than - ``np.asarray(..., dtype=object)`` so that a list of lists - of lists does not get converted to an array of dimension > - 2 + """ + if self.canvas.manager is None: + raise AttributeError( + "Figure.show works only for figures managed by pyplot, " + "normally created by pyplot.figure()") + try: + self.canvas.manager.show() + except NonGuiException as exc: + if warn: + _api.warn_external(str(exc)) - Returns - ------- - 2D object array + @property + def axes(self): + """ + List of Axes in the Figure. You can access and modify the Axes in the + Figure through this list. - """ - r0, *rest = inp - for j, r in enumerate(rest, start=1): - if isinstance(r, str): - raise ValueError('List layout specification must be 2D') - if len(r0) != len(r): - raise ValueError( - "All of the rows must be the same length, however " - f"the first row ({r0!r}) has length {len(r0)} " - f"and row {j} ({r!r}) has length {len(r)}." - ) - out = np.zeros((len(inp), len(r0)), dtype=object) - for j, r in enumerate(inp): - for k, v in enumerate(r): - out[j, k] = v - return out + Do not modify the list itself. Instead, use `~Figure.add_axes`, + `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an Axes. - def _identify_keys_and_nested(layout): - """ - Given a 2D object array, identify unique IDs and nested layouts + Note: The `.Figure.axes` property and `~.Figure.get_axes` method are + equivalent. + """ + return self._axstack.as_list() - Parameters - ---------- - layout : 2D numpy object array + get_axes = axes.fget + + @property + def number(self): + """The figure id, used to identify figures in `.pyplot`.""" + # Historically, pyplot dynamically added a number attribute to figure. + # However, this number must stay in sync with the figure manager. + # AFAICS overwriting the number attribute does not have the desired + # effect for pyplot. But there are some repos in GitHub that do change + # number. So let's take it slow and properly migrate away from writing. + # + # Making the dynamic attribute private and wrapping it in a property + # allows to maintain current behavior and deprecate write-access. + # + # When the deprecation expires, there's no need for duplicate state + # anymore and the private _number attribute can be replaced by + # `self.canvas.manager.num` if that exists and None otherwise. + if hasattr(self, '_number'): + return self._number + else: + raise AttributeError( + "'Figure' object has no attribute 'number'. In the future this" + "will change to returning 'None' instead.") + + @number.setter + def number(self, num): + _api.warn_deprecated( + "3.10", + message="Changing 'Figure.number' is deprecated since %(since)s and " + "will raise an error starting %(removal)s") + self._number = num + + def _get_renderer(self): + if hasattr(self.canvas, 'get_renderer'): + return self.canvas.get_renderer() + else: + return _get_renderer(self) - Returns - ------- - unique_ids : Set[object] - The unique non-sub layout entries in this layout - nested : Dict[Tuple[int, int]], 2D object array - """ - unique_ids = set() - nested = {} - for j, row in enumerate(layout): - for k, v in enumerate(row): - if v == empty_sentinel: - continue - elif not cbook.is_scalar_or_string(v): - nested[(j, k)] = _make_array(v) - else: - unique_ids.add(v) + def _get_dpi(self): + return self._dpi - return unique_ids, nested + def _set_dpi(self, dpi, forward=True): + """ + Parameters + ---------- + dpi : float - def _do_layout(gs, layout, unique_ids, nested): - """ - Recursively do the layout. + forward : bool + Passed on to `~.Figure.set_size_inches` + """ + if dpi == self._dpi: + # We don't want to cause undue events in backends. + return + self._dpi = dpi + self.dpi_scale_trans.clear().scale(dpi) + w, h = self.get_size_inches() + self.set_size_inches(w, h, forward=forward) - Parameters - ---------- - gs : GridSpec + dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.") - layout : 2D object array - The input converted to a 2D numpy array for this level. + def get_tight_layout(self): + """Return whether `.Figure.tight_layout` is called when drawing.""" + return isinstance(self.get_layout_engine(), TightLayoutEngine) - unique_ids : Set[object] - The identified scalar labels at this level of nesting. + @_api.deprecated("3.6", alternative="set_layout_engine", + pending=True) + def set_tight_layout(self, tight): + """ + Set whether and how `.Figure.tight_layout` is called when drawing. - nested : Dict[Tuple[int, int]], 2D object array - The identified nested layouts if any. + Parameters + ---------- + tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None + If a bool, sets whether to call `.Figure.tight_layout` upon drawing. + If ``None``, use :rc:`figure.autolayout` instead. + If a dict, pass it as kwargs to `.Figure.tight_layout`, overriding the + default paddings. + """ + tight = mpl._val_or_rc(tight, 'figure.autolayout') + _tight = 'tight' if bool(tight) else 'none' + _tight_parameters = tight if isinstance(tight, dict) else {} + self.set_layout_engine(_tight, **_tight_parameters) + self.stale = True - Returns - ------- - Dict[label, Axes] - A flat dict of all of the Axes created. - """ - rows, cols = layout.shape - output = dict() + def get_constrained_layout(self): + """ + Return whether constrained layout is being used. - # create the Axes at this level of nesting - for name in unique_ids: - indx = np.argwhere(layout == name) - start_row, start_col = np.min(indx, axis=0) - end_row, end_col = np.max(indx, axis=0) + 1 - slc = (slice(start_row, end_row), slice(start_col, end_col)) + See :ref:`constrainedlayout_guide`. + """ + return isinstance(self.get_layout_engine(), ConstrainedLayoutEngine) - if (layout[slc] != name).any(): - raise ValueError( - f"While trying to layout\n{layout!r}\n" - f"we found that the label {name!r} specifies a " - "non-rectangular or non-contiguous area.") + @_api.deprecated("3.6", alternative="set_layout_engine('constrained')", + pending=True) + def set_constrained_layout(self, constrained): + """ + Set whether ``constrained_layout`` is used upon drawing. - ax = self.add_subplot( - gs[slc], **{'label': str(name), **subplot_kw} - ) - output[name] = ax - - # do any sub-layouts - for (j, k), nested_layout in nested.items(): - rows, cols = nested_layout.shape - nested_output = _do_layout( - gs[j, k].subgridspec(rows, cols, **gridspec_kw), - nested_layout, - *_identify_keys_and_nested(nested_layout) - ) - overlap = set(output) & set(nested_output) - if overlap: - raise ValueError(f"There are duplicate keys {overlap} " - f"between the outer layout\n{layout!r}\n" - f"and the nested layout\n{nested_layout}") - output.update(nested_output) - return output + If None, :rc:`figure.constrained_layout.use` value will be used. - layout = _make_array(layout) - rows, cols = layout.shape - gs = self.add_gridspec(rows, cols, **gridspec_kw) - ret = _do_layout(gs, layout, *_identify_keys_and_nested(layout)) - for k, ax in ret.items(): - if isinstance(k, str): - ax.set_label(k) - return ret + When providing a dict containing the keys ``w_pad``, ``h_pad`` + the default ``constrained_layout`` paddings will be + overridden. These pads are in inches and default to 3.0/72.0. + ``w_pad`` is the width padding and ``h_pad`` is the height padding. - def delaxes(self, ax): + Parameters + ---------- + constrained : bool or dict or None """ - Remove the `~.axes.Axes` *ax* from the figure; update the current axes. + constrained = mpl._val_or_rc(constrained, 'figure.constrained_layout.use') + _constrained = 'constrained' if bool(constrained) else 'none' + _parameters = constrained if isinstance(constrained, dict) else {} + self.set_layout_engine(_constrained, **_parameters) + self.stale = True + + @_api.deprecated( + "3.6", alternative="figure.get_layout_engine().set()", + pending=True) + def set_constrained_layout_pads(self, **kwargs): """ + Set padding for ``constrained_layout``. - def _reset_locators_and_formatters(axis): - # Set the formatters and locators to be associated with axis - # (where previously they may have been associated with another - # Axis instance) - # - # Because set_major_formatter() etc. force isDefault_* to be False, - # we have to manually check if the original formatter was a - # default and manually set isDefault_* if that was the case. - majfmt = axis.get_major_formatter() - isDefault = majfmt.axis.isDefault_majfmt - axis.set_major_formatter(majfmt) - if isDefault: - majfmt.axis.isDefault_majfmt = True - - majloc = axis.get_major_locator() - isDefault = majloc.axis.isDefault_majloc - axis.set_major_locator(majloc) - if isDefault: - majloc.axis.isDefault_majloc = True - - minfmt = axis.get_minor_formatter() - isDefault = majloc.axis.isDefault_minfmt - axis.set_minor_formatter(minfmt) - if isDefault: - minfmt.axis.isDefault_minfmt = True - - minloc = axis.get_minor_locator() - isDefault = majloc.axis.isDefault_minloc - axis.set_minor_locator(minloc) - if isDefault: - minloc.axis.isDefault_minloc = True - - def _break_share_link(ax, grouper): - siblings = grouper.get_siblings(ax) - if len(siblings) > 1: - grouper.remove(ax) - for last_ax in siblings: - if ax is not last_ax: - return last_ax - return None + Tip: The parameters can be passed from a dictionary by using + ``fig.set_constrained_layout(**pad_dict)``. - self._axstack.remove(ax) - # self._localaxes.remove(ax) - self._axobservers.process("_axes_change_event", self) - self.stale = True + See :ref:`constrainedlayout_guide`. - last_ax = _break_share_link(ax, ax._shared_y_axes) - if last_ax is not None: - _reset_locators_and_formatters(last_ax.yaxis) + Parameters + ---------- + w_pad : float, default: :rc:`figure.constrained_layout.w_pad` + Width padding in inches. This is the pad around Axes + and is meant to make sure there is enough room for fonts to + look good. Defaults to 3 pts = 0.04167 inches - last_ax = _break_share_link(ax, ax._shared_x_axes) - if last_ax is not None: - _reset_locators_and_formatters(last_ax.xaxis) + h_pad : float, default: :rc:`figure.constrained_layout.h_pad` + Height padding in inches. Defaults to 3 pts. - def clf(self, keep_observers=False): - """ - Clear the figure. + wspace : float, default: :rc:`figure.constrained_layout.wspace` + Width padding between subplots, expressed as a fraction of the + subplot width. The total padding ends up being w_pad + wspace. - Set *keep_observers* to True if, for example, - a gui widget is tracking the axes in the figure. - """ - self.suppressComposite = None - self.callbacks = cbook.CallbackRegistry() + hspace : float, default: :rc:`figure.constrained_layout.hspace` + Height padding between subplots, expressed as a fraction of the + subplot width. The total padding ends up being h_pad + hspace. - for ax in tuple(self.axes): # Iterate over the copy. - ax.cla() - self.delaxes(ax) # removes ax from self._axstack + """ + if isinstance(self.get_layout_engine(), ConstrainedLayoutEngine): + self.get_layout_engine().set(**kwargs) - toolbar = getattr(self.canvas, 'toolbar', None) - if toolbar is not None: - toolbar.update() - self._axstack.clear() - self.artists = [] - self.lines = [] - self.patches = [] - self.texts = [] - self.images = [] - self.legends = [] - if not keep_observers: - self._axobservers = cbook.CallbackRegistry() - self._suptitle = None - if self.get_constrained_layout(): - self.init_layoutgrid() - self.stale = True + @_api.deprecated("3.6", alternative="fig.get_layout_engine().get()", + pending=True) + def get_constrained_layout_pads(self, relative=False): + """ + Get padding for ``constrained_layout``. - def clear(self, keep_observers=False): - """Clear the figure -- synonym for `clf`.""" - self.clf(keep_observers=keep_observers) + Returns a list of ``w_pad, h_pad`` in inches and + ``wspace`` and ``hspace`` as fractions of the subplot. + All values are None if ``constrained_layout`` is not used. - @_finalize_rasterization - @allow_rasterization - def draw(self, renderer): - # docstring inherited - self._cachedRenderer = renderer + See :ref:`constrainedlayout_guide`. - # draw the figure bounding box, perhaps none for white figure - if not self.get_visible(): - return + Parameters + ---------- + relative : bool + If `True`, then convert from inches to figure relative. + """ + if not isinstance(self.get_layout_engine(), ConstrainedLayoutEngine): + return None, None, None, None + info = self.get_layout_engine().get() + w_pad = info['w_pad'] + h_pad = info['h_pad'] + wspace = info['wspace'] + hspace = info['hspace'] - artists = self.get_children() - artists.remove(self.patch) - artists = sorted( - (artist for artist in artists if not artist.get_animated()), - key=lambda artist: artist.get_zorder()) + if relative and (w_pad is not None or h_pad is not None): + renderer = self._get_renderer() + dpi = renderer.dpi + w_pad = w_pad * dpi / renderer.width + h_pad = h_pad * dpi / renderer.height - for ax in self.axes: - locator = ax.get_axes_locator() - if locator: - pos = locator(ax, renderer) - ax.apply_aspect(pos) - else: - ax.apply_aspect() + return w_pad, h_pad, wspace, hspace - for child in ax.get_children(): - if hasattr(child, 'apply_aspect'): - locator = child.get_axes_locator() - if locator: - pos = locator(child, renderer) - child.apply_aspect(pos) - else: - child.apply_aspect() + def set_canvas(self, canvas): + """ + Set the canvas that contains the figure - try: - renderer.open_group('figure', gid=self.get_gid()) - if self.get_constrained_layout() and self.axes: - self.execute_constrained_layout(renderer) - if self.get_tight_layout() and self.axes: - try: - self.tight_layout(**self._tight_parameters) - except ValueError: - pass - # ValueError can occur when resizing a window. + Parameters + ---------- + canvas : FigureCanvas + """ + self.canvas = canvas - self.patch.draw(renderer) - mimage._draw_list_compositing_images( - renderer, self, artists, self.suppressComposite) + @_docstring.interpd + def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, + vmin=None, vmax=None, origin=None, resize=False, *, + colorizer=None, **kwargs): + """ + Add a non-resampled image to the figure. - renderer.close_group('figure') - finally: - self.stale = False + The image is attached to the lower or upper left corner depending on + *origin*. - self.canvas.draw_event(renderer) + Parameters + ---------- + X + The image data. This is an array of one of the following shapes: - def draw_artist(self, a): - """ - Draw `.Artist` instance *a* only. + - (M, N): an image with scalar data. Color-mapping is controlled + by *cmap*, *norm*, *vmin*, and *vmax*. + - (M, N, 3): an image with RGB values (0-1 float or 0-255 int). + - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int), + i.e. including transparency. - This can only be called after the figure has been drawn. - """ - if self._cachedRenderer is None: - raise AttributeError("draw_artist can only be used after an " - "initial draw which caches the renderer") - a.draw(self._cachedRenderer) + xo, yo : int + The *x*/*y* image offset in pixels. - # Note: in the docstring below, the newlines in the examples after the - # calls to legend() allow replacing it with figlegend() to generate the - # docstring of pyplot.figlegend. + alpha : None or float + The alpha blending value. - @docstring.dedent_interpd - def legend(self, *args, **kwargs): - """ - Place a legend on the figure. + %(cmap_doc)s - Call signatures:: + This parameter is ignored if *X* is RGB(A). - legend() - legend(labels) - legend(handles, labels) + %(norm_doc)s - The call signatures correspond to these three different ways to use - this method: + This parameter is ignored if *X* is RGB(A). - **1. Automatic detection of elements to be shown in the legend** + %(vmin_vmax_doc)s - The elements to be added to the legend are automatically determined, - when you do not pass in any extra arguments. + This parameter is ignored if *X* is RGB(A). - In this case, the labels are taken from the artist. You can specify - them either at artist creation or by calling the - :meth:`~.Artist.set_label` method on the artist:: + origin : {'upper', 'lower'}, default: :rc:`image.origin` + Indicates where the [0, 0] index of the array is in the upper left + or lower left corner of the Axes. - ax.plot([1, 2, 3], label='Inline label') - fig.legend() + resize : bool + If *True*, resize the figure to match the given image size. - or:: + %(colorizer_doc)s - line, = ax.plot([1, 2, 3]) - line.set_label('Label via method') - fig.legend() + This parameter is ignored if *X* is RGB(A). - Specific lines can be excluded from the automatic legend element - selection by defining a label starting with an underscore. - This is default for all artists, so calling `.Figure.legend` without - any arguments and without setting the labels manually will result in - no legend being drawn. + Returns + ------- + `matplotlib.image.FigureImage` + Other Parameters + ---------------- + **kwargs + Additional kwargs are `.Artist` kwargs passed on to `.FigureImage`. - **2. Labeling existing plot elements** + Notes + ----- + figimage complements the Axes image (`~matplotlib.axes.Axes.imshow`) + which will be resampled to fit the current Axes. If you want + a resampled image to fill the entire figure, you can define an + `~matplotlib.axes.Axes` with extent [0, 0, 1, 1]. - To make a legend for all artists on all Axes, call this function with - an iterable of strings, one for each legend item. For example:: + Examples + -------- + :: - fig, (ax1, ax2) = plt.subplots(1, 2) - ax1.plot([1, 3, 5], color='blue') - ax2.plot([2, 4, 6], color='red') - fig.legend(['the blues', 'the reds']) + f = plt.figure() + nx = int(f.get_figwidth() * f.dpi) + ny = int(f.get_figheight() * f.dpi) + data = np.random.random((ny, nx)) + f.figimage(data) + plt.show() + """ + if resize: + dpi = self.get_dpi() + figsize = [x / dpi for x in (X.shape[1], X.shape[0])] + self.set_size_inches(figsize, forward=True) - Note: This call signature is discouraged, because the relation between - plot elements and labels is only implicit by their order and can - easily be mixed up. + im = mimage.FigureImage(self, cmap=cmap, norm=norm, + colorizer=colorizer, + offsetx=xo, offsety=yo, + origin=origin, **kwargs) + im.stale_callback = _stale_figure_callback + im.set_array(X) + im.set_alpha(alpha) + if norm is None: + im._check_exclusionary_keywords(colorizer, vmin=vmin, vmax=vmax) + im.set_clim(vmin, vmax) + self.images.append(im) + im._remove_method = self.images.remove + self.stale = True + return im - **3. Explicitly defining the elements in the legend** + def set_size_inches(self, w, h=None, forward=True): + """ + Set the figure size in inches. - For full control of which artists have a legend entry, it is possible - to pass an iterable of legend artists followed by an iterable of - legend labels respectively:: + Call signatures:: - fig.legend([line1, line2, line3], ['label1', 'label2', 'label3']) + fig.set_size_inches(w, h) # OR + fig.set_size_inches((w, h)) Parameters ---------- - handles : list of `.Artist`, optional - A list of Artists (lines, patches) to be added to the legend. - Use this together with *labels*, if you need full control on what - is shown in the legend and the automatic mechanism described above - is not sufficient. + w : (float, float) or float + Width and height in inches (if height not specified as a separate + argument) or width. + h : float + Height in inches. + forward : bool, default: True + If ``True``, the canvas size is automatically updated, e.g., + you can resize the figure window from the shell. - The length of handles and labels should be the same in this - case. If they are not, they are truncated to the smaller length. + See Also + -------- + matplotlib.figure.Figure.get_size_inches + matplotlib.figure.Figure.set_figwidth + matplotlib.figure.Figure.set_figheight - labels : list of str, optional - A list of labels to show next to the artists. - Use this together with *handles*, if you need full control on what - is shown in the legend and the automatic mechanism described above - is not sufficient. + Notes + ----- + To transform from pixels to inches divide by `Figure.dpi`. + """ + if h is None: # Got called with a single pair as argument. + w, h = w + size = np.array([w, h]) + if not np.isfinite(size).all() or (size < 0).any(): + raise ValueError(f'figure size must be positive finite not {size}') + self.bbox_inches.p1 = size + if forward: + manager = self.canvas.manager + if manager is not None: + manager.resize(*(size * self.dpi).astype(int)) + self.stale = True + + def get_size_inches(self): + """ + Return the current size of the figure in inches. Returns ------- - `~matplotlib.legend.Legend` - - Other Parameters - ---------------- - %(_legend_kw_doc)s + ndarray + The size (width, height) of the figure in inches. See Also -------- - .Axes.legend + matplotlib.figure.Figure.set_size_inches + matplotlib.figure.Figure.get_figwidth + matplotlib.figure.Figure.get_figheight Notes ----- - Some artists are not supported by this function. See - :doc:`/tutorials/intermediate/legend_guide` for details. - """ - - handles, labels, extra_args, kwargs = mlegend._parse_legend_args( - self.axes, - *args, - **kwargs) - # check for third arg - if len(extra_args): - # cbook.warn_deprecated( - # "2.1", - # message="Figure.legend will accept no more than two " - # "positional arguments in the future. Use " - # "'fig.legend(handles, labels, loc=location)' " - # "instead.") - # kwargs['loc'] = extra_args[0] - # extra_args = extra_args[1:] - pass - transform = kwargs.pop('bbox_transform', self.transFigure) - # explicitly set the bbox transform if the user hasn't. - l = mlegend.Legend(self, handles, labels, *extra_args, - bbox_transform=transform, **kwargs) - self.legends.append(l) - l._remove_method = self.legends.remove - self.stale = True - return l - - @docstring.dedent_interpd - def text(self, x, y, s, fontdict=None, **kwargs): + The size in pixels can be obtained by multiplying with `Figure.dpi`. """ - Add text to figure. + return np.array(self.bbox_inches.p1) - Parameters - ---------- - x, y : float - The position to place the text. By default, this is in figure - coordinates, floats in [0, 1]. The coordinate system can be changed - using the *transform* keyword. + def get_figwidth(self): + """Return the figure width in inches.""" + return self.bbox_inches.width - s : str - The text string. + def get_figheight(self): + """Return the figure height in inches.""" + return self.bbox_inches.height - fontdict : dict, optional - A dictionary to override the default text properties. If not given, - the defaults are determined by :rc:`font.*`. Properties passed as - *kwargs* override the corresponding ones given in *fontdict*. + def get_dpi(self): + """Return the resolution in dots per inch as a float.""" + return self.dpi - Returns - ------- - `~.text.Text` + def set_dpi(self, val): + """ + Set the resolution of the figure in dots-per-inch. - Other Parameters - ---------------- - **kwargs : `~matplotlib.text.Text` properties - Other miscellaneous text parameters. + Parameters + ---------- + val : float + """ + self.dpi = val + self.stale = True + + def set_figwidth(self, val, forward=True): + """ + Set the width of the figure in inches. - %(Text)s + Parameters + ---------- + val : float + forward : bool + See `set_size_inches`. See Also -------- - .Axes.text - .pyplot.text + matplotlib.figure.Figure.set_figheight + matplotlib.figure.Figure.set_size_inches """ - effective_kwargs = { - 'transform': self.transFigure, - **(fontdict if fontdict is not None else {}), - **kwargs, - } - text = Text(x=x, y=y, text=s, **effective_kwargs) - text.set_figure(self) - text.stale_callback = _stale_figure_callback - - self.texts.append(text) - text._remove_method = self.texts.remove - self.stale = True - return text - - def _set_artist_props(self, a): - if a != self: - a.set_figure(self) - a.stale_callback = _stale_figure_callback - a.set_transform(self.transFigure) + self.set_size_inches(val, self.get_figheight(), forward=forward) - @docstring.dedent_interpd - def gca(self, **kwargs): + def set_figheight(self, val, forward=True): """ - Get the current axes, creating one if necessary. - - The following kwargs are supported for ensuring the returned axes - adheres to the given projection etc., and for axes creation if - the active axes does not exist: + Set the height of the figure in inches. - %(Axes)s + Parameters + ---------- + val : float + forward : bool + See `set_size_inches`. + See Also + -------- + matplotlib.figure.Figure.set_figwidth + matplotlib.figure.Figure.set_size_inches """ - ckey, cax = self._axstack.current_key_axes() - # if there exists an axes on the stack see if it matches - # the desired axes configuration - if cax is not None: + self.set_size_inches(self.get_figwidth(), val, forward=forward) - # if no kwargs are given just return the current axes - # this is a convenience for gca() on axes such as polar etc. - if not kwargs: - return cax + def clear(self, keep_observers=False): + # docstring inherited + super().clear(keep_observers=keep_observers) + # FigureBase.clear does not clear toolbars, as + # only Figure can have toolbars + toolbar = self.canvas.toolbar + if toolbar is not None: + toolbar.update() - # if the user has specified particular projection detail - # then build up a key which can represent this - else: - projection_class, _, key = \ - self._process_projection_requirements(**kwargs) - - # let the returned axes have any gridspec by removing it from - # the key - ckey = ckey[1:] - key = key[1:] - - # if the cax matches this key then return the axes, otherwise - # continue and a new axes will be created - if key == ckey and isinstance(cax, projection_class): - return cax - else: - cbook._warn_external('Requested projection is different ' - 'from current axis projection, ' - 'creating new axis with requested ' - 'projection.') + @_finalize_rasterization + @allow_rasterization + def draw(self, renderer): + # docstring inherited + if not self.get_visible(): + return - # no axes found, so create one which spans the figure - return self.add_subplot(1, 1, 1, **kwargs) + with self._render_lock: - def sca(self, a): - """Set the current axes to be *a* and return *a*.""" - self._axstack.bubble(a) - self._axobservers.process("_axes_change_event", self) - return a + artists = self._get_draw_artists(renderer) + try: + renderer.open_group('figure', gid=self.get_gid()) + if self.axes and self.get_layout_engine() is not None: + try: + self.get_layout_engine().execute(self) + except ValueError: + pass + # ValueError can occur when resizing a window. - def _gci(self): - # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere. - """ - Get the current colorable artist. + self.patch.draw(renderer) + mimage._draw_list_compositing_images( + renderer, self, artists, self.suppressComposite) - Specifically, returns the current `.ScalarMappable` instance (`.Image` - created by `imshow` or `figimage`, `.Collection` created by `pcolor` or - `scatter`, etc.), or *None* if no such instance has been defined. + renderer.close_group('figure') + finally: + self.stale = False - The current image is an attribute of the current axes, or the nearest - earlier axes in the current figure that contains an image. + DrawEvent("draw_event", self.canvas, renderer)._process() - Notes - ----- - Historically, the only colorable artists were images; hence the name - ``gci`` (get current image). + def draw_without_rendering(self): """ - # Look first for an image in the current Axes: - cax = self._axstack.current_key_axes()[1] - if cax is None: - return None - im = cax._gci() - if im is not None: - return im + Draw the figure with no output. Useful to get the final size of + artists that require a draw before their size is known (e.g. text). + """ + renderer = _get_renderer(self) + with renderer._draw_disabled(): + self.draw(renderer) - # If there is no image in the current Axes, search for - # one in a previously created Axes. Whether this makes - # sense is debatable, but it is the documented behavior. - for ax in reversed(self.axes): - im = ax._gci() - if im is not None: - return im - return None + def draw_artist(self, a): + """ + Draw `.Artist` *a* only. + """ + a.draw(self.canvas.get_renderer()) def __getstate__(self): state = super().__getstate__() @@ -2152,38 +3285,31 @@ def __getstate__(self): # re-attached to another. state.pop("canvas") - # Set cached renderer to None -- it can't be pickled. - state["_cachedRenderer"] = None + # discard any changes to the dpi due to pixel ratio changes + state["_dpi"] = state.get('_original_dpi', state['_dpi']) # add version information to the state - state['__mpl_version__'] = _mpl_version + state['__mpl_version__'] = mpl.__version__ # check whether the figure manager (if any) is registered with pyplot from matplotlib import _pylab_helpers - if getattr(self.canvas, 'manager', None) \ - in _pylab_helpers.Gcf.figs.values(): + if self.canvas.manager in _pylab_helpers.Gcf.figs.values(): state['_restore_to_pylab'] = True - - # set all the layoutgrid information to None. kiwisolver objects can't - # be pickled, so we lose the layout options at this point. - state.pop('_layoutgrid', None) - return state def __setstate__(self, state): version = state.pop('__mpl_version__') restore_to_pylab = state.pop('_restore_to_pylab', False) - if version != _mpl_version: - cbook._warn_external( + if version != mpl.__version__: + _api.warn_external( f"This figure was saved with matplotlib version {version} and " - f"is unlikely to function correctly.") - + f"loaded with {mpl.__version__} so may not function correctly." + ) self.__dict__ = state # re-initialise some of the unstored state information FigureCanvasBase(self) # Set self.canvas. - self._layoutgrid = None if restore_to_pylab: # lazy import to avoid circularity @@ -2191,34 +3317,36 @@ def __setstate__(self, state): import matplotlib._pylab_helpers as pylab_helpers allnums = plt.get_fignums() num = max(allnums) + 1 if allnums else 1 - mgr = plt._backend_mod.new_figure_manager_given_figure(num, self) + backend = plt._get_backend_mod() + mgr = backend.new_figure_manager_given_figure(num, self) pylab_helpers.Gcf._set_new_active_manager(mgr) plt.draw_if_interactive() self.stale = True def add_axobserver(self, func): - """Whenever the axes state change, ``func(self)`` will be called.""" + """Whenever the Axes state change, ``func(self)`` will be called.""" # Connect a wrapper lambda and not func itself, to avoid it being # weakref-collected. self._axobservers.connect("_axes_change_event", lambda arg: func(arg)) def savefig(self, fname, *, transparent=None, **kwargs): """ - Save the current figure. + Save the current figure as an image or vector graphic to a file. Call signature:: - savefig(fname, dpi=None, facecolor='w', edgecolor='w', - orientation='portrait', papertype=None, format=None, - transparent=False, bbox_inches=None, pad_inches=0.1, - frameon=None, metadata=None) + savefig(fname, *, transparent=None, dpi='figure', format=None, + metadata=None, bbox_inches=None, pad_inches=0.1, + facecolor='auto', edgecolor='auto', backend=None, + **kwargs + ) The available output formats depend on the backend being used. Parameters ---------- - fname : str or path-like or file-like + fname : str or path-like or binary file-like A path, or a Python file-like object, or possibly some backend-dependent object such as `matplotlib.backends.backend_pdf.PdfPages`. @@ -2236,83 +3364,30 @@ def savefig(self, fname, *, transparent=None, **kwargs): Other Parameters ---------------- - dpi : float or 'figure', default: :rc:`savefig.dpi` - The resolution in dots per inch. If 'figure', use the figure's - dpi value. - - quality : int, default: :rc:`savefig.jpeg_quality` - Applicable only if *format* is 'jpg' or 'jpeg', ignored otherwise. - - The image quality, on a scale from 1 (worst) to 95 (best). - Values above 95 should be avoided; 100 disables portions of - the JPEG compression algorithm, and results in large files - with hardly any gain in image quality. + transparent : bool, default: :rc:`savefig.transparent` + If *True*, the Axes patches will all be transparent; the + Figure patch will also be transparent unless *facecolor* + and/or *edgecolor* are specified via kwargs. - This parameter is deprecated. + If *False* has no effect and the color of the Axes and + Figure patches are unchanged (unless the Figure patch + is specified via the *facecolor* and/or *edgecolor* keyword + arguments in which case those colors are used). - optimize : bool, default: False - Applicable only if *format* is 'jpg' or 'jpeg', ignored otherwise. - - Whether the encoder should make an extra pass over the image - in order to select optimal encoder settings. - - This parameter is deprecated. - - progressive : bool, default: False - Applicable only if *format* is 'jpg' or 'jpeg', ignored otherwise. - - Whether the image should be stored as a progressive JPEG file. - - This parameter is deprecated. - - facecolor : color or 'auto', default: :rc:`savefig.facecolor` - The facecolor of the figure. If 'auto', use the current figure - facecolor. - - edgecolor : color or 'auto', default: :rc:`savefig.edgecolor` - The edgecolor of the figure. If 'auto', use the current figure - edgecolor. + The transparency of these patches will be restored to their + original values upon exit of this function. - orientation : {'landscape', 'portrait'} - Currently only supported by the postscript backend. + This is useful, for example, for displaying + a plot on top of a colored background on a web page. - papertype : str - One of 'letter', 'legal', 'executive', 'ledger', 'a0' through - 'a10', 'b0' through 'b10'. Only supported for postscript - output. + dpi : float or 'figure', default: :rc:`savefig.dpi` + The resolution in dots per inch. If 'figure', use the figure's + dpi value. format : str The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this is unset is documented under *fname*. - transparent : bool - If *True*, the axes patches will all be transparent; the - figure patch will also be transparent unless facecolor - and/or edgecolor are specified via kwargs. - This is useful, for example, for displaying - a plot on top of a colored background on a web page. The - transparency of these patches will be restored to their - original values upon exit of this function. - - bbox_inches : str or `.Bbox`, default: :rc:`savefig.bbox` - Bounding box in inches: only the given portion of the figure is - saved. If 'tight', try to figure out the tight bbox of the figure. - - pad_inches : float, default: :rc:`savefig.pad_inches` - Amount of padding around the figure when bbox_inches is 'tight'. - - bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional - A list of extra artists that will be considered when the - tight bbox is calculated. - - backend : str, optional - Use a non-default backend to render the file, e.g. to render a - png file with the "cairo" backend rather than the default "agg", - or a pdf file with the "pgf" backend rather than the default - "pdf". Note that the default backend is normally sufficient. See - :ref:`the-builtin-backends` for a list of valid backends for each - file format. Custom backends can be referenced as "module://...". - metadata : dict, optional Key/value pairs to store in the image metadata. The supported keys and defaults depend on the image format and backend: @@ -2325,117 +3400,92 @@ def savefig(self, fname, *, transparent=None, **kwargs): `~.FigureCanvasSVG.print_svg`. - 'eps' and 'ps' with PS backend: Only 'Creator' is supported. - pil_kwargs : dict, optional - Additional keyword arguments that are passed to - `PIL.Image.Image.save` when saving the figure. - """ + Not supported for 'pgf', 'raw', and 'rgba' as those formats do not support + embedding metadata. + Does not currently support 'jpg', 'tiff', or 'webp', but may include + embedding EXIF metadata in the future. - kwargs.setdefault('dpi', mpl.rcParams['savefig.dpi']) - if transparent is None: - transparent = mpl.rcParams['savefig.transparent'] - - if transparent: - kwargs.setdefault('facecolor', 'none') - kwargs.setdefault('edgecolor', 'none') - original_axes_colors = [] - for ax in self.axes: - patch = ax.patch - original_axes_colors.append((patch.get_facecolor(), - patch.get_edgecolor())) - patch.set_facecolor('none') - patch.set_edgecolor('none') - - self.canvas.print_figure(fname, **kwargs) - - if transparent: - for ax, cc in zip(self.axes, original_axes_colors): - ax.patch.set_facecolor(cc[0]) - ax.patch.set_edgecolor(cc[1]) - - @docstring.dedent_interpd - def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): - """%(colorbar_doc)s""" - if ax is None: - ax = self.gca() - if (hasattr(mappable, "axes") and ax is not mappable.axes - and cax is None): - cbook.warn_deprecated( - "3.4", message="Starting from Matplotlib 3.6, colorbar() " - "will steal space from the mappable's axes, rather than " - "from the current axes, to place the colorbar. To " - "silence this warning, explicitly pass the 'ax' argument " - "to colorbar().") - - # Store the value of gca so that we can set it back later on. - current_ax = self.gca() + bbox_inches : str or `.Bbox`, default: :rc:`savefig.bbox` + Bounding box in inches: only the given portion of the figure is + saved. If 'tight', try to figure out the tight bbox of the figure. - if cax is None: - if (use_gridspec and isinstance(ax, SubplotBase) - and not self.get_constrained_layout()): - cax, kw = cbar.make_axes_gridspec(ax, **kw) - else: - cax, kw = cbar.make_axes(ax, **kw) + pad_inches : float or 'layout', default: :rc:`savefig.pad_inches` + Amount of padding in inches around the figure when bbox_inches is + 'tight'. If 'layout' use the padding from the constrained or + compressed layout engine; ignored if one of those engines is not in + use. - # need to remove kws that cannot be passed to Colorbar - NON_COLORBAR_KEYS = ['fraction', 'pad', 'shrink', 'aspect', 'anchor', - 'panchor'] - cb_kw = {k: v for k, v in kw.items() if k not in NON_COLORBAR_KEYS} - cb = cbar.colorbar_factory(cax, mappable, **cb_kw) + facecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.facecolor` + The facecolor of the figure. If 'auto', use the current figure + facecolor. - self.sca(current_ax) - self.stale = True - return cb + edgecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.edgecolor` + The edgecolor of the figure. If 'auto', use the current figure + edgecolor. - def subplots_adjust(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): - """ - Adjust the subplot layout parameters. + backend : str, optional + Use a non-default backend to render the file, e.g. to render a + png file with the "cairo" backend rather than the default "agg", + or a pdf file with the "pgf" backend rather than the default + "pdf". Note that the default backend is normally sufficient. See + :ref:`the-builtin-backends` for a list of valid backends for each + file format. Custom backends can be referenced as "module://...". - Unset parameters are left unmodified; initial values are given by - :rc:`figure.subplot.[name]`. + orientation : {'landscape', 'portrait'} + Currently only supported by the postscript backend. - Parameters - ---------- - left : float, optional - The position of the left edge of the subplots, - as a fraction of the figure width. - right : float, optional - The position of the right edge of the subplots, - as a fraction of the figure width. - bottom : float, optional - The position of the bottom edge of the subplots, - as a fraction of the figure height. - top : float, optional - The position of the top edge of the subplots, - as a fraction of the figure height. - wspace : float, optional - The width of the padding between subplots, - as a fraction of the average axes width. - hspace : float, optional - The height of the padding between subplots, - as a fraction of the average axes height. - """ - if self.get_constrained_layout(): - self.set_constrained_layout(False) - cbook._warn_external("This figure was using " - "constrained_layout==True, but that is " - "incompatible with subplots_adjust and or " - "tight_layout: setting " - "constrained_layout==False. ") - self.subplotpars.update(left, bottom, right, top, wspace, hspace) - for ax in self.axes: - if not isinstance(ax, SubplotBase): - # Check if sharing a subplots axis - if isinstance(ax._sharex, SubplotBase): - ax._sharex.update_params() - ax.set_position(ax._sharex.figbox) - elif isinstance(ax._sharey, SubplotBase): - ax._sharey.update_params() - ax.set_position(ax._sharey.figbox) - else: - ax.update_params() - ax.set_position(ax.figbox) - self.stale = True + papertype : str + One of 'letter', 'legal', 'executive', 'ledger', 'a0' through + 'a10', 'b0' through 'b10'. Only supported for postscript + output. + + bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional + A list of extra artists that will be considered when the + tight bbox is calculated. + + pil_kwargs : dict, optional + Additional keyword arguments that are passed to + `PIL.Image.Image.save` when saving the figure. + + """ + + kwargs.setdefault('dpi', mpl.rcParams['savefig.dpi']) + transparent = mpl._val_or_rc(transparent, 'savefig.transparent') + + with ExitStack() as stack: + if transparent: + def _recursively_make_subfig_transparent(exit_stack, subfig): + exit_stack.enter_context( + subfig.patch._cm_set( + facecolor="none", edgecolor="none")) + for ax in subfig.axes: + exit_stack.enter_context( + ax.patch._cm_set( + facecolor="none", edgecolor="none")) + for sub_subfig in subfig.subfigs: + _recursively_make_subfig_transparent( + exit_stack, sub_subfig) + + def _recursively_make_axes_transparent(exit_stack, ax): + exit_stack.enter_context( + ax.patch._cm_set(facecolor="none", edgecolor="none")) + for child_ax in ax.child_axes: + exit_stack.enter_context( + child_ax.patch._cm_set( + facecolor="none", edgecolor="none")) + for child_childax in ax.child_axes: + _recursively_make_axes_transparent( + exit_stack, child_childax) + + kwargs.setdefault('facecolor', 'none') + kwargs.setdefault('edgecolor', 'none') + # set subfigure to appear transparent in printed image + for subfig in self.subfigs: + _recursively_make_subfig_transparent(stack, subfig) + # set Axes to be transparent + for ax in self.axes: + _recursively_make_axes_transparent(stack, ax) + self.canvas.print_figure(fname, **kwargs) def ginput(self, n=1, timeout=30, show_clicks=True, mouse_add=MouseButton.LEFT, @@ -2463,7 +3513,7 @@ def ginput(self, n=1, timeout=30, show_clicks=True, clicks until the input is terminated manually. timeout : float, default: 30 seconds Number of seconds to wait before timing out. If zero or negative - will never timeout. + will never time out. show_clicks : bool, default: True If True, show a red cross at the location of each click. mouse_add : `.MouseButton` or None, default: `.MouseButton.LEFT` @@ -2482,16 +3532,56 @@ def ginput(self, n=1, timeout=30, show_clicks=True, ----- The keyboard can also be used to select points in case your mouse does not have one or more of the buttons. The delete and backspace - keys act like right clicking (i.e., remove last point), the enter key + keys act like right-clicking (i.e., remove last point), the enter key terminates input and any other key (not already used by the window manager) selects a point. """ - blocking_mouse_input = BlockingMouseInput(self, - mouse_add=mouse_add, - mouse_pop=mouse_pop, - mouse_stop=mouse_stop) - return blocking_mouse_input(n=n, timeout=timeout, - show_clicks=show_clicks) + clicks = [] + marks = [] + + def handler(event): + is_button = event.name == "button_press_event" + is_key = event.name == "key_press_event" + # Quit (even if not in infinite mode; this is consistent with + # MATLAB and sometimes quite useful, but will require the user to + # test how many points were actually returned before using data). + if (is_button and event.button == mouse_stop + or is_key and event.key in ["escape", "enter"]): + self.canvas.stop_event_loop() + # Pop last click. + elif (is_button and event.button == mouse_pop + or is_key and event.key in ["backspace", "delete"]): + if clicks: + clicks.pop() + if show_clicks: + marks.pop().remove() + self.canvas.draw() + # Add new click. + elif (is_button and event.button == mouse_add + # On macOS/gtk, some keys return None. + or is_key and event.key is not None): + if event.inaxes: + clicks.append((event.xdata, event.ydata)) + _log.info("input %i: %f, %f", + len(clicks), event.xdata, event.ydata) + if show_clicks: + line = mpl.lines.Line2D([event.xdata], [event.ydata], + marker="+", color="r") + event.inaxes.add_line(line) + marks.append(line) + self.canvas.draw() + if len(clicks) == n and n > 0: + self.canvas.stop_event_loop() + + _blocking_input.blocking_input_loop( + self, ["button_press_event", "key_press_event"], timeout, handler) + + # Cleanup. + for mark in marks: + mark.remove() + self.canvas.draw() + + return clicks def waitforbuttonpress(self, timeout=-1): """ @@ -2501,124 +3591,28 @@ def waitforbuttonpress(self, timeout=-1): mouse button was pressed and None if no input was given within *timeout* seconds. Negative values deactivate *timeout*. """ - blocking_input = BlockingKeyMouseInput(self) - return blocking_input(timeout=timeout) - - def get_default_bbox_extra_artists(self): - bbox_artists = [artist for artist in self.get_children() - if (artist.get_visible() and artist.get_in_layout())] - for ax in self.axes: - if ax.get_visible(): - bbox_artists.extend(ax.get_default_bbox_extra_artists()) - return bbox_artists - - def get_tightbbox(self, renderer, bbox_extra_artists=None): - """ - Return a (tight) bounding box of the figure in inches. - - Artists that have ``artist.set_in_layout(False)`` are not included - in the bbox. - - Parameters - ---------- - renderer : `.RendererBase` subclass - renderer that will be used to draw the figures (i.e. - ``fig.canvas.get_renderer()``) - - bbox_extra_artists : list of `.Artist` or ``None`` - List of artists to include in the tight bounding box. If - ``None`` (default), then all artist children of each axes are - included in the tight bounding box. - - Returns - ------- - `.BboxBase` - containing the bounding box (in figure inches). - """ - - bb = [] - if bbox_extra_artists is None: - artists = self.get_default_bbox_extra_artists() - else: - artists = bbox_extra_artists - - for a in artists: - bbox = a.get_tightbbox(renderer) - if bbox is not None and (bbox.width != 0 or bbox.height != 0): - bb.append(bbox) - - for ax in self.axes: - if ax.get_visible(): - # some axes don't take the bbox_extra_artists kwarg so we - # need this conditional.... - try: - bbox = ax.get_tightbbox( - renderer, bbox_extra_artists=bbox_extra_artists) - except TypeError: - bbox = ax.get_tightbbox(renderer) - bb.append(bbox) - bb = [b for b in bb - if (np.isfinite(b.width) and np.isfinite(b.height) - and (b.width != 0 or b.height != 0))] - - if len(bb) == 0: - return self.bbox_inches - - _bbox = Bbox.union(bb) - - bbox_inches = TransformedBbox(_bbox, Affine2D().scale(1 / self.dpi)) - - return bbox_inches - - def init_layoutgrid(self): - """Initialize the layoutgrid for use in constrained_layout.""" - del(self._layoutgrid) - self._layoutgrid = layoutgrid.LayoutGrid( - parent=None, name='figlb') - - def execute_constrained_layout(self, renderer=None): - """ - Use ``layoutgrid`` to determine pos positions within axes. + event = None - See also `.set_constrained_layout_pads`. - """ + def handler(ev): + nonlocal event + event = ev + self.canvas.stop_event_loop() - from matplotlib._constrained_layout import do_constrained_layout - from matplotlib.tight_layout import get_renderer + _blocking_input.blocking_input_loop( + self, ["button_press_event", "key_press_event"], timeout, handler) - _log.debug('Executing constrainedlayout') - if self._layoutgrid is None: - cbook._warn_external("Calling figure.constrained_layout, but " - "figure not setup to do constrained layout. " - " You either called GridSpec without the " - "fig keyword, you are using plt.subplot, " - "or you need to call figure or subplots " - "with the constrained_layout=True kwarg.") - return - w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads() - # convert to unit-relative lengths - fig = self - width, height = fig.get_size_inches() - w_pad = w_pad / width - h_pad = h_pad / height - if renderer is None: - renderer = get_renderer(fig) - do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace) + return None if event is None else event.name == "key_press_event" - @cbook._delete_parameter("3.2", "renderer") - def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, - rect=None): + def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None): """ Adjust the padding between and around subplots. - To exclude an artist on the axes from the bounding box calculation + To exclude an artist on the Axes from the bounding box calculation that determines the subplot parameters (i.e. legend, or annotation), set ``a.set_in_layout(False)`` for that artist. Parameters ---------- - renderer : subclass of `~.backend_bases.RendererBase`, optional - Defaults to the renderer for the figure. Deprecated. pad : float, default: 1.08 Padding between the figure edge and the edges of subplots, as a fraction of the font size. @@ -2631,219 +3625,23 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, See Also -------- - .Figure.set_tight_layout + .Figure.set_layout_engine .pyplot.tight_layout """ - - from .tight_layout import ( - get_renderer, get_subplotspec_list, get_tight_layout_figure) - from contextlib import suppress - subplotspec_list = get_subplotspec_list(self.axes) - if None in subplotspec_list: - cbook._warn_external("This figure includes Axes that are not " - "compatible with tight_layout, so results " - "might be incorrect.") - - if renderer is None: - renderer = get_renderer(self) - ctx = (renderer._draw_disabled() - if hasattr(renderer, '_draw_disabled') - else suppress()) - with ctx: - kwargs = get_tight_layout_figure( - self, self.axes, subplotspec_list, renderer, - pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) - if kwargs: - self.subplots_adjust(**kwargs) - - def align_xlabels(self, axs=None): - """ - Align the xlabels of subplots in the same subplot column if label - alignment is being done automatically (i.e. the label position is - not manually set). - - Alignment persists for draw events after this is called. - - If a label is on the bottom, it is aligned with labels on axes that - also have their label on the bottom and that have the same - bottom-most subplot row. If the label is on the top, - it is aligned with labels on axes with the same top-most row. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list of (or ndarray) `~matplotlib.axes.Axes` - to align the xlabels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_ylabels - matplotlib.figure.Figure.align_labels - - Notes - ----- - This assumes that ``axs`` are from the same `.GridSpec`, so that - their `.SubplotSpec` positions correspond to figure positions. - - Examples - -------- - Example with rotated xtick labels:: - - fig, axs = plt.subplots(1, 2) - for tick in axs[0].get_xticklabels(): - tick.set_rotation(55) - axs[0].set_xlabel('XLabel 0') - axs[1].set_xlabel('XLabel 1') - fig.align_xlabels() - """ - if axs is None: - axs = self.axes - axs = np.ravel(axs) - for ax in axs: - _log.debug(' Working on: %s', ax.get_xlabel()) - rowspan = ax.get_subplotspec().rowspan - pos = ax.xaxis.get_label_position() # top or bottom - # Search through other axes for label positions that are same as - # this one and that share the appropriate row number. - # Add to a grouper associated with each axes of siblings. - # This list is inspected in `axis.draw` by - # `axis._update_label_position`. - for axc in axs: - if axc.xaxis.get_label_position() == pos: - rowspanc = axc.get_subplotspec().rowspan - if (pos == 'top' and rowspan.start == rowspanc.start or - pos == 'bottom' and rowspan.stop == rowspanc.stop): - # grouper for groups of xlabels to align - self._align_xlabel_grp.join(ax, axc) - - def align_ylabels(self, axs=None): - """ - Align the ylabels of subplots in the same subplot column if label - alignment is being done automatically (i.e. the label position is - not manually set). - - Alignment persists for draw events after this is called. - - If a label is on the left, it is aligned with labels on axes that - also have their label on the left and that have the same - left-most subplot column. If the label is on the right, - it is aligned with labels on axes with the same right-most column. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list (or ndarray) of `~matplotlib.axes.Axes` - to align the ylabels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_xlabels - matplotlib.figure.Figure.align_labels - - Notes - ----- - This assumes that ``axs`` are from the same `.GridSpec`, so that - their `.SubplotSpec` positions correspond to figure positions. - - Examples - -------- - Example with large yticks labels:: - - fig, axs = plt.subplots(2, 1) - axs[0].plot(np.arange(0, 1000, 50)) - axs[0].set_ylabel('YLabel 0') - axs[1].set_ylabel('YLabel 1') - fig.align_ylabels() - """ - if axs is None: - axs = self.axes - axs = np.ravel(axs) - for ax in axs: - _log.debug(' Working on: %s', ax.get_ylabel()) - colspan = ax.get_subplotspec().colspan - pos = ax.yaxis.get_label_position() # left or right - # Search through other axes for label positions that are same as - # this one and that share the appropriate column number. - # Add to a list associated with each axes of siblings. - # This list is inspected in `axis.draw` by - # `axis._update_label_position`. - for axc in axs: - if axc.yaxis.get_label_position() == pos: - colspanc = axc.get_subplotspec().colspan - if (pos == 'left' and colspan.start == colspanc.start or - pos == 'right' and colspan.stop == colspanc.stop): - # grouper for groups of ylabels to align - self._align_ylabel_grp.join(ax, axc) - - def align_labels(self, axs=None): - """ - Align the xlabels and ylabels of subplots with the same subplots - row or column (respectively) if label alignment is being - done automatically (i.e. the label position is not manually set). - - Alignment persists for draw events after this is called. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list (or ndarray) of `~matplotlib.axes.Axes` - to align the labels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_xlabels - - matplotlib.figure.Figure.align_ylabels - """ - self.align_xlabels(axs=axs) - self.align_ylabels(axs=axs) - - def add_gridspec(self, nrows=1, ncols=1, **kwargs): - """ - Return a `.GridSpec` that has this figure as a parent. This allows - complex layout of axes in the figure. - - Parameters - ---------- - nrows : int, default: 1 - Number of rows in grid. - - ncols : int, default: 1 - Number or columns in grid. - - Returns - ------- - `.GridSpec` - - Other Parameters - ---------------- - **kwargs - Keyword arguments are passed to `.GridSpec`. - - See Also - -------- - matplotlib.pyplot.subplots - - Examples - -------- - Adding a subplot that spans two rows:: - - fig = plt.figure() - gs = fig.add_gridspec(2, 2) - ax1 = fig.add_subplot(gs[0, 0]) - ax2 = fig.add_subplot(gs[1, 0]) - # spans two rows: - ax3 = fig.add_subplot(gs[:, 1]) - - """ - - _ = kwargs.pop('figure', None) # pop in case user has added this... - gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs) - self._gridspecs.append(gs) - return gs + # note that here we do not permanently set the figures engine to + # tight_layout but rather just perform the layout in place and remove + # any previous engines. + engine = TightLayoutEngine(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + try: + previous_engine = self.get_layout_engine() + self.set_layout_engine(engine) + engine.execute(self) + if previous_engine is not None and not isinstance( + previous_engine, (TightLayoutEngine, PlaceHolderLayoutEngine) + ): + _api.warn_external('The figure layout has changed to tight') + finally: + self.set_layout_engine('none') def figaspect(arg): @@ -2857,7 +3655,7 @@ def figaspect(arg): Parameters ---------- - arg : float or 2d array + arg : float or 2D array If a float, this defines the aspect ratio (i.e. the ratio height / width). In case of an array the aspect ratio is number of rows / number of @@ -2865,12 +3663,12 @@ def figaspect(arg): Returns ------- - width, height : float - The figure size in inches. + size : (2,) array + The width and height of the figure in inches. Notes ----- - If you want to create an axes within the figure, that still preserves the + If you want to create an Axes within the figure, that still preserves the aspect ratio, be sure to create it with equal width and height. See examples below. @@ -2925,4 +3723,45 @@ def figaspect(arg): newsize = np.clip(newsize, figsize_min, figsize_max) return newsize -docstring.interpd.update(Figure=martist.kwdoc(Figure)) + +def _parse_figsize(figsize, dpi): + """ + Convert a figsize expression to (width, height) in inches. + + Parameters + ---------- + figsize : (float, float) or (float, float, str) + This can be + + - a tuple ``(width, height, unit)``, where *unit* is one of "in" (inch), + "cm" (centimenter), "px" (pixel). + - a tuple ``(width, height)``, which is interpreted in inches, i.e. as + ``(width, height, "in")``. + + dpi : float + The dots-per-inch; used for converting 'px' to 'in'. + """ + num_parts = len(figsize) + if num_parts == 2: + return figsize + elif num_parts == 3: + x, y, unit = figsize + if unit == 'in': + pass + elif unit == 'cm': + x /= 2.54 + y /= 2.54 + elif unit == 'px': + x /= dpi + y /= dpi + else: + raise ValueError( + f"Invalid unit {unit!r} in 'figsize'; " + "supported units are 'in', 'cm', 'px'" + ) + return x, y + else: + raise ValueError( + "Invalid figsize format, expected (x, y) or (x, y, unit) but got " + f"{figsize!r}" + ) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi new file mode 100644 index 000000000000..e7c5175d8af9 --- /dev/null +++ b/lib/matplotlib/figure.pyi @@ -0,0 +1,434 @@ +from collections.abc import Callable, Hashable, Iterable, Sequence +import os +from typing import Any, IO, Literal, TypeVar, overload + +import numpy as np +from numpy.typing import ArrayLike + +from matplotlib.artist import Artist +from matplotlib.axes import Axes +from matplotlib.backend_bases import ( + FigureCanvasBase, + MouseButton, + MouseEvent, + RendererBase, +) +from matplotlib.colors import Colormap, Normalize +from matplotlib.colorbar import Colorbar +from matplotlib.colorizer import ColorizingArtist, Colorizer +from matplotlib.cm import ScalarMappable +from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams +from matplotlib.image import _ImageBase, FigureImage +from matplotlib.layout_engine import LayoutEngine +from matplotlib.legend import Legend +from matplotlib.lines import Line2D +from matplotlib.patches import Rectangle, Patch +from matplotlib.text import Text +from matplotlib.transforms import Affine2D, Bbox, BboxBase, Transform +from mpl_toolkits.mplot3d import Axes3D + +from .typing import ColorType, HashableList + +_T = TypeVar("_T") + +class FigureBase(Artist): + artists: list[Artist] + lines: list[Line2D] + patches: list[Patch] + texts: list[Text] + images: list[_ImageBase] + legends: list[Legend] + subfigs: list[SubFigure] + stale: bool + suppressComposite: bool | None + def __init__(self, **kwargs) -> None: ... + def autofmt_xdate( + self, + bottom: float = ..., + rotation: int = ..., + ha: Literal["left", "center", "right"] = ..., + which: Literal["major", "minor", "both"] = ..., + ) -> None: ... + def get_children(self) -> list[Artist]: ... + def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ... + def suptitle(self, t: str, **kwargs) -> Text: ... + def get_suptitle(self) -> str: ... + def supxlabel(self, t: str, **kwargs) -> Text: ... + def get_supxlabel(self) -> str: ... + def supylabel(self, t: str, **kwargs) -> Text: ... + def get_supylabel(self) -> str: ... + def get_edgecolor(self) -> ColorType: ... + def get_facecolor(self) -> ColorType: ... + def get_frameon(self) -> bool: ... + def set_linewidth(self, linewidth: float) -> None: ... + def get_linewidth(self) -> float: ... + def set_edgecolor(self, color: ColorType) -> None: ... + def set_facecolor(self, color: ColorType) -> None: ... + @overload + def get_figure(self, root: Literal[True]) -> Figure: ... + @overload + def get_figure(self, root: Literal[False]) -> Figure | SubFigure: ... + @overload + def get_figure(self, root: bool = ...) -> Figure | SubFigure: ... + def set_frameon(self, b: bool) -> None: ... + @property + def frameon(self) -> bool: ... + @frameon.setter + def frameon(self, b: bool) -> None: ... + def add_artist(self, artist: Artist, clip: bool = ...) -> Artist: ... + @overload + def add_axes(self, ax: Axes) -> Axes: ... + @overload + def add_axes( + self, + rect: tuple[float, float, float, float], + projection: None | str = ..., + polar: bool = ..., + **kwargs + ) -> Axes: ... + + # TODO: docstring indicates SubplotSpec a valid arg, but none of the listed signatures appear to be that + @overload + def add_subplot(self, *args, projection: Literal["3d"], **kwargs) -> Axes3D: ... + @overload + def add_subplot( + self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs + ) -> Axes: ... + @overload + def add_subplot(self, pos: int, **kwargs) -> Axes: ... + @overload + def add_subplot(self, ax: Axes, **kwargs) -> Axes: ... + @overload + def add_subplot(self, ax: SubplotSpec, **kwargs) -> Axes: ... + @overload + def add_subplot(self, **kwargs) -> Axes: ... + @overload + def subplots( + self, + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> Axes: ... + @overload + def subplots( + self, + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> np.ndarray: ... # TODO numpy/numpy#24738 + @overload + def subplots( + self, + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> Any: ... + def delaxes(self, ax: Axes) -> None: ... + def clear(self, keep_observers: bool = ...) -> None: ... + def clf(self, keep_observers: bool = ...) -> None: ... + + @overload + def legend(self) -> Legend: ... + @overload + def legend(self, handles: Iterable[Artist], labels: Iterable[str], **kwargs) -> Legend: ... + @overload + def legend(self, *, handles: Iterable[Artist], **kwargs) -> Legend: ... + @overload + def legend(self, labels: Iterable[str], **kwargs) -> Legend: ... + @overload + def legend(self, **kwargs) -> Legend: ... + + def text( + self, + x: float, + y: float, + s: str, + fontdict: dict[str, Any] | None = ..., + **kwargs + ) -> Text: ... + def colorbar( + self, + mappable: ScalarMappable | ColorizingArtist, + cax: Axes | None = ..., + ax: Axes | Iterable[Axes] | None = ..., + use_gridspec: bool = ..., + **kwargs + ) -> Colorbar: ... + def subplots_adjust( + self, + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + ) -> None: ... + def align_xlabels(self, axs: Iterable[Axes] | None = ...) -> None: ... + def align_ylabels(self, axs: Iterable[Axes] | None = ...) -> None: ... + def align_titles(self, axs: Iterable[Axes] | None = ...) -> None: ... + def align_labels(self, axs: Iterable[Axes] | None = ...) -> None: ... + def add_gridspec(self, nrows: int = ..., ncols: int = ..., **kwargs) -> GridSpec: ... + @overload + def subfigures( + self, + nrows: int = ..., + ncols: int = ..., + squeeze: Literal[False] = ..., + wspace: float | None = ..., + hspace: float | None = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + **kwargs + ) -> np.ndarray: ... + @overload + def subfigures( + self, + nrows: int = ..., + ncols: int = ..., + squeeze: Literal[True] = ..., + wspace: float | None = ..., + hspace: float | None = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + **kwargs + ) -> np.ndarray | SubFigure: ... + def add_subfigure(self, subplotspec: SubplotSpec, **kwargs) -> SubFigure: ... + def sca(self, a: Axes) -> Axes: ... + def gca(self) -> Axes: ... + def _gci(self) -> ColorizingArtist | None: ... + def _process_projection_requirements( + self, *, axes_class=None, polar=False, projection=None, **kwargs + ) -> tuple[type[Axes], dict[str, Any]]: ... + def get_default_bbox_extra_artists(self) -> list[Artist]: ... + def get_tightbbox( + self, + renderer: RendererBase | None = ..., + *, + bbox_extra_artists: Iterable[Artist] | None = ..., + ) -> Bbox: ... + @overload + def subplot_mosaic( + self, + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[str, Axes]: ... + @overload + def subplot_mosaic( + self, + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[_T, Axes]: ... + @overload + def subplot_mosaic( + self, + mosaic: list[HashableList[Hashable]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: Any = ..., + subplot_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[Hashable, Axes]: ... + +class SubFigure(FigureBase): + @property + def figure(self) -> Figure: ... + subplotpars: SubplotParams + dpi_scale_trans: Affine2D + transFigure: Transform + bbox_relative: Bbox + figbbox: BboxBase + bbox: BboxBase + transSubfigure: Transform + patch: Rectangle + def __init__( + self, + parent: Figure | SubFigure, + subplotspec: SubplotSpec, + *, + facecolor: ColorType | None = ..., + edgecolor: ColorType | None = ..., + linewidth: float = ..., + frameon: bool | None = ..., + **kwargs + ) -> None: ... + @property + def canvas(self) -> FigureCanvasBase: ... + @property + def dpi(self) -> float: ... + @dpi.setter + def dpi(self, value: float) -> None: ... + def get_dpi(self) -> float: ... + def set_dpi(self, val) -> None: ... + def get_constrained_layout(self) -> bool: ... + def get_constrained_layout_pads( + self, relative: bool = ... + ) -> tuple[float, float, float, float]: ... + def get_layout_engine(self) -> LayoutEngine: ... + @property # type: ignore[misc] + def axes(self) -> list[Axes]: ... # type: ignore[override] + def get_axes(self) -> list[Axes]: ... + +class Figure(FigureBase): + @property + def figure(self) -> Figure: ... + bbox_inches: Bbox + dpi_scale_trans: Affine2D + bbox: BboxBase + figbbox: BboxBase + transFigure: Transform + transSubfigure: Transform + patch: Rectangle + subplotpars: SubplotParams + def __init__( + self, + figsize: tuple[float, float] + | tuple[float, float, Literal["in", "cm", "px"]] + | None = ..., + dpi: float | None = ..., + *, + facecolor: ColorType | None = ..., + edgecolor: ColorType | None = ..., + linewidth: float = ..., + frameon: bool | None = ..., + subplotpars: SubplotParams | None = ..., + tight_layout: bool | dict[str, Any] | None = ..., + constrained_layout: bool | dict[str, Any] | None = ..., + layout: Literal["constrained", "compressed", "tight"] + | LayoutEngine + | None = ..., + **kwargs + ) -> None: ... + def pick(self, mouseevent: MouseEvent) -> None: ... + def set_layout_engine( + self, + layout: Literal["constrained", "compressed", "tight", "none"] + | LayoutEngine + | None = ..., + **kwargs + ) -> None: ... + def get_layout_engine(self) -> LayoutEngine | None: ... + def _repr_html_(self) -> str | None: ... + def show(self, warn: bool = ...) -> None: ... + @property + def number(self) -> int | str: ... + @number.setter + def number(self, num: int | str) -> None: ... + @property # type: ignore[misc] + def axes(self) -> list[Axes]: ... # type: ignore[override] + def get_axes(self) -> list[Axes]: ... + @property + def dpi(self) -> float: ... + @dpi.setter + def dpi(self, dpi: float) -> None: ... + def get_tight_layout(self) -> bool: ... + def get_constrained_layout_pads( + self, relative: bool = ... + ) -> tuple[float, float, float, float]: ... + def get_constrained_layout(self) -> bool: ... + canvas: FigureCanvasBase + def set_canvas(self, canvas: FigureCanvasBase) -> None: ... + def figimage( + self, + X: ArrayLike, + xo: int = ..., + yo: int = ..., + alpha: float | None = ..., + norm: str | Normalize | None = ..., + cmap: str | Colormap | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + origin: Literal["upper", "lower"] | None = ..., + resize: bool = ..., + *, + colorizer: Colorizer | None = ..., + **kwargs + ) -> FigureImage: ... + def set_size_inches( + self, w: float | tuple[float, float], h: float | None = ..., forward: bool = ... + ) -> None: ... + def get_size_inches(self) -> np.ndarray: ... + def get_figwidth(self) -> float: ... + def get_figheight(self) -> float: ... + def get_dpi(self) -> float: ... + def set_dpi(self, val: float) -> None: ... + def set_figwidth(self, val: float, forward: bool = ...) -> None: ... + def set_figheight(self, val: float, forward: bool = ...) -> None: ... + def clear(self, keep_observers: bool = ...) -> None: ... + def draw_without_rendering(self) -> None: ... + def draw_artist(self, a: Artist) -> None: ... + def add_axobserver(self, func: Callable[[Figure], Any]) -> None: ... + def savefig( + self, + fname: str | os.PathLike | IO, + *, + transparent: bool | None = ..., + **kwargs + ) -> None: ... + def ginput( + self, + n: int = ..., + timeout: float = ..., + show_clicks: bool = ..., + mouse_add: MouseButton = ..., + mouse_pop: MouseButton = ..., + mouse_stop: MouseButton = ..., + ) -> list[tuple[int, int]]: ... + def waitforbuttonpress(self, timeout: float = ...) -> None | bool: ... + def tight_layout( + self, + *, + pad: float = ..., + h_pad: float | None = ..., + w_pad: float | None = ..., + rect: tuple[float, float, float, float] | None = ... + ) -> None: ... + +def figaspect( + arg: float | ArrayLike, +) -> np.ndarray[tuple[Literal[2]], np.dtype[np.float64]]: ... + +def _parse_figsize( + figsize: tuple[float, float] | tuple[float, float, Literal["in", "cm", "px"]], + dpi: float +) -> tuple[float, float]: ... diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 5769d6d33fe0..2db98b75ab2e 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1,12 +1,14 @@ """ A module for finding, managing, and using fonts across platforms. -This module provides a single `FontManager` instance that can +This module provides a single `FontManager` instance, ``fontManager``, that can be shared across backends and platforms. The `findfont` function returns the best TrueType (TTF) font file in the local or system font path that matches the specified `FontProperties` instance. The `FontManager` also handles Adobe Font Metrics (AFM) font files for use by the PostScript backend. +The `FontManager.addfont` function adds a custom font from a file without +installing it into your operating system. The design is based on the `W3C Cascading Style Sheet, Level 1 (CSS1) font specification `_. @@ -23,23 +25,28 @@ # - setWeights function needs improvement # - 'light' is an invalid weight value, remove it. -from functools import lru_cache +from __future__ import annotations + +from base64 import b64encode +import copy +import dataclasses +from functools import cache, lru_cache +import functools +from io import BytesIO import json import logging from numbers import Number import os from pathlib import Path +import plistlib import re import subprocess import sys -try: - from threading import Timer -except ImportError: - from dummy_threading import Timer +import threading import matplotlib as mpl -from matplotlib import afm, cbook, ft2font, rcParams -from matplotlib.fontconfig_pattern import ( +from matplotlib import _api, _afm, cbook, ft2font +from matplotlib._fontconfig_pattern import ( parse_fontconfig_pattern, generate_fontconfig_pattern) from matplotlib.rcsetup import _validators @@ -125,16 +132,19 @@ 'sans', } - # OS Font paths +try: + _HOME = Path.home() +except Exception: # Exceptions thrown by home() are not specified... + _HOME = Path(os.devnull) # Just an arbitrary path with no children. MSFolders = \ r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' MSFontDirectories = [ r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts', r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts'] MSUserFontDirectories = [ - str(Path.home() / 'AppData/Local/Microsoft/Windows/Fonts'), - str(Path.home() / 'AppData/Roaming/Microsoft/Windows/Fonts'), + str(_HOME / 'AppData/Local/Microsoft/Windows/Fonts'), + str(_HOME / 'AppData/Roaming/Microsoft/Windows/Fonts'), ] X11FontDirectories = [ # an old standard installation point @@ -147,9 +157,9 @@ # common application, not really useful "/usr/lib/openoffice/share/fonts/truetype/", # user fonts - str(Path(os.environ.get('XDG_DATA_HOME', - Path.home() / ".local/share")) / "fonts"), - str(Path.home() / ".fonts"), + str((Path(os.environ.get('XDG_DATA_HOME') or _HOME / ".local/share")) + / "fonts"), + str(_HOME / ".fonts"), ] OSXFontDirectories = [ "/Library/Fonts/", @@ -158,18 +168,13 @@ # fonts installed via MacPorts "/opt/local/share/fonts", # user fonts - str(Path.home() / "Library/Fonts"), + str(_HOME / "Library/Fonts"), ] -@lru_cache(64) -def _cached_realpath(path): - return os.path.realpath(path) - - def get_fontext_synonyms(fontext): """ - Return a list of file extensions extensions that are synonyms for + Return a list of file extensions that are synonyms for the given file extension *fileext*. """ return { @@ -201,7 +206,7 @@ def win32FontDirectory(): \\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Fonts If the key is not found, ``%WINDIR%\Fonts`` will be returned. - """ + """ # noqa: E501 import winreg try: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) as user: @@ -210,88 +215,41 @@ def win32FontDirectory(): return os.path.join(os.environ['WINDIR'], 'Fonts') -def _win32RegistryFonts(reg_domain, base_dir): - r""" - Search for fonts in the Windows registry. - - Parameters - ---------- - reg_domain : int - The top level registry domain (e.g. HKEY_LOCAL_MACHINE). - - base_dir : str - The path to the folder where the font files are usually located (e.g. - C:\Windows\Fonts). If only the filename of the font is stored in the - registry, the absolute path is built relative to this base directory. - - Returns - ------- - `set` - `pathlib.Path` objects with the absolute path to the font files found. - - """ +def _get_win32_installed_fonts(): + """List the font paths known to the Windows registry.""" import winreg items = set() - - for reg_path in MSFontDirectories: - try: - with winreg.OpenKey(reg_domain, reg_path) as local: - for j in range(winreg.QueryInfoKey(local)[1]): - # value may contain the filename of the font or its - # absolute path. - key, value, tp = winreg.EnumValue(local, j) - if not isinstance(value, str): - continue - - # Work around for https://bugs.python.org/issue25778, which - # is fixed in Py>=3.6.1. - value = value.split("\0", 1)[0] - - try: - # If value contains already an absolute path, then it - # is not changed further. - path = Path(base_dir, value).resolve() - except RuntimeError: - # Don't fail with invalid entries. - continue - - items.add(path) - except (OSError, MemoryError): - continue - + # Search and resolve fonts listed in the registry. + for domain, base_dirs in [ + (winreg.HKEY_LOCAL_MACHINE, [win32FontDirectory()]), # System. + (winreg.HKEY_CURRENT_USER, MSUserFontDirectories), # User. + ]: + for base_dir in base_dirs: + for reg_path in MSFontDirectories: + try: + with winreg.OpenKey(domain, reg_path) as local: + for j in range(winreg.QueryInfoKey(local)[1]): + # value may contain the filename of the font or its + # absolute path. + key, value, tp = winreg.EnumValue(local, j) + if not isinstance(value, str): + continue + try: + # If value contains already an absolute path, + # then it is not changed further. + path = Path(base_dir, value).resolve() + except RuntimeError: + # Don't fail with invalid entries. + continue + items.add(path) + except (OSError, MemoryError): + continue return items -def win32InstalledFonts(directory=None, fontext='ttf'): - """ - Search for fonts in the specified font directory, or use the - system directories if none given. Additionally, it is searched for user - fonts installed. A list of TrueType font filenames are returned by default, - or AFM fonts if *fontext* == 'afm'. - """ - import winreg - - if directory is None: - directory = win32FontDirectory() - - fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)] - - items = set() - - # System fonts - items.update(_win32RegistryFonts(winreg.HKEY_LOCAL_MACHINE, directory)) - - # User fonts - for userdir in MSUserFontDirectories: - items.update(_win32RegistryFonts(winreg.HKEY_CURRENT_USER, userdir)) - - # Keep only paths with matching file extension. - return [str(path) for path in items if path.suffix.lower() in fontext] - - -@lru_cache() -def _call_fc_list(): - """Cache and list the font filenames known to `fc-list`.""" +@cache +def _get_fontconfig_fonts(): + """Cache and list the font paths known to ``fc-list``.""" try: if b'--format' not in subprocess.check_output(['fc-list', '--help']): _log.warning( # fontconfig 2.7 implemented --format. @@ -300,14 +258,18 @@ def _call_fc_list(): out = subprocess.check_output(['fc-list', '--format=%{file}\\n']) except (OSError, subprocess.CalledProcessError): return [] - return [os.fsdecode(fname) for fname in out.split(b'\n')] + return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')] -def get_fontconfig_fonts(fontext='ttf'): - """List font filenames known to `fc-list` having the given extension.""" - fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)] - return [fname for fname in _call_fc_list() - if Path(fname).suffix.lower() in fontext] +@cache +def _get_macos_fonts(): + """Cache and list the font paths known to ``system_profiler SPFontsDataType``.""" + try: + d, = plistlib.loads( + subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + except (OSError, subprocess.CalledProcessError, plistlib.InvalidFileException): + return [] + return [Path(entry["path"]) for entry in d["_items"]] def findSystemFonts(fontpaths=None, fontext='ttf'): @@ -323,14 +285,17 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): if fontpaths is None: if sys.platform == 'win32': - fontpaths = MSUserFontDirectories + [win32FontDirectory()] - # now get all installed fonts directly... - fontfiles.update(win32InstalledFonts(fontext=fontext)) + installed_fonts = _get_win32_installed_fonts() + fontpaths = [] else: - fontpaths = X11FontDirectories + installed_fonts = _get_fontconfig_fonts() if sys.platform == 'darwin': + installed_fonts += _get_macos_fonts() fontpaths = [*X11FontDirectories, *OSXFontDirectories] - fontfiles.update(get_fontconfig_fonts(fontext)) + else: + fontpaths = X11FontDirectories + fontfiles.update(str(path) for path in installed_fonts + if path.suffix.lower()[1:] in fontexts) elif isinstance(fontpaths, str): fontpaths = [fontpaths] @@ -341,35 +306,35 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): return [fname for fname in fontfiles if os.path.exists(fname)] +@dataclasses.dataclass(frozen=True) class FontEntry: """ - A class for storing Font properties. It is used when populating - the font lookup dictionary. + A class for storing Font properties. + + It is used when populating the font lookup dictionary. """ - def __init__(self, - fname ='', - name ='', - style ='normal', - variant='normal', - weight ='normal', - stretch='normal', - size ='medium', - ): - self.fname = fname - self.name = name - self.style = style - self.variant = variant - self.weight = weight - self.stretch = stretch - try: - self.size = str(float(size)) - except ValueError: - self.size = size - def __repr__(self): - return "" % ( - self.name, os.path.basename(self.fname), self.style, self.variant, - self.weight, self.stretch) + fname: str = '' + name: str = '' + style: str = 'normal' + variant: str = 'normal' + weight: str | int = 'normal' + stretch: str = 'normal' + size: str = 'medium' + + def _repr_html_(self) -> str: + png_stream = self._repr_png_() + png_b64 = b64encode(png_stream).decode() + return f"" + + def _repr_png_(self) -> bytes: + from matplotlib.figure import Figure # Circular import. + fig = Figure() + font_path = Path(self.fname) if self.fname != '' else None + fig.text(0, 0, self.name, font=font_path) + with BytesIO() as buf: + fig.savefig(buf, bbox_inches='tight', transparent=True) + return buf.getvalue() def ttfFontProperty(font): @@ -414,7 +379,7 @@ def ttfFontProperty(font): style = 'italic' elif sfnt2.find('regular') >= 0: style = 'normal' - elif font.style_flags & ft2font.ITALIC: + elif ft2font.StyleFlags.ITALIC in font.style_flags: style = 'italic' else: style = 'normal' @@ -463,7 +428,7 @@ def get_weight(): # From fontconfig's FcFreeTypeQueryFaceInternal. for regex, weight in _weight_regexes: if re.search(regex, style, re.I): return weight - if font.style_flags & ft2font.BOLD: + if ft2font.StyleFlags.BOLD in font.style_flags: return 700 # "bold" return 500 # "medium", not "regular"! @@ -505,7 +470,9 @@ def afmFontProperty(fontpath, font): Parameters ---------- - font : `.AFM` + fontpath : str + The filename corresponding to *font*. + font : AFM The AFM font file from which information will be extracted. Returns @@ -567,58 +534,55 @@ def afmFontProperty(fontpath, font): return FontEntry(fontpath, name, style, variant, weight, stretch, size) -@cbook.deprecated("3.2", alternative="FontManager.addfont") -def createFontList(fontfiles, fontext='ttf'): - """ - Create a font lookup list. The default is to create - a list of TrueType fonts. An AFM font list can optionally be - created. +def _cleanup_fontproperties_init(init_method): """ + A decorator to limit the call signature to single a positional argument + or alternatively only keyword arguments. - fontlist = [] - # Add fonts from list of known font files. - seen = set() - for fpath in fontfiles: - _log.debug('createFontDict: %s', fpath) - fname = os.path.split(fpath)[1] - if fname in seen: - continue - if fontext == 'afm': - try: - with open(fpath, 'rb') as fh: - font = afm.AFM(fh) - except EnvironmentError: - _log.info("Could not open font file %s", fpath) - continue - except RuntimeError: - _log.info("Could not parse font file %s", fpath) - continue - try: - prop = afmFontProperty(fpath, font) - except KeyError as exc: - _log.info("Could not extract properties for %s: %s", - fpath, exc) - continue - else: - try: - font = ft2font.FT2Font(fpath) - except (OSError, RuntimeError) as exc: - _log.info("Could not open font file %s: %s", fpath, exc) - continue - except UnicodeError: - _log.info("Cannot handle unicode filenames") - continue - try: - prop = ttfFontProperty(font) - except (KeyError, RuntimeError, ValueError, - NotImplementedError) as exc: - _log.info("Could not extract properties for %s: %s", - fpath, exc) - continue + We still accept but deprecate all other call signatures. + + When the deprecation expires we can switch the signature to:: - fontlist.append(prop) - seen.add(fname) - return fontlist + __init__(self, pattern=None, /, *, family=None, style=None, ...) + + plus a runtime check that pattern is not used alongside with the + keyword arguments. This results eventually in the two possible + call signatures:: + + FontProperties(pattern) + FontProperties(family=..., size=..., ...) + + """ + @functools.wraps(init_method) + def wrapper(self, *args, **kwargs): + # multiple args with at least some positional ones + if len(args) > 1 or len(args) == 1 and kwargs: + # Note: Both cases were previously handled as individual properties. + # Therefore, we do not mention the case of font properties here. + _api.warn_deprecated( + "3.10", + message="Passing individual properties to FontProperties() " + "positionally was deprecated in Matplotlib %(since)s and " + "will be removed in %(removal)s. Please pass all properties " + "via keyword arguments." + ) + # single non-string arg -> clearly a family not a pattern + if len(args) == 1 and not kwargs and not cbook.is_scalar_or_string(args[0]): + # Case font-family list passed as single argument + _api.warn_deprecated( + "3.10", + message="Passing family as positional argument to FontProperties() " + "was deprecated in Matplotlib %(since)s and will be removed " + "in %(removal)s. Please pass family names as keyword" + "argument." + ) + # Note on single string arg: + # This has been interpreted as pattern so far. We are already raising if a + # non-pattern compatible family string was given. Therefore, we do not need + # to warn for this case. + return init_method(self, *args, **kwargs) + + return wrapper class FontProperties: @@ -631,39 +595,34 @@ class FontProperties: specification and *math_fontfamily* for math fonts: - family: A list of font names in decreasing order of priority. - The items may include a generic font family name, either - 'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace'. - In that case, the actual font to be used will be looked up - from the associated rcParam. + The items may include a generic font family name, either 'sans-serif', + 'serif', 'cursive', 'fantasy', or 'monospace'. In that case, the actual + font to be used will be looked up from the associated rcParam during the + search process in `.findfont`. Default: :rc:`font.family` - style: Either 'normal', 'italic' or 'oblique'. + Default: :rc:`font.style` - variant: Either 'normal' or 'small-caps'. + Default: :rc:`font.variant` - stretch: A numeric value in the range 0-1000 or one of 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', - 'extra-expanded' or 'ultra-expanded'. + 'extra-expanded' or 'ultra-expanded'. Default: :rc:`font.stretch` - weight: A numeric value in the range 0-1000 or one of 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', - 'extra bold', 'black'. + 'extra bold', 'black'. Default: :rc:`font.weight` - - size: Either an relative value of 'xx-small', 'x-small', + - size: Either a relative value of 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' or an - absolute font size, e.g., 12. - - - math_fontfamily: The family of fonts used to render math text; overrides - :rc:`mathtext.fontset`. Supported values are the same as the ones - supported by :rc:`mathtext.fontset` :: - - 'dejavusans', 'dejavuserif', 'cm', 'stix', 'stixsans' and 'custom'. + absolute font size, e.g., 10. Default: :rc:`font.size` - The default font property for TrueType fonts (as specified in the - default rcParams) is :: - - sans-serif, normal, normal, normal, normal, scalable. + - math_fontfamily: The family of fonts used to render math text. + Supported values are: 'dejavusans', 'dejavuserif', 'cm', + 'stix', 'stixsans' and 'custom'. Default: :rc:`mathtext.fontset` Alternatively, a font may be specified using the absolute path to a font file, by using the *fname* kwarg. However, in this case, it is typically @@ -675,9 +634,14 @@ class FontProperties: approach allows all text sizes to be made larger or smaller based on the font manager's default font size. - This class will also accept a fontconfig_ pattern_, if it is the only - argument provided. This support does not depend on fontconfig; we are - merely borrowing its pattern syntax for use here. + This class accepts a single positional string as fontconfig_ pattern_, + or alternatively individual properties as keyword arguments:: + + FontProperties(pattern) + FontProperties(*, family=None, style=None, variant=None, ...) + + This support does not depend on fontconfig; we are merely borrowing its + pattern syntax for use here. .. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/ .. _pattern: @@ -689,33 +653,11 @@ class FontProperties: fontconfig. """ - def __init__(self, - family = None, - style = None, - variant= None, - weight = None, - stretch= None, - size = None, - fname = None, # if set, it's a hardcoded filename to use - math_fontfamily = None, - ): - self._family = _normalize_font_family(rcParams['font.family']) - self._slant = rcParams['font.style'] - self._variant = rcParams['font.variant'] - self._weight = rcParams['font.weight'] - self._stretch = rcParams['font.stretch'] - self._size = rcParams['font.size'] - self._file = None - self._math_fontfamily = None - - if isinstance(family, str): - # Treat family as a fontconfig pattern if it is the only - # parameter provided. - if (style is None and variant is None and weight is None and - stretch is None and size is None and fname is None): - self.set_fontconfig_pattern(family) - return - + @_cleanup_fontproperties_init + def __init__(self, family=None, style=None, variant=None, weight=None, + stretch=None, size=None, + fname=None, # if set, it's a hardcoded filename to use + math_fontfamily=None): self.set_family(family) self.set_style(style) self.set_variant(variant) @@ -724,6 +666,13 @@ def __init__(self, self.set_file(fname) self.set_size(size) self.set_math_fontfamily(math_fontfamily) + # Treat family as a fontconfig pattern if it is the only parameter + # provided. Even in that case, call the other setters first to set + # attributes not specified by the pattern to the rcParams defaults. + if (isinstance(family, str) + and style is None and variant is None and weight is None + and stretch is None and size is None and fname is None): + self.set_fontconfig_pattern(family) @classmethod def _from_any(cls, arg): @@ -737,10 +686,10 @@ def _from_any(cls, arg): - a `str`: it is parsed as a fontconfig pattern; - a `dict`: it is passed as ``**kwargs`` to `.FontProperties`. """ - if isinstance(arg, cls): - return arg - elif arg is None: + if arg is None: return cls() + elif isinstance(arg, cls): + return arg elif isinstance(arg, os.PathLike): return cls(fname=arg) elif isinstance(arg, str): @@ -754,7 +703,7 @@ def __hash__(self): self.get_variant(), self.get_weight(), self.get_stretch(), - self.get_size_in_points(), + self.get_size(), self.get_file(), self.get_math_fontfamily()) return hash(l) @@ -767,7 +716,11 @@ def __str__(self): def get_family(self): """ - Return a list of font names that comprise the font family. + Return a list of individual font family names or generic family names. + + The font families or generic font families (which will be resolved + from their respective rcParams when searching for a matching font) in + the order of preference. """ return self._family @@ -782,7 +735,6 @@ def get_style(self): Return the font style. Values are: 'normal', 'italic' or 'oblique'. """ return self._slant - get_slant = get_style def get_variant(self): """ @@ -813,9 +765,6 @@ def get_size(self): """ return self._size - def get_size_in_points(self): - return self._size - def get_file(self): """ Return the filename of the associated font. @@ -834,80 +783,103 @@ def get_fontconfig_pattern(self): def set_family(self, family): """ - Change the font family. May be either an alias (generic name + Change the font family. Can be either an alias (generic name is CSS parlance), such as: 'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace', a real font name or a list of real font names. Real font names are not supported when - :rc:`text.usetex` is `True`. + :rc:`text.usetex` is `True`. Default: :rc:`font.family` """ - if family is None: - family = rcParams['font.family'] - self._family = _normalize_font_family(family) - set_name = set_family + family = mpl._val_or_rc(family, 'font.family') + if isinstance(family, str): + family = [family] + self._family = family def set_style(self, style): """ - Set the font style. Values are: 'normal', 'italic' or 'oblique'. + Set the font style. + + Parameters + ---------- + style : {'normal', 'italic', 'oblique'}, default: :rc:`font.style` """ - if style is None: - style = rcParams['font.style'] - cbook._check_in_list(['normal', 'italic', 'oblique'], style=style) + style = mpl._val_or_rc(style, 'font.style') + _api.check_in_list(['normal', 'italic', 'oblique'], style=style) self._slant = style - set_slant = set_style def set_variant(self, variant): """ - Set the font variant. Values are: 'normal' or 'small-caps'. + Set the font variant. + + Parameters + ---------- + variant : {'normal', 'small-caps'}, default: :rc:`font.variant` """ - if variant is None: - variant = rcParams['font.variant'] - cbook._check_in_list(['normal', 'small-caps'], variant=variant) + variant = mpl._val_or_rc(variant, 'font.variant') + _api.check_in_list(['normal', 'small-caps'], variant=variant) self._variant = variant def set_weight(self, weight): """ - Set the font weight. May be either a numeric value in the - range 0-1000 or one of 'ultralight', 'light', 'normal', - 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', - 'demi', 'bold', 'heavy', 'extra bold', 'black' + Set the font weight. + + Parameters + ---------- + weight : int or {'ultralight', 'light', 'normal', 'regular', 'book', \ +'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', \ +'extra bold', 'black'}, default: :rc:`font.weight` + If int, must be in the range 0-1000. """ - if weight is None: - weight = rcParams['font.weight'] + weight = mpl._val_or_rc(weight, 'font.weight') + if weight in weight_dict: + self._weight = weight + return try: weight = int(weight) - if weight < 0 or weight > 1000: - raise ValueError() except ValueError: - if weight not in weight_dict: - raise ValueError("weight is invalid") - self._weight = weight + pass + else: + if 0 <= weight <= 1000: + self._weight = weight + return + raise ValueError(f"{weight=} is invalid") def set_stretch(self, stretch): """ - Set the font stretch or width. Options are: 'ultra-condensed', - 'extra-condensed', 'condensed', 'semi-condensed', 'normal', - 'semi-expanded', 'expanded', 'extra-expanded' or - 'ultra-expanded', or a numeric value in the range 0-1000. + Set the font stretch or width. + + Parameters + ---------- + stretch : int or {'ultra-condensed', 'extra-condensed', 'condensed', \ +'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', \ +'ultra-expanded'}, default: :rc:`font.stretch` + If int, must be in the range 0-1000. """ - if stretch is None: - stretch = rcParams['font.stretch'] + stretch = mpl._val_or_rc(stretch, 'font.stretch') + if stretch in stretch_dict: + self._stretch = stretch + return try: stretch = int(stretch) - if stretch < 0 or stretch > 1000: - raise ValueError() - except ValueError as err: - if stretch not in stretch_dict: - raise ValueError("stretch is invalid") from err - self._stretch = stretch + except ValueError: + pass + else: + if 0 <= stretch <= 1000: + self._stretch = stretch + return + raise ValueError(f"{stretch=} is invalid") def set_size(self, size): """ - Set the font size. Either an relative value of 'xx-small', - 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' - or an absolute font size, e.g., 12. + Set the font size. + + Parameters + ---------- + size : float or {'xx-small', 'x-small', 'small', 'medium', \ +'large', 'x-large', 'xx-large'}, default: :rc:`font.size` + If a float, the font size in points. The string values denote + sizes relative to the default font size. """ - if size is None: - size = rcParams['font.size'] + size = mpl._val_or_rc(size, 'font.size') try: size = float(size) except ValueError: @@ -940,7 +912,7 @@ def set_fontconfig_pattern(self, pattern): pattern syntax for use here. """ for key, val in parse_fontconfig_pattern(pattern).items(): - if type(val) == list: + if type(val) is list: getattr(self, "set_" + key)(val[0]) else: getattr(self, "set_" + key)(val) @@ -951,8 +923,6 @@ def get_math_fontfamily(self): The default font is :rc:`mathtext.fontset`. """ - if self._math_fontfamily is None: - return rcParams['mathtext.fontset'] return self._math_fontfamily def set_math_fontfamily(self, fontfamily): @@ -967,28 +937,30 @@ def set_math_fontfamily(self, fontfamily): The name of the font family. Available font families are defined in the - matplotlibrc.template file - :ref:`here ` + :ref:`default matplotlibrc file `. See Also -------- .text.Text.get_math_fontfamily """ if fontfamily is None: - self._math_fontfamily = None - return - - valid_fonts = _validators['mathtext.fontset'].valid.values() - # _check_in_list() Validates the parameter math_fontfamily as - # if it were passed to rcParams['mathtext.fontset'] - cbook._check_in_list(valid_fonts, math_fontfamily=fontfamily) + fontfamily = mpl.rcParams['mathtext.fontset'] + else: + valid_fonts = _validators['mathtext.fontset'].valid.values() + # _check_in_list() Validates the parameter math_fontfamily as + # if it were passed to rcParams['mathtext.fontset'] + _api.check_in_list(valid_fonts, math_fontfamily=fontfamily) self._math_fontfamily = fontfamily def copy(self): """Return a copy of self.""" - new = type(self)() - vars(new).update(vars(self)) - return new + return copy.copy(self) + + # Aliases + set_name = set_family + get_slant = get_style + set_slant = set_style + get_size_in_points = get_size class _JSONEncoder(json.JSONEncoder): @@ -1000,8 +972,7 @@ def default(self, o): try: # Cache paths of fonts shipped with Matplotlib relative to the # Matplotlib data path, which helps in the presence of venvs. - d["fname"] = str( - Path(d["fname"]).relative_to(mpl.get_data_path())) + d["fname"] = str(Path(d["fname"]).relative_to(mpl.get_data_path())) except ValueError: pass return d @@ -1009,11 +980,6 @@ def default(self, o): return super().default(o) -@cbook.deprecated("3.2", alternative="json_dump") -class JSONEncoder(_JSONEncoder): - pass - - def _json_decode(o): cls = o.pop('__class__', None) if cls is None: @@ -1023,10 +989,9 @@ def _json_decode(o): r.__dict__.update(o) return r elif cls == 'FontEntry': - r = FontEntry.__new__(FontEntry) - r.__dict__.update(o) - if not os.path.isabs(r.fname): - r.fname = os.path.join(mpl.get_data_path(), r.fname) + if not os.path.isabs(o['fname']): + o['fname'] = os.path.join(mpl.get_data_path(), o['fname']) + r = FontEntry(**o) return r else: raise ValueError("Don't know how to deserialize __class__=%s" % cls) @@ -1049,11 +1014,11 @@ def json_dump(data, filename): This function temporarily locks the output file to prevent multiple processes from overwriting one another's output. """ - with cbook._lock_path(filename), open(filename, 'w') as fh: - try: + try: + with cbook._lock_path(filename), open(filename, 'w') as fh: json.dump(data, fh, cls=_JSONEncoder, indent=2) - except OSError as e: - _log.warning('Could not save font_manager cache {}'.format(e)) + except OSError as e: + _log.warning('Could not save font_manager cache %s', e) def json_load(filename): @@ -1064,16 +1029,10 @@ def json_load(filename): -------- json_dump """ - with open(filename, 'r') as fh: + with open(filename) as fh: return json.load(fh, object_hook=_json_decode) -def _normalize_font_family(family): - if isinstance(family, str): - family = [family] - return family - - class FontManager: """ On import, the `FontManager` singleton instance creates a list of ttf and @@ -1081,11 +1040,32 @@ class FontManager: method does a nearest neighbor search to find the font that most closely matches the specification. If no good enough match is found, the default font is returned. + + Fonts added with the `FontManager.addfont` method will not persist in the + cache; therefore, `addfont` will need to be called every time Matplotlib is + imported. This method should only be used if and when a font cannot be + installed on your operating system by other means. + + Notes + ----- + The `FontManager.addfont` method must be called on the global `FontManager` + instance. + + Example usage:: + + import matplotlib.pyplot as plt + from matplotlib import font_manager + + font_dirs = ["/resources/fonts"] # The path to the custom font file. + font_files = font_manager.findSystemFonts(fontpaths=font_dirs) + + for font_file in font_files: + font_manager.fontManager.addfont(font_file) """ # Increment this version number whenever the font cache data - # format or behavior has changed and requires a existing font + # format or behavior has changed and requires an existing font # cache files to be rebuilt. - __version__ = 330 + __version__ = '3.11.0a1' def __init__(self, size=None, weight='normal'): self._version = self.__version__ @@ -1093,23 +1073,10 @@ def __init__(self, size=None, weight='normal'): self.__default_weight = weight self.default_size = size + # Create list of font paths. paths = [cbook._get_data_path('fonts', subdir) for subdir in ['ttf', 'afm', 'pdfcorefonts']] - # Create list of font paths - for pathname in ['TTFPATH', 'AFMPATH']: - if pathname in os.environ: - ttfpath = os.environ[pathname] - if ttfpath.find(';') >= 0: # win32 style - paths.extend(ttfpath.split(';')) - elif ttfpath.find(':') >= 0: # unix style - paths.extend(ttfpath.split(':')) - else: - paths.append(ttfpath) - cbook.warn_deprecated( - "3.3", name=pathname, obj_type="environment variable", - alternative="FontManager.addfont()") - _log.debug('font search path %s', str(paths)) - # Load TrueType fonts and create font dictionary. + _log.debug('font search path %s', paths) self.defaultFamily = { 'ttf': 'DejaVu Sans', @@ -1119,7 +1086,7 @@ def __init__(self, size=None, weight='normal'): self.ttflist = [] # Delay the warning by 5s. - timer = Timer(5, lambda: _log.warning( + timer = threading.Timer(5, lambda: _log.warning( 'Matplotlib is building the font cache; this may take a moment.')) timer.start() try: @@ -1144,16 +1111,26 @@ def addfont(self, path): Parameters ---------- path : str or path-like + + Notes + ----- + This method is useful for adding a custom font without installing it in + your operating system. See the `FontManager` singleton instance for + usage and caveats about this function. """ + # Convert to string in case of a path as + # afmFontProperty and FT2Font expect this + path = os.fsdecode(path) if Path(path).suffix.lower() == ".afm": with open(path, "rb") as fh: - font = afm.AFM(fh) + font = _afm.AFM(fh) prop = afmFontProperty(path, font) self.afmlist.append(prop) else: font = ft2font.FT2Font(path) prop = ttfFontProperty(font) self.ttflist.append(prop) + self._findfont_cached.cache_clear() @property def defaultFont(self): @@ -1173,7 +1150,7 @@ def get_default_size(): """ Return the default font size. """ - return rcParams['font.size'] + return mpl.rcParams['font.size'] def set_default_weight(self, weight): """ @@ -1181,6 +1158,12 @@ def set_default_weight(self, weight): """ self.__default_weight = weight + @staticmethod + def _expand_aliases(family): + if family in ('sans', 'sans serif'): + family = 'sans-serif' + return mpl.rcParams['font.' + family] + # Each of the scoring functions below should return a value between # 0.0 (perfect match) and 1.0 (terrible match) def score_family(self, families, family2): @@ -1203,10 +1186,7 @@ def score_family(self, families, family2): for i, family1 in enumerate(families): family1 = family1.lower() if family1 in font_family_aliases: - if family1 in ('sans', 'sans serif'): - family1 = 'sans-serif' - options = rcParams['font.' + family1] - options = [x.lower() for x in options] + options = [*map(str.lower, self._expand_aliases(family1))] if family2 in options: idx = options.index(family2) return (i + (idx / len(options))) * step @@ -1307,7 +1287,7 @@ def score_size(self, size1, size2): def findfont(self, prop, fontext='ttf', directory=None, fallback_to_default=True, rebuild_if_missing=True): """ - Find a font that most closely matches the given font properties. + Find the path to the font file most closely matching the given font properties. Parameters ---------- @@ -1326,7 +1306,7 @@ def findfont(self, prop, fontext='ttf', directory=None, If given, only search this directory and its subdirectories. fallback_to_default : bool - If True, will fallback to the default font family (usually + If True, will fall back to the default font family (usually "DejaVu Sans" or "Helvetica") if the first lookup hard-fails. rebuild_if_missing : bool @@ -1360,14 +1340,111 @@ def findfont(self, prop, fontext='ttf', directory=None, # Pass the relevant rcParams (and the font manager, as `self`) to # _findfont_cached so to prevent using a stale cache entry after an # rcParam was changed. - rc_params = tuple(tuple(rcParams[key]) for key in [ + rc_params = tuple(tuple(mpl.rcParams[key]) for key in [ "font.serif", "font.sans-serif", "font.cursive", "font.fantasy", "font.monospace"]) - return self._findfont_cached( + ret = self._findfont_cached( prop, fontext, directory, fallback_to_default, rebuild_if_missing, rc_params) + if isinstance(ret, cbook._ExceptionInfo): + raise ret.to_exception() + return ret + + def get_font_names(self): + """Return the list of available fonts.""" + return list({font.name for font in self.ttflist}) + + def _find_fonts_by_props(self, prop, fontext='ttf', directory=None, + fallback_to_default=True, rebuild_if_missing=True): + """ + Find the paths to the font files most closely matching the given properties. + + Parameters + ---------- + prop : str or `~matplotlib.font_manager.FontProperties` + The font properties to search for. This can be either a + `.FontProperties` object or a string defining a + `fontconfig patterns`_. + + fontext : {'ttf', 'afm'}, default: 'ttf' + The extension of the font file: + + - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf) + - 'afm': Adobe Font Metrics (.afm) + + directory : str, optional + If given, only search this directory and its subdirectories. + + fallback_to_default : bool + If True, will fall back to the default font family (usually + "DejaVu Sans" or "Helvetica") if none of the families were found. + + rebuild_if_missing : bool + Whether to rebuild the font cache and search again if the first + match appears to point to a nonexisting font (i.e., the font cache + contains outdated entries). + + Returns + ------- + list[str] + The paths of the fonts found. + + Notes + ----- + This is an extension/wrapper of the original findfont API, which only + returns a single font for given font properties. Instead, this API + returns a list of filepaths of multiple fonts which closely match the + given font properties. Since this internally uses the original API, + there's no change to the logic of performing the nearest neighbor + search. See `findfont` for more details. + """ + + prop = FontProperties._from_any(prop) + + fpaths = [] + for family in prop.get_family(): + cprop = prop.copy() + cprop.set_family(family) # set current prop's family + + try: + fpaths.append( + self.findfont( + cprop, fontext, directory, + fallback_to_default=False, # don't fallback to default + rebuild_if_missing=rebuild_if_missing, + ) + ) + except ValueError: + if family in font_family_aliases: + _log.warning( + "findfont: Generic family %r not found because " + "none of the following families were found: %s", + family, ", ".join(self._expand_aliases(family)) + ) + else: + _log.warning("findfont: Font family %r not found.", family) + + # only add default family if no other font was found and + # fallback_to_default is enabled + if not fpaths: + if fallback_to_default: + dfamily = self.defaultFamily[fontext] + cprop = prop.copy() + cprop.set_family(dfamily) + fpaths.append( + self.findfont( + cprop, fontext, directory, + fallback_to_default=True, + rebuild_if_missing=rebuild_if_missing, + ) + ) + else: + raise ValueError("Failed to find any font, and fallback " + "to the default font was disabled") - @lru_cache() + return fpaths + + @lru_cache(1024) def _findfont_cached(self, prop, fontext, directory, fallback_to_default, rebuild_if_missing, rc_params): @@ -1409,13 +1486,25 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, _log.warning( 'findfont: Font family %s not found. Falling back to %s.', prop.get_family(), self.defaultFamily[fontext]) + for family in map(str.lower, prop.get_family()): + if family in font_family_aliases: + _log.warning( + "findfont: Generic family %r not found because " + "none of the following families were found: %s", + family, ", ".join(self._expand_aliases(family))) default_prop = prop.copy() default_prop.set_family(self.defaultFamily[fontext]) return self.findfont(default_prop, fontext, directory, fallback_to_default=False) else: - raise ValueError(f"Failed to find font {prop}, and fallback " - f"to the default font was disabled") + # This return instead of raise is intentional, as we wish to + # cache that it was not found, which will not occur if it was + # actually raised. + return cbook._ExceptionInfo( + ValueError, + f"Failed to find font {prop}, and fallback to the default font was " + f"disabled" + ) else: _log.debug('findfont: Matching %s to %s (%r) with score of %f.', prop, best_font.name, best_font.fname, best_score) @@ -1434,17 +1523,20 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, return self.findfont( prop, fontext, directory, rebuild_if_missing=False) else: - raise ValueError("No valid font could be found") + # This return instead of raise is intentional, as we wish to + # cache that it was not found, which will not occur if it was + # actually raised. + return cbook._ExceptionInfo(ValueError, "No valid font could be found") return _cached_realpath(result) -@lru_cache() +@lru_cache def is_opentype_cff_font(filename): """ Return whether the given font is a Postscript Compact Font Format Font embedded in an OpenType wrapper. Used by the PostScript and PDF backends - that can not subset these fonts. + that cannot subset these fonts. """ if os.path.splitext(filename)[1].lower() == '.otf': with open(filename, 'rb') as fd: @@ -1453,23 +1545,95 @@ def is_opentype_cff_font(filename): return False -_get_font = lru_cache(64)(ft2font.FT2Font) +@lru_cache(64) +def _get_font(font_filepaths, hinting_factor, *, _kerning_factor, thread_id, + enable_last_resort): + first_fontpath, *rest = font_filepaths + fallback_list = [ + ft2font.FT2Font(fpath, hinting_factor, _kerning_factor=_kerning_factor) + for fpath in rest + ] + last_resort_path = _cached_realpath( + cbook._get_data_path('fonts', 'ttf', 'LastResortHE-Regular.ttf')) + try: + last_resort_index = font_filepaths.index(last_resort_path) + except ValueError: + last_resort_index = -1 + # Add Last Resort font so we always have glyphs regardless of font, unless we're + # already in the list. + if enable_last_resort: + fallback_list.append( + ft2font.FT2Font(last_resort_path, hinting_factor, + _kerning_factor=_kerning_factor, + _warn_if_used=True)) + last_resort_index = len(fallback_list) + font = ft2font.FT2Font( + first_fontpath, hinting_factor, + _fallback_list=fallback_list, + _kerning_factor=_kerning_factor + ) + # Ensure we are using the right charmap for the Last Resort font; FreeType picks the + # Unicode one by default, but this exists only for Windows, and is empty. + if last_resort_index == 0: + font.set_charmap(0) + elif last_resort_index > 0: + fallback_list[last_resort_index - 1].set_charmap(0) + return font + + # FT2Font objects cannot be used across fork()s because they reference the same # FT_Library object. While invalidating *all* existing FT2Fonts after a fork # would be too complicated to be worth it, the main way FT2Fonts get reused is -# via the cache of _get_font, which we can empty upon forking (in Py3.7+). +# via the cache of _get_font, which we can empty upon forking (not on Windows, +# which has no fork() or register_at_fork()). if hasattr(os, "register_at_fork"): os.register_at_fork(after_in_child=_get_font.cache_clear) -def get_font(filename, hinting_factor=None): +@lru_cache(64) +def _cached_realpath(path): # Resolving the path avoids embedding the font twice in pdf/ps output if a # single font is selected using two different relative paths. - filename = _cached_realpath(filename) - if hinting_factor is None: - hinting_factor = rcParams['text.hinting_factor'] - return _get_font(filename, hinting_factor, - _kerning_factor=rcParams['text.kerning_factor']) + return os.path.realpath(path) + + +def get_font(font_filepaths, hinting_factor=None): + """ + Get an `.ft2font.FT2Font` object given a list of file paths. + + Parameters + ---------- + font_filepaths : Iterable[str, Path, bytes], str, Path, bytes + Relative or absolute paths to the font files to be used. + + If a single string, bytes, or `pathlib.Path`, then it will be treated + as a list with that entry only. + + If more than one filepath is passed, then the returned FT2Font object + will fall back through the fonts, in the order given, to find a needed + glyph. + + Returns + ------- + `.ft2font.FT2Font` + + """ + if isinstance(font_filepaths, (str, Path, bytes)): + paths = (_cached_realpath(font_filepaths),) + else: + paths = tuple(_cached_realpath(fname) for fname in font_filepaths) + + hinting_factor = mpl._val_or_rc(hinting_factor, 'text.hinting_factor') + + return _get_font( + # must be a tuple to be cached + paths, + hinting_factor, + _kerning_factor=mpl.rcParams['text.kerning_factor'], + # also key on the thread ID to prevent segfaults with multi-threading + thread_id=threading.get_ident(), + enable_last_resort=mpl.rcParams['font.enable_last_resort'], + ) def _load_fontmanager(*, try_read_cache=True): @@ -1478,7 +1642,7 @@ def _load_fontmanager(*, try_read_cache=True): if try_read_cache: try: fm = json_load(fm_path) - except Exception as exc: + except Exception: pass else: if getattr(fm, "_version", object()) == FontManager.__version__: @@ -1492,3 +1656,4 @@ def _load_fontmanager(*, try_read_cache=True): fontManager = _load_fontmanager() findfont = fontManager.findfont +get_font_names = fontManager.get_font_names diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi new file mode 100644 index 000000000000..c64ddea3e073 --- /dev/null +++ b/lib/matplotlib/font_manager.pyi @@ -0,0 +1,136 @@ +from dataclasses import dataclass +import os + +from matplotlib._afm import AFM +from matplotlib import ft2font + +from pathlib import Path + +from collections.abc import Iterable +from typing import Any, Literal + +font_scalings: dict[str | None, float] +stretch_dict: dict[str, int] +weight_dict: dict[str, int] +font_family_aliases: set[str] +MSFolders: str +MSFontDirectories: list[str] +MSUserFontDirectories: list[str] +X11FontDirectories: list[str] +OSXFontDirectories: list[str] + +def get_fontext_synonyms(fontext: str) -> list[str]: ... +def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ... +def win32FontDirectory() -> str: ... +def _get_fontconfig_fonts() -> list[Path]: ... +def findSystemFonts( + fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ... +) -> list[str]: ... +@dataclass +class FontEntry: + fname: str = ... + name: str = ... + style: str = ... + variant: str = ... + weight: str | int = ... + stretch: str = ... + size: str = ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... + +def ttfFontProperty(font: ft2font.FT2Font) -> FontEntry: ... +def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ... + +class FontProperties: + def __init__( + self, + family: str | Iterable[str] | None = ..., + style: Literal["normal", "italic", "oblique"] | None = ..., + variant: Literal["normal", "small-caps"] | None = ..., + weight: int | str | None = ..., + stretch: int | str | None = ..., + size: float | str | None = ..., + fname: str | os.PathLike | Path | None = ..., + math_fontfamily: str | None = ..., + ) -> None: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + def get_family(self) -> list[str]: ... + def get_name(self) -> str: ... + def get_style(self) -> Literal["normal", "italic", "oblique"]: ... + def get_variant(self) -> Literal["normal", "small-caps"]: ... + def get_weight(self) -> int | str: ... + def get_stretch(self) -> int | str: ... + def get_size(self) -> float: ... + def get_file(self) -> str | bytes | None: ... + def get_fontconfig_pattern(self) -> dict[str, list[Any]]: ... + def set_family(self, family: str | Iterable[str] | None) -> None: ... + def set_style( + self, style: Literal["normal", "italic", "oblique"] | None + ) -> None: ... + def set_variant(self, variant: Literal["normal", "small-caps"] | None) -> None: ... + def set_weight(self, weight: int | str | None) -> None: ... + def set_stretch(self, stretch: int | str | None) -> None: ... + def set_size(self, size: float | str | None) -> None: ... + def set_file(self, file: str | os.PathLike | Path | None) -> None: ... + def set_fontconfig_pattern(self, pattern: str) -> None: ... + def get_math_fontfamily(self) -> str: ... + def set_math_fontfamily(self, fontfamily: str | None) -> None: ... + def copy(self) -> FontProperties: ... + # Aliases + set_name = set_family + get_slant = get_style + set_slant = set_style + get_size_in_points = get_size + +def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ... +def json_load(filename: str | Path | os.PathLike) -> FontManager: ... + +class FontManager: + __version__: str + default_size: float | None + defaultFamily: dict[str, str] + afmlist: list[FontEntry] + ttflist: list[FontEntry] + def __init__(self, size: float | None = ..., weight: str = ...) -> None: ... + def addfont(self, path: str | Path | os.PathLike) -> None: ... + @property + def defaultFont(self) -> dict[str, str]: ... + def get_default_weight(self) -> str: ... + @staticmethod + def get_default_size() -> float: ... + def set_default_weight(self, weight: str) -> None: ... + def score_family( + self, families: str | list[str] | tuple[str], family2: str + ) -> float: ... + def score_style(self, style1: str, style2: str) -> float: ... + def score_variant(self, variant1: str, variant2: str) -> float: ... + def score_stretch(self, stretch1: str | int, stretch2: str | int) -> float: ... + def score_weight(self, weight1: str | float, weight2: str | float) -> float: ... + def score_size(self, size1: str | float, size2: str | float) -> float: ... + def findfont( + self, + prop: str | FontProperties, + fontext: Literal["ttf", "afm"] = ..., + directory: str | None = ..., + fallback_to_default: bool = ..., + rebuild_if_missing: bool = ..., + ) -> str: ... + def get_font_names(self) -> list[str]: ... + +def is_opentype_cff_font(filename: str) -> bool: ... +def get_font( + font_filepaths: Iterable[str | Path | bytes] | str | Path | bytes, + hinting_factor: int | None = ..., +) -> ft2font.FT2Font: ... + +fontManager: FontManager + +def findfont( + prop: str | FontProperties, + fontext: Literal["ttf", "afm"] = ..., + directory: str | None = ..., + fallback_to_default: bool = ..., + rebuild_if_missing: bool = ..., +) -> str: ... +def get_font_names() -> list[str]: ... diff --git a/lib/matplotlib/fontconfig_pattern.py b/lib/matplotlib/fontconfig_pattern.py deleted file mode 100644 index c47e19bf99dc..000000000000 --- a/lib/matplotlib/fontconfig_pattern.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -A module for parsing and generating `fontconfig patterns`_. - -.. _fontconfig patterns: - https://www.freedesktop.org/software/fontconfig/fontconfig-user.html -""" - -# This class logically belongs in `matplotlib.font_manager`, but placing it -# there would have created cyclical dependency problems, because it also needs -# to be available from `matplotlib.rcsetup` (for parsing matplotlibrc files). - -from functools import lru_cache -import re -import numpy as np -from pyparsing import (Literal, ZeroOrMore, Optional, Regex, StringEnd, - ParseException, Suppress) - -family_punc = r'\\\-:,' -family_unescape = re.compile(r'\\([%s])' % family_punc).sub -family_escape = re.compile(r'([%s])' % family_punc).sub - -value_punc = r'\\=_:,' -value_unescape = re.compile(r'\\([%s])' % value_punc).sub -value_escape = re.compile(r'([%s])' % value_punc).sub - - -class FontconfigPatternParser: - """ - A simple pyparsing-based parser for `fontconfig patterns`_. - - .. _fontconfig patterns: - https://www.freedesktop.org/software/fontconfig/fontconfig-user.html - """ - - _constants = { - 'thin': ('weight', 'light'), - 'extralight': ('weight', 'light'), - 'ultralight': ('weight', 'light'), - 'light': ('weight', 'light'), - 'book': ('weight', 'book'), - 'regular': ('weight', 'regular'), - 'normal': ('weight', 'normal'), - 'medium': ('weight', 'medium'), - 'demibold': ('weight', 'demibold'), - 'semibold': ('weight', 'semibold'), - 'bold': ('weight', 'bold'), - 'extrabold': ('weight', 'extra bold'), - 'black': ('weight', 'black'), - 'heavy': ('weight', 'heavy'), - 'roman': ('slant', 'normal'), - 'italic': ('slant', 'italic'), - 'oblique': ('slant', 'oblique'), - 'ultracondensed': ('width', 'ultra-condensed'), - 'extracondensed': ('width', 'extra-condensed'), - 'condensed': ('width', 'condensed'), - 'semicondensed': ('width', 'semi-condensed'), - 'expanded': ('width', 'expanded'), - 'extraexpanded': ('width', 'extra-expanded'), - 'ultraexpanded': ('width', 'ultra-expanded') - } - - def __init__(self): - - family = Regex( - r'([^%s]|(\\[%s]))*' % (family_punc, family_punc) - ).setParseAction(self._family) - - size = Regex( - r"([0-9]+\.?[0-9]*|\.[0-9]+)" - ).setParseAction(self._size) - - name = Regex( - r'[a-z]+' - ).setParseAction(self._name) - - value = Regex( - r'([^%s]|(\\[%s]))*' % (value_punc, value_punc) - ).setParseAction(self._value) - - families = ( - family - + ZeroOrMore( - Literal(',') - + family) - ).setParseAction(self._families) - - point_sizes = ( - size - + ZeroOrMore( - Literal(',') - + size) - ).setParseAction(self._point_sizes) - - property = ( - (name - + Suppress(Literal('=')) - + value - + ZeroOrMore( - Suppress(Literal(',')) - + value)) - | name - ).setParseAction(self._property) - - pattern = ( - Optional( - families) - + Optional( - Literal('-') - + point_sizes) - + ZeroOrMore( - Literal(':') - + property) - + StringEnd() - ) - - self._parser = pattern - self.ParseException = ParseException - - def parse(self, pattern): - """ - Parse the given fontconfig *pattern* and return a dictionary - of key/value pairs useful for initializing a - `.font_manager.FontProperties` object. - """ - props = self._properties = {} - try: - self._parser.parseString(pattern) - except self.ParseException as e: - raise ValueError( - "Could not parse font string: '%s'\n%s" % (pattern, e)) from e - - self._properties = None - - self._parser.resetCache() - - return props - - def _family(self, s, loc, tokens): - return [family_unescape(r'\1', str(tokens[0]))] - - def _size(self, s, loc, tokens): - return [float(tokens[0])] - - def _name(self, s, loc, tokens): - return [str(tokens[0])] - - def _value(self, s, loc, tokens): - return [value_unescape(r'\1', str(tokens[0]))] - - def _families(self, s, loc, tokens): - self._properties['family'] = [str(x) for x in tokens] - return [] - - def _point_sizes(self, s, loc, tokens): - self._properties['size'] = [str(x) for x in tokens] - return [] - - def _property(self, s, loc, tokens): - if len(tokens) == 1: - if tokens[0] in self._constants: - key, val = self._constants[tokens[0]] - self._properties.setdefault(key, []).append(val) - else: - key = tokens[0] - val = tokens[1:] - self._properties.setdefault(key, []).extend(val) - return [] - - -# `parse_fontconfig_pattern` is a bottleneck during the tests because it is -# repeatedly called when the rcParams are reset (to validate the default -# fonts). In practice, the cache size doesn't grow beyond a few dozen entries -# during the test suite. -parse_fontconfig_pattern = lru_cache()(FontconfigPatternParser().parse) - - -def _escape_val(val, escape_func): - """ - Given a string value or a list of string values, run each value through - the input escape function to make the values into legal font config - strings. The result is returned as a string. - """ - if not np.iterable(val) or isinstance(val, str): - val = [val] - - return ','.join(escape_func(r'\\\1', str(x)) for x in val - if x is not None) - - -def generate_fontconfig_pattern(d): - """ - Given a dictionary of key/value pairs, generates a fontconfig - pattern string. - """ - props = [] - - # Family is added first w/o a keyword - family = d.get_family() - if family is not None and family != []: - props.append(_escape_val(family, family_escape)) - - # The other keys are added as key=value - for key in ['style', 'variant', 'weight', 'stretch', 'file', 'size']: - val = getattr(d, 'get_' + key)() - # Don't use 'if not val' because 0 is a valid input. - if val is not None and val != []: - props.append(":%s=%s" % (key, _escape_val(val, value_escape))) - - return ''.join(props) diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi new file mode 100644 index 000000000000..a413cd3c1a76 --- /dev/null +++ b/lib/matplotlib/ft2font.pyi @@ -0,0 +1,312 @@ +from enum import Enum, Flag +import sys +from typing import BinaryIO, Literal, TypedDict, final, overload, cast +from typing_extensions import Buffer # < Py 3.12 + +import numpy as np +from numpy.typing import NDArray + +__freetype_build_type__: str +__freetype_version__: str + +class FaceFlags(Flag): + SCALABLE = cast(int, ...) + FIXED_SIZES = cast(int, ...) + FIXED_WIDTH = cast(int, ...) + SFNT = cast(int, ...) + HORIZONTAL = cast(int, ...) + VERTICAL = cast(int, ...) + KERNING = cast(int, ...) + FAST_GLYPHS = cast(int, ...) + MULTIPLE_MASTERS = cast(int, ...) + GLYPH_NAMES = cast(int, ...) + EXTERNAL_STREAM = cast(int, ...) + HINTER = cast(int, ...) + CID_KEYED = cast(int, ...) + TRICKY = cast(int, ...) + COLOR = cast(int, ...) + VARIATION = cast(int, ...) + SVG = cast(int, ...) + SBIX = cast(int, ...) + SBIX_OVERLAY = cast(int, ...) + +class Kerning(Enum): + DEFAULT = cast(int, ...) + UNFITTED = cast(int, ...) + UNSCALED = cast(int, ...) + +class LoadFlags(Flag): + DEFAULT = cast(int, ...) + NO_SCALE = cast(int, ...) + NO_HINTING = cast(int, ...) + RENDER = cast(int, ...) + NO_BITMAP = cast(int, ...) + VERTICAL_LAYOUT = cast(int, ...) + FORCE_AUTOHINT = cast(int, ...) + CROP_BITMAP = cast(int, ...) + PEDANTIC = cast(int, ...) + IGNORE_GLOBAL_ADVANCE_WIDTH = cast(int, ...) + NO_RECURSE = cast(int, ...) + IGNORE_TRANSFORM = cast(int, ...) + MONOCHROME = cast(int, ...) + LINEAR_DESIGN = cast(int, ...) + NO_AUTOHINT = cast(int, ...) + COLOR = cast(int, ...) + COMPUTE_METRICS = cast(int, ...) + BITMAP_METRICS_ONLY = cast(int, ...) + NO_SVG = cast(int, ...) + # The following should be unique, but the above can be OR'd together. + TARGET_NORMAL = cast(int, ...) + TARGET_LIGHT = cast(int, ...) + TARGET_MONO = cast(int, ...) + TARGET_LCD = cast(int, ...) + TARGET_LCD_V = cast(int, ...) + +class StyleFlags(Flag): + NORMAL = cast(int, ...) + ITALIC = cast(int, ...) + BOLD = cast(int, ...) + +class _SfntHeadDict(TypedDict): + version: tuple[int, int] + fontRevision: tuple[int, int] + checkSumAdjustment: int + magicNumber: int + flags: int + unitsPerEm: int + created: tuple[int, int] + modified: tuple[int, int] + xMin: int + yMin: int + xMax: int + yMax: int + macStyle: int + lowestRecPPEM: int + fontDirectionHint: int + indexToLocFormat: int + glyphDataFormat: int + +class _SfntMaxpDict(TypedDict): + version: tuple[int, int] + numGlyphs: int + maxPoints: int + maxContours: int + maxComponentPoints: int + maxComponentContours: int + maxZones: int + maxTwilightPoints: int + maxStorage: int + maxFunctionDefs: int + maxInstructionDefs: int + maxStackElements: int + maxSizeOfInstructions: int + maxComponentElements: int + maxComponentDepth: int + +class _SfntOs2Dict(TypedDict): + version: int + xAvgCharWidth: int + usWeightClass: int + usWidthClass: int + fsType: int + ySubscriptXSize: int + ySubscriptYSize: int + ySubscriptXOffset: int + ySubscriptYOffset: int + ySuperscriptXSize: int + ySuperscriptYSize: int + ySuperscriptXOffset: int + ySuperscriptYOffset: int + yStrikeoutSize: int + yStrikeoutPosition: int + sFamilyClass: int + panose: bytes + ulCharRange: tuple[int, int, int, int] + achVendID: bytes + fsSelection: int + fsFirstCharIndex: int + fsLastCharIndex: int + +class _SfntHheaDict(TypedDict): + version: tuple[int, int] + ascent: int + descent: int + lineGap: int + advanceWidthMax: int + minLeftBearing: int + minRightBearing: int + xMaxExtent: int + caretSlopeRise: int + caretSlopeRun: int + caretOffset: int + metricDataFormat: int + numOfLongHorMetrics: int + +class _SfntVheaDict(TypedDict): + version: tuple[int, int] + vertTypoAscender: int + vertTypoDescender: int + vertTypoLineGap: int + advanceHeightMax: int + minTopSideBearing: int + minBottomSizeBearing: int + yMaxExtent: int + caretSlopeRise: int + caretSlopeRun: int + caretOffset: int + metricDataFormat: int + numOfLongVerMetrics: int + +class _SfntPostDict(TypedDict): + format: tuple[int, int] + italicAngle: tuple[int, int] + underlinePosition: int + underlineThickness: int + isFixedPitch: int + minMemType42: int + maxMemType42: int + minMemType1: int + maxMemType1: int + +class _SfntPcltDict(TypedDict): + version: tuple[int, int] + fontNumber: int + pitch: int + xHeight: int + style: int + typeFamily: int + capHeight: int + symbolSet: int + typeFace: bytes + characterComplement: bytes + strokeWeight: int + widthType: int + serifStyle: int + +@final +class FT2Font(Buffer): + def __init__( + self, + filename: str | BinaryIO, + hinting_factor: int = ..., + *, + _fallback_list: list[FT2Font] | None = ..., + _kerning_factor: int = ... + ) -> None: ... + if sys.version_info[:2] >= (3, 12): + def __buffer__(self, flags: int) -> memoryview: ... + def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ... + def clear(self) -> None: ... + def draw_glyph_to_bitmap( + self, image: NDArray[np.uint8], x: int, y: int, glyph: Glyph, antialiased: bool = ... + ) -> None: ... + def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ... + def get_bitmap_offset(self) -> tuple[int, int]: ... + def get_char_index(self, codepoint: int) -> int: ... + def get_charmap(self) -> dict[int, int]: ... + def get_descent(self) -> int: ... + def get_glyph_name(self, index: int) -> str: ... + def get_image(self) -> NDArray[np.uint8]: ... + def get_kerning(self, left: int, right: int, mode: Kerning) -> int: ... + def get_name_index(self, name: str) -> int: ... + def get_num_glyphs(self) -> int: ... + def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ... + def get_ps_font_info( + self, + ) -> tuple[str, str, str, str, str, int, int, int, int]: ... + def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ... + @overload + def get_sfnt_table(self, name: Literal["head"]) -> _SfntHeadDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["maxp"]) -> _SfntMaxpDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["OS/2"]) -> _SfntOs2Dict | None: ... + @overload + def get_sfnt_table(self, name: Literal["hhea"]) -> _SfntHheaDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["vhea"]) -> _SfntVheaDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["post"]) -> _SfntPostDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ... + def get_width_height(self) -> tuple[int, int]: ... + def load_char(self, charcode: int, flags: LoadFlags = ...) -> Glyph: ... + def load_glyph(self, glyphindex: int, flags: LoadFlags = ...) -> Glyph: ... + def select_charmap(self, i: int) -> None: ... + def set_charmap(self, i: int) -> None: ... + def set_size(self, ptsize: float, dpi: float) -> None: ... + def set_text( + self, string: str, angle: float = ..., flags: LoadFlags = ... + ) -> NDArray[np.float64]: ... + @property + def ascender(self) -> int: ... + @property + def bbox(self) -> tuple[int, int, int, int]: ... + @property + def descender(self) -> int: ... + @property + def face_flags(self) -> FaceFlags: ... + @property + def family_name(self) -> str: ... + @property + def fname(self) -> str: ... + @property + def height(self) -> int: ... + @property + def max_advance_height(self) -> int: ... + @property + def max_advance_width(self) -> int: ... + @property + def num_charmaps(self) -> int: ... + @property + def num_faces(self) -> int: ... + @property + def num_fixed_sizes(self) -> int: ... + @property + def num_glyphs(self) -> int: ... + @property + def num_named_instances(self) -> int: ... + @property + def postscript_name(self) -> str: ... + @property + def scalable(self) -> bool: ... + @property + def style_flags(self) -> StyleFlags: ... + @property + def style_name(self) -> str: ... + @property + def underline_position(self) -> int: ... + @property + def underline_thickness(self) -> int: ... + @property + def units_per_EM(self) -> int: ... + +@final +class FT2Image(Buffer): + def __init__(self, width: int, height: int) -> None: ... + def draw_rect_filled(self, x0: int, y0: int, x1: int, y1: int) -> None: ... + if sys.version_info[:2] >= (3, 12): + def __buffer__(self, flags: int) -> memoryview: ... + +@final +class Glyph: + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... + @property + def horiBearingX(self) -> int: ... + @property + def horiBearingY(self) -> int: ... + @property + def horiAdvance(self) -> int: ... + @property + def linearHoriAdvance(self) -> int: ... + @property + def vertBearingX(self) -> int: ... + @property + def vertBearingY(self) -> int: ... + @property + def vertAdvance(self) -> int: ... + @property + def bbox(self) -> tuple[int, int, int, int]: ... diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 192d22173d5b..5cd05bc167bb 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -5,8 +5,9 @@ The `GridSpec` specifies the overall grid structure. Individual cells within the grid are referenced by `SubplotSpec`\s. -See the tutorial :ref:`sphx_glr_tutorials_intermediate_gridspec.py` for a -comprehensive usage guide. +Often, users need not access this module directly, and can use higher-level +methods like `~.pyplot.subplots`, `~.pyplot.subplot_mosaic` and +`~.Figure.subfigures`. See the tutorial :ref:`arranging_axes` for a guide. """ import copy @@ -16,10 +17,9 @@ import numpy as np import matplotlib as mpl -from matplotlib import _pylab_helpers, cbook, tight_layout, rcParams +from matplotlib import _api, _pylab_helpers, _tight_layout +from matplotlib._api import UNSET as _UNSET from matplotlib.transforms import Bbox -import matplotlib._layoutgrid as layoutgrid - _log = logging.getLogger(__name__) @@ -41,24 +41,24 @@ def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None): relative width of ``width_ratios[i] / sum(width_ratios)``. If not given, all columns will have the same width. height_ratios : array-like of length *nrows*, optional - Defines the relative heights of the rows. Each column gets a + Defines the relative heights of the rows. Each row gets a relative height of ``height_ratios[i] / sum(height_ratios)``. If not given, all rows will have the same height. """ if not isinstance(nrows, Integral) or nrows <= 0: raise ValueError( - f"Number of rows must be a positive integer, not {nrows}") + f"Number of rows must be a positive integer, not {nrows!r}") if not isinstance(ncols, Integral) or ncols <= 0: raise ValueError( - f"Number of columns must be a positive integer, not {ncols}") + f"Number of columns must be a positive integer, not {ncols!r}") self._nrows, self._ncols = nrows, ncols self.set_height_ratios(height_ratios) self.set_width_ratios(width_ratios) def __repr__(self): - height_arg = (', height_ratios=%r' % (self._row_height_ratios,) + height_arg = (f', height_ratios={self._row_height_ratios!r}' if len(set(self._row_height_ratios)) != 1 else '') - width_arg = (', width_ratios=%r' % (self._col_width_ratios,) + width_arg = (f', width_ratios={self._col_width_ratios!r}' if len(set(self._col_width_ratios)) != 1 else '') return '{clsname}({nrows}, {ncols}{optionals})'.format( clsname=self.__class__.__name__, @@ -142,7 +142,7 @@ def get_height_ratios(self): """ return self._row_height_ratios - def get_grid_positions(self, fig, raw=False): + def get_grid_positions(self, fig): """ Return the positions of the grid cells in figure coordinates. @@ -151,11 +151,6 @@ def get_grid_positions(self, fig, raw=False): fig : `~matplotlib.figure.Figure` The figure the grid should be applied to. The subplot parameters (margins and spacing between subplots) are taken from *fig*. - raw : bool, default: False - If *True*, the subplot parameters of the figure are not taken - into account. The grid spans the range [0, 1] in both directions - without margins and there is no space between grid cells. This is - used for constrained_layout. Returns ------- @@ -164,22 +159,13 @@ def get_grid_positions(self, fig, raw=False): figure coordinates. """ nrows, ncols = self.get_geometry() - - if raw: - left = 0. - right = 1. - bottom = 0. - top = 1. - wspace = 0. - hspace = 0. - else: - subplot_params = self.get_subplot_params(fig) - left = subplot_params.left - right = subplot_params.right - bottom = subplot_params.bottom - top = subplot_params.top - wspace = subplot_params.wspace - hspace = subplot_params.hspace + subplot_params = self.get_subplot_params(fig) + left = subplot_params.left + right = subplot_params.right + bottom = subplot_params.bottom + top = subplot_params.top + wspace = subplot_params.wspace + hspace = subplot_params.hspace tot_width = right - left tot_height = top - bottom @@ -210,11 +196,11 @@ def _check_gridspec_exists(figure, nrows, ncols): or create a new one """ for ax in figure.get_axes(): - if hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() + gs = ax.get_gridspec() + if gs is not None: if hasattr(gs, 'get_topmost_subplotspec'): # This is needed for colorbar gridspec layouts. - # This is probably OK becase this whole logic tree + # This is probably OK because this whole logic tree # is for when the user is doing simple things with the # add_subplot command. For complicated layouts # like subgridspecs the proper gridspec is passed in... @@ -266,57 +252,7 @@ def subplots(self, *, sharex=False, sharey=False, squeeze=True, """ Add all subplots specified by this `GridSpec` to its parent figure. - This utility wrapper makes it convenient to create common layouts of - subplots in a single call. - - Parameters - ---------- - sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False - Controls sharing of properties among x (*sharex*) or y (*sharey*) - axes: - - - True or 'all': x- or y-axis will be shared among all subplots. - - False or 'none': each subplot x- or y-axis will be independent. - - 'row': each subplot row will share an x- or y-axis. - - 'col': each subplot column will share an x- or y-axis. - - When subplots have a shared x-axis along a column, only the x tick - labels of the bottom subplot are created. Similarly, when subplots - have a shared y-axis along a row, only the y tick labels of the - first column subplot are created. To later turn other subplots' - ticklabels on, use `~matplotlib.axes.Axes.tick_params`. - - squeeze : bool, optional, default: True - - If True, extra dimensions are squeezed out from the returned - array of Axes: - - - if only one subplot is constructed (nrows=ncols=1), the - resulting single Axes object is returned as a scalar. - - for Nx1 or 1xM subplots, the returned object is a 1D numpy - object array of Axes objects. - - for NxM, subplots with N>1 and M>1 are returned as a 2D array. - - - If False, no squeezing at all is done: the returned Axes object - is always a 2D array containing Axes instances, even if it ends - up being 1x1. - - subplot_kw : dict, optional - Dict with keywords passed to the `~.Figure.add_subplot` call used - to create each subplot. - - Returns - ------- - ax : `~.axes.Axes` object or array of Axes objects. - *ax* can be either a single `~matplotlib.axes.Axes` object or - an array of Axes objects if more than one subplot was created. The - dimensions of the resulting array can be controlled with the - squeeze keyword, see above. - - See Also - -------- - .pyplot.subplots - .Figure.add_subplot - .pyplot.subplot + See `.Figure.subplots` for detailed documentation. """ figure = self.figure @@ -325,27 +261,19 @@ def subplots(self, *, sharex=False, sharey=False, squeeze=True, raise ValueError("GridSpec.subplots() only works for GridSpecs " "created with a parent figure") - if isinstance(sharex, bool): + if not isinstance(sharex, str): sharex = "all" if sharex else "none" - if isinstance(sharey, bool): + if not isinstance(sharey, str): sharey = "all" if sharey else "none" - # This check was added because it is very easy to type - # `subplots(1, 2, 1)` when `subplot(1, 2, 1)` was intended. - # In most cases, no error will ever occur, but mysterious behavior - # will result because what was intended to be the subplot index is - # instead treated as a bool for sharex. - if isinstance(sharex, Integral): - cbook._warn_external( - "sharex argument to subplots() was an integer. Did you " - "intend to use subplot() (without 's')?") - cbook._check_in_list(["all", "row", "col", "none"], - sharex=sharex, sharey=sharey) + + _api.check_in_list(["all", "row", "col", "none", False, True], + sharex=sharex, sharey=sharey) if subplot_kw is None: subplot_kw = {} # don't mutate kwargs passed by user... subplot_kw = subplot_kw.copy() - # Create array to hold all axes. + # Create array to hold all Axes. axarr = np.empty((self._nrows, self._ncols), dtype=object) for row in range(self._nrows): for col in range(self._ncols): @@ -358,17 +286,11 @@ def subplots(self, *, sharex=False, sharey=False, squeeze=True, # turn off redundant tick labeling if sharex in ["col", "all"]: - # turn off all but the bottom row - for ax in axarr[:-1, :].flat: - ax.xaxis.set_tick_params(which='both', - labelbottom=False, labeltop=False) - ax.xaxis.offsetText.set_visible(False) + for ax in axarr.flat: + ax._label_outer_xaxis(skip_non_rectangular_axes=True) if sharey in ["row", "all"]: - # turn off all but the first column - for ax in axarr[:, 1:].flat: - ax.yaxis.set_tick_params(which='both', - labelleft=False, labelright=False) - ax.yaxis.offsetText.set_visible(False) + for ax in axarr.flat: + ax._label_outer_yaxis(skip_non_rectangular_axes=True) if squeeze: # Discarding unneeded dimensions that equal 1. If we only have one @@ -384,8 +306,10 @@ class GridSpec(GridSpecBase): A grid layout to place subplots within a figure. The location of the grid cells is determined in a similar way to - `~.figure.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace* + `.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace* and *hspace*. + + Indexing a GridSpec instance returns a `.SubplotSpec`. """ def __init__(self, nrows, ncols, figure=None, left=None, bottom=None, right=None, top=None, @@ -397,7 +321,7 @@ def __init__(self, nrows, ncols, figure=None, nrows, ncols : int The number of rows and columns of the grid. - figure : `~.figure.Figure`, optional + figure : `.Figure`, optional Only used for constrained layout to create a proper layoutgrid. left, right, top, bottom : float, optional @@ -424,7 +348,7 @@ def __init__(self, nrows, ncols, figure=None, If not given, all columns will have the same width. height_ratios : array-like of length *nrows*, optional - Defines the relative heights of the rows. Each column gets a + Defines the relative heights of the rows. Each row gets a relative height of ``height_ratios[i] / sum(height_ratios)``. If not given, all rows will have the same height. @@ -441,26 +365,10 @@ def __init__(self, nrows, ncols, figure=None, width_ratios=width_ratios, height_ratios=height_ratios) - # set up layoutgrid for constrained_layout: - self._layoutgrid = None - if self.figure is None or not self.figure.get_constrained_layout(): - self._layoutgrid = None - else: - self._toplayoutbox = self.figure._layoutgrid - self._layoutgrid = layoutgrid.LayoutGrid( - parent=self.figure._layoutgrid, - parent_inner=True, - name=(self.figure._layoutgrid.name + '.gridspec' + - layoutgrid.seq_id()), - ncols=ncols, nrows=nrows, width_ratios=width_ratios, - height_ratios=height_ratios) - _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"] - def __getstate__(self): - return {**self.__dict__, "_layoutgrid": None} - - def update(self, **kwargs): + def update(self, *, left=_UNSET, bottom=_UNSET, right=_UNSET, top=_UNSET, + wspace=_UNSET, hspace=_UNSET): """ Update the subplot parameters of the grid. @@ -471,47 +379,47 @@ def update(self, **kwargs): ---------- left, right, top, bottom : float or None, optional Extent of the subplots as a fraction of figure width or height. - wspace, hspace : float, optional + wspace, hspace : float or None, optional Spacing between the subplots as a fraction of the average subplot width / height. """ - for k, v in kwargs.items(): - if k in self._AllowedKeys: - setattr(self, k, v) - else: - raise AttributeError(f"{k} is an unknown keyword") + if left is not _UNSET: + self.left = left + if bottom is not _UNSET: + self.bottom = bottom + if right is not _UNSET: + self.right = right + if top is not _UNSET: + self.top = top + if wspace is not _UNSET: + self.wspace = wspace + if hspace is not _UNSET: + self.hspace = hspace + for figmanager in _pylab_helpers.Gcf.figs.values(): for ax in figmanager.canvas.figure.axes: - # copied from Figure.subplots_adjust - if not isinstance(ax, mpl.axes.SubplotBase): - # Check if sharing a subplots axis - if isinstance(ax._sharex, mpl.axes.SubplotBase): - if ax._sharex.get_subplotspec().get_gridspec() == self: - ax._sharex.update_params() - ax._set_position(ax._sharex.figbox) - elif isinstance(ax._sharey, mpl.axes.SubplotBase): - if ax._sharey.get_subplotspec().get_gridspec() == self: - ax._sharey.update_params() - ax._set_position(ax._sharey.figbox) - else: + if ax.get_subplotspec() is not None: ss = ax.get_subplotspec().get_topmost_subplotspec() if ss.get_gridspec() == self: - ax.update_params() - ax._set_position(ax.figbox) + fig = ax.get_figure(root=False) + ax._set_position(ax.get_subplotspec().get_position(fig)) def get_subplot_params(self, figure=None): """ - Return the `~.SubplotParams` for the GridSpec. + Return the `.SubplotParams` for the GridSpec. In order of precedence the values are taken from - non-*None* attributes of the GridSpec - the provided *figure* - :rc:`figure.subplot.*` + + Note that the ``figure`` attribute of the GridSpec is always ignored. """ if figure is None: - kw = {k: rcParams["figure.subplot."+k] for k in self._AllowedKeys} - subplotpars = mpl.figure.SubplotParams(**kw) + kw = {k: mpl.rcParams["figure.subplot."+k] + for k in self._AllowedKeys} + subplotpars = SubplotParams(**kw) else: subplotpars = copy.copy(figure.subplotpars) @@ -535,31 +443,27 @@ def tight_layout(self, figure, renderer=None, Parameters ---------- + figure : `.Figure` + The figure. + renderer : `.RendererBase` subclass, optional + The renderer to be used. pad : float Padding between the figure edge and the edges of subplots, as a fraction of the font-size. h_pad, w_pad : float, optional Padding (height/width) between edges of adjacent subplots. Defaults to *pad*. - rect : tuple of 4 floats, default: (0, 0, 1, 1), i.e. the whole figure + rect : tuple (left, bottom, right, top), default: None (left, bottom, right, top) rectangle in normalized figure coordinates that the whole subplots area (including labels) will - fit into. + fit into. Default (None) is the whole figure. """ - - subplotspec_list = tight_layout.get_subplotspec_list( - figure.axes, grid_spec=self) - if None in subplotspec_list: - cbook._warn_external("This figure includes Axes that are not " - "compatible with tight_layout, so results " - "might be incorrect.") - if renderer is None: - renderer = tight_layout.get_renderer(figure) - - kwargs = tight_layout.get_tight_layout_figure( - figure, figure.axes, subplotspec_list, renderer, - pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + renderer = figure._get_renderer() + kwargs = _tight_layout.get_tight_layout_figure( + figure, figure.axes, + _tight_layout.get_subplotspec_list(figure.axes, grid_spec=self), + renderer, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) if kwargs: self.update(**kwargs) @@ -574,55 +478,48 @@ def __init__(self, nrows, ncols, wspace=None, hspace=None, height_ratios=None, width_ratios=None): """ - The number of rows and number of columns of the grid need to - be set. An instance of SubplotSpec is also needed to be set - from which the layout parameters will be inherited. The wspace - and hspace of the layout can be optionally specified or the - default values (from the figure or rcParams) will be used. + Parameters + ---------- + nrows, ncols : int + Number of rows and number of columns of the grid. + subplot_spec : SubplotSpec + Spec from which the layout parameters are inherited. + wspace, hspace : float, optional + See `GridSpec` for more details. If not specified default values + (from the figure or rcParams) are used. + height_ratios : array-like of length *nrows*, optional + See `GridSpecBase` for details. + width_ratios : array-like of length *ncols*, optional + See `GridSpecBase` for details. """ self._wspace = wspace self._hspace = hspace - self._subplot_spec = subplot_spec + if isinstance(subplot_spec, SubplotSpec): + self._subplot_spec = subplot_spec + else: + raise TypeError( + "subplot_spec must be type SubplotSpec, " + "usually from GridSpec, or axes.get_subplotspec.") self.figure = self._subplot_spec.get_gridspec().figure super().__init__(nrows, ncols, width_ratios=width_ratios, height_ratios=height_ratios) - # do the layoutgrids for constrained_layout: - subspeclb = subplot_spec.get_gridspec()._layoutgrid - if subspeclb is None: - self._layoutgrid = None - else: - # this _toplayoutbox is a container that spans the cols and - # rows in the parent gridspec. Not yet implemented, - # but we do this so that it is possible to have subgridspec - # level artists. - self._toplayoutgrid = layoutgrid.LayoutGrid( - parent=subspeclb, - name=subspeclb.name + '.top' + layoutgrid.seq_id(), - nrows=1, ncols=1, - parent_pos=(subplot_spec.rowspan, subplot_spec.colspan)) - self._layoutgrid = layoutgrid.LayoutGrid( - parent=self._toplayoutgrid, - name=(self._toplayoutgrid.name + '.gridspec' + - layoutgrid.seq_id()), - nrows=nrows, ncols=ncols, - width_ratios=width_ratios, height_ratios=height_ratios) def get_subplot_params(self, figure=None): """Return a dictionary of subplot layout parameters.""" hspace = (self._hspace if self._hspace is not None else figure.subplotpars.hspace if figure is not None - else rcParams["figure.subplot.hspace"]) + else mpl.rcParams["figure.subplot.hspace"]) wspace = (self._wspace if self._wspace is not None else figure.subplotpars.wspace if figure is not None - else rcParams["figure.subplot.wspace"]) + else mpl.rcParams["figure.subplot.wspace"]) figbox = self._subplot_spec.get_position(figure) left, bottom, right, top = figbox.extents - return mpl.figure.SubplotParams(left=left, right=right, - bottom=bottom, top=top, - wspace=wspace, hspace=hspace) + return SubplotParams(left=left, right=right, + bottom=bottom, top=top, + wspace=wspace, hspace=hspace) def get_topmost_subplotspec(self): """ @@ -633,21 +530,21 @@ def get_topmost_subplotspec(self): class SubplotSpec: """ - Specifies the location of a subplot in a `GridSpec`. + The location of a subplot in a `GridSpec`. .. note:: - Likely, you'll never instantiate a `SubplotSpec` yourself. Instead you - will typically obtain one from a `GridSpec` using item-access. + Likely, you will never instantiate a `SubplotSpec` yourself. Instead, + you will typically obtain one from a `GridSpec` using item-access. Parameters ---------- gridspec : `~matplotlib.gridspec.GridSpec` The GridSpec, which the subplot is referencing. num1, num2 : int - The subplot will occupy the num1-th cell of the given - gridspec. If num2 is provided, the subplot will span between - num1-th cell and num2-th cell *inclusive*. + The subplot will occupy the *num1*-th cell of the given + *gridspec*. If *num2* is provided, the subplot will span between + *num1*-th cell and *num2*-th cell **inclusive**. The index starts from 0. """ @@ -669,51 +566,41 @@ def _from_subplot_args(figure, args): - a `.SubplotSpec` -- returned as is; - one or three numbers -- a MATLAB-style subplot specifier. """ - message = ("Passing non-integers as three-element position " - "specification is deprecated since %(since)s and will be " - "removed %(removal)s.") if len(args) == 1: arg, = args if isinstance(arg, SubplotSpec): return arg - else: - if not isinstance(arg, Integral): - cbook.warn_deprecated("3.3", message=message) - arg = str(arg) - try: - rows, cols, num = map(int, str(arg)) - except ValueError: - raise ValueError( - f"Single argument to subplot must be a three-digit " - f"integer, not {arg}") from None - i = j = num + elif not isinstance(arg, Integral): + raise ValueError( + f"Single argument to subplot must be a three-digit " + f"integer, not {arg!r}") + try: + rows, cols, num = map(int, str(arg)) + except ValueError: + raise ValueError( + f"Single argument to subplot must be a three-digit " + f"integer, not {arg!r}") from None elif len(args) == 3: rows, cols, num = args - if not (isinstance(rows, Integral) and isinstance(cols, Integral)): - cbook.warn_deprecated("3.3", message=message) - rows, cols = map(int, [rows, cols]) - gs = GridSpec(rows, cols, figure=figure) - if isinstance(num, tuple) and len(num) == 2: - if not all(isinstance(n, Integral) for n in num): - cbook.warn_deprecated("3.3", message=message) - i, j = map(int, num) - else: - i, j = num - else: - if not isinstance(num, Integral): - cbook.warn_deprecated("3.3", message=message) - num = int(num) - if num < 1 or num > rows*cols: - raise ValueError( - f"num must be 1 <= num <= {rows*cols}, not {num}") - i = j = num else: - raise TypeError(f"subplot() takes 1 or 3 positional arguments but " - f"{len(args)} were given") + raise _api.nargs_error("subplot", takes="1 or 3", given=len(args)) gs = GridSpec._check_gridspec_exists(figure, rows, cols) if gs is None: gs = GridSpec(rows, cols, figure=figure) + if isinstance(num, tuple) and len(num) == 2: + if not all(isinstance(n, Integral) for n in num): + raise ValueError( + f"Subplot specifier tuple must contain integers, not {num}" + ) + i, j = num + else: + if not isinstance(num, Integral) or num < 1 or num > rows*cols: + raise ValueError( + f"num must be an integer with 1 <= num <= {rows*cols}, " + f"not {num!r}" + ) + i = j = num return gs[i-1:j] # num2 is a property only to handle the case where it is None and someone @@ -727,9 +614,6 @@ def num2(self): def num2(self, value): self._num2 = value - def __getstate__(self): - return {**self.__dict__} - def get_gridspec(self): return self._gridspec @@ -744,18 +628,6 @@ def get_geometry(self): rows, cols = self.get_gridspec().get_geometry() return rows, cols, self.num1, self.num2 - @cbook.deprecated("3.3", alternative="rowspan, colspan") - def get_rows_columns(self): - """ - Return the subplot row and column numbers as a tuple - ``(n_rows, n_cols, row_start, row_stop, col_start, col_stop)``. - """ - gridspec = self.get_gridspec() - nrows, ncols = gridspec.get_geometry() - row_start, col_start = divmod(self.num1, ncols) - row_stop, col_stop = divmod(self.num2, ncols) - return nrows, ncols, row_start, row_stop, col_start, col_stop - @property def rowspan(self): """The rows spanned by this subplot, as a `range` object.""" @@ -766,12 +638,24 @@ def rowspan(self): def colspan(self): """The columns spanned by this subplot, as a `range` object.""" ncols = self.get_gridspec().ncols - # We explicitly support num2 refering to a column on num1's *left*, so + # We explicitly support num2 referring to a column on num1's *left*, so # we must sort the column indices here so that the range makes sense. c1, c2 = sorted([self.num1 % ncols, self.num2 % ncols]) return range(c1, c2 + 1) - def get_position(self, figure, return_all=False): + def is_first_row(self): + return self.rowspan.start == 0 + + def is_last_row(self): + return self.rowspan.stop == self.get_gridspec().nrows + + def is_first_col(self): + return self.colspan.start == 0 + + def is_last_col(self): + return self.colspan.stop == self.get_gridspec().ncols + + def get_position(self, figure): """ Update the subplot position from ``figure.subplotpars``. """ @@ -785,12 +669,7 @@ def get_position(self, figure, return_all=False): fig_top = fig_tops[rows].max() fig_left = fig_lefts[cols].min() fig_right = fig_rights[cols].max() - figbox = Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top) - - if return_all: - return figbox, rows[0], cols[0], nrows, ncols - else: - return figbox + return Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top) def get_topmost_subplotspec(self): """ @@ -829,7 +708,7 @@ def subgridspec(self, nrows, ncols, **kwargs): Number of rows in grid. ncols : int - Number or columns in grid. + Number of columns in grid. Returns ------- @@ -857,3 +736,72 @@ def subgridspec(self, nrows, ncols, **kwargs): fig.add_subplot(gssub[0, i]) """ return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs) + + +class SubplotParams: + """ + Parameters defining the positioning of a subplots grid in a figure. + """ + + def __init__(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Defaults are given by :rc:`figure.subplot.[name]`. + + Parameters + ---------- + left : float, optional + The position of the left edge of the subplots, + as a fraction of the figure width. + right : float, optional + The position of the right edge of the subplots, + as a fraction of the figure width. + bottom : float, optional + The position of the bottom edge of the subplots, + as a fraction of the figure height. + top : float, optional + The position of the top edge of the subplots, + as a fraction of the figure height. + wspace : float, optional + The width of the padding between subplots, + as a fraction of the average Axes width. + hspace : float, optional + The height of the padding between subplots, + as a fraction of the average Axes height. + """ + for key in ["left", "bottom", "right", "top", "wspace", "hspace"]: + setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"]) + self.update(left, bottom, right, top, wspace, hspace) + + def update(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Update the dimensions of the passed parameters. *None* means unchanged. + """ + if ((left if left is not None else self.left) + >= (right if right is not None else self.right)): + raise ValueError('left cannot be >= right') + if ((bottom if bottom is not None else self.bottom) + >= (top if top is not None else self.top)): + raise ValueError('bottom cannot be >= top') + if left is not None: + self.left = left + if right is not None: + self.right = right + if bottom is not None: + self.bottom = bottom + if top is not None: + self.top = top + if wspace is not None: + self.wspace = wspace + if hspace is not None: + self.hspace = hspace + + def reset(self): + """Restore the subplot positioning parameters to the default rcParams values""" + for key in self.to_dict(): + setattr(self, key, mpl.rcParams[f'figure.subplot.{key}']) + + def to_dict(self): + """Return a copy of the subplot parameters as a dict.""" + return self.__dict__.copy() diff --git a/lib/matplotlib/gridspec.pyi b/lib/matplotlib/gridspec.pyi new file mode 100644 index 000000000000..2a9068635c46 --- /dev/null +++ b/lib/matplotlib/gridspec.pyi @@ -0,0 +1,174 @@ +from typing import Any, Literal, overload + +from numpy.typing import ArrayLike +import numpy as np + +from matplotlib._api import _Unset +from matplotlib.axes import Axes +from matplotlib.backend_bases import RendererBase +from matplotlib.figure import Figure +from matplotlib.transforms import Bbox + +class GridSpecBase: + def __init__( + self, + nrows: int, + ncols: int, + height_ratios: ArrayLike | None = ..., + width_ratios: ArrayLike | None = ..., + ) -> None: ... + @property + def nrows(self) -> int: ... + @property + def ncols(self) -> int: ... + def get_geometry(self) -> tuple[int, int]: ... + def get_subplot_params(self, figure: Figure | None = ...) -> SubplotParams: ... + def new_subplotspec( + self, loc: tuple[int, int], rowspan: int = ..., colspan: int = ... + ) -> SubplotSpec: ... + def set_width_ratios(self, width_ratios: ArrayLike | None) -> None: ... + def get_width_ratios(self) -> ArrayLike: ... + def set_height_ratios(self, height_ratios: ArrayLike | None) -> None: ... + def get_height_ratios(self) -> ArrayLike: ... + def get_grid_positions( + self, fig: Figure + ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: ... + @staticmethod + def _check_gridspec_exists(figure: Figure, nrows: int, ncols: int) -> GridSpec: ... + def __getitem__( + self, key: tuple[int | slice, int | slice] | slice | int + ) -> SubplotSpec: ... + @overload + def subplots( + self, + *, + sharex: bool | Literal["all", "row", "col", "none"] = ..., + sharey: bool | Literal["all", "row", "col", "none"] = ..., + squeeze: Literal[False], + subplot_kw: dict[str, Any] | None = ... + ) -> np.ndarray: ... + @overload + def subplots( + self, + *, + sharex: bool | Literal["all", "row", "col", "none"] = ..., + sharey: bool | Literal["all", "row", "col", "none"] = ..., + squeeze: Literal[True] = ..., + subplot_kw: dict[str, Any] | None = ... + ) -> np.ndarray | Axes: ... + +class GridSpec(GridSpecBase): + left: float | None + bottom: float | None + right: float | None + top: float | None + wspace: float | None + hspace: float | None + figure: Figure | None + def __init__( + self, + nrows: int, + ncols: int, + figure: Figure | None = ..., + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + ) -> None: ... + def update( + self, + *, + left: float | None | _Unset = ..., + bottom: float | None | _Unset = ..., + right: float | None | _Unset = ..., + top: float | None | _Unset = ..., + wspace: float | None | _Unset = ..., + hspace: float | None | _Unset = ..., + ) -> None: ... + def locally_modified_subplot_params(self) -> list[str]: ... + def tight_layout( + self, + figure: Figure, + renderer: RendererBase | None = ..., + pad: float = ..., + h_pad: float | None = ..., + w_pad: float | None = ..., + rect: tuple[float, float, float, float] | None = ..., + ) -> None: ... + +class GridSpecFromSubplotSpec(GridSpecBase): + figure: Figure | None + def __init__( + self, + nrows: int, + ncols: int, + subplot_spec: SubplotSpec, + wspace: float | None = ..., + hspace: float | None = ..., + height_ratios: ArrayLike | None = ..., + width_ratios: ArrayLike | None = ..., + ) -> None: ... + def get_topmost_subplotspec(self) -> SubplotSpec: ... + +class SubplotSpec: + num1: int + def __init__( + self, gridspec: GridSpecBase, num1: int, num2: int | None = ... + ) -> None: ... + @staticmethod + def _from_subplot_args(figure, args): ... + @property + def num2(self) -> int: ... + @num2.setter + def num2(self, value: int) -> None: ... + def get_gridspec(self) -> GridSpecBase: ... + def get_geometry(self) -> tuple[int, int, int, int]: ... + @property + def rowspan(self) -> range: ... + @property + def colspan(self) -> range: ... + def is_first_row(self) -> bool: ... + def is_last_row(self) -> bool: ... + def is_first_col(self) -> bool: ... + def is_last_col(self) -> bool: ... + def get_position(self, figure: Figure) -> Bbox: ... + def get_topmost_subplotspec(self) -> SubplotSpec: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def subgridspec( + self, nrows: int, ncols: int, **kwargs + ) -> GridSpecFromSubplotSpec: ... + +class SubplotParams: + def __init__( + self, + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + ) -> None: ... + left: float + right: float + bottom: float + top: float + wspace: float + hspace: float + def update( + self, + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + ) -> None: ... + def to_dict( + self, + ) -> dict[str, float]: ... + def reset(self) -> None: ... diff --git a/lib/matplotlib/hatch.py b/lib/matplotlib/hatch.py index 9e2e7ee5eca0..5e0b6d761a98 100644 --- a/lib/matplotlib/hatch.py +++ b/lib/matplotlib/hatch.py @@ -1,8 +1,25 @@ -"""Contains classes for generating hatch patterns.""" +""" +Module for generating hatch patterns. + +For examples of using the hatch API, see +:ref:`sphx_glr_gallery_shapes_and_collections_hatch_style_reference.py`. + +The following hatching patterns are available, shown here at level 2 density: + +.. plot:: _embedded_plots/hatch_classes.py + :include-source: false + :alt: + 8 squares, each showing the pattern corresponding to the hatch symbol: + symbol '/' makes right leaning diagonals, '\\' makes left leaning diagonals, + '|' makes vertical lines, '-' makes horizontal lines, '+' makes a grid, + 'X' makes a grid rotated 90 degrees, 'o' makes small unfilled circles, + 'O' makes large unfilled circles, '.' makes small filled circles, and '*' makes + a star with 5 points +""" import numpy as np -from matplotlib import cbook +from matplotlib import _api from matplotlib.path import Path @@ -101,12 +118,13 @@ def __init__(self, hatch, density): def set_vertices_and_codes(self, vertices, codes): offset = 1.0 / self.num_rows shape_vertices = self.shape_vertices * offset * self.size - if not self.filled: - inner_vertices = shape_vertices[::-1] * 0.9 shape_codes = self.shape_codes - shape_size = len(shape_vertices) - - cursor = 0 + if not self.filled: + shape_vertices = np.concatenate( # Forward, then backward. + [shape_vertices, shape_vertices[::-1] * 0.9]) + shape_codes = np.concatenate([shape_codes, shape_codes]) + vertices_parts = [] + codes_parts = [] for row in range(self.num_rows + 1): if row % 2 == 0: cols = np.linspace(0, 1, self.num_rows + 1) @@ -114,15 +132,10 @@ def set_vertices_and_codes(self, vertices, codes): cols = np.linspace(offset / 2, 1 - offset / 2, self.num_rows) row_pos = row * offset for col_pos in cols: - vertices[cursor:cursor + shape_size] = (shape_vertices + - (col_pos, row_pos)) - codes[cursor:cursor + shape_size] = shape_codes - cursor += shape_size - if not self.filled: - vertices[cursor:cursor + shape_size] = (inner_vertices + - (col_pos, row_pos)) - codes[cursor:cursor + shape_size] = shape_codes - cursor += shape_size + vertices_parts.append(shape_vertices + [col_pos, row_pos]) + codes_parts.append(shape_codes) + np.concatenate(vertices_parts, out=vertices) + np.concatenate(codes_parts, out=codes) class Circles(Shapes): @@ -149,16 +162,13 @@ def __init__(self, hatch, density): super().__init__(hatch, density) -# TODO: __init__ and class attributes override all attributes set by -# SmallCircles. Should this class derive from Circles instead? -class SmallFilledCircles(SmallCircles): +class SmallFilledCircles(Circles): size = 0.1 filled = True def __init__(self, hatch, density): self.num_rows = (hatch.count('.')) * density - # Not super().__init__! - Circles.__init__(self, hatch, density) + super().__init__(hatch, density) class Stars(Shapes): @@ -172,6 +182,7 @@ def __init__(self, hatch, density): self.shape_codes = np.full(len(self.shape_vertices), Path.LINETO, dtype=Path.code_type) self.shape_codes[0] = Path.MOVETO + self.shape_codes[-1] = Path.CLOSEPOLY super().__init__(hatch, density) _hatch_types = [ @@ -193,12 +204,13 @@ def _validate_hatch_pattern(hatch): if invalids: valid = ''.join(sorted(valid_hatch_patterns)) invalids = ''.join(sorted(invalids)) - cbook.warn_deprecated( + _api.warn_deprecated( '3.4', + removal='3.11', # one release after custom hatches (#20690) message=f'hatch must consist of a string of "{valid}" or ' 'None, but found the following invalid values ' f'"{invalids}". Passing invalid values is deprecated ' - 'since %(since)s and will become an error %(removal)s.' + 'since %(since)s and will become an error in %(removal)s.' ) diff --git a/lib/matplotlib/hatch.pyi b/lib/matplotlib/hatch.pyi new file mode 100644 index 000000000000..348cf5214984 --- /dev/null +++ b/lib/matplotlib/hatch.pyi @@ -0,0 +1,68 @@ +from matplotlib.path import Path + +import numpy as np +from numpy.typing import ArrayLike + +class HatchPatternBase: ... + +class HorizontalHatch(HatchPatternBase): + num_lines: int + num_vertices: int + def __init__(self, hatch: str, density: int) -> None: ... + def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ... + +class VerticalHatch(HatchPatternBase): + num_lines: int + num_vertices: int + def __init__(self, hatch: str, density: int) -> None: ... + def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ... + +class NorthEastHatch(HatchPatternBase): + num_lines: int + num_vertices: int + def __init__(self, hatch: str, density: int) -> None: ... + def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ... + +class SouthEastHatch(HatchPatternBase): + num_lines: int + num_vertices: int + def __init__(self, hatch: str, density: int) -> None: ... + def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ... + +class Shapes(HatchPatternBase): + filled: bool + num_shapes: int + num_vertices: int + def __init__(self, hatch: str, density: int) -> None: ... + def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ... + +class Circles(Shapes): + shape_vertices: np.ndarray + shape_codes: np.ndarray + def __init__(self, hatch: str, density: int) -> None: ... + +class SmallCircles(Circles): + size: float + num_rows: int + def __init__(self, hatch: str, density: int) -> None: ... + +class LargeCircles(Circles): + size: float + num_rows: int + def __init__(self, hatch: str, density: int) -> None: ... + +class SmallFilledCircles(Circles): + size: float + filled: bool + num_rows: int + def __init__(self, hatch: str, density: int) -> None: ... + +class Stars(Shapes): + size: float + filled: bool + num_rows: int + shape_vertices: np.ndarray + shape_codes: np.ndarray + def __init__(self, hatch: str, density: int) -> None: ... + +def get_path(hatchpattern: str, density: int = ...) -> Path: ... diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 2ef2226dc0bf..6ca472518b65 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -6,31 +6,33 @@ import math import os import logging -from numbers import Number from pathlib import Path +import warnings import numpy as np +import PIL.Image import PIL.PngImagePlugin import matplotlib as mpl +from matplotlib import _api, cbook +# For clarity, names from _image are given explicitly in this module +from matplotlib import _image +# For user convenience, the names from _image are also imported into +# the image namespace +from matplotlib._image import * # noqa: F401, F403 import matplotlib.artist as martist +import matplotlib.colorizer as mcolorizer from matplotlib.backend_bases import FigureCanvasBase import matplotlib.colors as mcolors -import matplotlib.cm as cm -import matplotlib.cbook as cbook -# For clarity, names from _image are given explicitly in this module: -import matplotlib._image as _image -# For user convenience, the names from _image are also imported into -# the image namespace: -from matplotlib._image import * -from matplotlib.transforms import (Affine2D, BboxBase, Bbox, BboxTransform, - IdentityTransform, TransformedBbox) +from matplotlib.transforms import ( + Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo, + IdentityTransform, TransformedBbox) _log = logging.getLogger(__name__) # map interpolation strings to module constants _interpd_ = { - 'antialiased': _image.NEAREST, # this will use nearest or Hanning... + 'auto': _image.NEAREST, # this will use nearest or Hanning... 'none': _image.NEAREST, # fall back to nearest when not supported 'nearest': _image.NEAREST, 'bilinear': _image.BILINEAR, @@ -49,6 +51,7 @@ 'sinc': _image.SINC, 'lanczos': _image.LANCZOS, 'blackman': _image.BLACKMAN, + 'antialiased': _image.NEAREST, # this will use nearest or Hanning... } interpolations_names = set(_interpd_) @@ -62,8 +65,8 @@ def composite_images(images, renderer, magnification=1.0): Parameters ---------- images : list of Images - Each must have a `make_image` method. For each image, - `can_composite` should return `True`, though this is not + Each must have a `!make_image` method. For each image, + `!can_composite` should return `True`, though this is not enforced by this function. Each image must have a purely affine transformation with no shear. @@ -74,7 +77,7 @@ def composite_images(images, renderer, magnification=1.0): Returns ------- - image : uint8 3d array + image : (M, N, 4) `numpy.uint8` array The composited RGBA image. offset_x, offset_y : float The (left, bottom) offset where the composited image should be placed @@ -149,7 +152,7 @@ def flush_images(): for a in artists: if (isinstance(a, _ImageBase) and a.can_composite() and - a.get_clip_on()): + a.get_clip_on() and not a.get_clip_path()): image_group.append(a) else: flush_images() @@ -165,12 +168,27 @@ def _resample( allocating the output array and fetching the relevant properties from the Image object *image_obj*. """ - + # AGG can only handle coordinates smaller than 24-bit signed integers, + # so raise errors if the input data is larger than _image.resample can + # handle. + msg = ('Data with more than {n} cannot be accurately displayed. ' + 'Downsampling to less than {n} before displaying. ' + 'To remove this warning, manually downsample your data.') + if data.shape[1] > 2**23: + warnings.warn(msg.format(n='2**23 columns')) + step = int(np.ceil(data.shape[1] / 2**23)) + data = data[:, ::step] + transform = Affine2D().scale(step, 1) + transform + if data.shape[0] > 2**24: + warnings.warn(msg.format(n='2**24 rows')) + step = int(np.ceil(data.shape[0] / 2**24)) + data = data[::step, :] + transform = Affine2D().scale(1, step) + transform # decide if we need to apply anti-aliasing if the data is upsampled: # compare the number of displayed pixels to the number of # the data pixels. interpolation = image_obj.get_interpolation() - if interpolation == 'antialiased': + if interpolation in ['antialiased', 'auto']: # don't antialias if upsampling by an integer number or # if zooming in more than a factor of 3 pos = np.array([[0, 0], [data.shape[1], data.shape[0]]]) @@ -212,61 +230,75 @@ def _rgb_to_rgba(A): return rgba -class _ImageBase(martist.Artist, cm.ScalarMappable): +class _ImageBase(mcolorizer.ColorizingArtist): """ Base class for images. - interpolation and cmap default to their rc settings + *interpolation* and *cmap* default to their rc settings. - cmap is a colors.Colormap instance - norm is a colors.Normalize instance to map luminance to 0-1 + *cmap* is a `.colors.Colormap` instance. + *norm* is a `.colors.Normalize` instance to map luminance to 0-1. - extent is data axes (left, right, bottom, top) for making image plots - registered with data plots. Default is to label the pixel - centers with the zero-based row and column indices. + *extent* is a ``(left, right, bottom, top)`` tuple in data coordinates, for + making image plots registered with data plots; the default is to label the + pixel centers with the zero-based row and column indices. - Additional kwargs are matplotlib.artist properties + Additional kwargs are `.Artist` properties. """ + zorder = 0 def __init__(self, ax, cmap=None, norm=None, + colorizer=None, interpolation=None, origin=None, filternorm=True, filterrad=4.0, resample=False, + *, + interpolation_stage=None, **kwargs ): - martist.Artist.__init__(self) - cm.ScalarMappable.__init__(self, norm, cmap) - if origin is None: - origin = mpl.rcParams['image.origin'] - cbook._check_in_list(["upper", "lower"], origin=origin) + super().__init__(self._get_colorizer(cmap, norm, colorizer)) + origin = mpl._val_or_rc(origin, 'image.origin') + _api.check_in_list(["upper", "lower"], origin=origin) self.origin = origin self.set_filternorm(filternorm) self.set_filterrad(filterrad) self.set_interpolation(interpolation) + self.set_interpolation_stage(interpolation_stage) self.set_resample(resample) self.axes = ax self._imcache = None - self.update(kwargs) + self._internal_update(kwargs) + + def __str__(self): + try: + shape = self.get_shape() + return f"{type(self).__name__}(shape={shape!r})" + except RuntimeError: + return type(self).__name__ def __getstate__(self): - state = super().__getstate__() - # We can't pickle the C Image cached object. - state['_imcache'] = None - return state + # Save some space on the pickle by not saving the cache. + return {**super().__getstate__(), "_imcache": None} def get_size(self): """Return the size of the image as tuple (numrows, numcols).""" + return self.get_shape()[:2] + + def get_shape(self): + """ + Return the shape of the image as tuple (numrows, numcols, channels). + """ if self._A is None: raise RuntimeError('You must first set the image array') - return self._A.shape[:2] + return self._A.shape def set_alpha(self, alpha): """ @@ -274,16 +306,12 @@ def set_alpha(self, alpha): Parameters ---------- - alpha : float + alpha : float or 2D array-like or None """ - if alpha is not None and not isinstance(alpha, Number): - alpha = np.asarray(alpha) - if alpha.ndim != 2: - raise TypeError('alpha must be a float, two-dimensional ' - 'array, or None') - self._alpha = alpha - self.pchanged() - self.stale = True + martist.Artist._set_alpha_for_array(self, alpha) + if np.ndim(alpha) not in (0, 2): + raise TypeError('alpha must be a float, two-dimensional ' + 'array, or None') self._imcache = None def _get_scalar_alpha(self): @@ -304,8 +332,7 @@ def changed(self): Call this whenever the mappable is changed so observers can update. """ self._imcache = None - self._rgbacache = None - cm.ScalarMappable.changed(self) + super().changed() def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, unsampled=False, round_to_pixel_border=True): @@ -315,26 +342,42 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, the given *clip_bbox* (also in pixel space), and magnified by the *magnification* factor. - *A* may be a greyscale image (M, N) with a dtype of float32, float64, - float128, uint16 or uint8, or an (M, N, 4) RGBA image with a dtype of - float32, float64, float128, or uint8. + Parameters + ---------- + A : ndarray - If *unsampled* is True, the image will not be scaled, but an - appropriate affine transformation will be returned instead. + - a (M, N) array interpreted as scalar (greyscale) image, + with one of the dtypes `~numpy.float32`, `~numpy.float64`, + `~numpy.float128`, `~numpy.uint16` or `~numpy.uint8`. + - (M, N, 4) RGBA image with a dtype of `~numpy.float32`, + `~numpy.float64`, `~numpy.float128`, or `~numpy.uint8`. + + in_bbox : `~matplotlib.transforms.Bbox` + + out_bbox : `~matplotlib.transforms.Bbox` + + clip_bbox : `~matplotlib.transforms.Bbox` + + magnification : float, default: 1 + + unsampled : bool, default: False + If True, the image will not be scaled, but an appropriate + affine transformation will be returned instead. - If *round_to_pixel_border* is True, the output image size will be - rounded to the nearest pixel boundary. This makes the images align - correctly with the axes. It should not be used if exact scaling is - needed, such as for `FigureImage`. + round_to_pixel_border : bool, default: True + If True, the output image size will be rounded to the nearest pixel + boundary. This makes the images align correctly with the Axes. + It should not be used if exact scaling is needed, such as for + `.FigureImage`. Returns ------- - image : (M, N, 4) uint8 array + image : (M, N, 4) `numpy.uint8` array The RGBA image, resampled unless *unsampled* is True. x, y : float The upper left corner where the image should be drawn, in pixel space. - trans : Affine2D + trans : `~matplotlib.transforms.Affine2D` The affine transformation from image to pixel space. """ if A is None: @@ -377,10 +420,10 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, .translate(-clipped_bbox.x0, -clipped_bbox.y0) .scale(magnification))) - # So that the image is aligned with the edge of the axes, we want to + # So that the image is aligned with the edge of the Axes, we want to # round up the output width to the next integer. This also means # scaling the transform slightly to account for the extra subpixel. - if (t.is_affine and round_to_pixel_border and + if ((not unsampled) and t.is_affine and round_to_pixel_border and (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): out_width = math.ceil(out_width_base) out_height = math.ceil(out_height_base) @@ -396,180 +439,117 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)): raise ValueError(f"Invalid shape {A.shape} for image data") - if A.ndim == 2: + float_rgba_in = A.ndim == 3 and A.shape[-1] == 4 and A.dtype.kind == 'f' + + # if antialiased, this needs to change as window sizes + # change: + interpolation_stage = self._interpolation_stage + if interpolation_stage in ['antialiased', 'auto']: + pos = np.array([[0, 0], [A.shape[1], A.shape[0]]]) + disp = t.transform(pos) + dispx = np.abs(np.diff(disp[:, 0])) / A.shape[1] + dispy = np.abs(np.diff(disp[:, 1])) / A.shape[0] + if (dispx < 3) or (dispy < 3): + interpolation_stage = 'rgba' + else: + interpolation_stage = 'data' + + if A.ndim == 2 and interpolation_stage == 'data': # if we are a 2D array, then we are running through the # norm + colormap transformation. However, in general the # input data is not going to match the size on the screen so we # have to resample to the correct number of pixels - # TODO slice input array first - inp_dtype = A.dtype - a_min = A.min() - a_max = A.max() - # figure out the type we should scale to. For floats, - # leave as is. For integers cast to an appropriate-sized - # float. Small integers get smaller floats in an attempt - # to keep the memory footprint reasonable. - if a_min is np.ma.masked: - # all masked, so values don't matter - a_min, a_max = np.int32(0), np.int32(1) - if inp_dtype.kind == 'f': - scaled_dtype = A.dtype - # Cast to float64 - if A.dtype not in (np.float32, np.float16): - if A.dtype != np.float64: - cbook._warn_external( - f"Casting input data from '{A.dtype}' to " - f"'float64' for imshow") - scaled_dtype = np.float64 - else: - # probably an integer of some type. - da = a_max.astype(np.float64) - a_min.astype(np.float64) - # give more breathing room if a big dynamic range - scaled_dtype = np.float64 if da > 1e8 else np.float32 - - # scale the input data to [.1, .9]. The Agg - # interpolators clip to [0, 1] internally, use a - # smaller input scale to identify which of the - # interpolated points need to be should be flagged as - # over / under. - # This may introduce numeric instabilities in very broadly - # scaled data - # Always copy, and don't allow array subtypes. - A_scaled = np.array(A, dtype=scaled_dtype) - # clip scaled data around norm if necessary. - # This is necessary for big numbers at the edge of - # float64's ability to represent changes. Applying - # a norm first would be good, but ruins the interpolation - # of over numbers. - self.norm.autoscale_None(A) - dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin) - vmid = np.float64(self.norm.vmin) + dv / 2 - fact = 1e7 if scaled_dtype == np.float64 else 1e4 - newmin = vmid - dv * fact - if newmin < a_min: - newmin = None - else: - a_min = np.float64(newmin) - newmax = vmid + dv * fact - if newmax > a_max: - newmax = None - else: - a_max = np.float64(newmax) - if newmax is not None or newmin is not None: - np.clip(A_scaled, newmin, newmax, out=A_scaled) - - # used to rescale the raw data to [offset, 1-offset] - # so that the resampling code will run cleanly. Using - # dyadic numbers here could reduce the error, but - # would not full eliminate it and breaks a number of - # tests (due to the slightly different error bouncing - # some pixels across a boundary in the (very - # quantized) color mapping step). - offset = .1 - frac = .8 - # we need to run the vmin/vmax through the same rescaling - # that we run the raw data through because there are small - # errors in the round-trip due to float precision. If we - # do not run the vmin/vmax through the same pipeline we can - # have values close or equal to the boundaries end up on the - # wrong side. - vrange = np.array([self.norm.vmin, self.norm.vmax], - dtype=scaled_dtype) - - A_scaled -= a_min - vrange -= a_min - # a_min and a_max might be ndarray subclasses so use - # item to avoid errors - a_min = a_min.astype(scaled_dtype).item() - a_max = a_max.astype(scaled_dtype).item() - - if a_min != a_max: - A_scaled /= ((a_max - a_min) / frac) - vrange /= ((a_max - a_min) / frac) - A_scaled += offset - vrange += offset + if A.dtype.kind == 'f': # Float dtype: scale to same dtype. + scaled_dtype = np.dtype("f8" if A.dtype.itemsize > 4 else "f4") + if scaled_dtype.itemsize < A.dtype.itemsize: + _api.warn_external(f"Casting input data from {A.dtype}" + f" to {scaled_dtype} for imshow.") + else: # Int dtype, likely. + # TODO slice input array first + # Scale to appropriately sized float: use float32 if the + # dynamic range is small, to limit the memory footprint. + da = A.max().astype("f8") - A.min().astype("f8") + scaled_dtype = "f8" if da > 1e8 else "f4" + # resample the input data to the correct resolution and shape - A_resampled = _resample(self, A_scaled, out_shape, t) - # done with A_scaled now, remove from namespace to be sure! - del A_scaled - # un-scale the resampled data to approximately the - # original range things that interpolated to above / - # below the original min/max will still be above / - # below, but possibly clipped in the case of higher order - # interpolation + drastically changing data. - A_resampled -= offset - vrange -= offset - if a_min != a_max: - A_resampled *= ((a_max - a_min) / frac) - vrange *= ((a_max - a_min) / frac) - A_resampled += a_min - vrange += a_min + A_resampled = _resample(self, A.astype(scaled_dtype), out_shape, t) + # if using NoNorm, cast back to the original datatype if isinstance(self.norm, mcolors.NoNorm): A_resampled = A_resampled.astype(A.dtype) + # Compute out_mask (what screen pixels include "bad" data + # pixels) and out_alpha (to what extent screen pixels are + # covered by data pixels: 0 outside the data extent, 1 inside + # (even for bad data), and intermediate values at the edges). mask = (np.where(A.mask, np.float32(np.nan), np.float32(1)) if A.mask.shape == A.shape # nontrivial mask else np.ones_like(A, np.float32)) # we always have to interpolate the mask to account for # non-affine transformations out_alpha = _resample(self, mask, out_shape, t, resample=True) - # done with the mask now, delete from namespace to be sure! - del mask - # Agg updates out_alpha in place. If the pixel has no image - # data it will not be updated (and still be 0 as we initialized - # it), if input data that would go into that output pixel than - # it will be `nan`, if all the input data for a pixel is good - # it will be 1, and if there is _some_ good data in that output - # pixel it will be between [0, 1] (such as a rotated image). + del mask # Make sure we don't use mask anymore! out_mask = np.isnan(out_alpha) out_alpha[out_mask] = 1 # Apply the pixel-by-pixel alpha values if present alpha = self.get_alpha() if alpha is not None and np.ndim(alpha) > 0: - out_alpha *= _resample(self, alpha, out_shape, - t, resample=True) + out_alpha *= _resample(self, alpha, out_shape, t, resample=True) # mask and run through the norm resampled_masked = np.ma.masked_array(A_resampled, out_mask) - # we have re-set the vmin/vmax to account for small errors - # that may have moved input values in/out of range - with cbook._setattr_cm(self.norm, - vmin=vrange[0], - vmax=vrange[1], - ): - output = self.norm(resampled_masked) + res = self.norm(resampled_masked) else: - if A.shape[2] == 3: - A = _rgb_to_rgba(A) - alpha = self._get_scalar_alpha() - output_alpha = _resample( # resample alpha channel - self, A[..., 3], out_shape, t, alpha=alpha) - output = _resample( # resample rgb channels - self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) - output[..., 3] = output_alpha # recombine rgb and alpha - - # at this point output is either a 2D array of normed data - # (of int or float) + if A.ndim == 2: # interpolation_stage = 'rgba' + self.norm.autoscale_None(A) + A = self.to_rgba(A) + if A.dtype == np.uint8: + # uint8 is too imprecise for premultiplied alpha roundtrips. + A = np.divide(A, 0xff, dtype=np.float32) + alpha = self.get_alpha() + post_apply_alpha = False + if alpha is None: # alpha parameter not specified + if A.shape[2] == 3: # image has no alpha channel + A = np.dstack([A, np.ones(A.shape[:2])]) + elif np.ndim(alpha) > 0: # Array alpha + # user-specified array alpha overrides the existing alpha channel + A = np.dstack([A[..., :3], alpha]) + else: # Scalar alpha + if A.shape[2] == 3: # broadcast scalar alpha + A = np.dstack([A, np.full(A.shape[:2], alpha, np.float32)]) + else: # or apply scalar alpha to existing alpha channel + post_apply_alpha = True + # Resample in premultiplied alpha space. (TODO: Consider + # implementing premultiplied-space resampling in + # span_image_resample_rgba_affine::generate?) + if float_rgba_in and np.ndim(alpha) == 0 and np.any(A[..., 3] < 1): + # Do not modify original RGBA input + A = A.copy() + A[..., :3] *= A[..., 3:] + res = _resample(self, A, out_shape, t) + np.divide(res[..., :3], res[..., 3:], out=res[..., :3], + where=res[..., 3:] != 0) + if post_apply_alpha: + res[..., 3] *= alpha + + # res is now either a 2D array of normed (int or float) data # or an RGBA array of re-sampled input - output = self.to_rgba(output, bytes=True, norm=False) + output = self.to_rgba(res, bytes=True, norm=False) # output is now a correctly sized RGBA array of uint8 # Apply alpha *after* if the input was greyscale without a mask if A.ndim == 2: alpha = self._get_scalar_alpha() alpha_channel = output[:, :, 3] - alpha_channel[:] = np.asarray( - np.asarray(alpha_channel, np.float32) * out_alpha * alpha, - np.uint8) + alpha_channel[:] = ( # Assignment will cast to uint8. + alpha_channel.astype(np.float32) * out_alpha * alpha) else: if self._imcache is None: self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) output = self._imcache - # Subset the input image to only the part that will be - # displayed + # Subset the input image to only the part that will be displayed. subset = TransformedBbox(clip_bbox, t0.inverted()).frozen() output = output[ int(max(subset.ymin, 0)): @@ -592,12 +572,12 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): Returns ------- - image : (M, N, 4) uint8 array + image : (M, N, 4) `numpy.uint8` array The RGBA image, resampled unless *unsampled* is True. x, y : float The upper left corner where the image should be drawn, in pixel space. - trans : Affine2D + trans : `~matplotlib.transforms.Affine2D` The affine transformation from image to pixel space. """ raise NotImplementedError('The make_image method must be overridden') @@ -611,7 +591,7 @@ def _check_unsampled_image(self): return False @martist.allow_rasterization - def draw(self, renderer, *args, **kwargs): + def draw(self, renderer): # if not visible, declare victory and return if not self.get_visible(): self.stale = False @@ -643,32 +623,20 @@ def draw(self, renderer, *args, **kwargs): def contains(self, mouseevent): """Test whether the mouse event occurred within the image.""" - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info - # 1) This doesn't work for figimage; but figimage also needs a fix - # below (as the check cannot use x/ydata and extents). - # 2) As long as the check below uses x/ydata, we need to test axes - # identity instead of `self.axes.contains(event)` because even if - # axes overlap, x/ydata is only valid for event.inaxes anyways. - if self.axes is not mouseevent.inaxes: + if (self._different_canvas(mouseevent) + # This doesn't work for figimage. + or not self.axes.contains(mouseevent)[0]): return False, {} # TODO: make sure this is consistent with patch and patch # collection on nonlinear transformed coordinates. # TODO: consider returning image coordinates (shouldn't # be too difficult given that the image is rectilinear - x, y = mouseevent.xdata, mouseevent.ydata + trans = self.get_transform().inverted() + x, y = trans.transform([mouseevent.x, mouseevent.y]) xmin, xmax, ymin, ymax = self.get_extent() - if xmin > xmax: - xmin, xmax = xmax, xmin - if ymin > ymax: - ymin, ymax = ymax, ymin - - if x is not None and y is not None: - inside = (xmin <= x <= xmax) and (ymin <= y <= ymax) - else: - inside = False - + # This checks xmin <= x <= xmax *or* xmax <= x <= xmin. + inside = (x is not None and (x - xmin) * (x - xmax) <= 0 + and y is not None and (y - ymin) * (y - ymax) <= 0) return inside, {} def write_png(self, fname): @@ -677,6 +645,39 @@ def write_png(self, fname): bytes=True, norm=True) PIL.Image.fromarray(im).save(fname, format="png") + @staticmethod + def _normalize_image_array(A): + """ + Check validity of image-like input *A* and normalize it to a format suitable for + Image subclasses. + """ + A = cbook.safe_masked_invalid(A, copy=True) + if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"): + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to float") + if A.ndim == 3 and A.shape[-1] == 1: + A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap. + if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]): + raise TypeError(f"Invalid shape {A.shape} for image data") + if A.ndim == 3: + # If the input data has values outside the valid range (after + # normalisation), we issue a warning and then clip X to the bounds + # - otherwise casting wraps extreme values, hiding outliers and + # making reliable interpretation impossible. + high = 255 if np.issubdtype(A.dtype, np.integer) else 1 + if A.min() < 0 or high < A.max(): + _log.warning( + 'Clipping input data to the valid range for imshow with ' + 'RGB data ([0..1] for floats or [0..255] for integers). ' + 'Got range [%s..%s].', + A.min(), A.max() + ) + A = np.clip(A, 0, high) + # Cast unsupported integer types to uint8 + if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer): + A = A.astype(np.uint8) + return A + def set_data(self, A): """ Set the image array. @@ -689,41 +690,8 @@ def set_data(self, A): """ if isinstance(A, PIL.Image.Image): A = pil_to_array(A) # Needed e.g. to apply png palette. - self._A = cbook.safe_masked_invalid(A, copy=True) - - if (self._A.dtype != np.uint8 and - not np.can_cast(self._A.dtype, float, "same_kind")): - raise TypeError("Image data of dtype {} cannot be converted to " - "float".format(self._A.dtype)) - - if self._A.ndim == 3 and self._A.shape[-1] == 1: - # If just one dimension assume scalar and apply colormap - self._A = self._A[:, :, 0] - - if not (self._A.ndim == 2 - or self._A.ndim == 3 and self._A.shape[-1] in [3, 4]): - raise TypeError("Invalid shape {} for image data" - .format(self._A.shape)) - - if self._A.ndim == 3: - # If the input data has values outside the valid range (after - # normalisation), we issue a warning and then clip X to the bounds - # - otherwise casting wraps extreme values, hiding outliers and - # making reliable interpretation impossible. - high = 255 if np.issubdtype(self._A.dtype, np.integer) else 1 - if self._A.min() < 0 or high < self._A.max(): - _log.warning( - 'Clipping input data to the valid range for imshow with ' - 'RGB data ([0..1] for floats or [0..255] for integers).' - ) - self._A = np.clip(self._A, 0, high) - # Cast unsupported integer types to uint8 - if self._A.dtype != np.uint8 and np.issubdtype(self._A.dtype, - np.integer): - self._A = self._A.astype(np.uint8) - + self._A = self._normalize_image_array(A) self._imcache = None - self._rgbacache = None self.stale = True def set_array(self, A): @@ -742,9 +710,9 @@ def get_interpolation(self): """ Return the interpolation method the image uses when resizing. - One of 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', - 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', - 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', + One of 'auto', 'antialiased', 'nearest', 'bilinear', 'bicubic', + 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', + 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', or 'none'. """ return self._interpolation @@ -760,17 +728,39 @@ def set_interpolation(self, s): Parameters ---------- - s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', \ + s : {'auto', 'nearest', 'bilinear', 'bicubic', 'spline16', \ 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \ 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} or None """ - if s is None: - s = mpl.rcParams['image.interpolation'] - s = s.lower() - cbook._check_in_list(_interpd_, interpolation=s) + s = mpl._val_or_rc(s, 'image.interpolation').lower() + _api.check_in_list(interpolations_names, interpolation=s) self._interpolation = s self.stale = True + def get_interpolation_stage(self): + """ + Return when interpolation happens during the transform to RGBA. + + One of 'data', 'rgba', 'auto'. + """ + return self._interpolation_stage + + def set_interpolation_stage(self, s): + """ + Set when interpolation happens during the transform to RGBA. + + Parameters + ---------- + s : {'data', 'rgba', 'auto'}, default: :rc:`image.interpolation_stage` + Whether to apply resampling interpolation in data or RGBA space. + If 'auto', 'rgba' is used if the upsampling rate is less than 3, + otherwise 'data' is used. + """ + s = mpl._val_or_rc(s, 'image.interpolation_stage') + _api.check_in_list(['data', 'rgba', 'auto'], s=s) + self._interpolation_stage = s + self.stale = True + def can_composite(self): """Return whether the image can be composited with its neighbors.""" trans = self.get_transform() @@ -785,11 +775,9 @@ def set_resample(self, v): Parameters ---------- - v : bool or None - If None, use :rc:`image.resample`. + v : bool, default: :rc:`image.resample` """ - if v is None: - v = mpl.rcParams['image.resample'] + v = mpl._val_or_rc(v, 'image.resample') self._resample = v self.stale = True @@ -816,8 +804,10 @@ def get_filternorm(self): def set_filterrad(self, filterrad): """ - Set the resize filter radius only applicable to some - interpolation schemes -- see help for imshow + Set the resize filter radius (only applicable to some + interpolation schemes). + + See help for `~.Axes.imshow`. Parameters ---------- @@ -836,25 +826,30 @@ def get_filterrad(self): class AxesImage(_ImageBase): """ - An image attached to an Axes. + An image with pixels on a regular grid, attached to an Axes. Parameters ---------- - ax : `~.axes.Axes` - The axes the image will belong to. + ax : `~matplotlib.axes.Axes` + The Axes the image will belong to. cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` The Colormap instance or registered colormap name used to map scalar data to colors. - norm : `~matplotlib.colors.Normalize` + norm : str or `~matplotlib.colors.Normalize` Maps luminance to 0-1. interpolation : str, default: :rc:`image.interpolation` - Supported values are 'none', 'antialiased', 'nearest', 'bilinear', + Supported values are 'none', 'auto', 'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', - 'sinc', 'lanczos'. + 'sinc', 'lanczos', 'blackman'. + interpolation_stage : {'data', 'rgba'}, default: 'data' + If 'data', interpolation + is carried out on the data provided by the user. If 'rgba', the + interpolation is carried out after the colormapping has been + applied (visual interpolation). origin : {'upper', 'lower'}, default: :rc:`image.origin` Place the [0, 0] index of the array in the upper left or lower left - corner of the axes. The convention 'upper' is typically used for + corner of the Axes. The convention 'upper' is typically used for matrices and images. extent : tuple, optional The data axes (left, right, bottom, top) for making image plots @@ -874,20 +869,21 @@ class AxesImage(_ImageBase): resample : bool, default: False When True, use a full resampling method. When False, only resample when the output image is larger than the input image. - **kwargs : `.Artist` properties + **kwargs : `~matplotlib.artist.Artist` properties """ - def __str__(self): - return "AxesImage(%g,%g;%gx%g)" % tuple(self.axes.bbox.bounds) def __init__(self, ax, + *, cmap=None, norm=None, + colorizer=None, interpolation=None, origin=None, extent=None, filternorm=True, filterrad=4.0, resample=False, + interpolation_stage=None, **kwargs ): @@ -897,18 +893,20 @@ def __init__(self, ax, ax, cmap=cmap, norm=norm, + colorizer=colorizer, interpolation=interpolation, origin=origin, filternorm=filternorm, filterrad=filterrad, resample=resample, + interpolation_stage=interpolation_stage, **kwargs ) def get_window_extent(self, renderer=None): x0, x1, y0, y1 = self._extent bbox = Bbox.from_extents([x0, y0, x1, y1]) - return bbox.transformed(self.axes.transData) + return bbox.transformed(self.get_transform()) def make_image(self, renderer, magnification=1.0, unsampled=False): # docstring inherited @@ -918,7 +916,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): bbox = Bbox(np.array([[x1, y1], [x2, y2]])) transformed_bbox = TransformedBbox(bbox, trans) clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on() - else self.figure.bbox) + else self.get_figure(root=True).bbox) return self._make_image(self._A, bbox, transformed_bbox, clip, magnification, unsampled=unsampled) @@ -926,7 +924,7 @@ def _check_unsampled_image(self): """Return whether the image would be better drawn unsampled.""" return self.get_interpolation() == "none" - def set_extent(self, extent): + def set_extent(self, extent, **kwargs): """ Set the image extent. @@ -935,22 +933,42 @@ def set_extent(self, extent): extent : 4-tuple of float The position and size of the image as tuple ``(left, right, bottom, top)`` in data coordinates. + **kwargs + Other parameters from which unit info (i.e., the *xunits*, + *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for + polar Axes) entries are applied, if present. Notes ----- - This updates ``ax.dataLim``, and, if autoscaling, sets ``ax.viewLim`` - to tightly fit the image, regardless of ``dataLim``. Autoscaling - state is not changed, so following this with ``ax.autoscale_view()`` - will redo the autoscaling in accord with ``dataLim``. + This updates `.Axes.dataLim`, and, if autoscaling, sets `.Axes.viewLim` + to tightly fit the image, regardless of `~.Axes.dataLim`. Autoscaling + state is not changed, so a subsequent call to `.Axes.autoscale_view` + will redo the autoscaling in accord with `~.Axes.dataLim`. """ - self._extent = xmin, xmax, ymin, ymax = extent + (xmin, xmax), (ymin, ymax) = self.axes._process_unit_info( + [("x", [extent[0], extent[1]]), + ("y", [extent[2], extent[3]])], + kwargs) + if kwargs: + raise _api.kwarg_error("set_extent", kwargs) + xmin = self.axes._validate_converted_limits( + xmin, self.convert_xunits) + xmax = self.axes._validate_converted_limits( + xmax, self.convert_xunits) + ymin = self.axes._validate_converted_limits( + ymin, self.convert_yunits) + ymax = self.axes._validate_converted_limits( + ymax, self.convert_yunits) + extent = [xmin, xmax, ymin, ymax] + + self._extent = extent corners = (xmin, ymin), (xmax, ymax) self.axes.update_datalim(corners) self.sticky_edges.x[:] = [xmin, xmax] self.sticky_edges.y[:] = [ymin, ymax] - if self.axes._autoscaleXon: + if self.axes.get_autoscalex_on(): self.axes.set_xlim((xmin, xmax), auto=None) - if self.axes._autoscaleYon: + if self.axes.get_autoscaley_on(): self.axes.set_ylim((ymin, ymax), auto=None) self.stale = True @@ -979,37 +997,39 @@ def get_cursor_data(self, event): if self.origin == 'upper': ymin, ymax = ymax, ymin arr = self.get_array() - data_extent = Bbox([[ymin, xmin], [ymax, xmax]]) - array_extent = Bbox([[0, 0], arr.shape[:2]]) - trans = BboxTransform(boxin=data_extent, boxout=array_extent) - point = trans.transform([event.ydata, event.xdata]) + data_extent = Bbox([[xmin, ymin], [xmax, ymax]]) + array_extent = Bbox([[0, 0], [arr.shape[1], arr.shape[0]]]) + trans = self.get_transform().inverted() + trans += BboxTransform(boxin=data_extent, boxout=array_extent) + point = trans.transform([event.x, event.y]) if any(np.isnan(point)): return None - i, j = point.astype(int) + j, i = point.astype(int) # Clip the coordinates at array bounds if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]): return None else: return arr[i, j] - def format_cursor_data(self, data): - if np.ndim(data) == 0 and self.colorbar: - return ( - "[" - + cbook.strip_math( - self.colorbar.formatter.format_data_short(data)).strip() - + "]") - else: - return super().format_cursor_data(data) - class NonUniformImage(AxesImage): + """ + An image with pixels on a rectilinear grid. + + In contrast to `.AxesImage`, where pixels are on a regular grid, + NonUniformImage allows rows and columns with individual heights / widths. + + See also :doc:`/gallery/images_contours_and_fields/image_nonuniform`. + """ + def __init__(self, ax, *, interpolation='nearest', **kwargs): """ Parameters ---------- + ax : `~matplotlib.axes.Axes` + The Axes the image will belong to. interpolation : {'nearest', 'bilinear'}, default: 'nearest' - + The interpolation scheme used in the resampling. **kwargs All other keyword arguments are identical to those of `.AxesImage`. """ @@ -1020,8 +1040,6 @@ def _check_unsampled_image(self): """Return False. Do not use unsampled image.""" return False - is_grayscale = cbook._deprecate_privatize_attribute("3.3") - def make_image(self, renderer, magnification=1.0, unsampled=False): # docstring inherited if self._A is None: @@ -1032,11 +1050,9 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): if A.ndim == 2: if A.dtype != np.uint8: A = self.to_rgba(A, bytes=True) - self._is_grayscale = self.cmap.is_gray() else: A = np.repeat(A[:, :, np.newaxis], 4, 2) A[:, :, 3] = 255 - self._is_grayscale = True else: if A.dtype != np.uint8: A = (255*A).astype(np.uint8) @@ -1045,17 +1061,57 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): B[:, :, 0:3] = A B[:, :, 3] = 255 A = B - self._is_grayscale = False - vl = self.axes.viewLim l, b, r, t = self.axes.bbox.extents - width = (round(r) + 0.5) - (round(l) - 0.5) - height = (round(t) + 0.5) - (round(b) - 0.5) - width *= magnification - height *= magnification - im = _image.pcolor(self._Ax, self._Ay, A, - int(height), int(width), - (vl.x0, vl.x1, vl.y0, vl.y1), - _interpd_[self._interpolation]) + width = int(((round(r) + 0.5) - (round(l) - 0.5)) * magnification) + height = int(((round(t) + 0.5) - (round(b) - 0.5)) * magnification) + + invertedTransform = self.axes.transData.inverted() + x_pix = invertedTransform.transform( + [(x, b) for x in np.linspace(l, r, width)])[:, 0] + y_pix = invertedTransform.transform( + [(l, y) for y in np.linspace(b, t, height)])[:, 1] + + if self._interpolation == "nearest": + x_mid = (self._Ax[:-1] + self._Ax[1:]) / 2 + y_mid = (self._Ay[:-1] + self._Ay[1:]) / 2 + x_int = x_mid.searchsorted(x_pix) + y_int = y_mid.searchsorted(y_pix) + # The following is equal to `A[y_int[:, None], x_int[None, :]]`, + # but many times faster. Both casting to uint32 (to have an + # effectively 1D array) and manual index flattening matter. + im = ( + np.ascontiguousarray(A).view(np.uint32).ravel()[ + np.add.outer(y_int * A.shape[1], x_int)] + .view(np.uint8).reshape((height, width, 4))) + else: # self._interpolation == "bilinear" + # Use np.interp to compute x_int/x_float has similar speed. + x_int = np.clip( + self._Ax.searchsorted(x_pix) - 1, 0, len(self._Ax) - 2) + y_int = np.clip( + self._Ay.searchsorted(y_pix) - 1, 0, len(self._Ay) - 2) + idx_int = np.add.outer(y_int * A.shape[1], x_int) + x_frac = np.clip( + np.divide(x_pix - self._Ax[x_int], np.diff(self._Ax)[x_int], + dtype=np.float32), # Downcasting helps with speed. + 0, 1) + y_frac = np.clip( + np.divide(y_pix - self._Ay[y_int], np.diff(self._Ay)[y_int], + dtype=np.float32), + 0, 1) + f00 = np.outer(1 - y_frac, 1 - x_frac) + f10 = np.outer(y_frac, 1 - x_frac) + f01 = np.outer(1 - y_frac, x_frac) + f11 = np.outer(y_frac, x_frac) + im = np.empty((height, width, 4), np.uint8) + for chan in range(4): + ac = A[:, :, chan].reshape(-1) # reshape(-1) avoids a copy. + # Shifting the buffer start (`ac[offset:]`) avoids an array + # addition (`ac[idx_int + offset]`). + buf = f00 * ac[idx_int] + buf += f10 * ac[A.shape[1]:][idx_int] + buf += f01 * ac[1:][idx_int] + buf += f11 * ac[A.shape[1] + 1:][idx_int] + im[:, :, chan] = buf # Implicitly casts to uint8. return im, l, b, IdentityTransform() def set_data(self, x, y, A): @@ -1068,26 +1124,18 @@ def set_data(self, x, y, A): Monotonic arrays of shapes (N,) and (M,), respectively, specifying pixel centers. A : array-like - (M, N) ndarray or masked array of values to be colormapped, or - (M, N, 3) RGB array, or (M, N, 4) RGBA array. + (M, N) `~numpy.ndarray` or masked array of values to be + colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array. """ + A = self._normalize_image_array(A) x = np.array(x, np.float32) y = np.array(y, np.float32) - A = cbook.safe_masked_invalid(A, copy=True) - if not (x.ndim == y.ndim == 1 and A.shape[0:2] == y.shape + x.shape): + if not (x.ndim == y.ndim == 1 and A.shape[:2] == y.shape + x.shape): raise TypeError("Axes don't match array shape") - if A.ndim not in [2, 3]: - raise TypeError("Can only plot 2D or 3D data") - if A.ndim == 3 and A.shape[2] not in [1, 3, 4]: - raise TypeError("3D arrays must have three (RGB) " - "or four (RGBA) color components") - if A.ndim == 3 and A.shape[2] == 1: - A = A.squeeze(axis=-1) self._A = A self._Ax = x self._Ay = y self._imcache = None - self.stale = True def set_array(self, *args): @@ -1110,10 +1158,10 @@ def get_extent(self): raise RuntimeError('Must set data first') return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1] - def set_filternorm(self, s): + def set_filternorm(self, filternorm): pass - def set_filterrad(self, s): + def set_filterrad(self, filterrad): pass def set_norm(self, norm): @@ -1126,6 +1174,16 @@ def set_cmap(self, cmap): raise RuntimeError('Cannot change colors after loading data') super().set_cmap(cmap) + def get_cursor_data(self, event): + # docstring inherited + x, y = event.xdata, event.ydata + if (x < self._Ax[0] or x > self._Ax[-1] or + y < self._Ay[0] or y > self._Ay[-1]): + return None + j = np.searchsorted(self._Ax, x) - 1 + i = np.searchsorted(self._Ay, y) - 1 + return self._A[i, j] + class PcolorImage(AxesImage): """ @@ -1134,19 +1192,22 @@ class PcolorImage(AxesImage): This uses a variation of the original irregular image code, and it is used by pcolorfast for the corresponding grid type. """ + def __init__(self, ax, x=None, y=None, A=None, + *, cmap=None, norm=None, + colorizer=None, **kwargs ): """ Parameters ---------- - ax : `~.axes.Axes` - The axes the image will belong to. + ax : `~matplotlib.axes.Axes` + The Axes the image will belong to. x, y : 1D array-like, optional Monotonic arrays of length N+1 and M+1, respectively, specifying rectangle boundaries. If not given, will default to @@ -1155,51 +1216,53 @@ def __init__(self, ax, The data to be color-coded. The interpretation depends on the shape: - - (M, N) ndarray or masked array: values to be colormapped + - (M, N) `~numpy.ndarray` or masked array: values to be colormapped - (M, N, 3): RGB array - (M, N, 4): RGBA array cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` The Colormap instance or registered colormap name used to map scalar data to colors. - norm : `~matplotlib.colors.Normalize` + norm : str or `~matplotlib.colors.Normalize` Maps luminance to 0-1. - **kwargs : `.Artist` properties + **kwargs : `~matplotlib.artist.Artist` properties """ - super().__init__(ax, norm=norm, cmap=cmap) - self.update(kwargs) + super().__init__(ax, norm=norm, cmap=cmap, colorizer=colorizer) + self._internal_update(kwargs) if A is not None: self.set_data(x, y, A) - is_grayscale = cbook._deprecate_privatize_attribute("3.3") - def make_image(self, renderer, magnification=1.0, unsampled=False): # docstring inherited if self._A is None: raise RuntimeError('You must first set the image array') if unsampled: raise ValueError('unsampled not supported on PColorImage') - fc = self.axes.patch.get_facecolor() - bg = mcolors.to_rgba(fc, 0) - bg = (np.array(bg)*255).astype(np.uint8) + + if self._imcache is None: + A = self.to_rgba(self._A, bytes=True) + self._imcache = np.pad(A, [(1, 1), (1, 1), (0, 0)], "constant") + padded_A = self._imcache + bg = mcolors.to_rgba(self.axes.patch.get_facecolor(), 0) + bg = (np.array(bg) * 255).astype(np.uint8) + if (padded_A[0, 0] != bg).all(): + padded_A[[0, -1], :] = padded_A[:, [0, -1]] = bg + l, b, r, t = self.axes.bbox.extents width = (round(r) + 0.5) - (round(l) - 0.5) height = (round(t) + 0.5) - (round(b) - 0.5) - width = int(round(width * magnification)) - height = int(round(height * magnification)) - if self._rgbacache is None: - A = self.to_rgba(self._A, bytes=True) - self._rgbacache = A - if self._A.ndim == 2: - self._is_grayscale = self.cmap.is_gray() - else: - A = self._rgbacache + width = round(width * magnification) + height = round(height * magnification) vl = self.axes.viewLim - im = _image.pcolor2(self._Ax, self._Ay, A, - height, - width, - (vl.x0, vl.x1, vl.y0, vl.y1), - bg) + + x_pix = np.linspace(vl.x0, vl.x1, width) + y_pix = np.linspace(vl.y0, vl.y1, height) + x_int = self._Ax.searchsorted(x_pix) + y_int = self._Ay.searchsorted(y_pix) + im = ( # See comment in NonUniformImage.make_image re: performance. + padded_A.view(np.uint32).ravel()[ + np.add.outer(y_int * padded_A.shape[1], x_int)] + .view(np.uint8).reshape((height, width, 4))) return im, l, b, IdentityTransform() def _check_unsampled_image(self): @@ -1219,37 +1282,17 @@ def set_data(self, x, y, A): The data to be color-coded. The interpretation depends on the shape: - - (M, N) ndarray or masked array: values to be colormapped + - (M, N) `~numpy.ndarray` or masked array: values to be colormapped - (M, N, 3): RGB array - (M, N, 4): RGBA array """ - A = cbook.safe_masked_invalid(A, copy=True) - if x is None: - x = np.arange(0, A.shape[1]+1, dtype=np.float64) - else: - x = np.array(x, np.float64).ravel() - if y is None: - y = np.arange(0, A.shape[0]+1, dtype=np.float64) - else: - y = np.array(y, np.float64).ravel() - - if A.shape[:2] != (y.size-1, x.size-1): + A = self._normalize_image_array(A) + x = np.arange(0., A.shape[1] + 1) if x is None else np.array(x, float).ravel() + y = np.arange(0., A.shape[0] + 1) if y is None else np.array(y, float).ravel() + if A.shape[:2] != (y.size - 1, x.size - 1): raise ValueError( "Axes don't match array shape. Got %s, expected %s." % (A.shape[:2], (y.size - 1, x.size - 1))) - if A.ndim not in [2, 3]: - raise ValueError("A must be 2D or 3D") - if A.ndim == 3 and A.shape[2] == 1: - A = A.squeeze(axis=-1) - self._is_grayscale = False - if A.ndim == 3: - if A.shape[2] in [3, 4]: - if ((A[:, :, 0] == A[:, :, 1]).all() and - (A[:, :, 0] == A[:, :, 2]).all()): - self._is_grayscale = True - else: - raise ValueError("3D arrays must have RGB or RGBA as last dim") - # For efficient cursor readout, ensure x and y are increasing. if x[-1] < x[0]: x = x[::-1] @@ -1257,11 +1300,10 @@ def set_data(self, x, y, A): if y[-1] < y[0]: y = y[::-1] A = A[::-1] - self._A = A self._Ax = x self._Ay = y - self._rgbacache = None + self._imcache = None self.stale = True def set_array(self, *args): @@ -1275,10 +1317,7 @@ def get_cursor_data(self, event): return None j = np.searchsorted(self._Ax, x) - 1 i = np.searchsorted(self._Ay, y) - 1 - try: - return self._A[i, j] - except IndexError: - return None + return self._A[i, j] class FigureImage(_ImageBase): @@ -1289,8 +1328,10 @@ class FigureImage(_ImageBase): _interpolation = 'nearest' def __init__(self, fig, + *, cmap=None, norm=None, + colorizer=None, offsetx=0, offsety=0, origin=None, @@ -1306,12 +1347,13 @@ def __init__(self, fig, None, norm=norm, cmap=cmap, + colorizer=colorizer, origin=origin ) - self.figure = fig + self.set_figure(fig) self.ox = offsetx self.oy = offsety - self.update(kwargs) + self._internal_update(kwargs) self.magnification = 1.0 def get_extent(self): @@ -1322,14 +1364,15 @@ def get_extent(self): def make_image(self, renderer, magnification=1.0, unsampled=False): # docstring inherited - fac = renderer.dpi/self.figure.dpi + fig = self.get_figure(root=True) + fac = renderer.dpi/fig.dpi # fac here is to account for pdf, eps, svg backends where # figure.dpi is set to 72. This means we need to scale the # image (using magnification) and offset it appropriately. bbox = Bbox([[self.ox/fac, self.oy/fac], [(self.ox/fac + self._A.shape[1]), (self.oy/fac + self._A.shape[0])]]) - width, height = self.figure.get_size_inches() + width, height = fig.get_size_inches() width *= renderer.dpi height *= renderer.dpi clip = Bbox([[0, 0], [width, height]]) @@ -1339,17 +1382,62 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): def set_data(self, A): """Set the image array.""" - cm.ScalarMappable.set_array(self, - cbook.safe_masked_invalid(A, copy=True)) + super().set_data(A) self.stale = True class BboxImage(_ImageBase): - """The Image class whose size is determined by the given bbox.""" + """ + The Image class whose size is determined by the given bbox. + + Parameters + ---------- + bbox : BboxBase or Callable[RendererBase, BboxBase] + The bbox or a function to generate the bbox + + .. warning :: + + If using `matplotlib.artist.Artist.get_window_extent` as the + callable ensure that the other artist is drawn first (lower zorder) + or you may need to renderer the figure twice to ensure that the + computed bbox is accurate. + + cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` + The Colormap instance or registered colormap name used to map scalar + data to colors. + norm : str or `~matplotlib.colors.Normalize` + Maps luminance to 0-1. + interpolation : str, default: :rc:`image.interpolation` + Supported values are 'none', 'auto', 'nearest', 'bilinear', + 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', + 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', + 'sinc', 'lanczos', 'blackman'. + origin : {'upper', 'lower'}, default: :rc:`image.origin` + Place the [0, 0] index of the array in the upper left or lower left + corner of the Axes. The convention 'upper' is typically used for + matrices and images. + filternorm : bool, default: True + A parameter for the antigrain image resize filter + (see the antigrain documentation). + If filternorm is set, the filter normalizes integer values and corrects + the rounding errors. It doesn't do anything with the source floating + point values, it corrects only integers according to the rule of 1.0 + which means that any sum of pixel weights must be equal to 1.0. So, + the filter function must produce a graph of the proper shape. + filterrad : float > 0, default: 4 + The filter radius for filters that have a radius parameter, i.e. when + interpolation is one of: 'sinc', 'lanczos' or 'blackman'. + resample : bool, default: False + When True, use a full resampling method. When False, only resample when + the output image is larger than the input image. + **kwargs : `~matplotlib.artist.Artist` properties + """ def __init__(self, bbox, + *, cmap=None, norm=None, + colorizer=None, interpolation=None, origin=None, filternorm=True, @@ -1357,16 +1445,12 @@ def __init__(self, bbox, resample=False, **kwargs ): - """ - cmap is a colors.Colormap instance - norm is a colors.Normalize instance to map luminance to 0-1 - kwargs are an optional list of Artist keyword args - """ super().__init__( None, cmap=cmap, norm=norm, + colorizer=colorizer, interpolation=interpolation, origin=origin, filternorm=filternorm, @@ -1374,36 +1458,24 @@ def __init__(self, bbox, resample=resample, **kwargs ) - self.bbox = bbox - self._transform = IdentityTransform() - - def get_transform(self): - return self._transform def get_window_extent(self, renderer=None): - if renderer is None: - renderer = self.get_figure()._cachedRenderer - if isinstance(self.bbox, BboxBase): return self.bbox elif callable(self.bbox): + if renderer is None: + renderer = self.get_figure()._get_renderer() return self.bbox(renderer) else: raise ValueError("Unknown type of bbox") def contains(self, mouseevent): """Test whether the mouse event occurred within the image.""" - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info - - if not self.get_visible(): # or self.get_figure()._renderer is None: + if self._different_canvas(mouseevent) or not self.get_visible(): return False, {} - x, y = mouseevent.x, mouseevent.y inside = self.get_window_extent().contains(x, y) - return inside, {} def make_image(self, renderer, magnification=1.0, unsampled=False): @@ -1413,7 +1485,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): bbox_in._points /= [width, height] bbox_out = self.get_window_extent(renderer) clip = Bbox([[0, 0], [width, height]]) - self._transform = BboxTransform(Bbox([[0, 0], [1, 1]]), clip) + self._transform = BboxTransformTo(clip) return self._make_image( self._A, bbox_in, bbox_out, clip, magnification, unsampled=unsampled) @@ -1423,15 +1495,26 @@ def imread(fname, format=None): """ Read an image from a file into an array. + .. note:: + + This function exists for historical reasons. It is recommended to + use `PIL.Image.open` instead for loading images. + Parameters ---------- fname : str or file-like The image file to read: a filename, a URL or a file-like object opened in read-binary mode. + + Passing a URL is deprecated. Please open the URL + for reading and pass the result to Pillow, e.g. with + ``np.array(PIL.Image.open(urllib.request.urlopen(url)))``. format : str, optional - The image file format assumed for reading the data. If not - given, the format is deduced from the filename. If nothing can - be deduced, PNG is tried. + The image file format assumed for reading the data. The image is + loaded as a PNG file if *format* is set to "png", if *fname* is a path + or opened file with a ".png" extension, or if it is a URL. In all + other cases, *format* is ignored and the format is auto-detected by + `PIL.Image.open`. Returns ------- @@ -1441,6 +1524,10 @@ def imread(fname, format=None): - (M, N) for grayscale images. - (M, N, 3) for RGB images. - (M, N, 4) for RGBA images. + + PNG images are returned as float arrays (0-1). All other formats are + returned as int arrays, with a bit depth determined by the file's + contents. """ # hide imports to speed initial import on systems with slow linkers from urllib import parse @@ -1469,20 +1556,13 @@ def imread(fname, format=None): ext = format img_open = ( PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open) - if isinstance(fname, str): - - parsed = parse.urlparse(fname) - if len(parsed.scheme) > 1: # Pillow doesn't handle URLs directly. - # hide imports to speed initial import on systems with slow linkers - from urllib import request - with request.urlopen(fname, - context=mpl._get_ssl_context()) as response: - import io - try: - response.seek(0) - except (AttributeError, io.UnsupportedOperation): - response = io.BytesIO(response.read()) - return imread(response, format=ext) + if isinstance(fname, str) and len(parse.urlparse(fname).scheme) > 1: + # Pillow doesn't handle URLs directly. + raise ValueError( + "Please open the URL for reading and pass the " + "result to Pillow, e.g. with " + "``np.array(PIL.Image.open(urllib.request.urlopen(url)))``." + ) with img_open(fname) as image: return (_pil_png_to_float_array(image) if isinstance(image, PIL.PngImagePlugin.PngImageFile) else @@ -1492,7 +1572,15 @@ def imread(fname, format=None): def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, origin=None, dpi=100, *, metadata=None, pil_kwargs=None): """ - Save an array as an image file. + Colormap and save an array as an image file. + + RGB(A) images are passed through. Single channel images will be + colormapped according to *cmap* and *norm*. + + .. note:: + + If you want to save a single channel image as gray scale please use an + image I/O library (such as pillow, tifffile, or imageio) directly. Parameters ---------- @@ -1502,7 +1590,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, extension of *fname*, if any, and from :rc:`savefig.format` otherwise. If *format* is set, it determines the output format. arr : array-like - The image data. The shape can be one of + The image data. Accepts NumPy arrays or sequences + (e.g., lists or tuples). The shape can be one of MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA). vmin, vmax : float, optional *vmin* and *vmax* set the color scaling for the image by fixing the @@ -1517,7 +1606,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, is unset is documented under *fname*. origin : {'upper', 'lower'}, default: :rc:`image.origin` Indicates whether the ``(0, 0)`` index of the array is in the upper - left or lower left corner of the axes. + left or lower left corner of the Axes. dpi : float The DPI to store in the metadata of the file. This does not affect the resolution of the output image. Depending on file format, this may be @@ -1526,12 +1615,17 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, Metadata in the image file. The supported keys depend on the output format, see the documentation of the respective backends for more information. + Currently only supported for "png", "pdf", "ps", "eps", and "svg". pil_kwargs : dict, optional Keyword arguments passed to `PIL.Image.Image.save`. If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ from matplotlib.figure import Figure + + # Normalizing input (e.g., list or tuples) to NumPy array if needed + arr = np.asanyarray(arr) + if isinstance(fname, os.PathLike): fname = os.fspath(fname) if format is None: @@ -1550,24 +1644,28 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, else: # Don't bother creating an image; this avoids rounding errors on the # size when dividing and then multiplying by dpi. - sm = cm.ScalarMappable(cmap=cmap) - sm.set_clim(vmin, vmax) - if origin is None: - origin = mpl.rcParams["image.origin"] + origin = mpl._val_or_rc(origin, "image.origin") + _api.check_in_list(('upper', 'lower'), origin=origin) if origin == "lower": arr = arr[::-1] if (isinstance(arr, memoryview) and arr.format == "B" and arr.ndim == 3 and arr.shape[-1] == 4): - # Such an ``arr`` would also be handled fine by sm.to_rgba (after - # casting with asarray), but it is useful to special-case it + # Such an ``arr`` would also be handled fine by sm.to_rgba below + # (after casting with asarray), but it is useful to special-case it # because that's what backend_agg passes, and can be in fact used # as is, saving a few operations. rgba = arr else: + sm = mcolorizer.Colorizer(cmap=cmap) + sm.set_clim(vmin, vmax) rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: pil_kwargs = {} + else: + # we modify this below, so make a copy (don't modify caller's dict) + pil_kwargs = pil_kwargs.copy() pil_shape = (rgba.shape[1], rgba.shape[0]) + rgba = np.require(rgba, requirements='C') image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) if format == "png": @@ -1575,8 +1673,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, # semantics of duplicate keys in pnginfo is unclear. if "pnginfo" in pil_kwargs: if metadata: - cbook._warn_external("'metadata' is overridden by the " - "'pnginfo' entry in 'pil_kwargs'.") + _api.warn_external("'metadata' is overridden by the " + "'pnginfo' entry in 'pil_kwargs'.") else: metadata = { "Software": (f"Matplotlib version{mpl.__version__}, " @@ -1587,6 +1685,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, for k, v in metadata.items(): if v is not None: pnginfo.add_text(k, v) + elif metadata is not None: + raise ValueError(f"metadata not supported for format {format!r}") if format in ["jpg", "jpeg"]: format = "jpeg" # Pillow doesn't recognize "jpg". facecolor = mpl.rcParams["savefig.facecolor"] @@ -1680,7 +1780,7 @@ def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear', thus supports a wide range of file formats, including PNG, JPG, TIFF and others. - .. _Pillow: https://python-pillow.org/ + .. _Pillow: https://python-pillow.github.io thumbfile : str or file-like The thumbnail filename. @@ -1701,7 +1801,7 @@ def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear', Returns ------- - `~.figure.Figure` + `.Figure` The figure instance containing the thumbnail. """ diff --git a/lib/matplotlib/image.pyi b/lib/matplotlib/image.pyi new file mode 100644 index 000000000000..1fcc1a710bfd --- /dev/null +++ b/lib/matplotlib/image.pyi @@ -0,0 +1,215 @@ +from collections.abc import Callable, Sequence +import os +import pathlib +from typing import Any, BinaryIO, Literal + +import numpy as np +from numpy.typing import ArrayLike, NDArray +import PIL.Image + +from matplotlib.axes import Axes +from matplotlib import colorizer +from matplotlib.backend_bases import RendererBase, MouseEvent +from matplotlib.colorizer import Colorizer +from matplotlib.colors import Colormap, Normalize +from matplotlib.figure import Figure +from matplotlib.transforms import Affine2D, BboxBase, Bbox, Transform + +# +# These names are re-exported from matplotlib._image. +# + +BESSEL: int +BICUBIC: int +BILINEAR: int +BLACKMAN: int +CATROM: int +GAUSSIAN: int +HAMMING: int +HANNING: int +HERMITE: int +KAISER: int +LANCZOS: int +MITCHELL: int +NEAREST: int +QUADRIC: int +SINC: int +SPLINE16: int +SPLINE36: int + +def resample( + input_array: NDArray[np.float32] | NDArray[np.float64] | NDArray[np.int8], + output_array: NDArray[np.float32] | NDArray[np.float64] | NDArray[np.int8], + transform: Transform, + interpolation: int = ..., + resample: bool = ..., + alpha: float = ..., + norm: bool = ..., + radius: float = ..., +) -> None: ... + +# +# END names re-exported from matplotlib._image. +# + +interpolations_names: set[str] + +def composite_images( + images: Sequence[_ImageBase], renderer: RendererBase, magnification: float = ... +) -> tuple[np.ndarray, float, float]: ... + +class _ImageBase(colorizer.ColorizingArtist): + zorder: float + origin: Literal["upper", "lower"] + axes: Axes + def __init__( + self, + ax: Axes, + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + colorizer: Colorizer | None = ..., + interpolation: str | None = ..., + origin: Literal["upper", "lower"] | None = ..., + filternorm: bool = ..., + filterrad: float = ..., + resample: bool | None = ..., + *, + interpolation_stage: Literal["data", "rgba", "auto"] | None = ..., + **kwargs + ) -> None: ... + def get_size(self) -> tuple[int, int]: ... + def set_alpha(self, alpha: float | ArrayLike | None) -> None: ... + def changed(self) -> None: ... + def make_image( + self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ... + ) -> tuple[np.ndarray, float, float, Affine2D]: ... + def draw(self, renderer: RendererBase) -> None: ... + def write_png(self, fname: str | pathlib.Path | BinaryIO) -> None: ... + def set_data(self, A: ArrayLike | None) -> None: ... + def set_array(self, A: ArrayLike | None) -> None: ... + def get_shape(self) -> tuple[int, int, int]: ... + def get_interpolation(self) -> str: ... + def set_interpolation(self, s: str | None) -> None: ... + def get_interpolation_stage(self) -> Literal["data", "rgba", "auto"]: ... + def set_interpolation_stage(self, s: Literal["data", "rgba", "auto"]) -> None: ... + def can_composite(self) -> bool: ... + def set_resample(self, v: bool | None) -> None: ... + def get_resample(self) -> bool: ... + def set_filternorm(self, filternorm: bool) -> None: ... + def get_filternorm(self) -> bool: ... + def set_filterrad(self, filterrad: float) -> None: ... + def get_filterrad(self) -> float: ... + +class AxesImage(_ImageBase): + def __init__( + self, + ax: Axes, + *, + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + colorizer: Colorizer | None = ..., + interpolation: str | None = ..., + origin: Literal["upper", "lower"] | None = ..., + extent: tuple[float, float, float, float] | None = ..., + filternorm: bool = ..., + filterrad: float = ..., + resample: bool = ..., + interpolation_stage: Literal["data", "rgba", "auto"] | None = ..., + **kwargs + ) -> None: ... + def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... + def make_image( + self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ... + ) -> tuple[np.ndarray, float, float, Affine2D]: ... + def set_extent( + self, extent: tuple[float, float, float, float], **kwargs + ) -> None: ... + def get_extent(self) -> tuple[float, float, float, float]: ... + def get_cursor_data(self, event: MouseEvent) -> None | float: ... + +class NonUniformImage(AxesImage): + mouseover: bool + def __init__( + self, ax: Axes, *, interpolation: Literal["nearest", "bilinear"] = ..., **kwargs + ) -> None: ... + def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override] + # more limited interpolation available here than base class + def set_interpolation(self, s: Literal["nearest", "bilinear"]) -> None: ... # type: ignore[override] + +class PcolorImage(AxesImage): + def __init__( + self, + ax: Axes, + x: ArrayLike | None = ..., + y: ArrayLike | None = ..., + A: ArrayLike | None = ..., + *, + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + colorizer: Colorizer | None = ..., + **kwargs + ) -> None: ... + def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override] + +class FigureImage(_ImageBase): + zorder: float + figure: Figure + ox: float + oy: float + magnification: float + def __init__( + self, + fig: Figure, + *, + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + colorizer: Colorizer | None = ..., + offsetx: int = ..., + offsety: int = ..., + origin: Literal["upper", "lower"] | None = ..., + **kwargs + ) -> None: ... + def get_extent(self) -> tuple[float, float, float, float]: ... + +class BboxImage(_ImageBase): + bbox: BboxBase + def __init__( + self, + bbox: BboxBase | Callable[[RendererBase | None], Bbox], + *, + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + colorizer: Colorizer | None = ..., + interpolation: str | None = ..., + origin: Literal["upper", "lower"] | None = ..., + filternorm: bool = ..., + filterrad: float = ..., + resample: bool = ..., + **kwargs + ) -> None: ... + def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... + +def imread( + fname: str | pathlib.Path | BinaryIO, format: str | None = ... +) -> np.ndarray: ... +def imsave( + fname: str | os.PathLike | BinaryIO, + arr: ArrayLike, + vmin: float | None = ..., + vmax: float | None = ..., + cmap: str | Colormap | None = ..., + format: str | None = ..., + origin: Literal["upper", "lower"] | None = ..., + dpi: float = ..., + *, + metadata: dict[str, str] | None = ..., + pil_kwargs: dict[str, Any] | None = ... +) -> None: ... +def pil_to_array(pilImage: PIL.Image.Image) -> np.ndarray: ... +def thumbnail( + infile: str | BinaryIO, + thumbfile: str | BinaryIO, + scale: float = ..., + interpolation: str = ..., + preview: bool = ..., +) -> Figure: ... diff --git a/lib/matplotlib/inset.py b/lib/matplotlib/inset.py new file mode 100644 index 000000000000..fb5bfacff924 --- /dev/null +++ b/lib/matplotlib/inset.py @@ -0,0 +1,276 @@ +""" +The inset module defines the InsetIndicator class, which draws the rectangle and +connectors required for `.Axes.indicate_inset` and `.Axes.indicate_inset_zoom`. +""" + +from . import _api, artist, transforms +from matplotlib.patches import ConnectionPatch, PathPatch, Rectangle +from matplotlib.path import Path + + +_shared_properties = ('alpha', 'edgecolor', 'linestyle', 'linewidth') + + +class InsetIndicator(artist.Artist): + """ + An artist to highlight an area of interest. + + An inset indicator is a rectangle on the plot at the position indicated by + *bounds* that optionally has lines that connect the rectangle to an inset + Axes (`.Axes.inset_axes`). + + .. versionadded:: 3.10 + """ + zorder = 4.99 + + def __init__(self, bounds=None, inset_ax=None, zorder=None, **kwargs): + """ + Parameters + ---------- + bounds : [x0, y0, width, height], optional + Lower-left corner of rectangle to be marked, and its width + and height. If not set, the bounds will be calculated from the + data limits of inset_ax, which must be supplied. + + inset_ax : `~.axes.Axes`, optional + An optional inset Axes to draw connecting lines to. Two lines are + drawn connecting the indicator box to the inset Axes on corners + chosen so as to not overlap with the indicator box. + + zorder : float, default: 4.99 + Drawing order of the rectangle and connector lines. The default, + 4.99, is just below the default level of inset Axes. + + **kwargs + Other keyword arguments are passed on to the `.Rectangle` patch. + """ + if bounds is None and inset_ax is None: + raise ValueError("At least one of bounds or inset_ax must be supplied") + + self._inset_ax = inset_ax + + if bounds is None: + # Work out bounds from inset_ax + self._auto_update_bounds = True + bounds = self._bounds_from_inset_ax() + else: + self._auto_update_bounds = False + + x, y, width, height = bounds + + self._rectangle = Rectangle((x, y), width, height, clip_on=False, **kwargs) + + # Connector positions cannot be calculated till the artist has been added + # to an axes, so just make an empty list for now. + self._connectors = [] + + super().__init__() + self.set_zorder(zorder) + + # Initial style properties for the artist should match the rectangle. + for prop in _shared_properties: + setattr(self, f'_{prop}', artist.getp(self._rectangle, prop)) + + def _shared_setter(self, prop, val): + """ + Helper function to set the same style property on the artist and its children. + """ + setattr(self, f'_{prop}', val) + + artist.setp([self._rectangle, *self._connectors], prop, val) + + @artist.Artist.axes.setter + def axes(self, new_axes): + # Set axes on the rectangle (required for some external transforms to work) as + # well as the InsetIndicator artist. + self.rectangle.axes = new_axes + artist.Artist.axes.fset(self, new_axes) + + def set_alpha(self, alpha): + # docstring inherited + self._shared_setter('alpha', alpha) + + def set_edgecolor(self, color): + """ + Set the edge color of the rectangle and the connectors. + + Parameters + ---------- + color : :mpltype:`color` or None + """ + self._shared_setter('edgecolor', color) + + def set_color(self, c): + """ + Set the edgecolor of the rectangle and the connectors, and the + facecolor for the rectangle. + + Parameters + ---------- + c : :mpltype:`color` + """ + self._shared_setter('edgecolor', c) + self._shared_setter('facecolor', c) + + def set_linewidth(self, w): + """ + Set the linewidth in points of the rectangle and the connectors. + + Parameters + ---------- + w : float or None + """ + self._shared_setter('linewidth', w) + + def set_linestyle(self, ls): + """ + Set the linestyle of the rectangle and the connectors. + + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ + + Alternatively a dash tuple of the following form can be provided:: + + (offset, onoffseq) + + where ``onoffseq`` is an even length tuple of on and off ink in points. + + Parameters + ---------- + ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} + The line style. + """ + self._shared_setter('linestyle', ls) + + def _bounds_from_inset_ax(self): + xlim = self._inset_ax.get_xlim() + ylim = self._inset_ax.get_ylim() + return (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]) + + def _update_connectors(self): + (x, y) = self._rectangle.get_xy() + width = self._rectangle.get_width() + height = self._rectangle.get_height() + + existing_connectors = self._connectors or [None] * 4 + + # connect the inset_axes to the rectangle + for xy_inset_ax, existing in zip([(0, 0), (0, 1), (1, 0), (1, 1)], + existing_connectors): + # inset_ax positions are in axes coordinates + # The 0, 1 values define the four edges if the inset_ax + # lower_left, upper_left, lower_right upper_right. + ex, ey = xy_inset_ax + if self.axes.xaxis.get_inverted(): + ex = 1 - ex + if self.axes.yaxis.get_inverted(): + ey = 1 - ey + xy_data = x + ex * width, y + ey * height + if existing is None: + # Create new connection patch with styles inherited from the + # parent artist. + p = ConnectionPatch( + xyA=xy_inset_ax, coordsA=self._inset_ax.transAxes, + xyB=xy_data, coordsB=self.rectangle.get_data_transform(), + arrowstyle="-", + edgecolor=self._edgecolor, alpha=self.get_alpha(), + linestyle=self._linestyle, linewidth=self._linewidth) + self._connectors.append(p) + else: + # Only update positioning of existing connection patch. We + # do not want to override any style settings made by the user. + existing.xy1 = xy_inset_ax + existing.xy2 = xy_data + existing.coords1 = self._inset_ax.transAxes + existing.coords2 = self.rectangle.get_data_transform() + + if existing is None: + # decide which two of the lines to keep visible.... + pos = self._inset_ax.get_position() + bboxins = pos.transformed(self.get_figure(root=False).transSubfigure) + rectbbox = transforms.Bbox.from_bounds(x, y, width, height).transformed( + self._rectangle.get_transform()) + x0 = rectbbox.x0 < bboxins.x0 + x1 = rectbbox.x1 < bboxins.x1 + y0 = rectbbox.y0 < bboxins.y0 + y1 = rectbbox.y1 < bboxins.y1 + self._connectors[0].set_visible(x0 ^ y0) + self._connectors[1].set_visible(x0 == y1) + self._connectors[2].set_visible(x1 == y0) + self._connectors[3].set_visible(x1 ^ y1) + + @property + def rectangle(self): + """`.Rectangle`: the indicator frame.""" + return self._rectangle + + @property + def connectors(self): + """ + 4-tuple of `.patches.ConnectionPatch` or None + The four connector lines connecting to (lower_left, upper_left, + lower_right upper_right) corners of *inset_ax*. Two lines are + set with visibility to *False*, but the user can set the + visibility to True if the automatic choice is not deemed correct. + """ + if self._inset_ax is None: + return + + if self._auto_update_bounds: + self._rectangle.set_bounds(self._bounds_from_inset_ax()) + self._update_connectors() + return tuple(self._connectors) + + def draw(self, renderer): + # docstring inherited + conn_same_style = [] + + # Figure out which connectors have the same style as the box, so should + # be drawn as a single path. + for conn in self.connectors or []: + if conn.get_visible(): + drawn = False + for s in _shared_properties: + if artist.getp(self._rectangle, s) != artist.getp(conn, s): + # Draw this connector by itself + conn.draw(renderer) + drawn = True + break + + if not drawn: + # Connector has same style as box. + conn_same_style.append(conn) + + if conn_same_style: + # Since at least one connector has the same style as the rectangle, draw + # them as a compound path. + artists = [self._rectangle] + conn_same_style + paths = [a.get_transform().transform_path(a.get_path()) for a in artists] + path = Path.make_compound_path(*paths) + + # Create a temporary patch to draw the path. + p = PathPatch(path) + p.update_from(self._rectangle) + p.set_transform(transforms.IdentityTransform()) + p.draw(renderer) + + return + + # Just draw the rectangle + self._rectangle.draw(renderer) + + @_api.deprecated( + '3.10', + message=('Since Matplotlib 3.10 indicate_inset_[zoom] returns a single ' + 'InsetIndicator artist with a rectangle property and a connectors ' + 'property. From 3.12 it will no longer be possible to unpack the ' + 'return value into two elements.')) + def __getitem__(self, key): + return [self._rectangle, self.connectors][key] diff --git a/lib/matplotlib/inset.pyi b/lib/matplotlib/inset.pyi new file mode 100644 index 000000000000..e895fd7be27c --- /dev/null +++ b/lib/matplotlib/inset.pyi @@ -0,0 +1,25 @@ +from . import artist +from .axes import Axes +from .backend_bases import RendererBase +from .patches import ConnectionPatch, Rectangle + +from .typing import ColorType, LineStyleType + +class InsetIndicator(artist.Artist): + def __init__( + self, + bounds: tuple[float, float, float, float] | None = ..., + inset_ax: Axes | None = ..., + zorder: float | None = ..., + **kwargs + ) -> None: ... + def set_alpha(self, alpha: float | None) -> None: ... + def set_edgecolor(self, color: ColorType | None) -> None: ... + def set_color(self, c: ColorType | None) -> None: ... + def set_linewidth(self, w: float | None) -> None: ... + def set_linestyle(self, ls: LineStyleType | None) -> None: ... + @property + def rectangle(self) -> Rectangle: ... + @property + def connectors(self) -> tuple[ConnectionPatch, ConnectionPatch, ConnectionPatch, ConnectionPatch] | None: ... + def draw(self, renderer: RendererBase) -> None: ... diff --git a/lib/matplotlib/layout_engine.py b/lib/matplotlib/layout_engine.py new file mode 100644 index 000000000000..8a3276b53371 --- /dev/null +++ b/lib/matplotlib/layout_engine.py @@ -0,0 +1,309 @@ +""" +Classes to layout elements in a `.Figure`. + +Figures have a ``layout_engine`` property that holds a subclass of +`~.LayoutEngine` defined here (or *None* for no layout). At draw time +``figure.get_layout_engine().execute()`` is called, the goal of which is +usually to rearrange Axes on the figure to produce a pleasing layout. This is +like a ``draw`` callback but with two differences. First, when printing we +disable the layout engine for the final draw. Second, it is useful to know the +layout engine while the figure is being created. In particular, colorbars are +made differently with different layout engines (for historical reasons). + +Matplotlib has two built-in layout engines: + +- `.TightLayoutEngine` was the first layout engine added to Matplotlib. + See also :ref:`tight_layout_guide`. +- `.ConstrainedLayoutEngine` is more modern and generally gives better results. + See also :ref:`constrainedlayout_guide`. + +Third parties can create their own layout engine by subclassing `.LayoutEngine`. +""" + +from contextlib import nullcontext + +import matplotlib as mpl + +from matplotlib._constrained_layout import do_constrained_layout +from matplotlib._tight_layout import (get_subplotspec_list, + get_tight_layout_figure) + + +class LayoutEngine: + """ + Base class for Matplotlib layout engines. + + A layout engine can be passed to a figure at instantiation or at any time + with `~.figure.Figure.set_layout_engine`. Once attached to a figure, the + layout engine ``execute`` function is called at draw time by + `~.figure.Figure.draw`, providing a special draw-time hook. + + .. note:: + + However, note that layout engines affect the creation of colorbars, so + `~.figure.Figure.set_layout_engine` should be called before any + colorbars are created. + + Currently, there are two properties of `LayoutEngine` classes that are + consulted while manipulating the figure: + + - ``engine.colorbar_gridspec`` tells `.Figure.colorbar` whether to make the + axes using the gridspec method (see `.colorbar.make_axes_gridspec`) or + not (see `.colorbar.make_axes`); + - ``engine.adjust_compatible`` stops `.Figure.subplots_adjust` from being + run if it is not compatible with the layout engine. + + To implement a custom `LayoutEngine`: + + 1. override ``_adjust_compatible`` and ``_colorbar_gridspec`` + 2. override `LayoutEngine.set` to update *self._params* + 3. override `LayoutEngine.execute` with your implementation + + """ + # override these in subclass + _adjust_compatible = None + _colorbar_gridspec = None + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._params = {} + + def set(self, **kwargs): + """ + Set the parameters for the layout engine. + """ + raise NotImplementedError + + @property + def colorbar_gridspec(self): + """ + Return a boolean if the layout engine creates colorbars using a + gridspec. + """ + if self._colorbar_gridspec is None: + raise NotImplementedError + return self._colorbar_gridspec + + @property + def adjust_compatible(self): + """ + Return a boolean if the layout engine is compatible with + `~.Figure.subplots_adjust`. + """ + if self._adjust_compatible is None: + raise NotImplementedError + return self._adjust_compatible + + def get(self): + """ + Return copy of the parameters for the layout engine. + """ + return dict(self._params) + + def execute(self, fig): + """ + Execute the layout on the figure given by *fig*. + """ + # subclasses must implement this. + raise NotImplementedError + + +class PlaceHolderLayoutEngine(LayoutEngine): + """ + This layout engine does not adjust the figure layout at all. + + The purpose of this `.LayoutEngine` is to act as a placeholder when the user removes + a layout engine to ensure an incompatible `.LayoutEngine` cannot be set later. + + Parameters + ---------- + adjust_compatible, colorbar_gridspec : bool + Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever + layout engine it is replacing. + + """ + def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs): + self._adjust_compatible = adjust_compatible + self._colorbar_gridspec = colorbar_gridspec + super().__init__(**kwargs) + + def execute(self, fig): + """ + Do nothing. + """ + return + + +class TightLayoutEngine(LayoutEngine): + """ + Implements the ``tight_layout`` geometry management. See + :ref:`tight_layout_guide` for details. + """ + _adjust_compatible = True + _colorbar_gridspec = True + + def __init__(self, *, pad=1.08, h_pad=None, w_pad=None, + rect=(0, 0, 1, 1), **kwargs): + """ + Initialize tight_layout engine. + + Parameters + ---------- + pad : float, default: 1.08 + Padding between the figure edge and the edges of subplots, as a + fraction of the font size. + h_pad, w_pad : float + Padding (height/width) between edges of adjacent subplots. + Defaults to *pad*. + rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1). + rectangle in normalized figure coordinates that the subplots + (including labels) will fit into. + """ + super().__init__(**kwargs) + for td in ['pad', 'h_pad', 'w_pad', 'rect']: + # initialize these in case None is passed in above: + self._params[td] = None + self.set(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + + def execute(self, fig): + """ + Execute tight_layout. + + This decides the subplot parameters given the padding that + will allow the Axes labels to not be covered by other labels + and Axes. + + Parameters + ---------- + fig : `.Figure` to perform layout on. + + See Also + -------- + .figure.Figure.tight_layout + .pyplot.tight_layout + """ + info = self._params + renderer = fig._get_renderer() + with getattr(renderer, "_draw_disabled", nullcontext)(): + kwargs = get_tight_layout_figure( + fig, fig.axes, get_subplotspec_list(fig.axes), renderer, + pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'], + rect=info['rect']) + if kwargs: + fig.subplots_adjust(**kwargs) + + def set(self, *, pad=None, w_pad=None, h_pad=None, rect=None): + """ + Set the pads for tight_layout. + + Parameters + ---------- + pad : float + Padding between the figure edge and the edges of subplots, as a + fraction of the font size. + w_pad, h_pad : float + Padding (width/height) between edges of adjacent subplots. + Defaults to *pad*. + rect : tuple (left, bottom, right, top) + rectangle in normalized figure coordinates that the subplots + (including labels) will fit into. + """ + for td in self.set.__kwdefaults__: + if locals()[td] is not None: + self._params[td] = locals()[td] + + +class ConstrainedLayoutEngine(LayoutEngine): + """ + Implements the ``constrained_layout`` geometry management. See + :ref:`constrainedlayout_guide` for details. + """ + + _adjust_compatible = False + _colorbar_gridspec = False + + def __init__(self, *, h_pad=None, w_pad=None, + hspace=None, wspace=None, rect=(0, 0, 1, 1), + compress=False, **kwargs): + """ + Initialize ``constrained_layout`` settings. + + Parameters + ---------- + h_pad, w_pad : float + Padding around the Axes elements in inches. + Default to :rc:`figure.constrained_layout.h_pad` and + :rc:`figure.constrained_layout.w_pad`. + hspace, wspace : float + Fraction of the figure to dedicate to space between the + axes. These are evenly spread between the gaps between the Axes. + A value of 0.2 for a three-column layout would have a space + of 0.1 of the figure width between each column. + If h/wspace < h/w_pad, then the pads are used instead. + Default to :rc:`figure.constrained_layout.hspace` and + :rc:`figure.constrained_layout.wspace`. + rect : tuple of 4 floats + Rectangle in figure coordinates to perform constrained layout in + (left, bottom, width, height), each from 0-1. + compress : bool + Whether to shift Axes so that white space in between them is + removed. This is useful for simple grids of fixed-aspect Axes (e.g. + a grid of images). See :ref:`compressed_layout`. + """ + super().__init__(**kwargs) + # set the defaults: + self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'], + h_pad=mpl.rcParams['figure.constrained_layout.h_pad'], + wspace=mpl.rcParams['figure.constrained_layout.wspace'], + hspace=mpl.rcParams['figure.constrained_layout.hspace'], + rect=(0, 0, 1, 1)) + # set anything that was passed in (None will be ignored): + self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace, + rect=rect) + self._compress = compress + + def execute(self, fig): + """ + Perform constrained_layout and move and resize Axes accordingly. + + Parameters + ---------- + fig : `.Figure` to perform layout on. + """ + width, height = fig.get_size_inches() + # pads are relative to the current state of the figure... + w_pad = self._params['w_pad'] / width + h_pad = self._params['h_pad'] / height + + return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad, + wspace=self._params['wspace'], + hspace=self._params['hspace'], + rect=self._params['rect'], + compress=self._compress) + + def set(self, *, h_pad=None, w_pad=None, + hspace=None, wspace=None, rect=None): + """ + Set the pads for constrained_layout. + + Parameters + ---------- + h_pad, w_pad : float + Padding around the Axes elements in inches. + Default to :rc:`figure.constrained_layout.h_pad` and + :rc:`figure.constrained_layout.w_pad`. + hspace, wspace : float + Fraction of the figure to dedicate to space between the + axes. These are evenly spread between the gaps between the Axes. + A value of 0.2 for a three-column layout would have a space + of 0.1 of the figure width between each column. + If h/wspace < h/w_pad, then the pads are used instead. + Default to :rc:`figure.constrained_layout.hspace` and + :rc:`figure.constrained_layout.wspace`. + rect : tuple of 4 floats + Rectangle in figure coordinates to perform constrained layout in + (left, bottom, width, height), each from 0-1. + """ + for td in self.set.__kwdefaults__: + if locals()[td] is not None: + self._params[td] = locals()[td] diff --git a/lib/matplotlib/layout_engine.pyi b/lib/matplotlib/layout_engine.pyi new file mode 100644 index 000000000000..5b8c812ff47f --- /dev/null +++ b/lib/matplotlib/layout_engine.pyi @@ -0,0 +1,62 @@ +from matplotlib.figure import Figure + +from typing import Any + +class LayoutEngine: + def __init__(self, **kwargs: Any) -> None: ... + def set(self) -> None: ... + @property + def colorbar_gridspec(self) -> bool: ... + @property + def adjust_compatible(self) -> bool: ... + def get(self) -> dict[str, Any]: ... + def execute(self, fig: Figure) -> None: ... + +class PlaceHolderLayoutEngine(LayoutEngine): + def __init__( + self, adjust_compatible: bool, colorbar_gridspec: bool, **kwargs: Any + ) -> None: ... + def execute(self, fig: Figure) -> None: ... + +class TightLayoutEngine(LayoutEngine): + def __init__( + self, + *, + pad: float = ..., + h_pad: float | None = ..., + w_pad: float | None = ..., + rect: tuple[float, float, float, float] = ..., + **kwargs: Any + ) -> None: ... + def execute(self, fig: Figure) -> None: ... + def set( + self, + *, + pad: float | None = ..., + w_pad: float | None = ..., + h_pad: float | None = ..., + rect: tuple[float, float, float, float] | None = ... + ) -> None: ... + +class ConstrainedLayoutEngine(LayoutEngine): + def __init__( + self, + *, + h_pad: float | None = ..., + w_pad: float | None = ..., + hspace: float | None = ..., + wspace: float | None = ..., + rect: tuple[float, float, float, float] = ..., + compress: bool = ..., + **kwargs: Any + ) -> None: ... + def execute(self, fig: Figure) -> Any: ... + def set( + self, + *, + h_pad: float | None = ..., + w_pad: float | None = ..., + hspace: float | None = ..., + wspace: float | None = ..., + rect: tuple[float, float, float, float] | None = ... + ) -> None: ... diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 25f790893961..2fb14e52c58c 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1,48 +1,52 @@ """ The legend module defines the Legend class, which is responsible for -drawing legends associated with axes and/or figures. +drawing legends associated with Axes and/or figures. .. important:: It is unlikely that you would ever create a Legend instance manually. Most users would normally create a legend via the `~.Axes.legend` - function. For more details on legends there is also a :doc:`legend guide - `. + function. For more details on legends there is also a :ref:`legend guide + `. The `Legend` class is a container of legend handles and legend texts. The legend handler map specifies how to create legend handles from artists -(lines, patches, etc.) in the axes or figures. Default legend handlers are +(lines, patches, etc.) in the Axes or figures. Default legend handlers are defined in the :mod:`~matplotlib.legend_handler` module. While not all artist types are covered by the default legend handlers, custom legend handlers can be defined to support arbitrary objects. -See the :doc:`legend guide ` for more +See the :ref`` for more information. """ import itertools import logging +import numbers import time import numpy as np import matplotlib as mpl -from matplotlib import cbook, docstring, colors +from matplotlib import _api, _docstring, cbook, colors, offsetbox from matplotlib.artist import Artist, allow_rasterization from matplotlib.cbook import silent_list from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D -from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch -from matplotlib.collections import (LineCollection, RegularPolyCollection, - CircleCollection, PathCollection, - PolyCollection) +from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, + StepPatch) +from matplotlib.collections import ( + Collection, CircleCollection, LineCollection, PathCollection, + PolyCollection, RegularPolyCollection) +from matplotlib.text import Text from matplotlib.transforms import Bbox, BboxBase, TransformedBbox from matplotlib.transforms import BboxTransformTo, BboxTransformFrom - -from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea -from matplotlib.offsetbox import DraggableOffsetBox - +from matplotlib.offsetbox import ( + AnchoredOffsetbox, DraggableOffsetBox, + HPacker, VPacker, + DrawingArea, TextArea, +) from matplotlib.container import ErrorbarContainer, BarContainer, StemContainer from . import legend_handler @@ -65,7 +69,7 @@ def __init__(self, legend, use_blit=False, update="loc"): """ self.legend = legend - cbook._check_in_list(["loc", "bbox"], update=update) + _api.check_in_list(["loc", "bbox"], update=update) self._update = update super().__init__(legend, legend._legend_box, use_blit=use_blit) @@ -74,7 +78,7 @@ def finalize_offset(self): if self._update == "loc": self._update_loc(self.get_loc_in_canvas()) elif self._update == "bbox": - self._bbox_to_anchor(self.get_loc_in_canvas()) + self._update_bbox_to_anchor(self.get_loc_in_canvas()) def _update_loc(self, loc_in_canvas): bbox = self.legend.get_bbox_to_anchor() @@ -91,55 +95,11 @@ def _update_bbox_to_anchor(self, loc_in_canvas): self.legend.set_bbox_to_anchor(loc_in_bbox) -docstring.interpd.update(_legend_kw_doc=""" -loc : str or pair of floats, default: :rc:`legend.loc` ('best' for axes, \ -'upper right' for figures) - The location of the legend. - - The strings - ``'upper left', 'upper right', 'lower left', 'lower right'`` - place the legend at the corresponding corner of the axes/figure. - - The strings - ``'upper center', 'lower center', 'center left', 'center right'`` - place the legend at the center of the corresponding edge of the - axes/figure. - - The string ``'center'`` places the legend at the center of the axes/figure. - - The string ``'best'`` places the legend at the location, among the nine - locations defined so far, with the minimum overlap with other drawn - artists. This option can be quite slow for plots with large amounts of - data; your plotting speed may benefit from providing a specific location. - - The location can also be a 2-tuple giving the coordinates of the lower-left - corner of the legend in axes coordinates (in which case *bbox_to_anchor* - will be ignored). - - For back-compatibility, ``'center right'`` (but no other location) can also - be spelled ``'right'``, and each "string" locations can also be given as a - numeric value: - - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= - +_legend_kw_doc_base = """ bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats Box that is used to position the legend in conjunction with *loc*. - Defaults to `axes.bbox` (if called as a method to `.Axes.legend`) or - `figure.bbox` (if `.Figure.legend`). This argument allows arbitrary + Defaults to ``axes.bbox`` (if called as a method to `.Axes.legend`) or + ``figure.bbox`` (if ``figure.legend``). This argument allows arbitrary placement of the legend. Bbox coordinates are interpreted in the coordinate system given by @@ -149,20 +109,23 @@ def _update_bbox_to_anchor(self, loc_in_canvas): If a 4-tuple or `.BboxBase` is given, then it specifies the bbox ``(x, y, width, height)`` that the legend is placed in. To put the legend in the best location in the bottom right - quadrant of the axes (or figure):: + quadrant of the Axes (or figure):: loc='best', bbox_to_anchor=(0.5, 0., 0.5, 0.5) A 2-tuple ``(x, y)`` places the corner of the legend specified by *loc* at x, y. For example, to put the legend's upper right-hand corner in the - center of the axes (or figure) the following keywords can be used:: + center of the Axes (or figure) the following keywords can be used:: loc='upper right', bbox_to_anchor=(0.5, 0.5) -ncol : int, default: 1 +ncols : int, default: 1 The number of columns that the legend has. -prop : None or `matplotlib.font_manager.FontProperties` or dict + For backward compatibility, the spelling *ncol* is also supported + but it is discouraged. If both are given, *ncols* takes precedence. + +prop : None or `~matplotlib.font_manager.FontProperties` or dict The font properties of the legend. If None (default), the current :data:`matplotlib.rcParams` will be used. @@ -172,12 +135,15 @@ def _update_bbox_to_anchor(self, loc_in_canvas): absolute font size in points. String values are relative to the current default font size. This argument is only used if *prop* is not specified. -labelcolor : str or list +labelcolor : str or list, default: :rc:`legend.labelcolor` The color of the text in the legend. Either a valid color string (for example, 'red'), or a list of color strings. The labelcolor can also be made to match the color of the line or marker using 'linecolor', 'markerfacecolor' (or 'mfc'), or 'markeredgecolor' (or 'mec'). + Labelcolor can be set globally using :rc:`legend.labelcolor`. If None, + use :rc:`text.color`. + numpoints : int, default: :rc:`legend.numpoints` The number of marker points in the legend when creating a legend entry for a `.Line2D` (line). @@ -193,22 +159,29 @@ def _update_bbox_to_anchor(self, loc_in_canvas): same height, set to ``[0.5]``. markerscale : float, default: :rc:`legend.markerscale` - The relative size of legend markers compared with the originally - drawn ones. + The relative size of legend markers compared to the originally drawn ones. markerfirst : bool, default: True If *True*, legend marker is placed to the left of the legend label. If *False*, legend marker is placed to the right of the legend label. +reverse : bool, default: False + If *True*, the legend labels are displayed in reverse order from the input. + If *False*, the legend labels are displayed in the same order as the input. + + .. versionadded:: 3.7 + frameon : bool, default: :rc:`legend.frameon` Whether the legend should be drawn on a patch (frame). fancybox : bool, default: :rc:`legend.fancybox` - Whether round edges should be enabled around the `~.FancyBboxPatch` which + Whether round edges should be enabled around the `.FancyBboxPatch` which makes up the legend's background. -shadow : bool, default: :rc:`legend.shadow` +shadow : None, bool or dict, default: :rc:`legend.shadow` Whether to draw a shadow behind the legend. + The shadow can be configured using `.Patch` keywords. + Customization via :rc:`legend.shadow` is currently not supported. framealpha : float, default: :rc:`legend.framealpha` The alpha transparency of the legend's background. @@ -221,24 +194,36 @@ def _update_bbox_to_anchor(self, loc_in_canvas): edgecolor : "inherit" or color, default: :rc:`legend.edgecolor` The legend's background patch edge color. - If ``"inherit"``, use take :rc:`axes.edgecolor`. + If ``"inherit"``, use :rc:`axes.edgecolor`. mode : {"expand", None} If *mode* is set to ``"expand"`` the legend will be horizontally - expanded to fill the axes area (or *bbox_to_anchor* if defines + expanded to fill the Axes area (or *bbox_to_anchor* if defines the legend's size). -bbox_transform : None or `matplotlib.transforms.Transform` +bbox_transform : None or `~matplotlib.transforms.Transform` The transform for the bounding box (*bbox_to_anchor*). For a value of ``None`` (default) the Axes' - :data:`~matplotlib.axes.Axes.transAxes` transform will be used. + :data:`!matplotlib.axes.Axes.transAxes` transform will be used. title : str or None The legend's title. Default is no title (``None``). +title_fontproperties : None or `~matplotlib.font_manager.FontProperties` or dict + The font properties of the legend's title. If None (default), the + *title_fontsize* argument will be used if present; if *title_fontsize* is + also None, the current :rc:`legend.title_fontsize` will be used. + title_fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \ 'x-large', 'xx-large'}, default: :rc:`legend.title_fontsize` The font size of the legend's title. + Note: This cannot be combined with *title_fontproperties*. If you want + to set the fontsize alongside other font properties, use the *size* + parameter in *title_fontproperties*. + +alignment : {'center', 'left', 'right'}, default: 'center' + The alignment of the legend title and the box of entries. The entries + are aligned as a single block, so that markers always lined up. borderpad : float, default: :rc:`legend.borderpad` The fractional whitespace inside the legend border, in font-size units. @@ -249,11 +234,14 @@ def _update_bbox_to_anchor(self, loc_in_canvas): handlelength : float, default: :rc:`legend.handlelength` The length of the legend handles, in font-size units. +handleheight : float, default: :rc:`legend.handleheight` + The height of the legend handles, in font-size units. + handletextpad : float, default: :rc:`legend.handletextpad` The pad between the legend handle and text, in font-size units. borderaxespad : float, default: :rc:`legend.borderaxespad` - The pad between the axes and legend border, in font-size units. + The pad between the Axes and legend border, in font-size units. columnspacing : float, default: :rc:`legend.columnspacing` The spacing between columns, in font-size units. @@ -262,117 +250,184 @@ def _update_bbox_to_anchor(self, loc_in_canvas): The custom dictionary mapping instances or types to a legend handler. This *handler_map* updates the default handler map found at `matplotlib.legend.Legend.get_legend_handler_map`. -""") + +draggable : bool, default: False + Whether the legend can be dragged with the mouse. +""" + +_loc_doc_base = """ +loc : str or pair of floats, default: {default} + The location of the legend. + + The strings ``'upper left'``, ``'upper right'``, ``'lower left'``, + ``'lower right'`` place the legend at the corresponding corner of the + {parent}. + + The strings ``'upper center'``, ``'lower center'``, ``'center left'``, + ``'center right'`` place the legend at the center of the corresponding edge + of the {parent}. + + The string ``'center'`` places the legend at the center of the {parent}. +{best} + The location can also be a 2-tuple giving the coordinates of the lower-left + corner of the legend in {parent} coordinates (in which case *bbox_to_anchor* + will be ignored). + + For back-compatibility, ``'center right'`` (but no other location) can also + be spelled ``'right'``, and each "string" location can also be given as a + numeric value: + + ================== ============= + Location String Location Code + ================== ============= + 'best' (Axes only) 0 + 'upper right' 1 + 'upper left' 2 + 'lower left' 3 + 'lower right' 4 + 'right' 5 + 'center left' 6 + 'center right' 7 + 'lower center' 8 + 'upper center' 9 + 'center' 10 + ================== ============= + {outside}""" + +_loc_doc_best = """ + The string ``'best'`` places the legend at the location, among the nine + locations defined so far, with the minimum overlap with other drawn + artists. This option can be quite slow for plots with large amounts of + data; your plotting speed may benefit from providing a specific location. +""" + +_legend_kw_axes_st = ( + _loc_doc_base.format(parent='axes', default=':rc:`legend.loc`', + best=_loc_doc_best, outside='') + + _legend_kw_doc_base) +_docstring.interpd.register(_legend_kw_axes=_legend_kw_axes_st) + +_outside_doc = """ + If a figure is using the constrained layout manager, the string codes + of the *loc* keyword argument can get better layout behaviour using the + prefix 'outside'. There is ambiguity at the corners, so 'outside + upper right' will make space for the legend above the rest of the + axes in the layout, and 'outside right upper' will make space on the + right side of the layout. In addition to the values of *loc* + listed above, we have 'outside right upper', 'outside right lower', + 'outside left upper', and 'outside left lower'. See + :ref:`legend_guide` for more details. +""" + +_legend_kw_figure_st = ( + _loc_doc_base.format(parent='figure', default="'upper right'", + best='', outside=_outside_doc) + + _legend_kw_doc_base) +_docstring.interpd.register(_legend_kw_figure=_legend_kw_figure_st) + +_legend_kw_both_st = ( + _loc_doc_base.format(parent='axes/figure', + default=":rc:`legend.loc` for Axes, 'upper right' for Figure", + best=_loc_doc_best, outside=_outside_doc) + + _legend_kw_doc_base) +_docstring.interpd.register(_legend_kw_doc=_legend_kw_both_st) + +_legend_kw_set_loc_st = ( + _loc_doc_base.format(parent='axes/figure', + default=":rc:`legend.loc` for Axes, 'upper right' for Figure", + best=_loc_doc_best, outside=_outside_doc)) +_docstring.interpd.register(_legend_kw_set_loc_doc=_legend_kw_set_loc_st) class Legend(Artist): """ - Place a legend on the axes at location loc. - + Place a legend on the figure/axes. """ - codes = {'best': 0, # only implemented for axes legends - 'upper right': 1, - 'upper left': 2, - 'lower left': 3, - 'lower right': 4, - 'right': 5, - 'center left': 6, - 'center right': 7, - 'lower center': 8, - 'upper center': 9, - 'center': 10, - } + # 'best' is only implemented for Axes legends + codes = {'best': 0, **AnchoredOffsetbox.codes} zorder = 5 def __str__(self): return "Legend" - @docstring.dedent_interpd - def __init__(self, parent, handles, labels, - loc=None, - numpoints=None, # the number of points in the legend line - markerscale=None, # the relative size of legend markers - # vs. original - markerfirst=True, # controls ordering (left-to-right) of - # legend marker and label - scatterpoints=None, # number of scatter points - scatteryoffsets=None, - prop=None, # properties for the legend texts - fontsize=None, # keyword to set font size directly - labelcolor=None, # keyword to set the text color - - # spacing & pad defined as a fraction of the font-size - borderpad=None, # the whitespace inside the legend border - labelspacing=None, # the vertical space between the legend - # entries - handlelength=None, # the length of the legend handles - handleheight=None, # the height of the legend handles - handletextpad=None, # the pad between the legend handle - # and text - borderaxespad=None, # the pad between the axes and legend - # border - columnspacing=None, # spacing between columns - - ncol=1, # number of columns - mode=None, # mode for horizontal distribution of columns. - # None, "expand" - - fancybox=None, # True use a fancy box, false use a rounded - # box, none use rc - shadow=None, - title=None, # set a title for the legend - title_fontsize=None, # the font size for the title - framealpha=None, # set frame alpha - edgecolor=None, # frame patch edgecolor - facecolor=None, # frame patch facecolor - - bbox_to_anchor=None, # bbox that the legend will be anchored. - bbox_transform=None, # transform for the bbox - frameon=None, # draw frame - handler_map=None, - ): + @_docstring.interpd + def __init__( + self, parent, handles, labels, + *, + loc=None, + numpoints=None, # number of points in the legend line + markerscale=None, # relative size of legend markers vs. original + markerfirst=True, # left/right ordering of legend marker and label + reverse=False, # reverse ordering of legend marker and label + scatterpoints=None, # number of scatter points + scatteryoffsets=None, + prop=None, # properties for the legend texts + fontsize=None, # keyword to set font size directly + labelcolor=None, # keyword to set the text color + + # spacing & pad defined as a fraction of the font-size + borderpad=None, # whitespace inside the legend border + labelspacing=None, # vertical space between the legend entries + handlelength=None, # length of the legend handles + handleheight=None, # height of the legend handles + handletextpad=None, # pad between the legend handle and text + borderaxespad=None, # pad between the Axes and legend border + columnspacing=None, # spacing between columns + + ncols=1, # number of columns + mode=None, # horizontal distribution of columns: None or "expand" + + fancybox=None, # True: fancy box, False: rounded box, None: rcParam + shadow=None, + title=None, # legend title + title_fontsize=None, # legend title font size + framealpha=None, # set frame alpha + edgecolor=None, # frame patch edgecolor + facecolor=None, # frame patch facecolor + + bbox_to_anchor=None, # bbox to which the legend will be anchored + bbox_transform=None, # transform for the bbox + frameon=None, # draw frame + handler_map=None, + title_fontproperties=None, # properties for the legend title + alignment="center", # control the alignment within the legend box + ncol=1, # synonym for ncols (backward compatibility) + draggable=False # whether the legend can be dragged with the mouse + ): """ Parameters ---------- parent : `~matplotlib.axes.Axes` or `.Figure` The artist that contains the legend. - handles : list of `.Artist` + handles : list of (`.Artist` or tuple of `.Artist`) A list of Artists (lines, patches) to be added to the legend. labels : list of str A list of labels to show next to the artists. The length of handles and labels should be the same. If they are not, they are truncated - to the smaller of both lengths. + to the length of the shorter list. Other Parameters ---------------- %(_legend_kw_doc)s - Notes - ----- - Users can specify any arbitrary location for the legend using the - *bbox_to_anchor* keyword argument. *bbox_to_anchor* can be a - `.BboxBase` (or derived therefrom) or a tuple of 2 or 4 floats. - See `set_bbox_to_anchor` for more detail. + Attributes + ---------- + legend_handles + List of `.Artist` objects added as legend entries. - The legend location can be specified by setting *loc* with a tuple of - 2 floats, which is interpreted as the lower-left corner of the legend - in the normalized axes coordinate. + .. versionadded:: 3.7 """ # local import only to avoid circularity from matplotlib.axes import Axes - from matplotlib.figure import Figure + from matplotlib.figure import FigureBase super().__init__() if prop is None: - if fontsize is not None: - self.prop = FontProperties(size=fontsize) - else: - self.prop = FontProperties( - size=mpl.rcParams["legend.fontsize"]) + self.prop = FontProperties(size=mpl._val_or_rc(fontsize, "legend.fontsize")) else: self.prop = FontProperties._from_any(prop) if isinstance(prop, dict) and "size" not in prop: @@ -381,40 +436,33 @@ def __init__(self, parent, handles, labels, self._fontsize = self.prop.get_size_in_points() self.texts = [] - self.legendHandles = [] + self.legend_handles = [] self._legend_title_box = None #: A dictionary with the extra handler mappings for this Legend #: instance. self._custom_handler_map = handler_map - locals_view = locals() - for name in ["numpoints", "markerscale", "shadow", "columnspacing", - "scatterpoints", "handleheight", 'borderpad', - 'labelspacing', 'handlelength', 'handletextpad', - 'borderaxespad']: - if locals_view[name] is None: - value = mpl.rcParams["legend." + name] - else: - value = locals_view[name] - setattr(self, name, value) - del locals_view - # trim handles and labels if illegal label... - _lab, _hand = [], [] - for label, handle in zip(labels, handles): - if isinstance(label, str) and label.startswith('_'): - cbook._warn_external('The handle {!r} has a label of {!r} ' - 'which cannot be automatically added to' - ' the legend.'.format(handle, label)) - else: - _lab.append(label) - _hand.append(handle) - labels, handles = _lab, _hand + self.numpoints = mpl._val_or_rc(numpoints, 'legend.numpoints') + self.markerscale = mpl._val_or_rc(markerscale, 'legend.markerscale') + self.scatterpoints = mpl._val_or_rc(scatterpoints, 'legend.scatterpoints') + self.borderpad = mpl._val_or_rc(borderpad, 'legend.borderpad') + self.labelspacing = mpl._val_or_rc(labelspacing, 'legend.labelspacing') + self.handlelength = mpl._val_or_rc(handlelength, 'legend.handlelength') + self.handleheight = mpl._val_or_rc(handleheight, 'legend.handleheight') + self.handletextpad = mpl._val_or_rc(handletextpad, 'legend.handletextpad') + self.borderaxespad = mpl._val_or_rc(borderaxespad, 'legend.borderaxespad') + self.columnspacing = mpl._val_or_rc(columnspacing, 'legend.columnspacing') + self.shadow = mpl._val_or_rc(shadow, 'legend.shadow') + + if reverse: + labels = [*reversed(labels)] + handles = [*reversed(handles)] handles = list(handles) if len(handles) < 2: - ncol = 1 - self._ncol = ncol + ncols = 1 + self._ncols = ncols if ncols != 1 else ncol if self.numpoints <= 0: raise ValueError("numpoints must be > 0; it was %d" % numpoints) @@ -436,49 +484,47 @@ def __init__(self, parent, handles, labels, if isinstance(parent, Axes): self.isaxes = True self.axes = parent - self.set_figure(parent.figure) - elif isinstance(parent, Figure): + self.set_figure(parent.get_figure(root=False)) + elif isinstance(parent, FigureBase): self.isaxes = False self.set_figure(parent) else: - raise TypeError("Legend needs either Axes or Figure as parent") + raise TypeError( + "Legend needs either Axes or FigureBase as parent" + ) self.parent = parent - self._loc_used_default = loc is None - if loc is None: - loc = mpl.rcParams["legend.loc"] - if not self.isaxes and loc in [0, 'best']: - loc = 'upper right' - if isinstance(loc, str): - if loc not in self.codes: - raise ValueError( - "Unrecognized location {!r}. Valid locations are\n\t{}\n" - .format(loc, '\n\t'.join(self.codes))) - else: - loc = self.codes[loc] - if not self.isaxes and loc == 0: - raise ValueError( - "Automatic legend placement (loc='best') not implemented for " - "figure legend.") - self._mode = mode self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform) + # Figure out if self.shadow is valid + # If shadow was None, rcParams loads False + # So it shouldn't be None here + + self._shadow_props = {'ox': 2, 'oy': -2} # default location offsets + if isinstance(self.shadow, dict): + self._shadow_props.update(self.shadow) + self.shadow = True + elif self.shadow in (0, 1, True, False): + self.shadow = bool(self.shadow) + else: + raise ValueError( + 'Legend shadow must be a dict or bool, not ' + f'{self.shadow!r} of type {type(self.shadow)}.' + ) + # We use FancyBboxPatch to draw a legend frame. The location # and size of the box will be updated during the drawing time. - if facecolor is None: - facecolor = mpl.rcParams["legend.facecolor"] + facecolor = mpl._val_or_rc(facecolor, "legend.facecolor") if facecolor == 'inherit': facecolor = mpl.rcParams["axes.facecolor"] - if edgecolor is None: - edgecolor = mpl.rcParams["legend.edgecolor"] + edgecolor = mpl._val_or_rc(edgecolor, "legend.edgecolor") if edgecolor == 'inherit': edgecolor = mpl.rcParams["axes.edgecolor"] - if fancybox is None: - fancybox = mpl.rcParams["legend.fancybox"] + fancybox = mpl._val_or_rc(fancybox, "legend.fancybox") self.legendPatch = FancyBboxPatch( xy=(0, 0), width=1, height=1, @@ -493,24 +539,39 @@ def __init__(self, parent, handles, labels, else "square,pad=0"), mutation_scale=self._fontsize, snap=True, - visible=(frameon if frameon is not None - else mpl.rcParams["legend.frameon"]) + visible=mpl._val_or_rc(frameon, "legend.frameon") ) self._set_artist_props(self.legendPatch) + _api.check_in_list(["center", "left", "right"], alignment=alignment) + self._alignment = alignment + # init with null renderer self._init_legend_box(handles, labels, markerfirst) - tmp = self._loc_used_default - self._set_loc(loc) - self._loc_used_default = tmp # ignore changes done by _set_loc + # Set legend location + self.set_loc(loc) + + # figure out title font properties: + if title_fontsize is not None and title_fontproperties is not None: + raise ValueError( + "title_fontsize and title_fontproperties can't be specified " + "at the same time. Only use one of them. ") + title_prop_fp = FontProperties._from_any(title_fontproperties) + if isinstance(title_fontproperties, dict): + if "size" not in title_fontproperties: + title_fontsize = mpl.rcParams["legend.title_fontsize"] + title_prop_fp.set_size(title_fontsize) + elif title_fontsize is not None: + title_prop_fp.set_size(title_fontsize) + elif not isinstance(title_fontproperties, FontProperties): + title_fontsize = mpl.rcParams["legend.title_fontsize"] + title_prop_fp.set_size(title_fontsize) + + self.set_title(title, prop=title_prop_fp) - # figure out title fontsize: - if title_fontsize is None: - title_fontsize = mpl.rcParams['legend.title_fontsize'] - tprop = FontProperties(size=title_fontsize) - self.set_title(title, prop=tprop) self._draggable = None + self.set_draggable(state=draggable) # set the text color @@ -521,38 +582,120 @@ def __init__(self, parent, handles, labels, 'markeredgecolor': ['get_markeredgecolor', 'get_edgecolor'], 'mec': ['get_markeredgecolor', 'get_edgecolor'], } - if labelcolor is None: - pass - elif isinstance(labelcolor, str) and labelcolor in color_getters: + labelcolor = mpl._val_or_rc(mpl._val_or_rc(labelcolor, 'legend.labelcolor'), + 'text.color') + if isinstance(labelcolor, str) and labelcolor in color_getters: getter_names = color_getters[labelcolor] - for handle, text in zip(self.legendHandles, self.texts): + for handle, text in zip(self.legend_handles, self.texts): + try: + if handle.get_array() is not None: + continue + except AttributeError: + pass for getter_name in getter_names: try: color = getattr(handle, getter_name)() - text.set_color(color) + if isinstance(color, np.ndarray): + if ( + color.shape[0] == 1 + or np.isclose(color, color[0]).all() + ): + text.set_color(color[0]) + else: + pass + else: + text.set_color(color) break except AttributeError: pass + elif cbook._str_equal(labelcolor, 'none'): + for text in self.texts: + text.set_color(labelcolor) elif np.iterable(labelcolor): for text, color in zip(self.texts, itertools.cycle( colors.to_rgba_array(labelcolor))): text.set_color(color) else: - raise ValueError("Invalid argument for labelcolor : %s" % - str(labelcolor)) + raise ValueError(f"Invalid labelcolor: {labelcolor!r}") def _set_artist_props(self, a): """ - Set the boilerplate props for artists added to axes. + Set the boilerplate props for artists added to Axes. """ - a.set_figure(self.figure) + a.set_figure(self.get_figure(root=False)) if self.isaxes: - # a.set_axes(self.axes) a.axes = self.axes a.set_transform(self.get_transform()) + @_docstring.interpd + def set_loc(self, loc=None): + """ + Set the location of the legend. + + .. versionadded:: 3.8 + + Parameters + ---------- + %(_legend_kw_set_loc_doc)s + """ + loc0 = loc + self._loc_used_default = loc is None + if loc is None: + loc = mpl.rcParams["legend.loc"] + if not self.isaxes and loc in [0, 'best']: + loc = 'upper right' + + type_err_message = ("loc must be string, coordinate tuple, or" + f" an integer 0-10, not {loc!r}") + + # handle outside legends: + self._outside_loc = None + if isinstance(loc, str): + if loc.split()[0] == 'outside': + # strip outside: + loc = loc.split('outside ')[1] + # strip "center" at the beginning + self._outside_loc = loc.replace('center ', '') + # strip first + self._outside_loc = self._outside_loc.split()[0] + locs = loc.split() + if len(locs) > 1 and locs[0] in ('right', 'left'): + # locs doesn't accept "left upper", etc, so swap + if locs[0] != 'center': + locs = locs[::-1] + loc = locs[0] + ' ' + locs[1] + # check that loc is in acceptable strings + loc = _api.check_getitem(self.codes, loc=loc) + elif np.iterable(loc): + # coerce iterable into tuple + loc = tuple(loc) + # validate the tuple represents Real coordinates + if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc): + raise ValueError(type_err_message) + elif isinstance(loc, int): + # validate the integer represents a string numeric value + if loc < 0 or loc > 10: + raise ValueError(type_err_message) + else: + # all other cases are invalid values of loc + raise ValueError(type_err_message) + + if self.isaxes and self._outside_loc: + raise ValueError( + f"'outside' option for loc='{loc0}' keyword argument only " + "works for figure legends") + + if not self.isaxes and loc == 0: + raise ValueError( + "Automatic legend placement (loc='best') not implemented for " + "figure legend") + + tmp = self._loc_used_default + self._set_loc(loc) + self._loc_used_default = tmp # ignore changes done by _set_loc + def _set_loc(self, loc): # find_offset function will be provided to _legend_box and # _legend_box will draw itself at the location of the return @@ -562,6 +705,10 @@ def _set_loc(self, loc): self.stale = True self._legend_box.set_offset(self._findoffset) + def set_ncols(self, ncols): + """Set the number of columns.""" + self._ncols = ncols + def _get_loc(self): return self._loc_real @@ -603,11 +750,14 @@ def draw(self, renderer): # update the location and size of the legend. This needs to # be done in any case to clip the figure right. bbox = self._legend_box.get_window_extent(renderer) - self.legendPatch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) + self.legendPatch.set_bounds(bbox.bounds) self.legendPatch.set_mutation_scale(fontsize) + # self.shadow is validated in __init__ + # So by here it is a bool and self._shadow_props contains any configs + if self.shadow: - Shadow(self.legendPatch, 2, -2).draw(renderer) + Shadow(self.legendPatch, **self._shadow_props).draw(renderer) self.legendPatch.draw(renderer) self._legend_box.draw(renderer) @@ -623,6 +773,7 @@ def draw(self, renderer): ErrorbarContainer: legend_handler.HandlerErrorbar(), Line2D: legend_handler.HandlerLine2D(), Patch: legend_handler.HandlerPatch(), + StepPatch: legend_handler.HandlerStepPatch(), LineCollection: legend_handler.HandlerLineCollection(), RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), CircleCollection: legend_handler.HandlerCircleCollection(), @@ -638,38 +789,24 @@ def draw(self, renderer): @classmethod def get_default_handler_map(cls): - """ - A class method that returns the default handler map. - """ + """Return the global default handler map, shared by all legends.""" return cls._default_handler_map @classmethod def set_default_handler_map(cls, handler_map): - """ - A class method to set the default handler map. - """ + """Set the global default handler map, shared by all legends.""" cls._default_handler_map = handler_map @classmethod def update_default_handler_map(cls, handler_map): - """ - A class method to update the default handler map. - """ + """Update the global default handler map, shared by all legends.""" cls._default_handler_map.update(handler_map) def get_legend_handler_map(self): - """ - Return the handler map. - """ - + """Return this legend instance's handler map.""" default_handler_map = self.get_default_handler_map() - - if self._custom_handler_map: - hm = default_handler_map.copy() - hm.update(self._custom_handler_map) - return hm - else: - return default_handler_map + return ({**default_handler_map, **self._custom_handler_map} + if self._custom_handler_map else default_handler_map) @staticmethod def get_legend_handler(legend_handler_map, orig_handle): @@ -707,27 +844,19 @@ def _init_legend_box(self, handles, labels, markerfirst=True): fontsize = self._fontsize - # legend_box is a HPacker, horizontally packed with - # columns. Each column is a VPacker, vertically packed with - # legend items. Each legend item is HPacker packed with - # legend handleBox and labelBox. handleBox is an instance of - # offsetbox.DrawingArea which contains legend handle. labelBox - # is an instance of offsetbox.TextArea which contains legend - # text. + # legend_box is a HPacker, horizontally packed with columns. + # Each column is a VPacker, vertically packed with legend items. + # Each legend item is a HPacker packed with: + # - handlebox: a DrawingArea which contains the legend handle. + # - labelbox: a TextArea which contains the legend text. text_list = [] # the list of text instances - handle_list = [] # the list of text instances + handle_list = [] # the list of handle instances handles_and_labels = [] - label_prop = dict(verticalalignment='baseline', - horizontalalignment='left', - fontproperties=self.prop, - ) - # The approximate height and descent of text. These values are # only used for plotting the legend handle. - descent = 0.35 * fontsize * (self.handleheight - 0.7) - # 0.35 and 0.7 are just heuristic numbers and may need to be improved. + descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic. height = fontsize * self.handleheight - descent # each handle needs to be drawn inside a box of (x, y, w, h) = # (0, -descent, width, height). And their coordinates should @@ -739,22 +868,24 @@ def _init_legend_box(self, handles, labels, markerfirst=True): # manually set their transform to the self.get_transform(). legend_handler_map = self.get_legend_handler_map() - for orig_handle, lab in zip(handles, labels): + for orig_handle, label in zip(handles, labels): handler = self.get_legend_handler(legend_handler_map, orig_handle) if handler is None: - cbook._warn_external( - "Legend does not support {!r} instances.\nA proxy artist " - "may be used instead.\nSee: " - "https://matplotlib.org/users/legend_guide.html" - "#creating-artists-specifically-for-adding-to-the-legend-" - "aka-proxy-artists".format(orig_handle)) - # We don't have a handle for this artist, so we just defer - # to None. + _api.warn_external( + "Legend does not support handles for " + f"{type(orig_handle).__name__} " + "instances.\nA proxy artist may be used " + "instead.\nSee: https://matplotlib.org/" + "stable/users/explain/axes/legend_guide.html" + "#controlling-the-legend-entries") + # No handle for this artist, so we just defer to None. handle_list.append(None) else: - textbox = TextArea(lab, textprops=label_prop, - multilinebaseline=True, - minimumdescent=True) + textbox = TextArea(label, multilinebaseline=True, + textprops=dict( + verticalalignment='baseline', + horizontalalignment='left', + fontproperties=self.prop)) handlebox = DrawingArea(width=self.handlelength * fontsize, height=height, xdescent=0., ydescent=descent) @@ -766,40 +897,25 @@ def _init_legend_box(self, handles, labels, markerfirst=True): fontsize, handlebox)) handles_and_labels.append((handlebox, textbox)) - if handles_and_labels: - # We calculate number of rows in each column. The first - # (num_largecol) columns will have (nrows+1) rows, and remaining - # (num_smallcol) columns will have (nrows) rows. - ncol = min(self._ncol, len(handles_and_labels)) - nrows, num_largecol = divmod(len(handles_and_labels), ncol) - num_smallcol = ncol - num_largecol - # starting index of each column and number of rows in it. - rows_per_col = [nrows + 1] * num_largecol + [nrows] * num_smallcol - start_idxs = np.concatenate([[0], np.cumsum(rows_per_col)[:-1]]) - cols = zip(start_idxs, rows_per_col) - else: - cols = [] - columnbox = [] - for i0, di in cols: - # pack handleBox and labelBox into itemBox - itemBoxes = [HPacker(pad=0, + # array_split splits n handles_and_labels into ncols columns, with the + # first n%ncols columns having an extra entry. filter(len, ...) + # handles the case where n < ncols: the last ncols-n columns are empty + # and get filtered out. + for handles_and_labels_column in filter( + len, np.array_split(handles_and_labels, self._ncols)): + # pack handlebox and labelbox into itembox + itemboxes = [HPacker(pad=0, sep=self.handletextpad * fontsize, children=[h, t] if markerfirst else [t, h], align="baseline") - for h, t in handles_and_labels[i0:i0 + di]] - # minimumdescent=False for the text of the last row of the column - if markerfirst: - itemBoxes[-1].get_children()[1].set_minimumdescent(False) - else: - itemBoxes[-1].get_children()[0].set_minimumdescent(False) - - # pack columnBox + for h, t in handles_and_labels_column] + # pack columnbox alignment = "baseline" if markerfirst else "right" columnbox.append(VPacker(pad=0, sep=self.labelspacing * fontsize, align=alignment, - children=itemBoxes)) + children=itemboxes)) mode = "expand" if self._mode == "expand" else "fixed" sep = self.columnspacing * fontsize @@ -810,14 +926,15 @@ def _init_legend_box(self, handles, labels, markerfirst=True): self._legend_title_box = TextArea("") self._legend_box = VPacker(pad=self.borderpad * fontsize, sep=self.labelspacing * fontsize, - align="center", + align=self._alignment, children=[self._legend_title_box, self._legend_handle_box]) - self._legend_box.set_figure(self.figure) + self._legend_box.set_figure(self.get_figure(root=False)) + self._legend_box.axes = self.axes self.texts = text_list - self.legendHandles = handle_list + self.legend_handles = handle_list - def _auto_legend_data(self): + def _auto_legend_data(self, renderer): """ Return display coordinates for hit testing for "best" positioning. @@ -831,18 +948,29 @@ def _auto_legend_data(self): List of (x, y) offsets of all collection. """ assert self.isaxes # always holds, as this is only called internally - ax = self.parent - lines = [line.get_transform().transform_path(line.get_path()) - for line in ax.lines] - bboxes = [patch.get_bbox().transformed(patch.get_data_transform()) - if isinstance(patch, Rectangle) else - patch.get_path().get_extents(patch.get_transform()) - for patch in ax.patches] + bboxes = [] + lines = [] offsets = [] - for handle in ax.collections: - _, transOffset, hoffsets, _ = handle._prepare_points() - for offset in transOffset.transform(hoffsets): - offsets.append(offset) + for artist in self.parent._children: + if isinstance(artist, Line2D): + lines.append( + artist.get_transform().transform_path(artist.get_path())) + elif isinstance(artist, Rectangle): + bboxes.append( + artist.get_bbox().transformed(artist.get_data_transform())) + elif isinstance(artist, Patch): + lines.append( + artist.get_transform().transform_path(artist.get_path())) + elif isinstance(artist, PolyCollection): + lines.extend(artist.get_transform().transform_path(path) + for path in artist.get_paths()) + elif isinstance(artist, Collection): + transform, transOffset, hoffsets, _ = artist._prepare_points() + if len(hoffsets): + offsets.extend(transOffset.transform(hoffsets)) + elif isinstance(artist, Text): + bboxes.append(artist.get_window_extent(renderer)) + return bboxes, lines, offsets def get_children(self): @@ -855,22 +983,53 @@ def get_frame(self): def get_lines(self): r"""Return the list of `~.lines.Line2D`\s in the legend.""" - return [h for h in self.legendHandles if isinstance(h, Line2D)] + return [h for h in self.legend_handles if isinstance(h, Line2D)] def get_patches(self): r"""Return the list of `~.patches.Patch`\s in the legend.""" return silent_list('Patch', - [h for h in self.legendHandles + [h for h in self.legend_handles if isinstance(h, Patch)]) def get_texts(self): r"""Return the list of `~.text.Text`\s in the legend.""" return silent_list('Text', self.texts) + def set_alignment(self, alignment): + """ + Set the alignment of the legend title and the box of entries. + + The entries are aligned as a single block, so that markers always + lined up. + + Parameters + ---------- + alignment : {'center', 'left', 'right'}. + + """ + _api.check_in_list(["center", "left", "right"], alignment=alignment) + self._alignment = alignment + self._legend_box.align = alignment + + def get_alignment(self): + """Get the alignment value of the legend box""" + return self._legend_box.align + def set_title(self, title, prop=None): """ - Set the legend title. Fontproperties can be optionally set - with *prop* parameter. + Set legend title and title style. + + Parameters + ---------- + title : str + The legend title. + + prop : `.font_manager.FontProperties` or `str` or `pathlib.Path` + The font properties of the legend title. + If a `str`, it is interpreted as a fontconfig pattern parsed by + `.FontProperties`. If a `pathlib.Path`, it is interpreted as the + absolute path to a font file. + """ self._legend_title_box._text.set_text(title) if title: @@ -892,24 +1051,11 @@ def get_title(self): def get_window_extent(self, renderer=None): # docstring inherited if renderer is None: - renderer = self.figure._cachedRenderer + renderer = self.get_figure(root=True)._get_renderer() return self._legend_box.get_window_extent(renderer=renderer) - def get_tightbbox(self, renderer): - """ - Like `.Legend.get_window_extent`, but uses the box for the legend. - - Parameters - ---------- - renderer : `.RendererBase` subclass - renderer that will be used to draw the figures (i.e. - ``fig.canvas.get_renderer()``) - - Returns - ------- - `.BboxBase` - The bounding box in figure pixel coordinates. - """ + def get_tightbbox(self, renderer=None): + # docstring inherited return self._legend_box.get_window_extent(renderer) def get_frame_on(self): @@ -965,8 +1111,7 @@ def set_bbox_to_anchor(self, bbox, transform=None): try: l = len(bbox) except TypeError as err: - raise ValueError("Invalid argument for bbox : %s" % - str(bbox)) from err + raise ValueError(f"Invalid bbox: {bbox}") from err if l == 2: bbox = [bbox[0], bbox[1], 0, 0] @@ -994,54 +1139,27 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer): bbox to be placed, in display coordinates. parentbbox : `~matplotlib.transforms.Bbox` A parent box which will contain the bbox, in display coordinates. - - """ - assert loc in range(1, 11) # called only internally - - BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11) - - anchor_coefs = {UR: "NE", - UL: "NW", - LL: "SW", - LR: "SE", - R: "E", - CL: "W", - CR: "E", - LC: "S", - UC: "N", - C: "C"} - - c = anchor_coefs[loc] - - fontsize = renderer.points_to_pixels(self._fontsize) - container = parentbbox.padded(-self.borderaxespad * fontsize) - anchored_box = bbox.anchored(c, container=container) - return anchored_box.x0, anchored_box.y0 - - def _find_best_position(self, width, height, renderer, consider=None): """ - Determine the best location to place the legend. + return offsetbox._get_anchored_bbox( + loc, bbox, parentbbox, + self.borderaxespad * renderer.points_to_pixels(self._fontsize)) - *consider* is a list of ``(x, y)`` pairs to consider as a potential - lower-left corner of the legend. All are display coords. - """ + def _find_best_position(self, width, height, renderer): + """Determine the best location to place the legend.""" assert self.isaxes # always holds, as this is only called internally start_time = time.perf_counter() - bboxes, lines, offsets = self._auto_legend_data() + bboxes, lines, offsets = self._auto_legend_data(renderer) bbox = Bbox.from_bounds(0, 0, width, height) - if consider is None: - consider = [self._get_anchored_bbox(x, bbox, - self.get_bbox_to_anchor(), - renderer) - for x in range(1, len(self.codes))] candidates = [] - for idx, (l, b) in enumerate(consider): + for idx in range(1, len(self.codes)): + l, b = self._get_anchored_bbox(idx, bbox, + self.get_bbox_to_anchor(), + renderer) legendBox = Bbox.from_bounds(l, b, width, height) - badness = 0 # XXX TODO: If markers are present, it would be good to take them # into account when checking vertex overlaps in the next line. badness = (sum(legendBox.count_contains(line.vertices) @@ -1050,25 +1168,22 @@ def _find_best_position(self, width, height, renderer, consider=None): + legendBox.count_overlaps(bboxes) + sum(line.intersects_bbox(legendBox, filled=False) for line in lines)) - if badness == 0: - return l, b # Include the index to favor lower codes in case of a tie. candidates.append((badness, idx, (l, b))) + if badness == 0: + break _, _, (l, b) = min(candidates) if self._loc_used_default and time.perf_counter() - start_time > 1: - cbook._warn_external( + _api.warn_external( 'Creating legend with loc="best" can be slow with large ' 'amounts of data.') return l, b - def contains(self, event): - inside, info = self._default_contains(event) - if inside is not None: - return inside, info - return self.legendPatch.contains(event) + def contains(self, mouseevent): + return self.legendPatch.contains(mouseevent) def set_draggable(self, state, use_blit=False, update='loc'): """ @@ -1112,43 +1227,43 @@ def get_draggable(self): # Helper functions to parse legend arguments for both `figure.legend` and # `axes.legend`: def _get_legend_handles(axs, legend_handler_map=None): - """ - Return a generator of artists that can be used as handles in - a legend. - - """ + """Yield artists that can be used as handles in a legend.""" handles_original = [] for ax in axs: - handles_original += (ax.lines + ax.patches + - ax.collections + ax.containers) - # support parasite axes: + handles_original += [ + *(a for a in ax._children + if isinstance(a, (Line2D, Patch, Collection, Text))), + *ax.containers] + # support parasite Axes: if hasattr(ax, 'parasites'): for axx in ax.parasites: - handles_original += (axx.lines + axx.patches + - axx.collections + axx.containers) - - handler_map = Legend.get_default_handler_map() - - if legend_handler_map is not None: - handler_map = handler_map.copy() - handler_map.update(legend_handler_map) + handles_original += [ + *(a for a in axx._children + if isinstance(a, (Line2D, Patch, Collection, Text))), + *axx.containers] + handler_map = {**Legend.get_default_handler_map(), + **(legend_handler_map or {})} has_handler = Legend.get_legend_handler - for handle in handles_original: label = handle.get_label() if label != '_nolegend_' and has_handler(handler_map, handle): yield handle + elif (label and not label.startswith('_') and + not has_handler(handler_map, handle)): + _api.warn_external( + "Legend does not support handles for " + f"{type(handle).__name__} " + "instances.\nSee: https://matplotlib.org/stable/" + "tutorials/intermediate/legend_guide.html" + "#implementing-a-custom-legend-handler") + continue def _get_legend_handles_labels(axs, legend_handler_map=None): - """ - Return handles and labels for legend, internal method. - - """ + """Return handles and labels for legend.""" handles = [] labels = [] - for handle in _get_legend_handles(axs, legend_handler_map): label = handle.get_label() if label and not label.startswith('_'): @@ -1172,7 +1287,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): legend(handles=handles, labels=labels) The behavior for a mixture of positional and keyword handles and labels - is undefined and issues a warning. + is undefined and raises an error. Parameters ---------- @@ -1192,25 +1307,28 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): Returns ------- - handles : list of `.Artist` + handles : list of (`.Artist` or tuple of `.Artist`) The legend handles. labels : list of str The legend labels. - extra_args : tuple - *args* with positional handles and labels removed. kwargs : dict *kwargs* with keywords handles and labels removed. """ log = logging.getLogger(__name__) - handlers = kwargs.get('handler_map', {}) or {} - extra_args = () + handlers = kwargs.get('handler_map') if (handles is not None or labels is not None) and args: - cbook._warn_external("You have mixed positional and keyword " - "arguments, some input may be discarded.") - + raise TypeError("When passing handles and labels, they must both be " + "passed positionally or both as keywords.") + + if (hasattr(handles, "__len__") and + hasattr(labels, "__len__") and + len(handles) != len(labels)): + _api.warn_external(f"Mismatched number of handles and labels: " + f"len(handles) = {len(handles)} " + f"len(labels) = {len(labels)}") # if got both handles and labels as kwargs, make same length if handles and labels: handles, labels = zip(*zip(handles, labels)) @@ -1223,14 +1341,15 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): handles = [handle for handle, label in zip(_get_legend_handles(axs, handlers), labels)] - # No arguments - automatically detect labels and handles. - elif len(args) == 0: + elif len(args) == 0: # 0 args: automatically detect labels and handles. handles, labels = _get_legend_handles_labels(axs, handlers) if not handles: - log.warning('No handles with labels found to put in legend.') + _api.warn_external( + "No artists with labels found to put in legend. Note that " + "artists whose label start with an underscore are ignored " + "when legend() is called with no argument.") - # One argument. User defined labels - automatic handle detection. - elif len(args) == 1: + elif len(args) == 1: # 1 arg: user defined labels, automatic handle detection. labels, = args if any(isinstance(l, Artist) for l in labels): raise TypeError("A single argument passed to legend() must be a " @@ -1240,13 +1359,10 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): handles = [handle for handle, label in zip(_get_legend_handles(axs, handlers), labels)] - # Two arguments: - # * user defined handles and labels - elif len(args) >= 2: + elif len(args) == 2: # 2 args: user defined handles and labels. handles, labels = args[:2] - extra_args = args[2:] else: - raise TypeError('Invalid arguments to legend.') + raise _api.nargs_error('legend', '0-2', len(args)) - return handles, labels, extra_args, kwargs + return handles, labels, kwargs diff --git a/lib/matplotlib/legend.pyi b/lib/matplotlib/legend.pyi new file mode 100644 index 000000000000..dde5882da69d --- /dev/null +++ b/lib/matplotlib/legend.pyi @@ -0,0 +1,152 @@ +from matplotlib.axes import Axes +from matplotlib.artist import Artist +from matplotlib.backend_bases import MouseEvent +from matplotlib.figure import Figure +from matplotlib.font_manager import FontProperties +from matplotlib.legend_handler import HandlerBase +from matplotlib.lines import Line2D +from matplotlib.offsetbox import ( + DraggableOffsetBox, +) +from matplotlib.patches import FancyBboxPatch, Patch, Rectangle +from matplotlib.text import Text +from matplotlib.transforms import ( + BboxBase, + Transform, +) + + +import pathlib +from collections.abc import Iterable +from typing import Any, Literal, overload +from .typing import ColorType + +class DraggableLegend(DraggableOffsetBox): + legend: Legend + def __init__( + self, legend: Legend, use_blit: bool = ..., update: Literal["loc", "bbox"] = ... + ) -> None: ... + def finalize_offset(self) -> None: ... + +class Legend(Artist): + codes: dict[str, int] + zorder: float + prop: FontProperties + texts: list[Text] + legend_handles: list[Artist | None] + numpoints: int + markerscale: float + scatterpoints: int + borderpad: float + labelspacing: float + handlelength: float + handleheight: float + handletextpad: float + borderaxespad: float + columnspacing: float + shadow: bool + isaxes: bool + axes: Axes + parent: Axes | Figure + legendPatch: FancyBboxPatch + def __init__( + self, + parent: Axes | Figure, + handles: Iterable[Artist | tuple[Artist, ...]], + labels: Iterable[str], + *, + loc: str | tuple[float, float] | int | None = ..., + numpoints: int | None = ..., + markerscale: float | None = ..., + markerfirst: bool = ..., + reverse: bool = ..., + scatterpoints: int | None = ..., + scatteryoffsets: Iterable[float] | None = ..., + prop: FontProperties | dict[str, Any] | None = ..., + fontsize: float | str | None = ..., + labelcolor: ColorType + | Iterable[ColorType] + | Literal["linecolor", "markerfacecolor", "mfc", "markeredgecolor", "mec"] + | None = ..., + borderpad: float | None = ..., + labelspacing: float | None = ..., + handlelength: float | None = ..., + handleheight: float | None = ..., + handletextpad: float | None = ..., + borderaxespad: float | None = ..., + columnspacing: float | None = ..., + ncols: int = ..., + mode: Literal["expand"] | None = ..., + fancybox: bool | None = ..., + shadow: bool | dict[str, Any] | None = ..., + title: str | None = ..., + title_fontsize: float | None = ..., + framealpha: float | None = ..., + edgecolor: Literal["inherit"] | ColorType | None = ..., + facecolor: Literal["inherit"] | ColorType | None = ..., + bbox_to_anchor: BboxBase + | tuple[float, float] + | tuple[float, float, float, float] + | None = ..., + bbox_transform: Transform | None = ..., + frameon: bool | None = ..., + handler_map: dict[Artist | type, HandlerBase] | None = ..., + title_fontproperties: FontProperties | dict[str, Any] | None = ..., + alignment: Literal["center", "left", "right"] = ..., + ncol: int = ..., + draggable: bool = ... + ) -> None: ... + def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ... + def set_ncols(self, ncols: int) -> None: ... + @classmethod + def get_default_handler_map(cls) -> dict[type, HandlerBase]: ... + @classmethod + def set_default_handler_map(cls, handler_map: dict[type, HandlerBase]) -> None: ... + @classmethod + def update_default_handler_map( + cls, handler_map: dict[type, HandlerBase] + ) -> None: ... + def get_legend_handler_map(self) -> dict[type, HandlerBase]: ... + @staticmethod + def get_legend_handler( + legend_handler_map: dict[type, HandlerBase], orig_handle: Any + ) -> HandlerBase | None: ... + def get_children(self) -> list[Artist]: ... + def get_frame(self) -> Rectangle: ... + def get_lines(self) -> list[Line2D]: ... + def get_patches(self) -> list[Patch]: ... + def get_texts(self) -> list[Text]: ... + def set_alignment(self, alignment: Literal["center", "left", "right"]) -> None: ... + def get_alignment(self) -> Literal["center", "left", "right"]: ... + def set_loc(self, loc: str | tuple[float, float] | int | None = ...) -> None: ... + def set_title( + self, title: str, prop: FontProperties | str | pathlib.Path | None = ... + ) -> None: ... + def get_title(self) -> Text: ... + def get_frame_on(self) -> bool: ... + def set_frame_on(self, b: bool) -> None: ... + draw_frame = set_frame_on + def get_bbox_to_anchor(self) -> BboxBase: ... + def set_bbox_to_anchor( + self, + bbox: BboxBase + | tuple[float, float] + | tuple[float, float, float, float] + | None, + transform: Transform | None = ... + ) -> None: ... + @overload + def set_draggable( + self, + state: Literal[True], + use_blit: bool = ..., + update: Literal["loc", "bbox"] = ..., + ) -> DraggableLegend: ... + @overload + def set_draggable( + self, + state: Literal[False], + use_blit: bool = ..., + update: Literal["loc", "bbox"] = ..., + ) -> None: ... + def get_draggable(self) -> bool: ... diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 4b96dd4289c5..263945b050d0 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -1,20 +1,24 @@ """ Default legend handlers. -It is strongly encouraged to have read the :doc:`legend guide -` before this documentation. +.. important:: + + This is a low-level legend API, which most end users do not need. + + We recommend that you are familiar with the :ref:`legend guide + ` before reading this documentation. Legend handlers are expected to be a callable object with a following -signature. :: +signature:: legend_handler(legend, orig_handle, fontsize, handlebox) Where *legend* is the legend itself, *orig_handle* is the original -plot, *fontsize* is the fontsize in pixels, and *handlebox* is a -OffsetBox instance. Within the call, you should create relevant +plot, *fontsize* is the fontsize in pixels, and *handlebox* is an +`.OffsetBox` instance. Within the call, you should create relevant artists (using relevant properties from the *legend* and/or -*orig_handle*) and add them into the handlebox. The artists needs to -be scaled according to the fontsize (note that the size is in pixel, +*orig_handle*) and add them into the *handlebox*. The artists need to +be scaled according to the *fontsize* (note that the size is in pixels, i.e., this is dpi-scaled value). This module includes definition of several legend handler classes @@ -31,7 +35,6 @@ def legend_artist(self, legend, orig_handle, fontsize, handlebox) from matplotlib.lines import Line2D from matplotlib.patches import Rectangle import matplotlib.collections as mcoll -import matplotlib.colors as mcolors def update_from_first_child(tgt, src): @@ -42,10 +45,10 @@ def update_from_first_child(tgt, src): class HandlerBase: """ - A Base class for default legend handlers. + A base class for default legend handlers. The derived classes are meant to override *create_artists* method, which - has a following signature.:: + has the following signature:: def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, @@ -57,6 +60,17 @@ def create_artists(self, legend, orig_handle, """ def __init__(self, xpad=0., ypad=0., update_func=None): + """ + Parameters + ---------- + xpad : float, optional + Padding in x-direction. + ypad : float, optional + Padding in y-direction. + update_func : callable, optional + Function for updating the legend handler properties from another + legend handler, used by `~HandlerBase.update_prop`. + """ self._xpad, self._ypad = xpad, ypad self._update_prop_func = update_func @@ -101,7 +115,7 @@ def legend_artist(self, legend, orig_handle, fontsize : int The fontsize in pixels. The artists being created should be scaled according to the given fontsize. - handlebox : `matplotlib.offsetbox.OffsetBox` + handlebox : `~matplotlib.offsetbox.OffsetBox` The box which has been created to hold this legend entry's artists. Artists created in the `legend_artist` method must be added to this handlebox inside this method. @@ -126,6 +140,26 @@ def legend_artist(self, legend, orig_handle, def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): + """ + Return the legend artists generated. + + Parameters + ---------- + legend : `~matplotlib.legend.Legend` + The legend for which these legend artists are being created. + orig_handle : `~matplotlib.artist.Artist` or similar + The object for which these legend artists are being created. + xdescent, ydescent, width, height : int + The rectangle (*xdescent*, *ydescent*, *width*, *height*) that the + legend artists being created should fit within. + fontsize : int + The fontsize in pixels. The legend artists being created should + be scaled according to the given fontsize. + trans : `~matplotlib.transforms.Transform` + The transform that is applied to the legend artists being created. + Typically from unit coordinates in the handler box to screen + coordinates. + """ raise NotImplementedError('Derived must override') @@ -133,21 +167,19 @@ class HandlerNpoints(HandlerBase): """ A legend handler that shows *numpoints* points in the legend entry. """ - def __init__(self, marker_pad=0.3, numpoints=None, **kw): + + def __init__(self, marker_pad=0.3, numpoints=None, **kwargs): """ Parameters ---------- marker_pad : float Padding between points in legend entry. - numpoints : int Number of points to show in legend entry. - - Notes - ----- - Any other keyword arguments are given to `HandlerBase`. + **kwargs + Keyword arguments forwarded to `.HandlerBase`. """ - super().__init__(**kw) + super().__init__(**kwargs) self._numpoints = numpoints self._marker_pad = marker_pad @@ -178,22 +210,20 @@ class HandlerNpointsYoffsets(HandlerNpoints): A legend handler that shows *numpoints* in the legend, and allows them to be individually offset in the y-direction. """ - def __init__(self, numpoints=None, yoffsets=None, **kw): + + def __init__(self, numpoints=None, yoffsets=None, **kwargs): """ Parameters ---------- numpoints : int Number of points to show in legend entry. - yoffsets : array of floats Length *numpoints* list of y offsets for each point in legend entry. - - Notes - ----- - Any other keyword arguments are given to `HandlerNpoints`. + **kwargs + Keyword arguments forwarded to `.HandlerNpoints`. """ - super().__init__(numpoints=numpoints, **kw) + super().__init__(numpoints=numpoints, **kwargs) self._yoffsets = yoffsets def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): @@ -205,30 +235,16 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): return ydata -class HandlerLine2D(HandlerNpoints): +class HandlerLine2DCompound(HandlerNpoints): """ - Handler for `.Line2D` instances. + Original handler for `.Line2D` instances, that relies on combining + a line-only with a marker-only artist. May be deprecated in the future. """ - def __init__(self, marker_pad=0.3, numpoints=None, **kw): - """ - Parameters - ---------- - marker_pad : float - Padding between points in legend entry. - - numpoints : int - Number of points to show in legend entry. - - Notes - ----- - Any other keyword arguments are given to `HandlerNpoints`. - """ - super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kw) def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - + # docstring inherited xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, width, height, fontsize) @@ -256,11 +272,51 @@ def create_artists(self, legend, orig_handle, return [legline, legline_marker] +class HandlerLine2D(HandlerNpoints): + """ + Handler for `.Line2D` instances. + + See Also + -------- + HandlerLine2DCompound : An earlier handler implementation, which used one + artist for the line and another for the marker(s). + """ + + def create_artists(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize, + trans): + # docstring inherited + xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, + width, height, fontsize) + + markevery = None + if self.get_numpoints(legend) == 1: + # Special case: one wants a single marker in the center + # and a line that extends on both sides. One will use a + # 3 points line, but only mark the #1 (i.e. middle) point. + xdata = np.linspace(xdata[0], xdata[-1], 3) + markevery = [1] + + ydata = np.full_like(xdata, (height - ydescent) / 2) + legline = Line2D(xdata, ydata, markevery=markevery) + + self.update_prop(legline, orig_handle, legend) + + if legend.markerscale != 1: + newsz = legline.get_markersize() * legend.markerscale + legline.set_markersize(newsz) + + legline.set_transform(trans) + + return [legline] + + class HandlerPatch(HandlerBase): """ Handler for `.Patch` instances. """ - def __init__(self, patch_func=None, **kw): + + def __init__(self, patch_func=None, **kwargs): """ Parameters ---------- @@ -272,14 +328,13 @@ def patch_func(legend=legend, orig_handle=orig_handle, xdescent=xdescent, ydescent=ydescent, width=width, height=height, fontsize=fontsize) - Subsequently the created artist will have its ``update_prop`` + Subsequently, the created artist will have its ``update_prop`` method called and the appropriate transform will be applied. - Notes - ----- - Any other keyword arguments are given to `HandlerBase`. + **kwargs + Keyword arguments forwarded to `.HandlerBase`. """ - super().__init__(**kw) + super().__init__(**kwargs) self._patch_func = patch_func def _create_patch(self, legend, orig_handle, @@ -295,6 +350,7 @@ def _create_patch(self, legend, orig_handle, def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): + # docstring inherited p = self._create_patch(legend, orig_handle, xdescent, ydescent, width, height, fontsize) self.update_prop(p, orig_handle, legend) @@ -302,6 +358,43 @@ def create_artists(self, legend, orig_handle, return [p] +class HandlerStepPatch(HandlerBase): + """ + Handler for `~.matplotlib.patches.StepPatch` instances. + """ + + @staticmethod + def _create_patch(orig_handle, xdescent, ydescent, width, height): + return Rectangle(xy=(-xdescent, -ydescent), width=width, + height=height, color=orig_handle.get_facecolor()) + + @staticmethod + def _create_line(orig_handle, width, height): + # Unfilled StepPatch should show as a line + legline = Line2D([0, width], [height/2, height/2], + color=orig_handle.get_edgecolor(), + linestyle=orig_handle.get_linestyle(), + linewidth=orig_handle.get_linewidth(), + ) + + # Overwrite manually because patch and line properties don't mix + legline.set_drawstyle('default') + legline.set_marker("") + return legline + + def create_artists(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize, trans): + # docstring inherited + if orig_handle.get_fill() or (orig_handle.get_hatch() is not None): + p = self._create_patch(orig_handle, xdescent, ydescent, width, + height) + self.update_prop(p, orig_handle, legend) + else: + p = self._create_line(orig_handle, width, height) + p.set_transform(trans) + return [p] + + class HandlerLineCollection(HandlerLine2D): """ Handler for `.LineCollection` instances. @@ -322,7 +415,7 @@ def _default_update_prop(self, legend_handle, orig_handle): def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - + # docstring inherited xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, width, height, fontsize) ydata = np.full_like(xdata, (height - ydescent) / 2) @@ -337,8 +430,8 @@ def create_artists(self, legend, orig_handle, class HandlerRegularPolyCollection(HandlerNpointsYoffsets): r"""Handler for `.RegularPolyCollection`\s.""" - def __init__(self, yoffsets=None, sizes=None, **kw): - super().__init__(yoffsets=yoffsets, **kw) + def __init__(self, yoffsets=None, sizes=None, **kwargs): + super().__init__(yoffsets=yoffsets, **kwargs) self._sizes = sizes @@ -373,23 +466,22 @@ def update_prop(self, legend_handle, orig_handle, legend): self._update_prop(legend_handle, orig_handle) - legend_handle.set_figure(legend.figure) + legend_handle.set_figure(legend.get_figure(root=False)) # legend._set_artist_props(legend_handle) legend_handle.set_clip_box(None) legend_handle.set_clip_path(None) - def create_collection(self, orig_handle, sizes, offsets, transOffset): - p = type(orig_handle)(orig_handle.get_numsides(), - rotation=orig_handle.get_rotation(), - sizes=sizes, - offsets=offsets, - transOffset=transOffset, - ) - return p + def create_collection(self, orig_handle, sizes, offsets, offset_transform): + return type(orig_handle)( + orig_handle.get_numsides(), + rotation=orig_handle.get_rotation(), sizes=sizes, + offsets=offsets, offset_transform=offset_transform, + ) def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): + # docstring inherited xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, width, height, fontsize) @@ -399,46 +491,43 @@ def create_artists(self, legend, orig_handle, sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent, width, height, fontsize) - p = self.create_collection(orig_handle, sizes, - offsets=list(zip(xdata_marker, ydata)), - transOffset=trans) + p = self.create_collection( + orig_handle, sizes, + offsets=list(zip(xdata_marker, ydata)), offset_transform=trans) self.update_prop(p, orig_handle, legend) - p._transOffset = trans + p.set_offset_transform(trans) return [p] class HandlerPathCollection(HandlerRegularPolyCollection): r"""Handler for `.PathCollection`\s, which are used by `~.Axes.scatter`.""" - def create_collection(self, orig_handle, sizes, offsets, transOffset): - p = type(orig_handle)([orig_handle.get_paths()[0]], - sizes=sizes, - offsets=offsets, - transOffset=transOffset, - ) - return p + + def create_collection(self, orig_handle, sizes, offsets, offset_transform): + return type(orig_handle)( + [orig_handle.get_paths()[0]], sizes=sizes, + offsets=offsets, offset_transform=offset_transform, + ) class HandlerCircleCollection(HandlerRegularPolyCollection): r"""Handler for `.CircleCollection`\s.""" - def create_collection(self, orig_handle, sizes, offsets, transOffset): - p = type(orig_handle)(sizes, - offsets=offsets, - transOffset=transOffset, - ) - return p + + def create_collection(self, orig_handle, sizes, offsets, offset_transform): + return type(orig_handle)( + sizes, offsets=offsets, offset_transform=offset_transform) class HandlerErrorbar(HandlerLine2D): """Handler for Errorbars.""" def __init__(self, xerr_size=0.5, yerr_size=None, - marker_pad=0.3, numpoints=None, **kw): + marker_pad=0.3, numpoints=None, **kwargs): self._xerr_size = xerr_size self._yerr_size = yerr_size - super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kw) + super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kwargs) def get_err_size(self, legend, xdescent, ydescent, width, height, fontsize): @@ -454,7 +543,7 @@ def get_err_size(self, legend, xdescent, ydescent, def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - + # docstring inherited plotlines, caplines, barlinecols = orig_handle xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, @@ -480,7 +569,7 @@ def create_artists(self, legend, orig_handle, self.update_prop(legline, plotlines, legend) legline.set_drawstyle('default') - legline.set_marker('None') + legline.set_marker('none') self.update_prop(legline_marker, plotlines, legend) legline_marker.set_linestyle('None') @@ -540,30 +629,26 @@ class HandlerStem(HandlerNpointsYoffsets): """ Handler for plots produced by `~.Axes.stem`. """ + def __init__(self, marker_pad=0.3, numpoints=None, - bottom=None, yoffsets=None, **kw): + bottom=None, yoffsets=None, **kwargs): """ Parameters ---------- marker_pad : float, default: 0.3 Padding between points in legend entry. - numpoints : int, optional Number of points to show in legend entry. - bottom : float, optional yoffsets : array of floats, optional Length *numpoints* list of y offsets for each point in legend entry. - - Notes - ----- - Any other keyword arguments are given to `HandlerNpointsYoffsets`. + **kwargs + Keyword arguments forwarded to `.HandlerNpointsYoffsets`. """ - super().__init__(marker_pad=marker_pad, numpoints=numpoints, - yoffsets=yoffsets, **kw) + yoffsets=yoffsets, **kwargs) self._bottom = bottom def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): @@ -577,6 +662,7 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): + # docstring inherited markerline, stemlines, baseline = orig_handle # Check to see if the stemcontainer is storing lines as a list or a # LineCollection. Eventually using a list will be removed, and this @@ -633,19 +719,20 @@ def _copy_collection_props(self, legend_handle, orig_handle): class HandlerTuple(HandlerBase): """ Handler for Tuple. - - Additional kwargs are passed through to `HandlerBase`. - - Parameters - ---------- - ndivide : int, default: 1 - The number of sections to divide the legend area into. If None, - use the length of the input tuple. - pad : float, default: :rc:`legend.borderpad` - Padding in units of fraction of font size. """ def __init__(self, ndivide=1, pad=None, **kwargs): + """ + Parameters + ---------- + ndivide : int or None, default: 1 + The number of sections to divide the legend area into. If None, + use the length of the input tuple. + pad : float, default: :rc:`legend.borderpad` + Padding in units of fraction of font size. + **kwargs + Keyword arguments forwarded to `.HandlerBase`. + """ self._ndivide = ndivide self._pad = pad super().__init__(**kwargs) @@ -653,7 +740,7 @@ def __init__(self, ndivide=1, pad=None, **kwargs): def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - + # docstring inherited handler_map = legend.get_legend_handler_map() if self._ndivide is None: @@ -689,35 +776,35 @@ class HandlerPolyCollection(HandlerBase): """ def _update_prop(self, legend_handle, orig_handle): def first_color(colors): - if colors is None: - return None - colors = mcolors.to_rgba_array(colors) - if len(colors): - return colors[0] - else: - return "none" + if colors.size == 0: + return (0, 0, 0, 0) + return tuple(colors[0]) def get_first(prop_array): if len(prop_array): return prop_array[0] else: return None - edgecolor = getattr(orig_handle, '_original_edgecolor', - orig_handle.get_edgecolor()) - legend_handle.set_edgecolor(first_color(edgecolor)) - facecolor = getattr(orig_handle, '_original_facecolor', - orig_handle.get_facecolor()) - legend_handle.set_facecolor(first_color(facecolor)) - legend_handle.set_fill(orig_handle.get_fill()) - legend_handle.set_hatch(orig_handle.get_hatch()) + + # orig_handle is a PolyCollection and legend_handle is a Patch. + # Directly set Patch color attributes (must be RGBA tuples). + legend_handle._facecolor = first_color(orig_handle.get_facecolor()) + legend_handle._edgecolor = first_color(orig_handle.get_edgecolor()) + legend_handle._hatch_color = first_color(orig_handle.get_hatchcolor()) + legend_handle._original_facecolor = orig_handle._original_facecolor + legend_handle._original_edgecolor = orig_handle._original_edgecolor + legend_handle._fill = orig_handle.get_fill() + legend_handle._hatch = orig_handle.get_hatch() + # Setters are fine for the remaining attributes. legend_handle.set_linewidth(get_first(orig_handle.get_linewidths())) legend_handle.set_linestyle(get_first(orig_handle.get_linestyles())) legend_handle.set_transform(get_first(orig_handle.get_transforms())) legend_handle.set_figure(orig_handle.get_figure()) - legend_handle.set_alpha(orig_handle.get_alpha()) + # Alpha is already taken into account by the color attributes. def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): + # docstring inherited p = Rectangle(xy=(-xdescent, -ydescent), width=width, height=height) self.update_prop(p, orig_handle, legend) diff --git a/lib/matplotlib/legend_handler.pyi b/lib/matplotlib/legend_handler.pyi new file mode 100644 index 000000000000..db028a136a48 --- /dev/null +++ b/lib/matplotlib/legend_handler.pyi @@ -0,0 +1,294 @@ +from collections.abc import Callable, Sequence +from matplotlib.artist import Artist +from matplotlib.legend import Legend +from matplotlib.offsetbox import OffsetBox +from matplotlib.transforms import Transform + +from typing import TypeVar + +from numpy.typing import ArrayLike + +def update_from_first_child(tgt: Artist, src: Artist) -> None: ... + +class HandlerBase: + def __init__( + self, + xpad: float = ..., + ypad: float = ..., + update_func: Callable[[Artist, Artist], None] | None = ..., + ) -> None: ... + def update_prop( + self, legend_handle: Artist, orig_handle: Artist, legend: Legend + ) -> None: ... + def adjust_drawing_area( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> tuple[float, float, float, float]: ... + def legend_artist( + self, legend: Legend, orig_handle: Artist, fontsize: float, handlebox: OffsetBox + ) -> Artist: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerNpoints(HandlerBase): + def __init__( + self, marker_pad: float = ..., numpoints: int | None = ..., **kwargs + ) -> None: ... + def get_numpoints(self, legend: Legend) -> int | None: ... + def get_xdata( + self, + legend: Legend, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> tuple[ArrayLike, ArrayLike]: ... + +class HandlerNpointsYoffsets(HandlerNpoints): + def __init__( + self, + numpoints: int | None = ..., + yoffsets: Sequence[float] | None = ..., + **kwargs + ) -> None: ... + def get_ydata( + self, + legend: Legend, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> ArrayLike: ... + +class HandlerLine2DCompound(HandlerNpoints): + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerLine2D(HandlerNpoints): + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerPatch(HandlerBase): + def __init__(self, patch_func: Callable | None = ..., **kwargs) -> None: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerStepPatch(HandlerBase): + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerLineCollection(HandlerLine2D): + def get_numpoints(self, legend: Legend) -> int: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +_T = TypeVar("_T", bound=Artist) + +class HandlerRegularPolyCollection(HandlerNpointsYoffsets): + def __init__( + self, + yoffsets: Sequence[float] | None = ..., + sizes: Sequence[float] | None = ..., + **kwargs + ) -> None: ... + def get_numpoints(self, legend: Legend) -> int: ... + def get_sizes( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> Sequence[float]: ... + def update_prop( + self, legend_handle, orig_handle: Artist, legend: Legend + ) -> None: ... + def create_collection( + self, + orig_handle: _T, + sizes: Sequence[float] | None, + offsets: Sequence[float] | None, + offset_transform: Transform, + ) -> _T: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerPathCollection(HandlerRegularPolyCollection): + def create_collection( + self, + orig_handle: _T, + sizes: Sequence[float] | None, + offsets: Sequence[float] | None, + offset_transform: Transform, + ) -> _T: ... + +class HandlerCircleCollection(HandlerRegularPolyCollection): + def create_collection( + self, + orig_handle: _T, + sizes: Sequence[float] | None, + offsets: Sequence[float] | None, + offset_transform: Transform, + ) -> _T: ... + +class HandlerErrorbar(HandlerLine2D): + def __init__( + self, + xerr_size: float = ..., + yerr_size: float | None = ..., + marker_pad: float = ..., + numpoints: int | None = ..., + **kwargs + ) -> None: ... + def get_err_size( + self, + legend: Legend, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> tuple[float, float]: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerStem(HandlerNpointsYoffsets): + def __init__( + self, + marker_pad: float = ..., + numpoints: int | None = ..., + bottom: float | None = ..., + yoffsets: Sequence[float] | None = ..., + **kwargs + ) -> None: ... + def get_ydata( + self, + legend: Legend, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + ) -> ArrayLike: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerTuple(HandlerBase): + def __init__( + self, ndivide: int | None = ..., pad: float | None = ..., **kwargs + ) -> None: ... + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... + +class HandlerPolyCollection(HandlerBase): + def create_artists( + self, + legend: Legend, + orig_handle: Artist, + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Transform, + ) -> Sequence[Artist]: ... diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 33759ac1cf0a..72c57bf77b5c 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1,29 +1,28 @@ """ -The 2D line class which can draw with a variety of line styles, markers and -colors. +2D lines with support for a variety of line styles, markers, colors, etc. """ -# TODO: expose cap and join style attrs +import copy + from numbers import Integral, Number, Real import logging import numpy as np import matplotlib as mpl -from . import artist, cbook, colors as mcolors, docstring, rcParams +from . import _api, cbook, colors as mcolors, _docstring from .artist import Artist, allow_rasterization from .cbook import ( _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP) -from .colors import is_color_like, get_named_colors_mapping from .markers import MarkerStyle from .path import Path -from .transforms import ( - Affine2D, Bbox, BboxTransformFrom, BboxTransformTo, TransformedPath) +from .transforms import Bbox, BboxTransformTo, TransformedPath +from ._enums import JoinStyle, CapStyle # Imported here for backward compatibility, even though they don't # really belong. from . import _path -from .markers import ( +from .markers import ( # noqa CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE, TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN) @@ -43,18 +42,14 @@ def _get_dash_pattern(style): # dashed styles elif style in ['dashed', 'dashdot', 'dotted']: offset = 0 - dashes = tuple(rcParams['lines.{}_pattern'.format(style)]) + dashes = tuple(mpl.rcParams[f'lines.{style}_pattern']) # elif isinstance(style, tuple): offset, dashes = style if offset is None: - cbook.warn_deprecated( - "3.3", message="Passing the dash offset as None is deprecated " - "since %(since)s and support for it will be removed " - "%(removal)s; pass it as zero instead.") - offset = 0 + raise ValueError(f'Unrecognized linestyle: {style!r}') else: - raise ValueError('Unrecognized linestyle: %s' % str(style)) + raise ValueError(f'Unrecognized linestyle: {style!r}') # normalize offset to be positive and shorter than the dash cycle if dashes is not None: @@ -65,8 +60,34 @@ def _get_dash_pattern(style): return offset, dashes +def _get_dash_patterns(styles): + """Convert linestyle or sequence of linestyles to list of dash patterns.""" + try: + patterns = [_get_dash_pattern(styles)] + except ValueError: + try: + patterns = [_get_dash_pattern(x) for x in styles] + except ValueError as err: + emsg = f'Do not know how to convert {styles!r} to dashes' + raise ValueError(emsg) from err + + return patterns + + +def _get_inverse_dash_pattern(offset, dashes): + """Return the inverse of the given dash pattern, for filling the gaps.""" + # Define the inverse pattern by moving the last gap to the start of the + # sequence. + gaps = dashes[-1:] + dashes[:-1] + # Set the offset so that this new first segment is skipped + # (see backend_bases.GraphicsContextBase.set_dashes for offset definition). + offset_gaps = offset + dashes[-1] + + return offset_gaps, gaps + + def _scale_dashes(offset, dashes, lw): - if not rcParams['lines.scale_dashes']: + if not mpl.rcParams['lines.scale_dashes']: return offset, dashes scaled_offset = offset * lw scaled_dashes = ([x * lw if x is not None else None for x in dashes] @@ -111,7 +132,7 @@ def segment_hits(cx, cy, x, y, radius): return np.concatenate((points, lines)) -def _mark_every_path(markevery, tpath, affine, ax_transform): +def _mark_every_path(markevery, tpath, affine, ax): """ Helper function that sorts out how to deal the input `markevery` and returns the points where markers should be drawn. @@ -138,7 +159,7 @@ def _slice_or_none(in_v, slc): if isinstance(markevery, tuple): if len(markevery) != 2: raise ValueError('`markevery` is a tuple but its len is not 2; ' - 'markevery={}'.format(markevery)) + f'markevery={markevery}') start, step = markevery # if step is an int, old behavior if isinstance(step, Integral): @@ -146,8 +167,8 @@ def _slice_or_none(in_v, slc): if not isinstance(start, Integral): raise ValueError( '`markevery` is a tuple with len 2 and second element is ' - 'an int, but the first element is not an int; markevery={}' - .format(markevery)) + 'an int, but the first element is not an int; ' + f'markevery={markevery}') # just return, we are done here return Path(verts[slice(start, None, step)], @@ -158,16 +179,24 @@ def _slice_or_none(in_v, slc): raise ValueError( '`markevery` is a tuple with len 2 and second element is ' 'a float, but the first element is not a float or an int; ' - 'markevery={}'.format(markevery)) + f'markevery={markevery}') + if ax is None: + raise ValueError( + "markevery is specified relative to the Axes size, but " + "the line does not have a Axes as parent") + # calc cumulative distance along path (in display coords): - disp_coords = affine.transform(tpath.vertices) + fin = np.isfinite(verts).all(axis=1) + fverts = verts[fin] + disp_coords = affine.transform(fverts) + delta = np.empty((len(disp_coords), 2)) delta[0, :] = 0 delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :] delta = np.hypot(*delta.T).cumsum() - # calc distance between markers along path based on the axes + # calc distance between markers along path based on the Axes # bounding box diagonal being a distance of unity: - (x0, y0), (x1, y1) = ax_transform.transform([[0, 0], [1, 1]]) + (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]]) scale = np.hypot(x1 - x0, y1 - y0) marker_delta = np.arange(start * scale, delta[-1], step * scale) # find closest actual data point that is closest to @@ -176,7 +205,7 @@ def _slice_or_none(in_v, slc): inds = inds.argmin(axis=1) inds = np.unique(inds) # return, we are done here - return Path(verts[inds], _slice_or_none(codes, inds)) + return Path(fverts[inds], _slice_or_none(codes, inds)) else: raise ValueError( f"markevery={markevery!r} is a tuple with len 2, but its " @@ -198,7 +227,8 @@ def _slice_or_none(in_v, slc): raise ValueError(f"markevery={markevery!r} is not a recognized value") -@cbook._define_aliases({ +@_docstring.interpd +@_api.define_aliases({ "antialiased": ["aa"], "color": ["c"], "drawstyle": ["ds"], @@ -251,8 +281,8 @@ class Line2D(Artist): fillStyles = MarkerStyle.fillstyles zorder = 2 - validCap = ('butt', 'round', 'projecting') - validJoin = ('miter', 'round', 'bevel') + + _subslice_optim_min_size = 1000 def __str__(self): if self._label != "": @@ -260,17 +290,19 @@ def __str__(self): elif self._x is None: return "Line2D()" elif len(self._x) > 3: - return "Line2D((%g,%g),(%g,%g),...,(%g,%g))" % ( - self._x[0], self._y[0], self._x[0], - self._y[0], self._x[-1], self._y[-1]) + return "Line2D(({:g},{:g}),({:g},{:g}),...,({:g},{:g}))".format( + self._x[0], self._y[0], + self._x[1], self._y[1], + self._x[-1], self._y[-1]) else: return "Line2D(%s)" % ",".join( map("({:g},{:g})".format, self._x, self._y)) - def __init__(self, xdata, ydata, + def __init__(self, xdata, ydata, *, linewidth=None, # all Nones default to rc linestyle=None, color=None, + gapcolor=None, marker=None, markersize=None, markeredgewidth=None, @@ -294,7 +326,7 @@ def __init__(self, xdata, ydata, Additional keyword arguments are `.Line2D` properties: - %(_Line2D_docstr)s + %(Line2D:kwdoc)s See :meth:`set_linestyle` for a description of the line styles, :meth:`set_marker` for a description of the markers, and @@ -303,38 +335,22 @@ def __init__(self, xdata, ydata, """ super().__init__() - #convert sequences to numpy arrays + # Convert sequences to NumPy arrays. if not np.iterable(xdata): raise RuntimeError('xdata must be a sequence') if not np.iterable(ydata): raise RuntimeError('ydata must be a sequence') - if linewidth is None: - linewidth = rcParams['lines.linewidth'] - - if linestyle is None: - linestyle = rcParams['lines.linestyle'] - if marker is None: - marker = rcParams['lines.marker'] - if markerfacecolor is None: - markerfacecolor = rcParams['lines.markerfacecolor'] - if markeredgecolor is None: - markeredgecolor = rcParams['lines.markeredgecolor'] - if color is None: - color = rcParams['lines.color'] - - if markersize is None: - markersize = rcParams['lines.markersize'] - if antialiased is None: - antialiased = rcParams['lines.antialiased'] - if dash_capstyle is None: - dash_capstyle = rcParams['lines.dash_capstyle'] - if dash_joinstyle is None: - dash_joinstyle = rcParams['lines.dash_joinstyle'] - if solid_capstyle is None: - solid_capstyle = rcParams['lines.solid_capstyle'] - if solid_joinstyle is None: - solid_joinstyle = rcParams['lines.solid_joinstyle'] + linewidth = mpl._val_or_rc(linewidth, 'lines.linewidth') + linestyle = mpl._val_or_rc(linestyle, 'lines.linestyle') + marker = mpl._val_or_rc(marker, 'lines.marker') + color = mpl._val_or_rc(color, 'lines.color') + markersize = mpl._val_or_rc(markersize, 'lines.markersize') + antialiased = mpl._val_or_rc(antialiased, 'lines.antialiased') + dash_capstyle = mpl._val_or_rc(dash_capstyle, 'lines.dash_capstyle') + dash_joinstyle = mpl._val_or_rc(dash_joinstyle, 'lines.dash_joinstyle') + solid_capstyle = mpl._val_or_rc(solid_capstyle, 'lines.solid_capstyle') + solid_joinstyle = mpl._val_or_rc(solid_joinstyle, 'lines.solid_joinstyle') if drawstyle is None: drawstyle = 'default' @@ -351,14 +367,8 @@ def __init__(self, xdata, ydata, self._linestyles = None self._drawstyle = None self._linewidth = linewidth - - # scaled dash + offset - self._dashSeq = None - self._dashOffset = 0 - # unscaled dash + offset - # this is needed scaling the dash pattern by linewidth - self._us_dashSeq = None - self._us_dashOffset = 0 + self._unscaled_dash_pattern = (0, None) # offset, dash + self._dash_pattern = (0, None) # offset, dash (scaled by linewidth) self.set_linewidth(linewidth) self.set_linestyle(linestyle) @@ -366,7 +376,15 @@ def __init__(self, xdata, ydata, self._color = None self.set_color(color) - self._marker = MarkerStyle(marker, fillstyle) + if marker is None: + marker = 'none' # Default. + if not isinstance(marker, MarkerStyle): + self._marker = MarkerStyle(marker, fillstyle) + else: + self._marker = marker + + self._gapcolor = None + self.set_gapcolor(gapcolor) self._markevery = None self._markersize = None @@ -381,16 +399,19 @@ def __init__(self, xdata, ydata, self._markerfacecolor = None self._markerfacecoloralt = None - self.set_markerfacecolor(markerfacecolor) + self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc. self.set_markerfacecoloralt(markerfacecoloralt) - self.set_markeredgecolor(markeredgecolor) + self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc. self.set_markeredgewidth(markeredgewidth) # update kwargs before updating data to give the caller a # chance to init axes (and hence unit support) - self.update(kwargs) + self._internal_update(kwargs) self.pickradius = pickradius self.ind_offset = 0 + if (isinstance(self._picker, Number) and + not isinstance(self._picker, bool)): + self._pickradius = self._picker self._xorig = np.asarray([]) self._yorig = np.asarray([]) @@ -417,7 +438,7 @@ def contains(self, mouseevent): Parameters ---------- - mouseevent : `matplotlib.backend_bases.MouseEvent` + mouseevent : `~matplotlib.backend_bases.MouseEvent` Returns ------- @@ -430,9 +451,8 @@ def contains(self, mouseevent): TODO: sort returned indices by distance """ - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} # Make sure we have data to plot if self._invalidy or self._invalidx: @@ -449,11 +469,12 @@ def contains(self, mouseevent): yt = xy[:, 1] # Convert pick radius from points to pixels - if self.figure is None: + fig = self.get_figure(root=True) + if fig is None: _log.warning('no figure set when check if mouse is on line') - pixels = self.pickradius + pixels = self._pickradius else: - pixels = self.figure.dpi / 72. * self.pickradius + pixels = fig.dpi / 72. * self._pickradius # The math involved in checking for containment (here and inside of # segment_hits) assumes that it is OK to overflow, so temporarily set @@ -484,7 +505,7 @@ def get_pickradius(self): """ return self._pickradius - def set_pickradius(self, d): + def set_pickradius(self, pickradius): """ Set the pick radius used for containment tests. @@ -492,12 +513,12 @@ def set_pickradius(self, d): Parameters ---------- - d : float + pickradius : float Pick radius, in points. """ - if not isinstance(d, Number) or d < 0: + if not isinstance(pickradius, Real) or pickradius < 0: raise ValueError("pick radius should be a distance") - self._pickradius = d + self._pickradius = pickradius pickradius = property(get_pickradius, set_pickradius) @@ -526,7 +547,7 @@ def set_fillstyle(self, fs): For examples see :ref:`marker_fill_styles`. """ - self._marker.set_fillstyle(fs) + self.set_marker(MarkerStyle(self._marker.get_marker(), fs)) self.stale = True def set_markevery(self, every): @@ -537,41 +558,43 @@ def set_markevery(self, every): Parameters ---------- - every : None or int or (int, int) or slice or List[int] or float or \ -(float, float) or List[bool] + every : None or int or (int, int) or slice or list[int] or float or \ +(float, float) or list[bool] Which markers to plot. - - every=None, every point will be plotted. - - every=N, every N-th marker will be plotted starting with + - ``every=None``: every point will be plotted. + - ``every=N``: every N-th marker will be plotted starting with marker 0. - - every=(start, N), every N-th marker, starting at point - start, will be plotted. - - every=slice(start, end, N), every N-th marker, starting at - point start, up to but not including point end, will be plotted. - - every=[i, j, m, n], only markers at points i, j, m, and n - will be plotted. - - every=[True, False, True], positions that are True will be + - ``every=(start, N)``: every N-th marker, starting at index + *start*, will be plotted. + - ``every=slice(start, end, N)``: every N-th marker, starting at + index *start*, up to but not including index *end*, will be plotted. - - every=0.1, (i.e. a float) then markers will be spaced at - approximately equal distances along the line; the distance + - ``every=[i, j, m, ...]``: only markers at the given indices + will be plotted. + - ``every=[True, False, True, ...]``: only positions that are True + will be plotted. The list must have the same length as the data + points. + - ``every=0.1``, (i.e. a float): markers will be spaced at + approximately equal visual distances along the line; the distance along the line between markers is determined by multiplying the - display-coordinate distance of the axes bounding-box diagonal - by the value of every. - - every=(0.5, 0.1) (i.e. a length-2 tuple of float), the same - functionality as every=0.1 is exhibited but the first marker will - be 0.5 multiplied by the display-coordinate-diagonal-distance - along the line. + display-coordinate distance of the Axes bounding-box diagonal + by the value of *every*. + - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar + to ``every=0.1`` but the first marker will be offset along the + line by 0.5 multiplied by the + display-coordinate-diagonal-distance along the line. For examples see :doc:`/gallery/lines_bars_and_markers/markevery_demo`. Notes ----- - Setting the markevery property will only show markers at actual data - points. When using float arguments to set the markevery property - on irregularly spaced data, the markers will likely not appear evenly - spaced because the actual data points do not coincide with the - theoretical spacing between markers. + Setting *markevery* will still only draw markers at actual data points. + While the float argument form aims for uniform visual spacing, it has + to coerce from the ideal spacing to the nearest available data point. + Depending on the number and distribution of data points, the result + may still not look evenly spaced. When using a start offset to specify the first marker, the offset will be from the first data point which may be different from the first @@ -595,40 +618,35 @@ def get_markevery(self): return self._markevery def set_picker(self, p): - # docstring inherited - if isinstance(p, Number) and not isinstance(p, bool): - # After deprecation, the whole method can be deleted and inherited. - cbook.warn_deprecated( - "3.3", message="Setting the line's pick radius via set_picker " - "is deprecated since %(since)s and will be removed " - "%(removal)s; use set_pickradius instead.") - self.pickradius = p + """ + Set the event picker details for the line. + + Parameters + ---------- + p : float or callable[[Artist, Event], tuple[bool, dict]] + If a float, it is used as the pick radius in points. + """ + if not callable(p): + self.set_pickradius(p) self._picker = p - def get_window_extent(self, renderer): + def get_bbox(self): + """Get the bounding box of this line.""" + bbox = Bbox([[0, 0], [0, 0]]) + bbox.update_from_data_xy(self.get_xydata()) + return bbox + + def get_window_extent(self, renderer=None): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()), ignore=True) # correct for marker size, if any if self._marker: - ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 + ms = (self._markersize / 72.0 * self.get_figure(root=True).dpi) * 0.5 bbox = bbox.padded(ms) return bbox - @Artist.axes.setter - def axes(self, ax): - # call the set method from the base-class property - Artist.axes.fset(self, ax) - if ax is not None: - # connect unit-related callbacks - if ax.xaxis is not None: - self._xcid = ax.xaxis.callbacks.connect('units', - self.recache_always) - if ax.yaxis is not None: - self._ycid = ax.yaxis.callbacks.connect('units', - self.recache_always) - def set_data(self, *args): """ Set the x and y data. @@ -636,6 +654,11 @@ def set_data(self, *args): Parameters ---------- *args : (2, N) array or two 1D arrays + + See Also + -------- + set_xdata + set_ydata """ if len(args) == 1: (x, y), = args @@ -664,11 +687,14 @@ def recache(self, always=False): self._x, self._y = self._xy.T # views self._subslice = False - if (self.axes and len(x) > 1000 and self._is_sorted(x) and - self.axes.name == 'rectilinear' and - self.axes.get_xscale() == 'linear' and - self._markevery is None and - self.get_clip_on()): + if (self.axes + and len(x) > self._subslice_optim_min_size + and _path.is_sorted_and_has_non_nan(x) + and self.axes.name == 'rectilinear' + and self.axes.get_xscale() == 'linear' + and self._markevery is None + and self.get_clip_on() + and self.get_transform() == self.axes.transData): self._subslice = True nanmask = np.isnan(x) if nanmask.any(): @@ -692,7 +718,7 @@ def recache(self, always=False): def _transform_path(self, subslice=None): """ - Puts a TransformedPath instance at self._transformed_path; + Put a TransformedPath instance at self._transformed_path; all invalidation of the transform is then handled by the TransformedPath instance. """ @@ -706,31 +732,16 @@ def _transform_path(self, subslice=None): self._transformed_path = TransformedPath(_path, self.get_transform()) def _get_transformed_path(self): - """ - Return the :class:`~matplotlib.transforms.TransformedPath` instance - of this line. - """ + """Return this line's `~matplotlib.transforms.TransformedPath`.""" if self._transformed_path is None: self._transform_path() return self._transformed_path def set_transform(self, t): - """ - Set the Transformation instance used by this artist. - - Parameters - ---------- - t : `matplotlib.transforms.Transform` - """ - super().set_transform(t) + # docstring inherited self._invalidx = True self._invalidy = True - self.stale = True - - def _is_sorted(self, x): - """Return whether x is sorted in ascending order.""" - # We don't handle the monotonically decreasing case. - return _path.is_sorted(x) + super().set_transform(t) @allow_rasterization def draw(self, renderer): @@ -765,9 +776,6 @@ def draw(self, renderer): self._set_gc_clip(gc) gc.set_url(self.get_url()) - lc_rgba = mcolors.to_rgba(self._color, self._alpha) - gc.set_foreground(lc_rgba, isRGBA=True) - gc.set_antialiased(self._antialiased) gc.set_linewidth(self._linewidth) @@ -783,7 +791,21 @@ def draw(self, renderer): if self.get_sketch_params() is not None: gc.set_sketch_params(*self.get_sketch_params()) - gc.set_dashes(self._dashOffset, self._dashSeq) + # We first draw a path within the gaps if needed. + if self.is_dashed() and self._gapcolor is not None: + lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha) + gc.set_foreground(lc_rgba, isRGBA=True) + + offset_gaps, gaps = _get_inverse_dash_pattern( + *self._dash_pattern) + + gc.set_dashes(offset_gaps, gaps) + renderer.draw_path(gc, tpath, affine.frozen()) + + lc_rgba = mcolors.to_rgba(self._color, self._alpha) + gc.set_foreground(lc_rgba, isRGBA=True) + + gc.set_dashes(*self._dash_pattern) renderer.draw_path(gc, tpath, affine.frozen()) gc.restore() @@ -830,8 +852,8 @@ def draw(self, renderer): # subsample the markers if markevery is not None markevery = self.get_markevery() if markevery is not None: - subsampled = _mark_every_path(markevery, tpath, - affine, self.axes.transAxes) + subsampled = _mark_every_path( + markevery, tpath, affine, self.axes) else: subsampled = tpath @@ -887,6 +909,14 @@ def get_drawstyle(self): """ return self._drawstyle + def get_gapcolor(self): + """ + Return the line gapcolor. + + See also `~.Line2D.set_gapcolor`. + """ + return self._gapcolor + def get_linestyle(self): """ Return the linestyle. @@ -919,10 +949,11 @@ def get_markeredgecolor(self): """ mec = self._markeredgecolor if cbook._str_equal(mec, 'auto'): - if rcParams['_internal.classic_mode']: + if mpl.rcParams['_internal.classic_mode']: if self._marker.get_marker() in ('.', ','): return self._color - if self._marker.is_filled() and self.get_fillstyle() != 'none': + if (self._marker.is_filled() + and self._marker.get_fillstyle() != 'none'): return 'k' # Bad hard-wired default... return self._color else: @@ -937,7 +968,7 @@ def get_markeredgewidth(self): return self._markeredgewidth def _get_markerfacecolor(self, alt=False): - if self.get_fillstyle() == 'none': + if self._marker.get_fillstyle() == 'none': return 'none' fc = self._markerfacecoloralt if alt else self._markerfacecolor if cbook._str_lower_equal(fc, 'auto'): @@ -971,7 +1002,7 @@ def get_markersize(self): def get_data(self, orig=True): """ - Return the xdata, ydata. + Return the line data as an ``(xdata, ydata)`` pair. If *orig* is *True*, return the original data. """ @@ -1004,18 +1035,13 @@ def get_ydata(self, orig=True): return self._y def get_path(self): - """ - Return the :class:`~matplotlib.path.Path` object associated - with this line. - """ + """Return the `~matplotlib.path.Path` associated with this line.""" if self._invalidy or self._invalidx: self.recache() return self._path def get_xydata(self): - """ - Return the *xy* data as a Nx2 numpy array. - """ + """Return the *xy* data as a (N, 2) array.""" if self._invalidy or self._invalidx: self.recache() return self._xy @@ -1038,11 +1064,9 @@ def set_color(self, color): Parameters ---------- - color : color + color : :mpltype:`color` """ - if not is_color_like(color) and color != 'auto': - cbook._check_in_list(get_named_colors_mapping(), - _print_supported_values=False, color=color) + mcolors._check_color_like(color=color) self._color = color self.stale = True @@ -1074,13 +1098,36 @@ def set_drawstyle(self, drawstyle): """ if drawstyle is None: drawstyle = 'default' - cbook._check_in_list(self.drawStyles, drawstyle=drawstyle) + _api.check_in_list(self.drawStyles, drawstyle=drawstyle) if self._drawstyle != drawstyle: self.stale = True # invalidate to trigger a recache of the path self._invalidx = True self._drawstyle = drawstyle + def set_gapcolor(self, gapcolor): + """ + Set a color to fill the gaps in the dashed line style. + + .. note:: + + Striped lines are created by drawing two interleaved dashed lines. + There can be overlaps between those two, which may result in + artifacts when using transparency. + + This functionality is experimental and may change. + + Parameters + ---------- + gapcolor : :mpltype:`color` or None + The color with which to fill the gaps. If None, the gaps are + unfilled. + """ + if gapcolor is not None: + mcolors._check_color_like(color=gapcolor) + self._gapcolor = gapcolor + self.stale = True + def set_linewidth(self, w): """ Set the line width in points. @@ -1091,13 +1138,10 @@ def set_linewidth(self, w): Line width, in points. """ w = float(w) - if self._linewidth != w: self.stale = True self._linewidth = w - # rescale the dashes + offset - self._dashOffset, self._dashSeq = _scale_dashes( - self._us_dashOffset, self._us_dashSeq, self._linewidth) + self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w) def set_linestyle(self, ls): """ @@ -1110,15 +1154,15 @@ def set_linestyle(self, ls): - A string: - =============================== ================= - Linestyle Description - =============================== ================= - ``'-'`` or ``'solid'`` solid line - ``'--'`` or ``'dashed'`` dashed line - ``'-.'`` or ``'dashdot'`` dash-dotted line - ``':'`` or ``'dotted'`` dotted line - ``'None'`` or ``' '`` or ``''`` draw nothing - =============================== ================= + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ - Alternatively a dash tuple of the following form can be provided:: @@ -1133,21 +1177,18 @@ def set_linestyle(self, ls): if isinstance(ls, str): if ls in [' ', '', 'none']: ls = 'None' - - cbook._check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls) + _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls) if ls not in self._lineStyles: ls = ls_mapper_r[ls] self._linestyle = ls else: self._linestyle = '--' + self._unscaled_dash_pattern = _get_dash_pattern(ls) + self._dash_pattern = _scale_dashes( + *self._unscaled_dash_pattern, self._linewidth) + self.stale = True - # get the unscaled dashes - self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls) - # compute the linewidth scaled dashes - self._dashOffset, self._dashSeq = _scale_dashes( - self._us_dashOffset, self._us_dashSeq, self._linewidth) - - @docstring.dedent_interpd + @_docstring.interpd def set_marker(self, marker): """ Set the line marker. @@ -1158,66 +1199,66 @@ def set_marker(self, marker): See `~matplotlib.markers` for full description of possible arguments. """ - self._marker.set_marker(marker) + self._marker = MarkerStyle(marker, self._marker.get_fillstyle()) self.stale = True + def _set_markercolor(self, name, has_rcdefault, val): + if val is None: + val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto" + attr = f"_{name}" + current = getattr(self, attr) + if current is None: + self.stale = True + else: + neq = current != val + # Much faster than `np.any(current != val)` if no arrays are used. + if neq.any() if isinstance(neq, np.ndarray) else neq: + self.stale = True + setattr(self, attr, val) + def set_markeredgecolor(self, ec): """ Set the marker edge color. Parameters ---------- - ec : color + ec : :mpltype:`color` """ - if ec is None: - ec = 'auto' - if (self._markeredgecolor is None - or np.any(self._markeredgecolor != ec)): - self.stale = True - self._markeredgecolor = ec + self._set_markercolor("markeredgecolor", True, ec) - def set_markeredgewidth(self, ew): + def set_markerfacecolor(self, fc): """ - Set the marker edge width in points. + Set the marker face color. Parameters ---------- - ew : float - Marker edge width, in points. + fc : :mpltype:`color` """ - if ew is None: - ew = rcParams['lines.markeredgewidth'] - if self._markeredgewidth != ew: - self.stale = True - self._markeredgewidth = ew + self._set_markercolor("markerfacecolor", True, fc) - def set_markerfacecolor(self, fc): + def set_markerfacecoloralt(self, fc): """ - Set the marker face color. + Set the alternate marker face color. Parameters ---------- - fc : color + fc : :mpltype:`color` """ - if fc is None: - fc = 'auto' - if np.any(self._markerfacecolor != fc): - self.stale = True - self._markerfacecolor = fc + self._set_markercolor("markerfacecoloralt", False, fc) - def set_markerfacecoloralt(self, fc): + def set_markeredgewidth(self, ew): """ - Set the alternate marker face color. + Set the marker edge width in points. Parameters ---------- - fc : color + ew : float + Marker edge width, in points. """ - if fc is None: - fc = 'auto' - if np.any(self._markerfacecoloralt != fc): + ew = mpl._val_or_rc(ew, 'lines.markeredgewidth') + if self._markeredgewidth != ew: self.stale = True - self._markerfacecoloralt = fc + self._markeredgewidth = ew def set_markersize(self, sz): """ @@ -1240,8 +1281,15 @@ def set_xdata(self, x): Parameters ---------- x : 1D array + + See Also + -------- + set_data + set_ydata """ - self._xorig = x + if not np.iterable(x): + raise RuntimeError('x must be a sequence') + self._xorig = copy.copy(x) self._invalidx = True self.stale = True @@ -1252,8 +1300,15 @@ def set_ydata(self, y): Parameters ---------- y : 1D array + + See Also + -------- + set_data + set_xdata """ - self._yorig = y + if not np.iterable(y): + raise RuntimeError('y must be a sequence') + self._yorig = copy.copy(y) self._invalidy = True self.stale = True @@ -1267,6 +1322,9 @@ def set_dashes(self, seq): For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point dashes separated by 2 point spaces. + See also `~.Line2D.set_gapcolor`, which allows those spaces to be + filled with a color. + Parameters ---------- seq : sequence of floats (on/off ink in points) or (None, None) @@ -1284,172 +1342,298 @@ def update_from(self, other): self._linestyle = other._linestyle self._linewidth = other._linewidth self._color = other._color + self._gapcolor = other._gapcolor self._markersize = other._markersize self._markerfacecolor = other._markerfacecolor self._markerfacecoloralt = other._markerfacecoloralt self._markeredgecolor = other._markeredgecolor self._markeredgewidth = other._markeredgewidth - self._dashSeq = other._dashSeq - self._us_dashSeq = other._us_dashSeq - self._dashOffset = other._dashOffset - self._us_dashOffset = other._us_dashOffset + self._unscaled_dash_pattern = other._unscaled_dash_pattern + self._dash_pattern = other._dash_pattern self._dashcapstyle = other._dashcapstyle self._dashjoinstyle = other._dashjoinstyle self._solidcapstyle = other._solidcapstyle self._solidjoinstyle = other._solidjoinstyle - self._linestyle = other._linestyle - self._marker = MarkerStyle(other._marker.get_marker(), - other._marker.get_fillstyle()) + self._marker = MarkerStyle(marker=other._marker) self._drawstyle = other._drawstyle + @_docstring.interpd def set_dash_joinstyle(self, s): """ - Set the join style for dashed lines. + How to join segments of the line if it `~Line2D.is_dashed`. + + The default joinstyle is :rc:`lines.dash_joinstyle`. Parameters ---------- - s : {'miter', 'round', 'bevel'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.JoinStyle` or %(JoinStyle)s """ - mpl.rcsetup.validate_joinstyle(s) - if self._dashjoinstyle != s: + js = JoinStyle(s) + if self._dashjoinstyle != js: self.stale = True - self._dashjoinstyle = s + self._dashjoinstyle = js + @_docstring.interpd def set_solid_joinstyle(self, s): """ - Set the join style for solid lines. + How to join segments if the line is solid (not `~Line2D.is_dashed`). + + The default joinstyle is :rc:`lines.solid_joinstyle`. Parameters ---------- - s : {'miter', 'round', 'bevel'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.JoinStyle` or %(JoinStyle)s """ - mpl.rcsetup.validate_joinstyle(s) - if self._solidjoinstyle != s: + js = JoinStyle(s) + if self._solidjoinstyle != js: self.stale = True - self._solidjoinstyle = s + self._solidjoinstyle = js def get_dash_joinstyle(self): """ - Return the join style for dashed lines. + Return the `.JoinStyle` for dashed lines. See also `~.Line2D.set_dash_joinstyle`. """ - return self._dashjoinstyle + return self._dashjoinstyle.name def get_solid_joinstyle(self): """ - Return the join style for solid lines. + Return the `.JoinStyle` for solid lines. See also `~.Line2D.set_solid_joinstyle`. """ - return self._solidjoinstyle + return self._solidjoinstyle.name + @_docstring.interpd def set_dash_capstyle(self, s): """ - Set the cap style for dashed lines. + How to draw the end caps if the line is `~Line2D.is_dashed`. + + The default capstyle is :rc:`lines.dash_capstyle`. Parameters ---------- - s : {'butt', 'round', 'projecting'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.CapStyle` or %(CapStyle)s """ - mpl.rcsetup.validate_capstyle(s) - if self._dashcapstyle != s: + cs = CapStyle(s) + if self._dashcapstyle != cs: self.stale = True - self._dashcapstyle = s + self._dashcapstyle = cs + @_docstring.interpd def set_solid_capstyle(self, s): """ - Set the cap style for solid lines. + How to draw the end caps if the line is solid (not `~Line2D.is_dashed`) + + The default capstyle is :rc:`lines.solid_capstyle`. Parameters ---------- - s : {'butt', 'round', 'projecting'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.CapStyle` or %(CapStyle)s """ - mpl.rcsetup.validate_capstyle(s) - if self._solidcapstyle != s: + cs = CapStyle(s) + if self._solidcapstyle != cs: self.stale = True - self._solidcapstyle = s + self._solidcapstyle = cs def get_dash_capstyle(self): """ - Return the cap style for dashed lines. + Return the `.CapStyle` for dashed lines. See also `~.Line2D.set_dash_capstyle`. """ - return self._dashcapstyle + return self._dashcapstyle.name def get_solid_capstyle(self): """ - Return the cap style for solid lines. + Return the `.CapStyle` for solid lines. See also `~.Line2D.set_solid_capstyle`. """ - return self._solidcapstyle + return self._solidcapstyle.name def is_dashed(self): """ Return whether line has a dashed linestyle. + A custom linestyle is assumed to be dashed, we do not inspect the + ``onoffseq`` directly. + See also `~.Line2D.set_linestyle`. """ return self._linestyle in ('--', '-.', ':') -class _AxLine(Line2D): +class AxLine(Line2D): """ A helper class that implements `~.Axes.axline`, by recomputing the artist transform at draw time. """ + def __init__(self, xy1, xy2, slope, **kwargs): + """ + Parameters + ---------- + xy1 : (float, float) + The first set of (x, y) coordinates for the line to pass through. + xy2 : (float, float) or None + The second set of (x, y) coordinates for the line to pass through. + Both *xy2* and *slope* must be passed, but one of them must be None. + slope : float or None + The slope of the line. Both *xy2* and *slope* must be passed, but one of + them must be None. + """ + super().__init__([0, 1], [0, 1], **kwargs) + + if (xy2 is None and slope is None or + xy2 is not None and slope is not None): + raise TypeError( + "Exactly one of 'xy2' and 'slope' must be given") + + self._slope = slope + self._xy1 = xy1 + self._xy2 = xy2 + def get_transform(self): ax = self.axes - (x1, y1), (x2, y2) = ax.transScale.transform([*zip(*self.get_data())]) - dx = x2 - x1 - dy = y2 - y1 - if np.allclose(x1, x2): - if np.allclose(y1, y2): - raise ValueError( - f"Cannot draw a line through two identical points " - f"(x={self.get_xdata()}, y={self.get_ydata()})") - # First send y1 to 0 and y2 to 1. - return (Affine2D.from_values(1, 0, 0, 1 / dy, 0, -y1 / dy) - + ax.get_xaxis_transform(which="grid")) - if np.allclose(y1, y2): - # First send x1 to 0 and x2 to 1. - return (Affine2D.from_values(1 / dx, 0, 0, 1, -x1 / dx, 0) - + ax.get_yaxis_transform(which="grid")) + points_transform = self._transform - ax.transData + ax.transScale + + if self._xy2 is not None: + # two points were given + (x1, y1), (x2, y2) = \ + points_transform.transform([self._xy1, self._xy2]) + dx = x2 - x1 + dy = y2 - y1 + if dx == 0: + if dy == 0: + raise ValueError( + f"Cannot draw a line through two identical points " + f"(x={(x1, x2)}, y={(y1, y2)})") + slope = np.inf + else: + slope = dy / dx + else: + # one point and a slope were given + x1, y1 = points_transform.transform(self._xy1) + slope = self._slope (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) # General case: find intersections with view limits in either # direction, and draw between the middle two points. - _, start, stop, _ = sorted([ - (vxlo, y1 + (vxlo - x1) * dy / dx), - (vxhi, y1 + (vxhi - x1) * dy / dx), - (x1 + (vylo - y1) * dx / dy, vylo), - (x1 + (vyhi - y1) * dx / dy, vyhi), - ]) - return (BboxTransformFrom(Bbox([*zip(*self.get_data())])) - + BboxTransformTo(Bbox([start, stop])) + if slope == 0: + start = vxlo, y1 + stop = vxhi, y1 + elif np.isinf(slope): + start = x1, vylo + stop = x1, vyhi + else: + _, start, stop, _ = sorted([ + (vxlo, y1 + (vxlo - x1) * slope), + (vxhi, y1 + (vxhi - x1) * slope), + (x1 + (vylo - y1) / slope, vylo), + (x1 + (vyhi - y1) / slope, vyhi), + ]) + return (BboxTransformTo(Bbox([start, stop])) + ax.transLimits + ax.transAxes) def draw(self, renderer): self._transformed_path = None # Force regen. super().draw(renderer) + def get_xy1(self): + """Return the *xy1* value of the line.""" + return self._xy1 + + def get_xy2(self): + """Return the *xy2* value of the line.""" + return self._xy2 + + def get_slope(self): + """Return the *slope* value of the line.""" + return self._slope + + def set_xy1(self, *args, **kwargs): + """ + Set the *xy1* value of the line. + + Parameters + ---------- + xy1 : tuple[float, float] + Points for the line to pass through. + """ + params = _api.select_matching_signature([ + lambda self, x, y: locals(), lambda self, xy1: locals(), + ], self, *args, **kwargs) + if "x" in params: + _api.warn_deprecated("3.10", message=( + "Passing x and y separately to AxLine.set_xy1 is deprecated since " + "%(since)s; pass them as a single tuple instead.")) + xy1 = params["x"], params["y"] + else: + xy1 = params["xy1"] + self._xy1 = xy1 + + def set_xy2(self, *args, **kwargs): + """ + Set the *xy2* value of the line. + + .. note:: + + You can only set *xy2* if the line was created using the *xy2* + parameter. If the line was created using *slope*, please use + `~.AxLine.set_slope`. + + Parameters + ---------- + xy2 : tuple[float, float] + Points for the line to pass through. + """ + if self._slope is None: + params = _api.select_matching_signature([ + lambda self, x, y: locals(), lambda self, xy2: locals(), + ], self, *args, **kwargs) + if "x" in params: + _api.warn_deprecated("3.10", message=( + "Passing x and y separately to AxLine.set_xy2 is deprecated since " + "%(since)s; pass them as a single tuple instead.")) + xy2 = params["x"], params["y"] + else: + xy2 = params["xy2"] + self._xy2 = xy2 + else: + raise ValueError("Cannot set an 'xy2' value while 'slope' is set;" + " they differ but their functionalities overlap") + + def set_slope(self, slope): + """ + Set the *slope* value of the line. + + .. note:: + + You can only set *slope* if the line was created using the *slope* + parameter. If the line was created using *xy2*, please use + `~.AxLine.set_xy2`. + + Parameters + ---------- + slope : float + The slope of the line. + """ + if self._xy2 is None: + self._slope = slope + else: + raise ValueError("Cannot set a 'slope' value while 'xy2' is set;" + " they differ but their functionalities overlap") + class VertexSelector: """ - Manage the callbacks to maintain a list of selected vertices for - `.Line2D`. Derived classes should override - :meth:`~matplotlib.lines.VertexSelector.process_selected` to do + Manage the callbacks to maintain a list of selected vertices for `.Line2D`. + Derived classes should override the `process_selected` method to do something with the picks. - Here is an example which highlights the selected verts with red - circles:: + Here is an example which highlights the selected verts with red circles:: import numpy as np import matplotlib.pyplot as plt @@ -1457,7 +1641,7 @@ class VertexSelector: class HighlightSelected(lines.VertexSelector): def __init__(self, line, fmt='ro', **kwargs): - lines.VertexSelector.__init__(self, line) + super().__init__(line) self.markers, = self.axes.plot([], [], fmt, **kwargs) def process_selected(self, ind, xs, ys): @@ -1470,32 +1654,32 @@ def process_selected(self, ind, xs, ys): selector = HighlightSelected(line) plt.show() - """ + def __init__(self, line): """ - Initialize the class with a `.Line2D` instance. The line should - already be added to some :class:`matplotlib.axes.Axes` instance and - should have the picker property set. + Parameters + ---------- + line : `~matplotlib.lines.Line2D` + The line must already have been added to an `~.axes.Axes` and must + have its picker property set. """ if line.axes is None: raise RuntimeError('You must first add the line to the Axes') - if line.get_picker() is None: raise RuntimeError('You must first set the picker property ' 'of the line') - self.axes = line.axes self.line = line - self.canvas = self.axes.figure.canvas - self.cid = self.canvas.mpl_connect('pick_event', self.onpick) - + self.cid = self.canvas.callbacks._connect_picklable( + 'pick_event', self.onpick) self.ind = set() + canvas = property(lambda self: self.axes.get_figure(root=True).canvas) + def process_selected(self, ind, xs, ys): """ - Default "do nothing" implementation of the - :meth:`process_selected` method. + Default "do nothing" implementation of the `process_selected` method. Parameters ---------- @@ -1520,9 +1704,3 @@ def onpick(self, event): lineMarkers = MarkerStyle.markers drawStyles = Line2D.drawStyles fillStyles = MarkerStyle.fillstyles - -docstring.interpd.update(_Line2D_docstr=artist.kwdoc(Line2D)) - -# You can not set the docstring of an instancemethod, -# but you can on the underlying function. Go figure. -docstring.dedent_interpd(Line2D.__init__) diff --git a/lib/matplotlib/lines.pyi b/lib/matplotlib/lines.pyi new file mode 100644 index 000000000000..7989a03dae3a --- /dev/null +++ b/lib/matplotlib/lines.pyi @@ -0,0 +1,153 @@ +from .artist import Artist +from .axes import Axes +from .backend_bases import MouseEvent, FigureCanvasBase +from .path import Path +from .transforms import Bbox + +from collections.abc import Callable, Sequence +from typing import Any, Literal, overload +from .typing import ( + ColorType, + DrawStyleType, + FillStyleType, + LineStyleType, + CapStyleType, + JoinStyleType, + MarkEveryType, + MarkerType, +) +from numpy.typing import ArrayLike + +def segment_hits( + cx: ArrayLike, cy: ArrayLike, x: ArrayLike, y: ArrayLike, radius: ArrayLike +) -> ArrayLike: ... + +class Line2D(Artist): + lineStyles: dict[str, str] + drawStyles: dict[str, str] + drawStyleKeys: list[str] + markers: dict[str | int, str] + filled_markers: tuple[str, ...] + fillStyles: tuple[str, ...] + zorder: float + ind_offset: float + def __init__( + self, + xdata: ArrayLike, + ydata: ArrayLike, + *, + linewidth: float | None = ..., + linestyle: LineStyleType | None = ..., + color: ColorType | None = ..., + gapcolor: ColorType | None = ..., + marker: MarkerType | None = ..., + markersize: float | None = ..., + markeredgewidth: float | None = ..., + markeredgecolor: ColorType | None = ..., + markerfacecolor: ColorType | None = ..., + markerfacecoloralt: ColorType = ..., + fillstyle: FillStyleType | None = ..., + antialiased: bool | None = ..., + dash_capstyle: CapStyleType | None = ..., + solid_capstyle: CapStyleType | None = ..., + dash_joinstyle: JoinStyleType | None = ..., + solid_joinstyle: JoinStyleType | None = ..., + pickradius: float = ..., + drawstyle: DrawStyleType | None = ..., + markevery: MarkEveryType | None = ..., + **kwargs + ) -> None: ... + def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict]: ... + def get_pickradius(self) -> float: ... + def set_pickradius(self, pickradius: float) -> None: ... + pickradius: float + def get_fillstyle(self) -> FillStyleType: ... + stale: bool + def set_fillstyle(self, fs: FillStyleType) -> None: ... + def set_markevery(self, every: MarkEveryType) -> None: ... + def get_markevery(self) -> MarkEveryType: ... + def set_picker( + self, p: None | bool | float | Callable[[Artist, MouseEvent], tuple[bool, dict]] + ) -> None: ... + def get_bbox(self) -> Bbox: ... + @overload + def set_data(self, args: ArrayLike) -> None: ... + @overload + def set_data(self, x: ArrayLike, y: ArrayLike) -> None: ... + def recache_always(self) -> None: ... + def recache(self, always: bool = ...) -> None: ... + def get_antialiased(self) -> bool: ... + def get_color(self) -> ColorType: ... + def get_drawstyle(self) -> DrawStyleType: ... + def get_gapcolor(self) -> ColorType: ... + def get_linestyle(self) -> LineStyleType: ... + def get_linewidth(self) -> float: ... + def get_marker(self) -> MarkerType: ... + def get_markeredgecolor(self) -> ColorType: ... + def get_markeredgewidth(self) -> float: ... + def get_markerfacecolor(self) -> ColorType: ... + def get_markerfacecoloralt(self) -> ColorType: ... + def get_markersize(self) -> float: ... + def get_data(self, orig: bool = ...) -> tuple[ArrayLike, ArrayLike]: ... + def get_xdata(self, orig: bool = ...) -> ArrayLike: ... + def get_ydata(self, orig: bool = ...) -> ArrayLike: ... + def get_path(self) -> Path: ... + def get_xydata(self) -> ArrayLike: ... + def set_antialiased(self, b: bool) -> None: ... + def set_color(self, color: ColorType) -> None: ... + def set_drawstyle(self, drawstyle: DrawStyleType | None) -> None: ... + def set_gapcolor(self, gapcolor: ColorType | None) -> None: ... + def set_linewidth(self, w: float) -> None: ... + def set_linestyle(self, ls: LineStyleType) -> None: ... + def set_marker(self, marker: MarkerType) -> None: ... + def set_markeredgecolor(self, ec: ColorType | None) -> None: ... + def set_markerfacecolor(self, fc: ColorType | None) -> None: ... + def set_markerfacecoloralt(self, fc: ColorType | None) -> None: ... + def set_markeredgewidth(self, ew: float | None) -> None: ... + def set_markersize(self, sz: float) -> None: ... + def set_xdata(self, x: ArrayLike) -> None: ... + def set_ydata(self, y: ArrayLike) -> None: ... + def set_dashes(self, seq: Sequence[float] | tuple[None, None]) -> None: ... + def update_from(self, other: Artist) -> None: ... + def set_dash_joinstyle(self, s: JoinStyleType) -> None: ... + def set_solid_joinstyle(self, s: JoinStyleType) -> None: ... + def get_dash_joinstyle(self) -> Literal["miter", "round", "bevel"]: ... + def get_solid_joinstyle(self) -> Literal["miter", "round", "bevel"]: ... + def set_dash_capstyle(self, s: CapStyleType) -> None: ... + def set_solid_capstyle(self, s: CapStyleType) -> None: ... + def get_dash_capstyle(self) -> Literal["butt", "projecting", "round"]: ... + def get_solid_capstyle(self) -> Literal["butt", "projecting", "round"]: ... + def is_dashed(self) -> bool: ... + +class AxLine(Line2D): + def __init__( + self, + xy1: tuple[float, float], + xy2: tuple[float, float] | None, + slope: float | None, + **kwargs + ) -> None: ... + def get_xy1(self) -> tuple[float, float] | None: ... + def get_xy2(self) -> tuple[float, float] | None: ... + def get_slope(self) -> float: ... + def set_xy1(self, xy1: tuple[float, float]) -> None: ... + def set_xy2(self, xy2: tuple[float, float]) -> None: ... + def set_slope(self, slope: float) -> None: ... + +class VertexSelector: + axes: Axes + line: Line2D + cid: int + ind: set[int] + def __init__(self, line: Line2D) -> None: ... + @property + def canvas(self) -> FigureCanvasBase: ... + def process_selected( + self, ind: Sequence[int], xs: ArrayLike, ys: ArrayLike + ) -> None: ... + def onpick(self, event: Any) -> None: ... + +lineStyles: dict[str, str] +lineMarkers: dict[str | int, str] +drawStyles: dict[str, str] +fillStyles: tuple[FillStyleType, ...] diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index ed3d3b18583a..9c6b3da73bcd 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -1,6 +1,7 @@ r""" Functions to handle markers; used by the marker functionality of -`~matplotlib.axes.Axes.plot` and `~matplotlib.axes.Axes.scatter`. +`~matplotlib.axes.Axes.plot`, `~matplotlib.axes.Axes.scatter`, and +`~matplotlib.axes.Axes.errorbar`. All possible markers are defined here: @@ -44,8 +45,9 @@ ``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base) ``10`` (``CARETUPBASE``) |m35| caretup (centered at base) ``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base) -``"None"``, ``" "`` or ``""`` nothing -``'$...$'`` |m37| Render the string using mathtext. +``"none"`` or ``"None"`` nothing +``" "`` or ``""`` nothing +``"$...$"`` |m37| Render the string using mathtext. E.g ``"$f$"`` for marker showing the letter ``f``. ``verts`` A list of (x, y) pairs used for Path @@ -53,7 +55,7 @@ located at (0, 0) and the size is normalized, such that the created path is encapsulated inside the unit cell. -path A `~matplotlib.path.Path` instance. +``path`` A `~matplotlib.path.Path` instance. ``(numsides, 0, angle)`` A regular polygon with ``numsides`` sides, rotated by ``angle``. ``(numsides, 1, angle)`` A star-like symbol with ``numsides`` @@ -62,12 +64,8 @@ rotated by ``angle``. ============================== ====== ========================================= -``None`` is the default which means 'nothing', however this table is -referred to from other docs for the valid inputs from marker inputs and in -those cases ``None`` still means 'default'. - Note that special symbols can be defined via the -:doc:`STIX math font `, +:ref:`STIX math font `, e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the `STIX font table `_. Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`. @@ -79,12 +77,16 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) +Markers join and cap styles can be customized by creating a new instance of +MarkerStyle. +A MarkerStyle can also have a custom `~matplotlib.transforms.Transform` +allowing it to be arbitrarily rotated or offset. + Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` -* :doc:`/gallery/shapes_and_collections/marker_path` * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly` - +* :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot` .. |m00| image:: /_static/markers/m00.png .. |m01| image:: /_static/markers/m01.png @@ -125,14 +127,17 @@ .. |m36| image:: /_static/markers/m36.png .. |m37| image:: /_static/markers/m37.png """ +import copy from collections.abc import Sized import numpy as np -from . import cbook, rcParams +import matplotlib as mpl +from . import _api, cbook from .path import Path from .transforms import IdentityTransform, Affine2D +from ._enums import JoinStyle, CapStyle # special-purpose marker identifiers: (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, @@ -146,13 +151,16 @@ class MarkerStyle: """ A class representing marker types. + Instances are immutable. If you need to change anything, create a new + instance. + Attributes ---------- - markers : list + markers : dict All known markers. - filled_markers : list + filled_markers : tuple All known filled markers. This is a subset of *markers*. - fillstyles : list + fillstyles : tuple The supported fillstyles. """ @@ -195,7 +203,7 @@ class MarkerStyle: CARETUPBASE: 'caretupbase', CARETDOWNBASE: 'caretdownbase', "None": 'nothing', - None: 'nothing', + "none": 'nothing', ' ': 'nothing', '': 'nothing' } @@ -203,29 +211,41 @@ class MarkerStyle: # Just used for informational purposes. is_filled() # is calculated in the _set_* functions. filled_markers = ( - 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', + '.', 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X') fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none') _half_fillstyles = ('left', 'right', 'bottom', 'top') - # TODO: Is this ever used as a non-constant? - _point_size_reduction = 0.5 - - def __init__(self, marker=None, fillstyle=None): + def __init__(self, marker, + fillstyle=None, transform=None, capstyle=None, joinstyle=None): """ Parameters ---------- - marker : str or array-like or None, default: None - *None* means no marker. For other possible marker values see the - module docstring `matplotlib.markers`. + marker : str, array-like, Path, MarkerStyle + - Another instance of `MarkerStyle` copies the details of that *marker*. + - For other possible marker values, see the module docstring + `matplotlib.markers`. - fillstyle : str, default: 'full' + fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. + + transform : `~matplotlib.transforms.Transform`, optional + Transform that will be combined with the native transform of the + marker. + + capstyle : `.CapStyle` or %(CapStyle)s, optional + Cap style that will override the default cap style of the marker. + + joinstyle : `.JoinStyle` or %(JoinStyle)s, optional + Join style that will override the default join style of the marker. """ self._marker_function = None - self.set_fillstyle(fillstyle) - self.set_marker(marker) + self._user_transform = transform + self._user_capstyle = CapStyle(capstyle) if capstyle is not None else None + self._user_joinstyle = JoinStyle(joinstyle) if joinstyle is not None else None + self._set_fillstyle(fillstyle) + self._set_marker(marker) def _recache(self): if self._marker_function is None: @@ -235,8 +255,8 @@ def _recache(self): self._alt_path = None self._alt_transform = None self._snap_threshold = None - self._joinstyle = 'round' - self._capstyle = 'butt' + self._joinstyle = JoinStyle.round + self._capstyle = self._user_capstyle or CapStyle.butt # Initial guess: Assume the marker is filled unless the fillstyle is # set to 'none'. The marker function will override this for unfilled # markers. @@ -252,7 +272,7 @@ def is_filled(self): def get_fillstyle(self): return self._fillstyle - def set_fillstyle(self, fillstyle): + def _set_fillstyle(self, fillstyle): """ Set the fillstyle. @@ -262,54 +282,51 @@ def set_fillstyle(self, fillstyle): The part of the marker surface that is colored with markerfacecolor. """ - if fillstyle is None: - fillstyle = rcParams['markers.fillstyle'] - cbook._check_in_list(self.fillstyles, fillstyle=fillstyle) + fillstyle = mpl._val_or_rc(fillstyle, 'markers.fillstyle') + _api.check_in_list(self.fillstyles, fillstyle=fillstyle) self._fillstyle = fillstyle - self._recache() def get_joinstyle(self): - return self._joinstyle + return self._joinstyle.name def get_capstyle(self): - return self._capstyle + return self._capstyle.name def get_marker(self): return self._marker - def set_marker(self, marker): + def _set_marker(self, marker): """ Set the marker. Parameters ---------- - marker : str or array-like or None, default: None - *None* means no marker. For other possible marker values see the - module docstring `matplotlib.markers`. + marker : str, array-like, Path, MarkerStyle + - Another instance of `MarkerStyle` copies the details of that *marker*. + - For other possible marker values see the module docstring + `matplotlib.markers`. """ - if (isinstance(marker, np.ndarray) and marker.ndim == 2 and + if isinstance(marker, str) and cbook.is_math_text(marker): + self._marker_function = self._set_mathtext_path + elif isinstance(marker, (int, str)) and marker in self.markers: + self._marker_function = getattr(self, '_set_' + self.markers[marker]) + elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and marker.shape[1] == 2): self._marker_function = self._set_vertices - elif isinstance(marker, str) and cbook.is_math_text(marker): - self._marker_function = self._set_mathtext_path elif isinstance(marker, Path): self._marker_function = self._set_path_marker elif (isinstance(marker, Sized) and len(marker) in (2, 3) and marker[1] in (0, 1, 2)): self._marker_function = self._set_tuple_marker - elif (not isinstance(marker, (np.ndarray, list)) and - marker in self.markers): - self._marker_function = getattr( - self, '_set_' + self.markers[marker]) elif isinstance(marker, MarkerStyle): - self.__dict__.update(marker.__dict__) + self.__dict__ = copy.deepcopy(marker.__dict__) else: try: Path(marker) self._marker_function = self._set_vertices except ValueError as err: - raise ValueError('Unrecognized marker style {!r}' - .format(marker)) from err + raise ValueError( + f'Unrecognized marker style {marker!r}') from err if not isinstance(marker, MarkerStyle): self._marker = marker @@ -329,7 +346,10 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - return self._transform.frozen() + if self._user_transform is None: + return self._transform.frozen() + else: + return (self._transform + self._user_transform).frozen() def get_alt_path(self): """ @@ -345,11 +365,86 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - return self._alt_transform.frozen() + if self._user_transform is None: + return self._alt_transform.frozen() + else: + return (self._alt_transform + self._user_transform).frozen() def get_snap_threshold(self): return self._snap_threshold + def get_user_transform(self): + """Return user supplied part of marker transform.""" + if self._user_transform is not None: + return self._user_transform.frozen() + + def transformed(self, transform): + """ + Return a new version of this marker with the transform applied. + + Parameters + ---------- + transform : `~matplotlib.transforms.Affine2D` + Transform will be combined with current user supplied transform. + """ + new_marker = MarkerStyle(self) + if new_marker._user_transform is not None: + new_marker._user_transform += transform + else: + new_marker._user_transform = transform + return new_marker + + def rotated(self, *, deg=None, rad=None): + """ + Return a new version of this marker rotated by specified angle. + + Parameters + ---------- + deg : float, optional + Rotation angle in degrees. + + rad : float, optional + Rotation angle in radians. + + .. note:: You must specify exactly one of deg or rad. + """ + if deg is None and rad is None: + raise ValueError('One of deg or rad is required') + if deg is not None and rad is not None: + raise ValueError('Only one of deg and rad can be supplied') + new_marker = MarkerStyle(self) + if new_marker._user_transform is None: + new_marker._user_transform = Affine2D() + + if deg is not None: + new_marker._user_transform.rotate_deg(deg) + if rad is not None: + new_marker._user_transform.rotate(rad) + + return new_marker + + def scaled(self, sx, sy=None): + """ + Return new marker scaled by specified scale factors. + + If *sy* is not given, the same scale is applied in both the *x*- and + *y*-directions. + + Parameters + ---------- + sx : float + *X*-direction scaling factor. + sy : float, optional + *Y*-direction scaling factor. + """ + if sy is None: + sy = sx + + new_marker = MarkerStyle(self) + _transform = new_marker._user_transform or Affine2D() + new_marker._user_transform = _transform.scale(sx, sy) + return new_marker + def _set_nothing(self): self._filled = False @@ -373,21 +468,21 @@ def _set_tuple_marker(self): symstyle = marker[1] if symstyle == 0: self._path = Path.unit_regular_polygon(numsides) - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter elif symstyle == 1: self._path = Path.unit_regular_star(numsides) - self._joinstyle = 'bevel' + self._joinstyle = self._user_joinstyle or JoinStyle.bevel elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False - self._joinstyle = 'bevel' + self._joinstyle = self._user_joinstyle or JoinStyle.bevel else: raise ValueError(f"Unexpected tuple marker: {marker}") self._transform = Affine2D().scale(0.5).rotate_deg(rotation) def _set_mathtext_path(self): """ - Draws mathtext markers '$...$' using TextPath object. + Draw mathtext markers '$...$' using `.TextPath` object. Submitted by tcb """ @@ -396,45 +491,37 @@ def _set_mathtext_path(self): # again, the properties could be initialised just once outside # this function text = TextPath(xy=(0, 0), s=self.get_marker(), - usetex=rcParams['text.usetex']) + usetex=mpl.rcParams['text.usetex']) if len(text.vertices) == 0: return - xmin, ymin = text.vertices.min(axis=0) - xmax, ymax = text.vertices.max(axis=0) - width = xmax - xmin - height = ymax - ymin - max_dim = max(width, height) - self._transform = Affine2D() \ - .translate(-xmin + 0.5 * -width, -ymin + 0.5 * -height) \ - .scale(1.0 / max_dim) + bbox = text.get_extents() + max_dim = max(bbox.width, bbox.height) + self._transform = ( + Affine2D() + .translate(-bbox.xmin + 0.5 * -bbox.width, -bbox.ymin + 0.5 * -bbox.height) + .scale(1.0 / max_dim)) self._path = text self._snap = False def _half_fill(self): return self.get_fillstyle() in self._half_fillstyles - def _set_circle(self, reduction=1.0): - self._transform = Affine2D().scale(0.5 * reduction) + def _set_circle(self, size=1.0): + self._transform = Affine2D().scale(0.5 * size) self._snap_threshold = np.inf - fs = self.get_fillstyle() if not self._half_fill(): self._path = Path.unit_circle() else: - # build a right-half circle - if fs == 'bottom': - rotate = 270. - elif fs == 'top': - rotate = 90. - elif fs == 'left': - rotate = 180. - else: - rotate = 0. - self._path = self._alt_path = Path.unit_circle_righthalf() - self._transform.rotate_deg(rotate) + fs = self.get_fillstyle() + self._transform.rotate_deg( + {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.) + def _set_point(self): + self._set_circle(size=0.5) + def _set_pixel(self): self._path = Path.unit_rectangle() # Ideally, you'd want -0.5, -0.5 here, but then the snapping @@ -449,23 +536,17 @@ def _set_pixel(self): self._transform = Affine2D().translate(-0.49999, -0.49999) self._snap_threshold = None - def _set_point(self): - self._set_circle(reduction=self._point_size_reduction) - - _triangle_path = Path([[0, 1], [-1, -1], [1, -1], [0, 1]], closed=True) + _triangle_path = Path._create_closed([[0, 1], [-1, -1], [1, -1]]) # Going down halfway looks to small. Golden ratio is too far. - _triangle_path_u = Path([[0, 1], [-3/5, -1/5], [3/5, -1/5], [0, 1]], - closed=True) - _triangle_path_d = Path( - [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1], [-3/5, -1/5]], - closed=True) - _triangle_path_l = Path([[0, 1], [0, -1], [-1, -1], [0, 1]], closed=True) - _triangle_path_r = Path([[0, 1], [0, -1], [1, -1], [0, 1]], closed=True) + _triangle_path_u = Path._create_closed([[0, 1], [-3/5, -1/5], [3/5, -1/5]]) + _triangle_path_d = Path._create_closed( + [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1]]) + _triangle_path_l = Path._create_closed([[0, 1], [0, -1], [-1, -1]]) + _triangle_path_r = Path._create_closed([[0, 1], [0, -1], [1, -1]]) def _set_triangle(self, rot, skip): self._transform = Affine2D().scale(0.5).rotate_deg(rot) self._snap_threshold = 5.0 - fs = self.get_fillstyle() if not self._half_fill(): self._path = self._triangle_path @@ -475,6 +556,7 @@ def _set_triangle(self, rot, skip): self._triangle_path_d, self._triangle_path_r] + fs = self.get_fillstyle() if fs == 'top': self._path = mpaths[(0 + skip) % 4] self._alt_path = mpaths[(2 + skip) % 4] @@ -490,7 +572,7 @@ def _set_triangle(self, rot, skip): self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_triangle_up(self): return self._set_triangle(0.0, 0) @@ -507,51 +589,34 @@ def _set_triangle_right(self): def _set_square(self): self._transform = Affine2D().translate(-0.5, -0.5) self._snap_threshold = 2.0 - fs = self.get_fillstyle() if not self._half_fill(): self._path = Path.unit_rectangle() else: - # build a bottom filled square out of two rectangles, one - # filled. Use the rotation to support left, right, bottom - # or top - if fs == 'bottom': - rotate = 0. - elif fs == 'top': - rotate = 180. - elif fs == 'left': - rotate = 270. - else: - rotate = 90. - + # Build a bottom filled square out of two rectangles, one filled. self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 0.5], [0.0, 0.5], [0.0, 0.0]]) self._alt_path = Path([[0.0, 0.5], [1.0, 0.5], [1.0, 1.0], [0.0, 1.0], [0.0, 0.5]]) + fs = self.get_fillstyle() + rotate = {'bottom': 0, 'right': 90, 'top': 180, 'left': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_diamond(self): self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) self._snap_threshold = 5.0 - fs = self.get_fillstyle() if not self._half_fill(): self._path = Path.unit_rectangle() else: self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]]) self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]]) - if fs == 'bottom': - rotate = 270. - elif fs == 'top': - rotate = 90. - elif fs == 'left': - rotate = 180. - else: - rotate = 0. + fs = self.get_fillstyle() + rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_thin_diamond(self): self._set_diamond() @@ -562,138 +627,100 @@ def _set_pentagon(self): self._snap_threshold = 5.0 polypath = Path.unit_regular_polygon(5) - fs = self.get_fillstyle() if not self._half_fill(): self._path = polypath else: verts = polypath.vertices - y = (1 + np.sqrt(5)) / 4. - top = Path([verts[0], verts[1], verts[4], verts[0]]) - bottom = Path([verts[1], verts[2], verts[3], verts[4], verts[1]]) + top = Path(verts[[0, 1, 4, 0]]) + bottom = Path(verts[[1, 2, 3, 4, 1]]) left = Path([verts[0], verts[1], verts[2], [0, -y], verts[0]]) right = Path([verts[0], verts[4], verts[3], [0, -y], verts[0]]) - - if fs == 'top': - mpath, mpath_alt = top, bottom - elif fs == 'bottom': - mpath, mpath_alt = bottom, top - elif fs == 'left': - mpath, mpath_alt = left, right - else: - mpath, mpath_alt = right, left - self._path = mpath - self._alt_path = mpath_alt + self._path, self._alt_path = { + 'top': (top, bottom), 'bottom': (bottom, top), + 'left': (left, right), 'right': (right, left), + }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_star(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 - fs = self.get_fillstyle() polypath = Path.unit_regular_star(5, innerCircle=0.381966) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices - - top = Path(np.vstack((verts[0:4, :], verts[7:10, :], verts[0]))) - bottom = Path(np.vstack((verts[3:8, :], verts[3]))) - left = Path(np.vstack((verts[0:6, :], verts[0]))) - right = Path(np.vstack((verts[0], verts[5:10, :], verts[0]))) - - if fs == 'top': - mpath, mpath_alt = top, bottom - elif fs == 'bottom': - mpath, mpath_alt = bottom, top - elif fs == 'left': - mpath, mpath_alt = left, right - else: - mpath, mpath_alt = right, left - self._path = mpath - self._alt_path = mpath_alt + top = Path(np.concatenate([verts[0:4], verts[7:10], verts[0:1]])) + bottom = Path(np.concatenate([verts[3:8], verts[3:4]])) + left = Path(np.concatenate([verts[0:6], verts[0:1]])) + right = Path(np.concatenate([verts[0:1], verts[5:10], verts[0:1]])) + self._path, self._alt_path = { + 'top': (top, bottom), 'bottom': (bottom, top), + 'left': (left, right), 'right': (right, left), + }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'bevel' + self._joinstyle = self._user_joinstyle or JoinStyle.bevel def _set_hexagon1(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = None - fs = self.get_fillstyle() polypath = Path.unit_regular_polygon(6) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices - # not drawing inside lines x = np.abs(np.cos(5 * np.pi / 6.)) - top = Path(np.vstack(([-x, 0], verts[(1, 0, 5), :], [x, 0]))) - bottom = Path(np.vstack(([-x, 0], verts[2:5, :], [x, 0]))) - left = Path(verts[(0, 1, 2, 3), :]) - right = Path(verts[(0, 5, 4, 3), :]) - - if fs == 'top': - mpath, mpath_alt = top, bottom - elif fs == 'bottom': - mpath, mpath_alt = bottom, top - elif fs == 'left': - mpath, mpath_alt = left, right - else: - mpath, mpath_alt = right, left - - self._path = mpath - self._alt_path = mpath_alt + top = Path(np.concatenate([[(-x, 0)], verts[[1, 0, 5]], [(x, 0)]])) + bottom = Path(np.concatenate([[(-x, 0)], verts[2:5], [(x, 0)]])) + left = Path(verts[0:4]) + right = Path(verts[[0, 5, 4, 3]]) + self._path, self._alt_path = { + 'top': (top, bottom), 'bottom': (bottom, top), + 'left': (left, right), 'right': (right, left), + }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_hexagon2(self): self._transform = Affine2D().scale(0.5).rotate_deg(30) self._snap_threshold = None - fs = self.get_fillstyle() polypath = Path.unit_regular_polygon(6) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices - # not drawing inside lines x, y = np.sqrt(3) / 4, 3 / 4. - top = Path(verts[(1, 0, 5, 4, 1), :]) - bottom = Path(verts[(1, 2, 3, 4), :]) - left = Path(np.vstack(([x, y], verts[(0, 1, 2), :], - [-x, -y], [x, y]))) - right = Path(np.vstack(([x, y], verts[(5, 4, 3), :], [-x, -y]))) - - if fs == 'top': - mpath, mpath_alt = top, bottom - elif fs == 'bottom': - mpath, mpath_alt = bottom, top - elif fs == 'left': - mpath, mpath_alt = left, right - else: - mpath, mpath_alt = right, left - - self._path = mpath - self._alt_path = mpath_alt + top = Path(verts[[1, 0, 5, 4, 1]]) + bottom = Path(verts[1:5]) + left = Path(np.concatenate([ + [(x, y)], verts[:3], [(-x, -y), (x, y)]])) + right = Path(np.concatenate([ + [(x, y)], verts[5:2:-1], [(-x, -y)]])) + self._path, self._alt_path = { + 'top': (top, bottom), 'bottom': (bottom, top), + 'left': (left, right), 'right': (right, left), + }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_octagon(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 - fs = self.get_fillstyle() polypath = Path.unit_regular_polygon(8) if not self._half_fill(): @@ -701,23 +728,15 @@ def _set_octagon(self): self._path = polypath else: x = np.sqrt(2.) / 4. - half = Path([[0, -1], [0, 1], [-x, 1], [-1, x], - [-1, -x], [-x, -1], [0, -1]]) - - if fs == 'bottom': - rotate = 90. - elif fs == 'top': - rotate = 270. - elif fs == 'right': - rotate = 180. - else: - rotate = 0. - - self._transform.rotate_deg(rotate) - self._path = self._alt_path = half + self._path = self._alt_path = Path( + [[0, -1], [0, 1], [-x, 1], [-1, x], + [-1, -x], [-x, -1], [0, -1]]) + fs = self.get_fillstyle() + self._transform.rotate_deg( + {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.0) - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]]) @@ -791,7 +810,7 @@ def _set_caretdown(self): self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path - self._joinstyle = 'miter' + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_caretup(self): self._set_caretdown() @@ -845,66 +864,44 @@ def _set_x(self): self._filled = False self._path = self._x_path - _plus_filled_path = Path( - [(1/3, 0), (2/3, 0), (2/3, 1/3), (1, 1/3), (1, 2/3), (2/3, 2/3), - (2/3, 1), (1/3, 1), (1/3, 2/3), (0, 2/3), (0, 1/3), (1/3, 1/3), - (1/3, 0)], closed=True) - _plus_filled_path_t = Path( - [(1, 1/2), (1, 2/3), (2/3, 2/3), (2/3, 1), (1/3, 1), (1/3, 2/3), - (0, 2/3), (0, 1/2), (1, 1/2)], closed=True) + _plus_filled_path = Path._create_closed(np.array([ + (-1, -3), (+1, -3), (+1, -1), (+3, -1), (+3, +1), (+1, +1), + (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, -1), (-1, -1)]) / 6) + _plus_filled_path_t = Path._create_closed(np.array([ + (+3, 0), (+3, +1), (+1, +1), (+1, +3), + (-1, +3), (-1, +1), (-3, +1), (-3, 0)]) / 6) def _set_plus_filled(self): - self._transform = Affine2D().translate(-0.5, -0.5) + self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = 'miter' - fs = self.get_fillstyle() + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._plus_filled_path else: # Rotate top half path to support all partitions - if fs == 'top': - rotate, rotate_alt = 0, 180 - elif fs == 'bottom': - rotate, rotate_alt = 180, 0 - elif fs == 'left': - rotate, rotate_alt = 90, 270 - else: - rotate, rotate_alt = 270, 90 - - self._path = self._plus_filled_path_t - self._alt_path = self._plus_filled_path_t - self._alt_transform = Affine2D().translate(-0.5, -0.5) - self._transform.rotate_deg(rotate) - self._alt_transform.rotate_deg(rotate_alt) - - _x_filled_path = Path( - [(0.25, 0), (0.5, 0.25), (0.75, 0), (1, 0.25), (0.75, 0.5), (1, 0.75), - (0.75, 1), (0.5, 0.75), (0.25, 1), (0, 0.75), (0.25, 0.5), (0, 0.25), - (0.25, 0)], closed=True) - _x_filled_path_t = Path( - [(0.75, 0.5), (1, 0.75), (0.75, 1), (0.5, 0.75), (0.25, 1), (0, 0.75), - (0.25, 0.5), (0.75, 0.5)], closed=True) + self._path = self._alt_path = self._plus_filled_path_t + fs = self.get_fillstyle() + self._transform.rotate_deg( + {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs]) + self._alt_transform = self._transform.frozen().rotate_deg(180) + + _x_filled_path = Path._create_closed(np.array([ + (-1, -2), (0, -1), (+1, -2), (+2, -1), (+1, 0), (+2, +1), + (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0), (-2, -1)]) / 4) + _x_filled_path_t = Path._create_closed(np.array([ + (+1, 0), (+2, +1), (+1, +2), (0, +1), + (-1, +2), (-2, +1), (-1, 0)]) / 4) def _set_x_filled(self): - self._transform = Affine2D().translate(-0.5, -0.5) + self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = 'miter' - fs = self.get_fillstyle() + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._x_filled_path else: # Rotate top half path to support all partitions - if fs == 'top': - rotate, rotate_alt = 0, 180 - elif fs == 'bottom': - rotate, rotate_alt = 180, 0 - elif fs == 'left': - rotate, rotate_alt = 90, 270 - else: - rotate, rotate_alt = 270, 90 - - self._path = self._x_filled_path_t - self._alt_path = self._x_filled_path_t - self._alt_transform = Affine2D().translate(-0.5, -0.5) - self._transform.rotate_deg(rotate) - self._alt_transform.rotate_deg(rotate_alt) + self._path = self._alt_path = self._x_filled_path_t + fs = self.get_fillstyle() + self._transform.rotate_deg( + {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs]) + self._alt_transform = self._transform.frozen().rotate_deg(180) diff --git a/lib/matplotlib/markers.pyi b/lib/matplotlib/markers.pyi new file mode 100644 index 000000000000..bd2f7dbcaf0c --- /dev/null +++ b/lib/matplotlib/markers.pyi @@ -0,0 +1,51 @@ +from typing import Literal + +from .path import Path +from .transforms import Affine2D, Transform + +from numpy.typing import ArrayLike +from .typing import CapStyleType, FillStyleType, JoinStyleType + +TICKLEFT: int +TICKRIGHT: int +TICKUP: int +TICKDOWN: int +CARETLEFT: int +CARETRIGHT: int +CARETUP: int +CARETDOWN: int +CARETLEFTBASE: int +CARETRIGHTBASE: int +CARETUPBASE: int +CARETDOWNBASE: int + +class MarkerStyle: + markers: dict[str | int, str] + filled_markers: tuple[str, ...] + fillstyles: tuple[FillStyleType, ...] + + def __init__( + self, + marker: str | ArrayLike | Path | MarkerStyle, + fillstyle: FillStyleType | None = ..., + transform: Transform | None = ..., + capstyle: CapStyleType | None = ..., + joinstyle: JoinStyleType | None = ..., + ) -> None: ... + def __bool__(self) -> bool: ... + def is_filled(self) -> bool: ... + def get_fillstyle(self) -> FillStyleType: ... + def get_joinstyle(self) -> Literal["miter", "round", "bevel"]: ... + def get_capstyle(self) -> Literal["butt", "projecting", "round"]: ... + def get_marker(self) -> str | ArrayLike | Path | None: ... + def get_path(self) -> Path: ... + def get_transform(self) -> Transform: ... + def get_alt_path(self) -> Path | None: ... + def get_alt_transform(self) -> Transform: ... + def get_snap_threshold(self) -> float | None: ... + def get_user_transform(self) -> Transform | None: ... + def transformed(self, transform: Affine2D) -> MarkerStyle: ... + def rotated( + self, *, deg: float | None = ..., rad: float | None = ... + ) -> MarkerStyle: ... + def scaled(self, sx: float, sy: float | None = ...) -> MarkerStyle: ... diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index 8b9df7e19203..a88c35c15676 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -2,7 +2,7 @@ A module for parsing a subset of the TeX math syntax and rendering it to a Matplotlib backend. -For a tutorial of its usage, see :doc:`/tutorials/text/mathtext`. This +For a tutorial of its usage, see :ref:`mathtext`. This document is primarily concerned with implementation details. The module uses pyparsing_ to parse the TeX expression. @@ -15,3299 +15,20 @@ metrics for those fonts. """ -from collections import namedtuple import functools -from io import StringIO import logging -import os -import types -import unicodedata -import numpy as np -from PIL import Image -from pyparsing import ( - Combine, Empty, FollowedBy, Forward, Group, Literal, oneOf, OneOrMore, - Optional, ParseBaseException, ParseFatalException, ParserElement, - ParseResults, QuotedString, Regex, StringEnd, Suppress, ZeroOrMore) +import matplotlib as mpl +from matplotlib import _api, _mathtext +from matplotlib.ft2font import LoadFlags +from matplotlib.font_manager import FontProperties +from ._mathtext import ( # noqa: F401, reexported API + RasterParse, VectorParse, get_unicode_index) -from matplotlib import cbook, colors as mcolors, rcParams -from matplotlib.afm import AFM -from matplotlib.ft2font import FT2Image, KERNING_DEFAULT, LOAD_NO_HINTING -from matplotlib.font_manager import findfont, FontProperties, get_font -from matplotlib._mathtext_data import (latex_to_bakoma, latex_to_standard, - tex2uni, latex_to_cmex, - stix_virtual_fonts) - -ParserElement.enablePackrat() -_log = logging.getLogger(__name__) - - -############################################################################## -# FONTS - -def get_unicode_index(symbol, math=True): - r""" - Return the integer index (from the Unicode table) of *symbol*. - - Parameters - ---------- - symbol : str - A single unicode character, a TeX command (e.g. r'\pi') or a Type1 - symbol name (e.g. 'phi'). - math : bool, default: True - If False, always treat as a single unicode character. - """ - # for a non-math symbol, simply return its unicode index - if not math: - return ord(symbol) - # From UTF #25: U+2212 minus sign is the preferred - # representation of the unary and binary minus sign rather than - # the ASCII-derived U+002D hyphen-minus, because minus sign is - # unambiguous and because it is rendered with a more desirable - # length, usually longer than a hyphen. - if symbol == '-': - return 0x2212 - try: # This will succeed if symbol is a single unicode char - return ord(symbol) - except TypeError: - pass - try: # Is symbol a TeX symbol (i.e. \alpha) - return tex2uni[symbol.strip("\\")] - except KeyError as err: - raise ValueError( - "'{}' is not a valid Unicode character or TeX/Type1 symbol" - .format(symbol)) from err - - -class MathtextBackend: - """ - The base class for the mathtext backend-specific code. `MathtextBackend` - subclasses interface between mathtext and specific Matplotlib graphics - backends. - - Subclasses need to override the following: - - - :meth:`render_glyph` - - :meth:`render_rect_filled` - - :meth:`get_results` - - And optionally, if you need to use a FreeType hinting style: - - - :meth:`get_hinting_type` - """ - def __init__(self): - self.width = 0 - self.height = 0 - self.depth = 0 - - def set_canvas_size(self, w, h, d): - """Set the dimension of the drawing canvas.""" - self.width = w - self.height = h - self.depth = d - - def render_glyph(self, ox, oy, info): - """ - Draw a glyph described by *info* to the reference point (*ox*, - *oy*). - """ - raise NotImplementedError() - - def render_rect_filled(self, x1, y1, x2, y2): - """ - Draw a filled black rectangle from (*x1*, *y1*) to (*x2*, *y2*). - """ - raise NotImplementedError() - - def get_results(self, box): - """ - Return a backend-specific tuple to return to the backend after - all processing is done. - """ - raise NotImplementedError() - - def get_hinting_type(self): - """ - Get the FreeType hinting type to use with this particular - backend. - """ - return LOAD_NO_HINTING - - -class MathtextBackendAgg(MathtextBackend): - """ - Render glyphs and rectangles to an FTImage buffer, which is later - transferred to the Agg image by the Agg backend. - """ - def __init__(self): - self.ox = 0 - self.oy = 0 - self.image = None - self.mode = 'bbox' - self.bbox = [0, 0, 0, 0] - super().__init__() - - def _update_bbox(self, x1, y1, x2, y2): - self.bbox = [min(self.bbox[0], x1), - min(self.bbox[1], y1), - max(self.bbox[2], x2), - max(self.bbox[3], y2)] - - def set_canvas_size(self, w, h, d): - super().set_canvas_size(w, h, d) - if self.mode != 'bbox': - self.image = FT2Image(np.ceil(w), np.ceil(h + max(d, 0))) - - def render_glyph(self, ox, oy, info): - if self.mode == 'bbox': - self._update_bbox(ox + info.metrics.xmin, - oy - info.metrics.ymax, - ox + info.metrics.xmax, - oy - info.metrics.ymin) - else: - info.font.draw_glyph_to_bitmap( - self.image, ox, oy - info.metrics.iceberg, info.glyph, - antialiased=rcParams['text.antialiased']) - - def render_rect_filled(self, x1, y1, x2, y2): - if self.mode == 'bbox': - self._update_bbox(x1, y1, x2, y2) - else: - height = max(int(y2 - y1) - 1, 0) - if height == 0: - center = (y2 + y1) / 2.0 - y = int(center - (height + 1) / 2.0) - else: - y = int(y1) - self.image.draw_rect_filled(int(x1), y, np.ceil(x2), y + height) - - def get_results(self, box, used_characters): - self.mode = 'bbox' - orig_height = box.height - orig_depth = box.depth - ship(0, 0, box) - bbox = self.bbox - bbox = [bbox[0] - 1, bbox[1] - 1, bbox[2] + 1, bbox[3] + 1] - self.mode = 'render' - self.set_canvas_size( - bbox[2] - bbox[0], - (bbox[3] - bbox[1]) - orig_depth, - (bbox[3] - bbox[1]) - orig_height) - ship(-bbox[0], -bbox[1], box) - result = (self.ox, - self.oy, - self.width, - self.height + self.depth, - self.depth, - self.image, - used_characters) - self.image = None - return result - - def get_hinting_type(self): - from matplotlib.backends import backend_agg - return backend_agg.get_hinting_flag() - - -class MathtextBackendBitmap(MathtextBackendAgg): - def get_results(self, box, used_characters): - ox, oy, width, height, depth, image, characters = \ - super().get_results(box, used_characters) - return image, depth - - -@cbook.deprecated("3.4", alternative="MathtextBackendPath") -class MathtextBackendPs(MathtextBackend): - """ - Store information to write a mathtext rendering to the PostScript backend. - """ - - _PSResult = namedtuple( - "_PSResult", "width height depth pswriter used_characters") - - def __init__(self): - self.pswriter = StringIO() - self.lastfont = None - - def render_glyph(self, ox, oy, info): - oy = self.height - oy + info.offset - postscript_name = info.postscript_name - fontsize = info.fontsize - - if (postscript_name, fontsize) != self.lastfont: - self.lastfont = postscript_name, fontsize - self.pswriter.write( - f"/{postscript_name} findfont\n" - f"{fontsize} scalefont\n" - f"setfont\n") - - self.pswriter.write( - f"{ox:f} {oy:f} moveto\n" - f"/{info.symbol_name} glyphshow\n") - - def render_rect_filled(self, x1, y1, x2, y2): - ps = "%f %f %f %f rectfill\n" % ( - x1, self.height - y2, x2 - x1, y2 - y1) - self.pswriter.write(ps) - - def get_results(self, box, used_characters): - ship(0, 0, box) - return self._PSResult(self.width, - self.height + self.depth, - self.depth, - self.pswriter, - used_characters) - - -@cbook.deprecated("3.4", alternative="MathtextBackendPath") -class MathtextBackendPdf(MathtextBackend): - """Store information to write a mathtext rendering to the PDF backend.""" - - _PDFResult = namedtuple( - "_PDFResult", "width height depth glyphs rects used_characters") - - def __init__(self): - self.glyphs = [] - self.rects = [] - - def render_glyph(self, ox, oy, info): - filename = info.font.fname - oy = self.height - oy + info.offset - self.glyphs.append( - (ox, oy, filename, info.fontsize, - info.num, info.symbol_name)) - - def render_rect_filled(self, x1, y1, x2, y2): - self.rects.append((x1, self.height - y2, x2 - x1, y2 - y1)) - - def get_results(self, box, used_characters): - ship(0, 0, box) - return self._PDFResult(self.width, - self.height + self.depth, - self.depth, - self.glyphs, - self.rects, - used_characters) - - -@cbook.deprecated("3.4", alternative="MathtextBackendPath") -class MathtextBackendSvg(MathtextBackend): - """ - Store information to write a mathtext rendering to the SVG - backend. - """ - def __init__(self): - self.svg_glyphs = [] - self.svg_rects = [] - - def render_glyph(self, ox, oy, info): - oy = self.height - oy + info.offset - - self.svg_glyphs.append( - (info.font, info.fontsize, info.num, ox, oy, info.metrics)) - - def render_rect_filled(self, x1, y1, x2, y2): - self.svg_rects.append( - (x1, self.height - y1 + 1, x2 - x1, y2 - y1)) - - def get_results(self, box, used_characters): - ship(0, 0, box) - svg_elements = types.SimpleNamespace(svg_glyphs=self.svg_glyphs, - svg_rects=self.svg_rects) - return (self.width, - self.height + self.depth, - self.depth, - svg_elements, - used_characters) - - -class MathtextBackendPath(MathtextBackend): - """ - Store information to write a mathtext rendering to the text path - machinery. - """ - - _Result = namedtuple("_Result", "width height depth glyphs rects") - - def __init__(self): - self.glyphs = [] - self.rects = [] - - def render_glyph(self, ox, oy, info): - oy = self.height - oy + info.offset - self.glyphs.append((info.font, info.fontsize, info.num, ox, oy)) - - def render_rect_filled(self, x1, y1, x2, y2): - self.rects.append((x1, self.height - y2, x2 - x1, y2 - y1)) - - def get_results(self, box, used_characters): - ship(0, 0, box) - return self._Result(self.width, - self.height + self.depth, - self.depth, - self.glyphs, - self.rects) - - -@cbook.deprecated("3.4", alternative="MathtextBackendPath") -class MathtextBackendCairo(MathtextBackend): - """ - Store information to write a mathtext rendering to the Cairo - backend. - """ - - def __init__(self): - self.glyphs = [] - self.rects = [] - - def render_glyph(self, ox, oy, info): - oy = oy - info.offset - self.height - thetext = chr(info.num) - self.glyphs.append( - (info.font, info.fontsize, thetext, ox, oy)) - - def render_rect_filled(self, x1, y1, x2, y2): - self.rects.append( - (x1, y1 - self.height, x2 - x1, y2 - y1)) - - def get_results(self, box, used_characters): - ship(0, 0, box) - return (self.width, - self.height + self.depth, - self.depth, - self.glyphs, - self.rects) - - -class Fonts: - """ - An abstract base class for a system of fonts to use for mathtext. - - The class must be able to take symbol keys and font file names and - return the character metrics. It also delegates to a backend class - to do the actual drawing. - """ - - def __init__(self, default_font_prop, mathtext_backend): - """ - Parameters - ---------- - default_font_prop: `~.font_manager.FontProperties` - The default non-math font, or the base font for Unicode (generic) - font rendering. - mathtext_backend: `MathtextBackend` subclass - Backend to which rendering is actually delegated. - """ - self.default_font_prop = default_font_prop - self.mathtext_backend = mathtext_backend - self.used_characters = {} - - @cbook.deprecated("3.4") - def destroy(self): - """ - Fix any cyclical references before the object is about - to be destroyed. - """ - self.used_characters = None - - def get_kern(self, font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi): - """ - Get the kerning distance for font between *sym1* and *sym2*. - - See `~.Fonts.get_metrics` for a detailed description of the parameters. - """ - return 0. - - def get_metrics(self, font, font_class, sym, fontsize, dpi, math=True): - r""" - Parameters - ---------- - font : str - One of the TeX font names: "tt", "it", "rm", "cal", "sf", "bf", - "default", "regular", "bb", "frak", "scr". "default" and "regular" - are synonyms and use the non-math font. - font_class : str - One of the TeX font names (as for *font*), but **not** "bb", - "frak", or "scr". This is used to combine two font classes. The - only supported combination currently is ``get_metrics("frak", "bf", - ...)``. - sym : str - A symbol in raw TeX form, e.g., "1", "x", or "\sigma". - fontsize : float - Font size in points. - dpi : float - Rendering dots-per-inch. - math : bool - Whether we are currently in math mode or not. - - Returns - ------- - object - - The returned object has the following attributes (all floats, - except *slanted*): - - - *advance*: The advance distance (in points) of the glyph. - - *height*: The height of the glyph in points. - - *width*: The width of the glyph in points. - - *xmin*, *xmax*, *ymin*, *ymax*: The ink rectangle of the glyph - - *iceberg*: The distance from the baseline to the top of the - glyph. (This corresponds to TeX's definition of "height".) - - *slanted*: Whether the glyph should be considered as "slanted" - (currently used for kerning sub/superscripts). - """ - info = self._get_info(font, font_class, sym, fontsize, dpi, math) - return info.metrics - - def set_canvas_size(self, w, h, d): - """ - Set the size of the buffer used to render the math expression. - Only really necessary for the bitmap backends. - """ - self.width, self.height, self.depth = np.ceil([w, h, d]) - self.mathtext_backend.set_canvas_size( - self.width, self.height, self.depth) - - @cbook._rename_parameter("3.4", "facename", "font") - def render_glyph(self, ox, oy, font, font_class, sym, fontsize, dpi): - """ - At position (*ox*, *oy*), draw the glyph specified by the remaining - parameters (see `get_metrics` for their detailed description). - """ - info = self._get_info(font, font_class, sym, fontsize, dpi) - self.used_characters.setdefault(info.font.fname, set()).add(info.num) - self.mathtext_backend.render_glyph(ox, oy, info) - - def render_rect_filled(self, x1, y1, x2, y2): - """ - Draw a filled rectangle from (*x1*, *y1*) to (*x2*, *y2*). - """ - self.mathtext_backend.render_rect_filled(x1, y1, x2, y2) - - def get_xheight(self, font, fontsize, dpi): - """ - Get the xheight for the given *font* and *fontsize*. - """ - raise NotImplementedError() - - def get_underline_thickness(self, font, fontsize, dpi): - """ - Get the line thickness that matches the given font. Used as a - base unit for drawing lines such as in a fraction or radical. - """ - raise NotImplementedError() - - def get_used_characters(self): - """ - Get the set of characters that were used in the math - expression. Used by backends that need to subset fonts so - they know which glyphs to include. - """ - return self.used_characters - - def get_results(self, box): - """ - Get the data needed by the backend to render the math - expression. The return value is backend-specific. - """ - result = self.mathtext_backend.get_results( - box, self.get_used_characters()) - if self.destroy != TruetypeFonts.destroy.__get__(self): - destroy = cbook._deprecate_method_override( - __class__.destroy, self, since="3.4") - if destroy: - destroy() - return result - - def get_sized_alternatives_for_symbol(self, fontname, sym): - """ - Override if your font provides multiple sizes of the same - symbol. Should return a list of symbols matching *sym* in - various sizes. The expression renderer will select the most - appropriate size for a given situation from this list. - """ - return [(fontname, sym)] - - -class TruetypeFonts(Fonts): - """ - A generic base class for all font setups that use Truetype fonts - (through FT2Font). - """ - def __init__(self, default_font_prop, mathtext_backend): - super().__init__(default_font_prop, mathtext_backend) - self.glyphd = {} - self._fonts = {} - - filename = findfont(default_font_prop) - default_font = get_font(filename) - self._fonts['default'] = default_font - self._fonts['regular'] = default_font - - @cbook.deprecated("3.4") - def destroy(self): - self.glyphd = None - super().destroy() - - def _get_font(self, font): - if font in self.fontmap: - basename = self.fontmap[font] - else: - basename = font - cached_font = self._fonts.get(basename) - if cached_font is None and os.path.exists(basename): - cached_font = get_font(basename) - self._fonts[basename] = cached_font - self._fonts[cached_font.postscript_name] = cached_font - self._fonts[cached_font.postscript_name.lower()] = cached_font - return cached_font - - def _get_offset(self, font, glyph, fontsize, dpi): - if font.postscript_name == 'Cmex10': - return (glyph.height / 64 / 2) + (fontsize/3 * dpi/72) - return 0. - - def _get_info(self, fontname, font_class, sym, fontsize, dpi, math=True): - key = fontname, font_class, sym, fontsize, dpi - bunch = self.glyphd.get(key) - if bunch is not None: - return bunch - - font, num, symbol_name, fontsize, slanted = \ - self._get_glyph(fontname, font_class, sym, fontsize, math) - - font.set_size(fontsize, dpi) - glyph = font.load_char( - num, - flags=self.mathtext_backend.get_hinting_type()) - - xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox] - offset = self._get_offset(font, glyph, fontsize, dpi) - metrics = types.SimpleNamespace( - advance = glyph.linearHoriAdvance/65536.0, - height = glyph.height/64.0, - width = glyph.width/64.0, - xmin = xmin, - xmax = xmax, - ymin = ymin+offset, - ymax = ymax+offset, - # iceberg is the equivalent of TeX's "height" - iceberg = glyph.horiBearingY/64.0 + offset, - slanted = slanted - ) - - result = self.glyphd[key] = types.SimpleNamespace( - font = font, - fontsize = fontsize, - postscript_name = font.postscript_name, - metrics = metrics, - symbol_name = symbol_name, - num = num, - glyph = glyph, - offset = offset - ) - return result - - def get_xheight(self, fontname, fontsize, dpi): - font = self._get_font(fontname) - font.set_size(fontsize, dpi) - pclt = font.get_sfnt_table('pclt') - if pclt is None: - # Some fonts don't store the xHeight, so we do a poor man's xHeight - metrics = self.get_metrics( - fontname, rcParams['mathtext.default'], 'x', fontsize, dpi) - return metrics.iceberg - xHeight = (pclt['xHeight'] / 64.0) * (fontsize / 12.0) * (dpi / 100.0) - return xHeight - - def get_underline_thickness(self, font, fontsize, dpi): - # This function used to grab underline thickness from the font - # metrics, but that information is just too un-reliable, so it - # is now hardcoded. - return ((0.75 / 12.0) * fontsize * dpi) / 72.0 - - def get_kern(self, font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi): - if font1 == font2 and fontsize1 == fontsize2: - info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi) - info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi) - font = info1.font - return font.get_kerning(info1.num, info2.num, KERNING_DEFAULT) / 64 - return super().get_kern(font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi) - - -class BakomaFonts(TruetypeFonts): - """ - Use the Bakoma TrueType fonts for rendering. - - Symbols are strewn about a number of font files, each of which has - its own proprietary 8-bit encoding. - """ - _fontmap = { - 'cal': 'cmsy10', - 'rm': 'cmr10', - 'tt': 'cmtt10', - 'it': 'cmmi10', - 'bf': 'cmb10', - 'sf': 'cmss10', - 'ex': 'cmex10', - } - - def __init__(self, *args, **kwargs): - self._stix_fallback = StixFonts(*args, **kwargs) - - super().__init__(*args, **kwargs) - self.fontmap = {} - for key, val in self._fontmap.items(): - fullpath = findfont(val) - self.fontmap[key] = fullpath - self.fontmap[val] = fullpath - - _slanted_symbols = set(r"\int \oint".split()) - - def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): - symbol_name = None - font = None - if fontname in self.fontmap and sym in latex_to_bakoma: - basename, num = latex_to_bakoma[sym] - slanted = (basename == "cmmi10") or sym in self._slanted_symbols - font = self._get_font(basename) - elif len(sym) == 1: - slanted = (fontname == "it") - font = self._get_font(fontname) - if font is not None: - num = ord(sym) - - if font is not None: - gid = font.get_char_index(num) - if gid != 0: - symbol_name = font.get_glyph_name(gid) - - if symbol_name is None: - return self._stix_fallback._get_glyph( - fontname, font_class, sym, fontsize, math) - - return font, num, symbol_name, fontsize, slanted - - # The Bakoma fonts contain many pre-sized alternatives for the - # delimiters. The AutoSizedChar class will use these alternatives - # and select the best (closest sized) glyph. - _size_alternatives = { - '(': [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'), - ('ex', '\xb5'), ('ex', '\xc3')], - ')': [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'), - ('ex', '\xb6'), ('ex', '\x21')], - '{': [('cal', '{'), ('ex', '\xa9'), ('ex', '\x6e'), - ('ex', '\xbd'), ('ex', '\x28')], - '}': [('cal', '}'), ('ex', '\xaa'), ('ex', '\x6f'), - ('ex', '\xbe'), ('ex', '\x29')], - # The fourth size of '[' is mysteriously missing from the BaKoMa - # font, so I've omitted it for both '[' and ']' - '[': [('rm', '['), ('ex', '\xa3'), ('ex', '\x68'), - ('ex', '\x22')], - ']': [('rm', ']'), ('ex', '\xa4'), ('ex', '\x69'), - ('ex', '\x23')], - r'\lfloor': [('ex', '\xa5'), ('ex', '\x6a'), - ('ex', '\xb9'), ('ex', '\x24')], - r'\rfloor': [('ex', '\xa6'), ('ex', '\x6b'), - ('ex', '\xba'), ('ex', '\x25')], - r'\lceil': [('ex', '\xa7'), ('ex', '\x6c'), - ('ex', '\xbb'), ('ex', '\x26')], - r'\rceil': [('ex', '\xa8'), ('ex', '\x6d'), - ('ex', '\xbc'), ('ex', '\x27')], - r'\langle': [('ex', '\xad'), ('ex', '\x44'), - ('ex', '\xbf'), ('ex', '\x2a')], - r'\rangle': [('ex', '\xae'), ('ex', '\x45'), - ('ex', '\xc0'), ('ex', '\x2b')], - r'\__sqrt__': [('ex', '\x70'), ('ex', '\x71'), - ('ex', '\x72'), ('ex', '\x73')], - r'\backslash': [('ex', '\xb2'), ('ex', '\x2f'), - ('ex', '\xc2'), ('ex', '\x2d')], - r'/': [('rm', '/'), ('ex', '\xb1'), ('ex', '\x2e'), - ('ex', '\xcb'), ('ex', '\x2c')], - r'\widehat': [('rm', '\x5e'), ('ex', '\x62'), ('ex', '\x63'), - ('ex', '\x64')], - r'\widetilde': [('rm', '\x7e'), ('ex', '\x65'), ('ex', '\x66'), - ('ex', '\x67')], - r'<': [('cal', 'h'), ('ex', 'D')], - r'>': [('cal', 'i'), ('ex', 'E')] - } - - for alias, target in [(r'\leftparen', '('), - (r'\rightparent', ')'), - (r'\leftbrace', '{'), - (r'\rightbrace', '}'), - (r'\leftbracket', '['), - (r'\rightbracket', ']'), - (r'\{', '{'), - (r'\}', '}'), - (r'\[', '['), - (r'\]', ']')]: - _size_alternatives[alias] = _size_alternatives[target] - - def get_sized_alternatives_for_symbol(self, fontname, sym): - return self._size_alternatives.get(sym, [(fontname, sym)]) - - -class UnicodeFonts(TruetypeFonts): - """ - An abstract base class for handling Unicode fonts. - - While some reasonably complete Unicode fonts (such as DejaVu) may - work in some situations, the only Unicode font I'm aware of with a - complete set of math symbols is STIX. - - This class will "fallback" on the Bakoma fonts when a required - symbol can not be found in the font. - """ - use_cmex = True - - def __init__(self, *args, **kwargs): - # This must come first so the backend's owner is set correctly - fallback_rc = rcParams['mathtext.fallback'] - if rcParams['mathtext.fallback_to_cm'] is not None: - fallback_rc = ('cm' if rcParams['mathtext.fallback_to_cm'] - else None) - font_cls = {'stix': StixFonts, - 'stixsans': StixSansFonts, - 'cm': BakomaFonts - }.get(fallback_rc) - self.cm_fallback = font_cls(*args, **kwargs) if font_cls else None - - super().__init__(*args, **kwargs) - self.fontmap = {} - for texfont in "cal rm tt it bf sf".split(): - prop = rcParams['mathtext.' + texfont] - font = findfont(prop) - self.fontmap[texfont] = font - prop = FontProperties('cmex10') - font = findfont(prop) - self.fontmap['ex'] = font - - # include STIX sized alternatives for glyphs if fallback is STIX - if isinstance(self.cm_fallback, StixFonts): - stixsizedaltfonts = { - 0: 'STIXGeneral', - 1: 'STIXSizeOneSym', - 2: 'STIXSizeTwoSym', - 3: 'STIXSizeThreeSym', - 4: 'STIXSizeFourSym', - 5: 'STIXSizeFiveSym'} - - for size, name in stixsizedaltfonts.items(): - fullpath = findfont(name) - self.fontmap[size] = fullpath - self.fontmap[name] = fullpath - - _slanted_symbols = set(r"\int \oint".split()) - - def _map_virtual_font(self, fontname, font_class, uniindex): - return fontname, uniindex - - def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): - found_symbol = False - - if self.use_cmex: - uniindex = latex_to_cmex.get(sym) - if uniindex is not None: - fontname = 'ex' - found_symbol = True - - if not found_symbol: - try: - uniindex = get_unicode_index(sym, math) - found_symbol = True - except ValueError: - uniindex = ord('?') - _log.warning( - "No TeX to unicode mapping for {!a}.".format(sym)) - - fontname, uniindex = self._map_virtual_font( - fontname, font_class, uniindex) - - new_fontname = fontname - - # Only characters in the "Letter" class should be italicized in 'it' - # mode. Greek capital letters should be Roman. - if found_symbol: - if fontname == 'it' and uniindex < 0x10000: - char = chr(uniindex) - if (unicodedata.category(char)[0] != "L" - or unicodedata.name(char).startswith("GREEK CAPITAL")): - new_fontname = 'rm' - - slanted = (new_fontname == 'it') or sym in self._slanted_symbols - found_symbol = False - font = self._get_font(new_fontname) - if font is not None: - glyphindex = font.get_char_index(uniindex) - if glyphindex != 0: - found_symbol = True - - if not found_symbol: - if self.cm_fallback: - if (fontname in ('it', 'regular') - and isinstance(self.cm_fallback, StixFonts)): - fontname = 'rm' - - g = self.cm_fallback._get_glyph(fontname, font_class, - sym, fontsize) - fname = g[0].family_name - if fname in list(BakomaFonts._fontmap.values()): - fname = "Computer Modern" - _log.info("Substituting symbol %s from %s", sym, fname) - return g - - else: - if (fontname in ('it', 'regular') - and isinstance(self, StixFonts)): - return self._get_glyph('rm', font_class, sym, fontsize) - _log.warning("Font {!r} does not have a glyph for {!a} " - "[U+{:x}], substituting with a dummy " - "symbol.".format(new_fontname, sym, uniindex)) - fontname = 'rm' - font = self._get_font(fontname) - uniindex = 0xA4 # currency char, for lack of anything better - glyphindex = font.get_char_index(uniindex) - slanted = False - - symbol_name = font.get_glyph_name(glyphindex) - return font, uniindex, symbol_name, fontsize, slanted - - def get_sized_alternatives_for_symbol(self, fontname, sym): - if self.cm_fallback: - return self.cm_fallback.get_sized_alternatives_for_symbol( - fontname, sym) - return [(fontname, sym)] - - -class DejaVuFonts(UnicodeFonts): - use_cmex = False - - def __init__(self, *args, **kwargs): - # This must come first so the backend's owner is set correctly - if isinstance(self, DejaVuSerifFonts): - self.cm_fallback = StixFonts(*args, **kwargs) - else: - self.cm_fallback = StixSansFonts(*args, **kwargs) - self.bakoma = BakomaFonts(*args, **kwargs) - TruetypeFonts.__init__(self, *args, **kwargs) - self.fontmap = {} - # Include Stix sized alternatives for glyphs - self._fontmap.update({ - 1: 'STIXSizeOneSym', - 2: 'STIXSizeTwoSym', - 3: 'STIXSizeThreeSym', - 4: 'STIXSizeFourSym', - 5: 'STIXSizeFiveSym', - }) - for key, name in self._fontmap.items(): - fullpath = findfont(name) - self.fontmap[key] = fullpath - self.fontmap[name] = fullpath - - def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): - # Override prime symbol to use Bakoma. - if sym == r'\prime': - return self.bakoma._get_glyph( - fontname, font_class, sym, fontsize, math) - else: - # check whether the glyph is available in the display font - uniindex = get_unicode_index(sym) - font = self._get_font('ex') - if font is not None: - glyphindex = font.get_char_index(uniindex) - if glyphindex != 0: - return super()._get_glyph( - 'ex', font_class, sym, fontsize, math) - # otherwise return regular glyph - return super()._get_glyph( - fontname, font_class, sym, fontsize, math) - - -class DejaVuSerifFonts(DejaVuFonts): - """ - A font handling class for the DejaVu Serif fonts - - If a glyph is not found it will fallback to Stix Serif - """ - _fontmap = { - 'rm': 'DejaVu Serif', - 'it': 'DejaVu Serif:italic', - 'bf': 'DejaVu Serif:weight=bold', - 'sf': 'DejaVu Sans', - 'tt': 'DejaVu Sans Mono', - 'ex': 'DejaVu Serif Display', - 0: 'DejaVu Serif', - } - - -class DejaVuSansFonts(DejaVuFonts): - """ - A font handling class for the DejaVu Sans fonts - - If a glyph is not found it will fallback to Stix Sans - """ - _fontmap = { - 'rm': 'DejaVu Sans', - 'it': 'DejaVu Sans:italic', - 'bf': 'DejaVu Sans:weight=bold', - 'sf': 'DejaVu Sans', - 'tt': 'DejaVu Sans Mono', - 'ex': 'DejaVu Sans Display', - 0: 'DejaVu Sans', - } - - -class StixFonts(UnicodeFonts): - """ - A font handling class for the STIX fonts. - - In addition to what UnicodeFonts provides, this class: - - - supports "virtual fonts" which are complete alpha numeric - character sets with different font styles at special Unicode - code points, such as "Blackboard". - - - handles sized alternative characters for the STIXSizeX fonts. - """ - _fontmap = { - 'rm': 'STIXGeneral', - 'it': 'STIXGeneral:italic', - 'bf': 'STIXGeneral:weight=bold', - 'nonunirm': 'STIXNonUnicode', - 'nonuniit': 'STIXNonUnicode:italic', - 'nonunibf': 'STIXNonUnicode:weight=bold', - 0: 'STIXGeneral', - 1: 'STIXSizeOneSym', - 2: 'STIXSizeTwoSym', - 3: 'STIXSizeThreeSym', - 4: 'STIXSizeFourSym', - 5: 'STIXSizeFiveSym', - } - use_cmex = False - cm_fallback = False - _sans = False - - def __init__(self, *args, **kwargs): - TruetypeFonts.__init__(self, *args, **kwargs) - self.fontmap = {} - for key, name in self._fontmap.items(): - fullpath = findfont(name) - self.fontmap[key] = fullpath - self.fontmap[name] = fullpath - - def _map_virtual_font(self, fontname, font_class, uniindex): - # Handle these "fonts" that are actually embedded in - # other fonts. - mapping = stix_virtual_fonts.get(fontname) - if (self._sans and mapping is None - and fontname not in ('regular', 'default')): - mapping = stix_virtual_fonts['sf'] - doing_sans_conversion = True - else: - doing_sans_conversion = False - - if mapping is not None: - if isinstance(mapping, dict): - try: - mapping = mapping[font_class] - except KeyError: - mapping = mapping['rm'] - - # Binary search for the source glyph - lo = 0 - hi = len(mapping) - while lo < hi: - mid = (lo+hi)//2 - range = mapping[mid] - if uniindex < range[0]: - hi = mid - elif uniindex <= range[1]: - break - else: - lo = mid + 1 - - if range[0] <= uniindex <= range[1]: - uniindex = uniindex - range[0] + range[3] - fontname = range[2] - elif not doing_sans_conversion: - # This will generate a dummy character - uniindex = 0x1 - fontname = rcParams['mathtext.default'] - - # Handle private use area glyphs - if fontname in ('it', 'rm', 'bf') and 0xe000 <= uniindex <= 0xf8ff: - fontname = 'nonuni' + fontname - - return fontname, uniindex - - @functools.lru_cache() - def get_sized_alternatives_for_symbol(self, fontname, sym): - fixes = { - '\\{': '{', '\\}': '}', '\\[': '[', '\\]': ']', - '<': '\N{MATHEMATICAL LEFT ANGLE BRACKET}', - '>': '\N{MATHEMATICAL RIGHT ANGLE BRACKET}', - } - sym = fixes.get(sym, sym) - try: - uniindex = get_unicode_index(sym) - except ValueError: - return [(fontname, sym)] - alternatives = [(i, chr(uniindex)) for i in range(6) - if self._get_font(i).get_char_index(uniindex) != 0] - # The largest size of the radical symbol in STIX has incorrect - # metrics that cause it to be disconnected from the stem. - if sym == r'\__sqrt__': - alternatives = alternatives[:-1] - return alternatives - - -class StixSansFonts(StixFonts): - """ - A font handling class for the STIX fonts (that uses sans-serif - characters by default). - """ - _sans = True - - -class StandardPsFonts(Fonts): - """ - Use the standard postscript fonts for rendering to backend_ps - - Unlike the other font classes, BakomaFont and UnicodeFont, this - one requires the Ps backend. - """ - basepath = str(cbook._get_data_path('fonts/afm')) - - fontmap = { - 'cal': 'pzcmi8a', # Zapf Chancery - 'rm': 'pncr8a', # New Century Schoolbook - 'tt': 'pcrr8a', # Courier - 'it': 'pncri8a', # New Century Schoolbook Italic - 'sf': 'phvr8a', # Helvetica - 'bf': 'pncb8a', # New Century Schoolbook Bold - None: 'psyr', # Symbol - } - - def __init__(self, default_font_prop): - super().__init__(default_font_prop, MathtextBackendPath()) - self.glyphd = {} - self.fonts = {} - - filename = findfont(default_font_prop, fontext='afm', - directory=self.basepath) - if filename is None: - filename = findfont('Helvetica', fontext='afm', - directory=self.basepath) - with open(filename, 'rb') as fd: - default_font = AFM(fd) - default_font.fname = filename - - self.fonts['default'] = default_font - self.fonts['regular'] = default_font - - @cbook.deprecated("3.4") - @property - def pswriter(self): - return StringIO() - - def _get_font(self, font): - if font in self.fontmap: - basename = self.fontmap[font] - else: - basename = font - - cached_font = self.fonts.get(basename) - if cached_font is None: - fname = os.path.join(self.basepath, basename + ".afm") - with open(fname, 'rb') as fd: - cached_font = AFM(fd) - cached_font.fname = fname - self.fonts[basename] = cached_font - self.fonts[cached_font.get_fontname()] = cached_font - return cached_font - - def _get_info(self, fontname, font_class, sym, fontsize, dpi, math=True): - """Load the cmfont, metrics and glyph with caching.""" - key = fontname, sym, fontsize, dpi - tup = self.glyphd.get(key) - - if tup is not None: - return tup - - # Only characters in the "Letter" class should really be italicized. - # This class includes greek letters, so we're ok - if (fontname == 'it' and - (len(sym) > 1 - or not unicodedata.category(sym).startswith("L"))): - fontname = 'rm' - - found_symbol = False - - if sym in latex_to_standard: - fontname, num = latex_to_standard[sym] - glyph = chr(num) - found_symbol = True - elif len(sym) == 1: - glyph = sym - num = ord(glyph) - found_symbol = True - else: - _log.warning( - "No TeX to built-in Postscript mapping for {!r}".format(sym)) - - slanted = (fontname == 'it') - font = self._get_font(fontname) - - if found_symbol: - try: - symbol_name = font.get_name_char(glyph) - except KeyError: - _log.warning( - "No glyph in standard Postscript font {!r} for {!r}" - .format(font.get_fontname(), sym)) - found_symbol = False - - if not found_symbol: - glyph = '?' - num = ord(glyph) - symbol_name = font.get_name_char(glyph) - - offset = 0 - - scale = 0.001 * fontsize - - xmin, ymin, xmax, ymax = [val * scale - for val in font.get_bbox_char(glyph)] - metrics = types.SimpleNamespace( - advance = font.get_width_char(glyph) * scale, - width = font.get_width_char(glyph) * scale, - height = font.get_height_char(glyph) * scale, - xmin = xmin, - xmax = xmax, - ymin = ymin+offset, - ymax = ymax+offset, - # iceberg is the equivalent of TeX's "height" - iceberg = ymax + offset, - slanted = slanted - ) - - self.glyphd[key] = types.SimpleNamespace( - font = font, - fontsize = fontsize, - postscript_name = font.get_fontname(), - metrics = metrics, - symbol_name = symbol_name, - num = num, - glyph = glyph, - offset = offset - ) - - return self.glyphd[key] - - def get_kern(self, font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi): - if font1 == font2 and fontsize1 == fontsize2: - info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi) - info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi) - font = info1.font - return (font.get_kern_dist(info1.glyph, info2.glyph) - * 0.001 * fontsize1) - return super().get_kern(font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi) - - def get_xheight(self, font, fontsize, dpi): - font = self._get_font(font) - return font.get_xheight() * 0.001 * fontsize - - def get_underline_thickness(self, font, fontsize, dpi): - font = self._get_font(font) - return font.get_underline_thickness() * 0.001 * fontsize - - -############################################################################## -# TeX-LIKE BOX MODEL - -# The following is based directly on the document 'woven' from the -# TeX82 source code. This information is also available in printed -# form: -# -# Knuth, Donald E.. 1986. Computers and Typesetting, Volume B: -# TeX: The Program. Addison-Wesley Professional. -# -# The most relevant "chapters" are: -# Data structures for boxes and their friends -# Shipping pages out (Ship class) -# Packaging (hpack and vpack) -# Data structures for math mode -# Subroutines for math mode -# Typesetting math formulas -# -# Many of the docstrings below refer to a numbered "node" in that -# book, e.g., node123 -# -# Note that (as TeX) y increases downward, unlike many other parts of -# matplotlib. - -# How much text shrinks when going to the next-smallest level. GROW_FACTOR -# must be the inverse of SHRINK_FACTOR. -SHRINK_FACTOR = 0.7 -GROW_FACTOR = 1.0 / SHRINK_FACTOR -# The number of different sizes of chars to use, beyond which they will not -# get any smaller -NUM_SIZE_LEVELS = 6 - - -class FontConstantsBase: - """ - A set of constants that controls how certain things, such as sub- - and superscripts are laid out. These are all metrics that can't - be reliably retrieved from the font metrics in the font itself. - """ - # Percentage of x-height of additional horiz. space after sub/superscripts - script_space = 0.05 - - # Percentage of x-height that sub/superscripts drop below the baseline - subdrop = 0.4 - - # Percentage of x-height that superscripts are raised from the baseline - sup1 = 0.7 - - # Percentage of x-height that subscripts drop below the baseline - sub1 = 0.3 - - # Percentage of x-height that subscripts drop below the baseline when a - # superscript is present - sub2 = 0.5 - - # Percentage of x-height that sub/supercripts are offset relative to the - # nucleus edge for non-slanted nuclei - delta = 0.025 - - # Additional percentage of last character height above 2/3 of the - # x-height that supercripts are offset relative to the subscript - # for slanted nuclei - delta_slanted = 0.2 - - # Percentage of x-height that supercripts and subscripts are offset for - # integrals - delta_integral = 0.1 - - -class ComputerModernFontConstants(FontConstantsBase): - script_space = 0.075 - subdrop = 0.2 - sup1 = 0.45 - sub1 = 0.2 - sub2 = 0.3 - delta = 0.075 - delta_slanted = 0.3 - delta_integral = 0.3 - - -class STIXFontConstants(FontConstantsBase): - script_space = 0.1 - sup1 = 0.8 - sub2 = 0.6 - delta = 0.05 - delta_slanted = 0.3 - delta_integral = 0.3 - - -class STIXSansFontConstants(FontConstantsBase): - script_space = 0.05 - sup1 = 0.8 - delta_slanted = 0.6 - delta_integral = 0.3 - - -class DejaVuSerifFontConstants(FontConstantsBase): - pass - - -class DejaVuSansFontConstants(FontConstantsBase): - pass - - -# Maps font family names to the FontConstantBase subclass to use -_font_constant_mapping = { - 'DejaVu Sans': DejaVuSansFontConstants, - 'DejaVu Sans Mono': DejaVuSansFontConstants, - 'DejaVu Serif': DejaVuSerifFontConstants, - 'cmb10': ComputerModernFontConstants, - 'cmex10': ComputerModernFontConstants, - 'cmmi10': ComputerModernFontConstants, - 'cmr10': ComputerModernFontConstants, - 'cmss10': ComputerModernFontConstants, - 'cmsy10': ComputerModernFontConstants, - 'cmtt10': ComputerModernFontConstants, - 'STIXGeneral': STIXFontConstants, - 'STIXNonUnicode': STIXFontConstants, - 'STIXSizeFiveSym': STIXFontConstants, - 'STIXSizeFourSym': STIXFontConstants, - 'STIXSizeThreeSym': STIXFontConstants, - 'STIXSizeTwoSym': STIXFontConstants, - 'STIXSizeOneSym': STIXFontConstants, - # Map the fonts we used to ship, just for good measure - 'Bitstream Vera Sans': DejaVuSansFontConstants, - 'Bitstream Vera': DejaVuSansFontConstants, - } - - -def _get_font_constant_set(state): - constants = _font_constant_mapping.get( - state.font_output._get_font(state.font).family_name, - FontConstantsBase) - # STIX sans isn't really its own fonts, just different code points - # in the STIX fonts, so we have to detect this one separately. - if (constants is STIXFontConstants and - isinstance(state.font_output, StixSansFonts)): - return STIXSansFontConstants - return constants - - -class MathTextWarning(Warning): - pass - - -class Node: - """A node in the TeX box model.""" - - def __init__(self): - self.size = 0 - - def __repr__(self): - return self.__class__.__name__ - - def get_kerning(self, next): - return 0.0 - - def shrink(self): - """ - Shrinks one level smaller. There are only three levels of - sizes, after which things will no longer get smaller. - """ - self.size += 1 - - def grow(self): - """ - Grows one level larger. There is no limit to how big - something can get. - """ - self.size -= 1 - - def render(self, x, y): - pass - - -class Box(Node): - """A node with a physical location.""" - - def __init__(self, width, height, depth): - super().__init__() - self.width = width - self.height = height - self.depth = depth - - def shrink(self): - super().shrink() - if self.size < NUM_SIZE_LEVELS: - self.width *= SHRINK_FACTOR - self.height *= SHRINK_FACTOR - self.depth *= SHRINK_FACTOR - - def grow(self): - super().grow() - self.width *= GROW_FACTOR - self.height *= GROW_FACTOR - self.depth *= GROW_FACTOR - - def render(self, x1, y1, x2, y2): - pass - - -class Vbox(Box): - """A box with only height (zero width).""" - - def __init__(self, height, depth): - super().__init__(0., height, depth) - - -class Hbox(Box): - """A box with only width (zero height and depth).""" - - def __init__(self, width): - super().__init__(width, 0., 0.) - - -class Char(Node): - """ - A single character. - - Unlike TeX, the font information and metrics are stored with each `Char` - to make it easier to lookup the font metrics when needed. Note that TeX - boxes have a width, height, and depth, unlike Type1 and TrueType which use - a full bounding box and an advance in the x-direction. The metrics must - be converted to the TeX model, and the advance (if different from width) - must be converted into a `Kern` node when the `Char` is added to its parent - `Hlist`. - """ - - def __init__(self, c, state, math=True): - super().__init__() - self.c = c - self.font_output = state.font_output - self.font = state.font - self.font_class = state.font_class - self.fontsize = state.fontsize - self.dpi = state.dpi - self.math = math - # The real width, height and depth will be set during the - # pack phase, after we know the real fontsize - self._update_metrics() - - def __repr__(self): - return '`%s`' % self.c - - def _update_metrics(self): - metrics = self._metrics = self.font_output.get_metrics( - self.font, self.font_class, self.c, self.fontsize, self.dpi, - self.math) - if self.c == ' ': - self.width = metrics.advance - else: - self.width = metrics.width - self.height = metrics.iceberg - self.depth = -(metrics.iceberg - metrics.height) - - def is_slanted(self): - return self._metrics.slanted - - def get_kerning(self, next): - """ - Return the amount of kerning between this and the given character. - - This method is called when characters are strung together into `Hlist` - to create `Kern` nodes. - """ - advance = self._metrics.advance - self.width - kern = 0. - if isinstance(next, Char): - kern = self.font_output.get_kern( - self.font, self.font_class, self.c, self.fontsize, - next.font, next.font_class, next.c, next.fontsize, - self.dpi) - return advance + kern - - def render(self, x, y): - """ - Render the character to the canvas - """ - self.font_output.render_glyph( - x, y, - self.font, self.font_class, self.c, self.fontsize, self.dpi) - - def shrink(self): - super().shrink() - if self.size < NUM_SIZE_LEVELS: - self.fontsize *= SHRINK_FACTOR - self.width *= SHRINK_FACTOR - self.height *= SHRINK_FACTOR - self.depth *= SHRINK_FACTOR - - def grow(self): - super().grow() - self.fontsize *= GROW_FACTOR - self.width *= GROW_FACTOR - self.height *= GROW_FACTOR - self.depth *= GROW_FACTOR - - -class Accent(Char): - """ - The font metrics need to be dealt with differently for accents, - since they are already offset correctly from the baseline in - TrueType fonts. - """ - def _update_metrics(self): - metrics = self._metrics = self.font_output.get_metrics( - self.font, self.font_class, self.c, self.fontsize, self.dpi) - self.width = metrics.xmax - metrics.xmin - self.height = metrics.ymax - metrics.ymin - self.depth = 0 - - def shrink(self): - super().shrink() - self._update_metrics() - - def grow(self): - super().grow() - self._update_metrics() - - def render(self, x, y): - """ - Render the character to the canvas. - """ - self.font_output.render_glyph( - x - self._metrics.xmin, y + self._metrics.ymin, - self.font, self.font_class, self.c, self.fontsize, self.dpi) - - -class List(Box): - """A list of nodes (either horizontal or vertical).""" - - def __init__(self, elements): - super().__init__(0., 0., 0.) - self.shift_amount = 0. # An arbitrary offset - self.children = elements # The child nodes of this list - # The following parameters are set in the vpack and hpack functions - self.glue_set = 0. # The glue setting of this list - self.glue_sign = 0 # 0: normal, -1: shrinking, 1: stretching - self.glue_order = 0 # The order of infinity (0 - 3) for the glue - - def __repr__(self): - return '[%s <%.02f %.02f %.02f %.02f> %s]' % ( - super().__repr__(), - self.width, self.height, - self.depth, self.shift_amount, - ' '.join([repr(x) for x in self.children])) - - @staticmethod - def _determine_order(totals): - """ - Determine the highest order of glue used by the members of this list. - - Helper function used by vpack and hpack. - """ - for i in range(len(totals))[::-1]: - if totals[i] != 0: - return i - return 0 - - def _set_glue(self, x, sign, totals, error_type): - o = self._determine_order(totals) - self.glue_order = o - self.glue_sign = sign - if totals[o] != 0.: - self.glue_set = x / totals[o] - else: - self.glue_sign = 0 - self.glue_ratio = 0. - if o == 0: - if len(self.children): - _log.warning("%s %s: %r", - error_type, self.__class__.__name__, self) - - def shrink(self): - for child in self.children: - child.shrink() - super().shrink() - if self.size < NUM_SIZE_LEVELS: - self.shift_amount *= SHRINK_FACTOR - self.glue_set *= SHRINK_FACTOR - - def grow(self): - for child in self.children: - child.grow() - super().grow() - self.shift_amount *= GROW_FACTOR - self.glue_set *= GROW_FACTOR - - -class Hlist(List): - """A horizontal list of boxes.""" - - def __init__(self, elements, w=0., m='additional', do_kern=True): - super().__init__(elements) - if do_kern: - self.kern() - self.hpack() - - def kern(self): - """ - Insert `Kern` nodes between `Char` nodes to set kerning. - - The `Char` nodes themselves determine the amount of kerning they need - (in `~Char.get_kerning`), and this function just creates the correct - linked list. - """ - new_children = [] - num_children = len(self.children) - if num_children: - for i in range(num_children): - elem = self.children[i] - if i < num_children - 1: - next = self.children[i + 1] - else: - next = None - - new_children.append(elem) - kerning_distance = elem.get_kerning(next) - if kerning_distance != 0.: - kern = Kern(kerning_distance) - new_children.append(kern) - self.children = new_children - - # This is a failed experiment to fake cross-font kerning. -# def get_kerning(self, next): -# if len(self.children) >= 2 and isinstance(self.children[-2], Char): -# if isinstance(next, Char): -# print "CASE A" -# return self.children[-2].get_kerning(next) -# elif (isinstance(next, Hlist) and len(next.children) -# and isinstance(next.children[0], Char)): -# print "CASE B" -# result = self.children[-2].get_kerning(next.children[0]) -# print result -# return result -# return 0.0 - - def hpack(self, w=0., m='additional'): - r""" - Compute the dimensions of the resulting boxes, and adjust the glue if - one of those dimensions is pre-specified. The computed sizes normally - enclose all of the material inside the new box; but some items may - stick out if negative glue is used, if the box is overfull, or if a - ``\vbox`` includes other boxes that have been shifted left. - - Parameters - ---------- - w : float, default: 0 - A width. - m : {'exactly', 'additional'}, default: 'additional' - Whether to produce a box whose width is 'exactly' *w*; or a box - with the natural width of the contents, plus *w* ('additional'). - - Notes - ----- - The defaults produce a box with the natural width of the contents. - """ - # I don't know why these get reset in TeX. Shift_amount is pretty - # much useless if we do. - # self.shift_amount = 0. - h = 0. - d = 0. - x = 0. - total_stretch = [0.] * 4 - total_shrink = [0.] * 4 - for p in self.children: - if isinstance(p, Char): - x += p.width - h = max(h, p.height) - d = max(d, p.depth) - elif isinstance(p, Box): - x += p.width - if not np.isinf(p.height) and not np.isinf(p.depth): - s = getattr(p, 'shift_amount', 0.) - h = max(h, p.height - s) - d = max(d, p.depth + s) - elif isinstance(p, Glue): - glue_spec = p.glue_spec - x += glue_spec.width - total_stretch[glue_spec.stretch_order] += glue_spec.stretch - total_shrink[glue_spec.shrink_order] += glue_spec.shrink - elif isinstance(p, Kern): - x += p.width - self.height = h - self.depth = d - - if m == 'additional': - w += x - self.width = w - x = w - x - - if x == 0.: - self.glue_sign = 0 - self.glue_order = 0 - self.glue_ratio = 0. - return - if x > 0.: - self._set_glue(x, 1, total_stretch, "Overfull") - else: - self._set_glue(x, -1, total_shrink, "Underfull") - - -class Vlist(List): - """A vertical list of boxes.""" - - def __init__(self, elements, h=0., m='additional'): - super().__init__(elements) - self.vpack() - - def vpack(self, h=0., m='additional', l=np.inf): - """ - Compute the dimensions of the resulting boxes, and to adjust the glue - if one of those dimensions is pre-specified. - - Parameters - ---------- - h : float, default: 0 - A height. - m : {'exactly', 'additional'}, default: 'additional' - Whether to produce a box whose height is 'exactly' *w*; or a box - with the natural height of the contents, plus *w* ('additional'). - l : float, default: np.inf - The maximum height. - - Notes - ----- - The defaults produce a box with the natural height of the contents. - """ - # I don't know why these get reset in TeX. Shift_amount is pretty - # much useless if we do. - # self.shift_amount = 0. - w = 0. - d = 0. - x = 0. - total_stretch = [0.] * 4 - total_shrink = [0.] * 4 - for p in self.children: - if isinstance(p, Box): - x += d + p.height - d = p.depth - if not np.isinf(p.width): - s = getattr(p, 'shift_amount', 0.) - w = max(w, p.width + s) - elif isinstance(p, Glue): - x += d - d = 0. - glue_spec = p.glue_spec - x += glue_spec.width - total_stretch[glue_spec.stretch_order] += glue_spec.stretch - total_shrink[glue_spec.shrink_order] += glue_spec.shrink - elif isinstance(p, Kern): - x += d + p.width - d = 0. - elif isinstance(p, Char): - raise RuntimeError( - "Internal mathtext error: Char node found in Vlist") - - self.width = w - if d > l: - x += d - l - self.depth = l - else: - self.depth = d - - if m == 'additional': - h += x - self.height = h - x = h - x - - if x == 0: - self.glue_sign = 0 - self.glue_order = 0 - self.glue_ratio = 0. - return - - if x > 0.: - self._set_glue(x, 1, total_stretch, "Overfull") - else: - self._set_glue(x, -1, total_shrink, "Underfull") - - -class Rule(Box): - """ - A solid black rectangle. - - It has *width*, *depth*, and *height* fields just as in an `Hlist`. - However, if any of these dimensions is inf, the actual value will be - determined by running the rule up to the boundary of the innermost - enclosing box. This is called a "running dimension". The width is never - running in an `Hlist`; the height and depth are never running in a `Vlist`. - """ - - def __init__(self, width, height, depth, state): - super().__init__(width, height, depth) - self.font_output = state.font_output - - def render(self, x, y, w, h): - self.font_output.render_rect_filled(x, y, x + w, y + h) - - -class Hrule(Rule): - """Convenience class to create a horizontal rule.""" - - def __init__(self, state, thickness=None): - if thickness is None: - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - height = depth = thickness * 0.5 - super().__init__(np.inf, height, depth, state) - - -class Vrule(Rule): - """Convenience class to create a vertical rule.""" - - def __init__(self, state): - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - super().__init__(thickness, np.inf, np.inf, state) - - -_GlueSpec = namedtuple( - "_GlueSpec", "width stretch stretch_order shrink shrink_order") -_GlueSpec._named = { - 'fil': _GlueSpec(0., 1., 1, 0., 0), - 'fill': _GlueSpec(0., 1., 2, 0., 0), - 'filll': _GlueSpec(0., 1., 3, 0., 0), - 'neg_fil': _GlueSpec(0., 0., 0, 1., 1), - 'neg_fill': _GlueSpec(0., 0., 0, 1., 2), - 'neg_filll': _GlueSpec(0., 0., 0, 1., 3), - 'empty': _GlueSpec(0., 0., 0, 0., 0), - 'ss': _GlueSpec(0., 1., 1, -1., 1), -} - - -class Glue(Node): - """ - Most of the information in this object is stored in the underlying - ``_GlueSpec`` class, which is shared between multiple glue objects. - (This is a memory optimization which probably doesn't matter anymore, but - it's easier to stick to what TeX does.) - """ - - @cbook.deprecated("3.3") - @property - def glue_subtype(self): - return "normal" - - @cbook._delete_parameter("3.3", "copy") - def __init__(self, glue_type, copy=False): - super().__init__() - if isinstance(glue_type, str): - glue_spec = _GlueSpec._named[glue_type] - elif isinstance(glue_type, _GlueSpec): - glue_spec = glue_type - else: - raise ValueError("glue_type must be a glue spec name or instance") - self.glue_spec = glue_spec - - def shrink(self): - super().shrink() - if self.size < NUM_SIZE_LEVELS: - g = self.glue_spec - self.glue_spec = g._replace(width=g.width * SHRINK_FACTOR) - - def grow(self): - super().grow() - g = self.glue_spec - self.glue_spec = g._replace(width=g.width * GROW_FACTOR) - - -@cbook.deprecated("3.3") -class GlueSpec: - """See `Glue`.""" - - def __init__(self, width=0., stretch=0., stretch_order=0, - shrink=0., shrink_order=0): - self.width = width - self.stretch = stretch - self.stretch_order = stretch_order - self.shrink = shrink - self.shrink_order = shrink_order - - def copy(self): - return GlueSpec( - self.width, - self.stretch, - self.stretch_order, - self.shrink, - self.shrink_order) - - @classmethod - def factory(cls, glue_type): - return cls._types[glue_type] - - -with cbook._suppress_matplotlib_deprecation_warning(): - GlueSpec._types = {k: GlueSpec(**v._asdict()) - for k, v in _GlueSpec._named.items()} - - -# Some convenient ways to get common kinds of glue - - -@cbook.deprecated("3.3", alternative="Glue('fil')") -class Fil(Glue): - def __init__(self): - super().__init__('fil') - - -@cbook.deprecated("3.3", alternative="Glue('fill')") -class Fill(Glue): - def __init__(self): - super().__init__('fill') - - -@cbook.deprecated("3.3", alternative="Glue('filll')") -class Filll(Glue): - def __init__(self): - super().__init__('filll') - - -@cbook.deprecated("3.3", alternative="Glue('neg_fil')") -class NegFil(Glue): - def __init__(self): - super().__init__('neg_fil') - - -@cbook.deprecated("3.3", alternative="Glue('neg_fill')") -class NegFill(Glue): - def __init__(self): - super().__init__('neg_fill') - - -@cbook.deprecated("3.3", alternative="Glue('neg_filll')") -class NegFilll(Glue): - def __init__(self): - super().__init__('neg_filll') - - -@cbook.deprecated("3.3", alternative="Glue('ss')") -class SsGlue(Glue): - def __init__(self): - super().__init__('ss') - - -class HCentered(Hlist): - """ - A convenience class to create an `Hlist` whose contents are - centered within its enclosing box. - """ - - def __init__(self, elements): - super().__init__([Glue('ss'), *elements, Glue('ss')], do_kern=False) - - -class VCentered(Vlist): - """ - A convenience class to create a `Vlist` whose contents are - centered within its enclosing box. - """ - - def __init__(self, elements): - super().__init__([Glue('ss'), *elements, Glue('ss')]) - - -class Kern(Node): - """ - A `Kern` node has a width field to specify a (normally - negative) amount of spacing. This spacing correction appears in - horizontal lists between letters like A and V when the font - designer said that it looks better to move them closer together or - further apart. A kern node can also appear in a vertical list, - when its *width* denotes additional spacing in the vertical - direction. - """ - - height = 0 - depth = 0 - - def __init__(self, width): - super().__init__() - self.width = width - - def __repr__(self): - return "k%.02f" % self.width - - def shrink(self): - super().shrink() - if self.size < NUM_SIZE_LEVELS: - self.width *= SHRINK_FACTOR - - def grow(self): - super().grow() - self.width *= GROW_FACTOR - - -class SubSuperCluster(Hlist): - """ - A hack to get around that fact that this code does a two-pass parse like - TeX. This lets us store enough information in the hlist itself, namely the - nucleus, sub- and super-script, such that if another script follows that - needs to be attached, it can be reconfigured on the fly. - """ - - def __init__(self): - self.nucleus = None - self.sub = None - self.super = None - super().__init__([]) - - -class AutoHeightChar(Hlist): - """ - A character as close to the given height and depth as possible. - - When using a font with multiple height versions of some characters (such as - the BaKoMa fonts), the correct glyph will be selected, otherwise this will - always just return a scaled version of the glyph. - """ - - def __init__(self, c, height, depth, state, always=False, factor=None): - alternatives = state.font_output.get_sized_alternatives_for_symbol( - state.font, c) - - xHeight = state.font_output.get_xheight( - state.font, state.fontsize, state.dpi) - - state = state.copy() - target_total = height + depth - for fontname, sym in alternatives: - state.font = fontname - char = Char(sym, state) - # Ensure that size 0 is chosen when the text is regular sized but - # with descender glyphs by subtracting 0.2 * xHeight - if char.height + char.depth >= target_total - 0.2 * xHeight: - break - - shift = 0 - if state.font != 0: - if factor is None: - factor = target_total / (char.height + char.depth) - state.fontsize *= factor - char = Char(sym, state) - - shift = (depth - char.depth) - - super().__init__([char]) - self.shift_amount = shift - - -class AutoWidthChar(Hlist): - """ - A character as close to the given width as possible. - - When using a font with multiple width versions of some characters (such as - the BaKoMa fonts), the correct glyph will be selected, otherwise this will - always just return a scaled version of the glyph. - """ - - def __init__(self, c, width, state, always=False, char_class=Char): - alternatives = state.font_output.get_sized_alternatives_for_symbol( - state.font, c) - - state = state.copy() - for fontname, sym in alternatives: - state.font = fontname - char = char_class(sym, state) - if char.width >= width: - break - - factor = width / char.width - state.fontsize *= factor - char = char_class(sym, state) - - super().__init__([char]) - self.width = char.width - - -class Ship: - """ - Ship boxes to output once they have been set up, this sends them to output. - - Since boxes can be inside of boxes inside of boxes, the main work of `Ship` - is done by two mutually recursive routines, `hlist_out` and `vlist_out`, - which traverse the `Hlist` nodes and `Vlist` nodes inside of horizontal - and vertical boxes. The global variables used in TeX to store state as it - processes have become member variables here. - """ - - def __call__(self, ox, oy, box): - self.max_push = 0 # Deepest nesting of push commands so far - self.cur_s = 0 - self.cur_v = 0. - self.cur_h = 0. - self.off_h = ox - self.off_v = oy + box.height - self.hlist_out(box) - - @staticmethod - def clamp(value): - if value < -1000000000.: - return -1000000000. - if value > 1000000000.: - return 1000000000. - return value - - def hlist_out(self, box): - cur_g = 0 - cur_glue = 0. - glue_order = box.glue_order - glue_sign = box.glue_sign - base_line = self.cur_v - left_edge = self.cur_h - self.cur_s += 1 - self.max_push = max(self.cur_s, self.max_push) - clamp = self.clamp - - for p in box.children: - if isinstance(p, Char): - p.render(self.cur_h + self.off_h, self.cur_v + self.off_v) - self.cur_h += p.width - elif isinstance(p, Kern): - self.cur_h += p.width - elif isinstance(p, List): - # node623 - if len(p.children) == 0: - self.cur_h += p.width - else: - edge = self.cur_h - self.cur_v = base_line + p.shift_amount - if isinstance(p, Hlist): - self.hlist_out(p) - else: - # p.vpack(box.height + box.depth, 'exactly') - self.vlist_out(p) - self.cur_h = edge + p.width - self.cur_v = base_line - elif isinstance(p, Box): - # node624 - rule_height = p.height - rule_depth = p.depth - rule_width = p.width - if np.isinf(rule_height): - rule_height = box.height - if np.isinf(rule_depth): - rule_depth = box.depth - if rule_height > 0 and rule_width > 0: - self.cur_v = base_line + rule_depth - p.render(self.cur_h + self.off_h, - self.cur_v + self.off_v, - rule_width, rule_height) - self.cur_v = base_line - self.cur_h += rule_width - elif isinstance(p, Glue): - # node625 - glue_spec = p.glue_spec - rule_width = glue_spec.width - cur_g - if glue_sign != 0: # normal - if glue_sign == 1: # stretching - if glue_spec.stretch_order == glue_order: - cur_glue += glue_spec.stretch - cur_g = round(clamp(box.glue_set * cur_glue)) - elif glue_spec.shrink_order == glue_order: - cur_glue += glue_spec.shrink - cur_g = round(clamp(box.glue_set * cur_glue)) - rule_width += cur_g - self.cur_h += rule_width - self.cur_s -= 1 - - def vlist_out(self, box): - cur_g = 0 - cur_glue = 0. - glue_order = box.glue_order - glue_sign = box.glue_sign - self.cur_s += 1 - self.max_push = max(self.max_push, self.cur_s) - left_edge = self.cur_h - self.cur_v -= box.height - top_edge = self.cur_v - clamp = self.clamp - - for p in box.children: - if isinstance(p, Kern): - self.cur_v += p.width - elif isinstance(p, List): - if len(p.children) == 0: - self.cur_v += p.height + p.depth - else: - self.cur_v += p.height - self.cur_h = left_edge + p.shift_amount - save_v = self.cur_v - p.width = box.width - if isinstance(p, Hlist): - self.hlist_out(p) - else: - self.vlist_out(p) - self.cur_v = save_v + p.depth - self.cur_h = left_edge - elif isinstance(p, Box): - rule_height = p.height - rule_depth = p.depth - rule_width = p.width - if np.isinf(rule_width): - rule_width = box.width - rule_height += rule_depth - if rule_height > 0 and rule_depth > 0: - self.cur_v += rule_height - p.render(self.cur_h + self.off_h, - self.cur_v + self.off_v, - rule_width, rule_height) - elif isinstance(p, Glue): - glue_spec = p.glue_spec - rule_height = glue_spec.width - cur_g - if glue_sign != 0: # normal - if glue_sign == 1: # stretching - if glue_spec.stretch_order == glue_order: - cur_glue += glue_spec.stretch - cur_g = round(clamp(box.glue_set * cur_glue)) - elif glue_spec.shrink_order == glue_order: # shrinking - cur_glue += glue_spec.shrink - cur_g = round(clamp(box.glue_set * cur_glue)) - rule_height += cur_g - self.cur_v += rule_height - elif isinstance(p, Char): - raise RuntimeError( - "Internal mathtext error: Char node found in vlist") - self.cur_s -= 1 - - -ship = Ship() - - -############################################################################## -# PARSER - - -def Error(msg): - """Helper class to raise parser errors.""" - def raise_error(s, loc, toks): - raise ParseFatalException(s, loc, msg) - - empty = Empty() - empty.setParseAction(raise_error) - return empty - - -class Parser: - """ - A pyparsing-based parser for strings containing math expressions. - - Raw text may also appear outside of pairs of ``$``. - - The grammar is based directly on that in TeX, though it cuts a few corners. - """ - - _math_style_dict = dict(displaystyle=0, textstyle=1, - scriptstyle=2, scriptscriptstyle=3) - - _binary_operators = set(''' - + * - - \\pm \\sqcap \\rhd - \\mp \\sqcup \\unlhd - \\times \\vee \\unrhd - \\div \\wedge \\oplus - \\ast \\setminus \\ominus - \\star \\wr \\otimes - \\circ \\diamond \\oslash - \\bullet \\bigtriangleup \\odot - \\cdot \\bigtriangledown \\bigcirc - \\cap \\triangleleft \\dagger - \\cup \\triangleright \\ddagger - \\uplus \\lhd \\amalg'''.split()) - - _relation_symbols = set(''' - = < > : - \\leq \\geq \\equiv \\models - \\prec \\succ \\sim \\perp - \\preceq \\succeq \\simeq \\mid - \\ll \\gg \\asymp \\parallel - \\subset \\supset \\approx \\bowtie - \\subseteq \\supseteq \\cong \\Join - \\sqsubset \\sqsupset \\neq \\smile - \\sqsubseteq \\sqsupseteq \\doteq \\frown - \\in \\ni \\propto \\vdash - \\dashv \\dots \\dotplus \\doteqdot'''.split()) - - _arrow_symbols = set(''' - \\leftarrow \\longleftarrow \\uparrow - \\Leftarrow \\Longleftarrow \\Uparrow - \\rightarrow \\longrightarrow \\downarrow - \\Rightarrow \\Longrightarrow \\Downarrow - \\leftrightarrow \\longleftrightarrow \\updownarrow - \\Leftrightarrow \\Longleftrightarrow \\Updownarrow - \\mapsto \\longmapsto \\nearrow - \\hookleftarrow \\hookrightarrow \\searrow - \\leftharpoonup \\rightharpoonup \\swarrow - \\leftharpoondown \\rightharpoondown \\nwarrow - \\rightleftharpoons \\leadsto'''.split()) - - _spaced_symbols = _binary_operators | _relation_symbols | _arrow_symbols - - _punctuation_symbols = set(r', ; . ! \ldotp \cdotp'.split()) - - _overunder_symbols = set(r''' - \sum \prod \coprod \bigcap \bigcup \bigsqcup \bigvee - \bigwedge \bigodot \bigotimes \bigoplus \biguplus - '''.split()) - - _overunder_functions = set( - "lim liminf limsup sup max min".split()) - - _dropsub_symbols = set(r'''\int \oint'''.split()) - - _fontnames = set("rm cal it tt sf bf default bb frak scr regular".split()) - - _function_names = set(""" - arccos csc ker min arcsin deg lg Pr arctan det lim sec arg dim - liminf sin cos exp limsup sinh cosh gcd ln sup cot hom log tan - coth inf max tanh""".split()) - - _ambi_delim = set(""" - | \\| / \\backslash \\uparrow \\downarrow \\updownarrow \\Uparrow - \\Downarrow \\Updownarrow . \\vert \\Vert \\\\|""".split()) - - _left_delim = set(r"( [ \{ < \lfloor \langle \lceil".split()) - - _right_delim = set(r") ] \} > \rfloor \rangle \rceil".split()) - - def __init__(self): - p = types.SimpleNamespace() - # All forward declarations are here - p.accent = Forward() - p.ambi_delim = Forward() - p.apostrophe = Forward() - p.auto_delim = Forward() - p.binom = Forward() - p.bslash = Forward() - p.c_over_c = Forward() - p.customspace = Forward() - p.end_group = Forward() - p.float_literal = Forward() - p.font = Forward() - p.frac = Forward() - p.dfrac = Forward() - p.function = Forward() - p.genfrac = Forward() - p.group = Forward() - p.int_literal = Forward() - p.latexfont = Forward() - p.lbracket = Forward() - p.left_delim = Forward() - p.lbrace = Forward() - p.main = Forward() - p.math = Forward() - p.math_string = Forward() - p.non_math = Forward() - p.operatorname = Forward() - p.overline = Forward() - p.placeable = Forward() - p.rbrace = Forward() - p.rbracket = Forward() - p.required_group = Forward() - p.right_delim = Forward() - p.right_delim_safe = Forward() - p.simple = Forward() - p.simple_group = Forward() - p.single_symbol = Forward() - p.accentprefixed = Forward() - p.space = Forward() - p.sqrt = Forward() - p.stackrel = Forward() - p.start_group = Forward() - p.subsuper = Forward() - p.subsuperop = Forward() - p.symbol = Forward() - p.symbol_name = Forward() - p.token = Forward() - p.unknown_symbol = Forward() - - # Set names on everything -- very useful for debugging - for key, val in vars(p).items(): - if not key.startswith('_'): - val.setName(key) - - p.float_literal <<= Regex(r"[-+]?([0-9]+\.?[0-9]*|\.[0-9]+)") - p.int_literal <<= Regex("[-+]?[0-9]+") - - p.lbrace <<= Literal('{').suppress() - p.rbrace <<= Literal('}').suppress() - p.lbracket <<= Literal('[').suppress() - p.rbracket <<= Literal(']').suppress() - p.bslash <<= Literal('\\') - - p.space <<= oneOf(list(self._space_widths)) - p.customspace <<= ( - Suppress(Literal(r'\hspace')) - - ((p.lbrace + p.float_literal + p.rbrace) - | Error(r"Expected \hspace{n}")) - ) - - unicode_range = "\U00000080-\U0001ffff" - p.single_symbol <<= Regex( - r"([a-zA-Z0-9 +\-*/<>=:,.;!\?&'@()\[\]|%s])|(\\[%%${}\[\]_|])" % - unicode_range) - p.accentprefixed <<= Suppress(p.bslash) + oneOf(self._accentprefixed) - p.symbol_name <<= ( - Combine(p.bslash + oneOf(list(tex2uni))) - + FollowedBy(Regex("[^A-Za-z]").leaveWhitespace() | StringEnd()) - ) - p.symbol <<= (p.single_symbol | p.symbol_name).leaveWhitespace() - - p.apostrophe <<= Regex("'+") - - p.c_over_c <<= ( - Suppress(p.bslash) - + oneOf(list(self._char_over_chars)) - ) - - p.accent <<= Group( - Suppress(p.bslash) - + oneOf([*self._accent_map, *self._wide_accents]) - - p.placeable - ) - - p.function <<= ( - Suppress(p.bslash) - + oneOf(list(self._function_names)) - ) - - p.start_group <<= Optional(p.latexfont) + p.lbrace - p.end_group <<= p.rbrace.copy() - p.simple_group <<= Group(p.lbrace + ZeroOrMore(p.token) + p.rbrace) - p.required_group <<= Group(p.lbrace + OneOrMore(p.token) + p.rbrace) - p.group <<= Group( - p.start_group + ZeroOrMore(p.token) + p.end_group - ) - - p.font <<= Suppress(p.bslash) + oneOf(list(self._fontnames)) - p.latexfont <<= ( - Suppress(p.bslash) - + oneOf(['math' + x for x in self._fontnames]) - ) - - p.frac <<= Group( - Suppress(Literal(r"\frac")) - - ((p.required_group + p.required_group) - | Error(r"Expected \frac{num}{den}")) - ) - - p.dfrac <<= Group( - Suppress(Literal(r"\dfrac")) - - ((p.required_group + p.required_group) - | Error(r"Expected \dfrac{num}{den}")) - ) - - p.stackrel <<= Group( - Suppress(Literal(r"\stackrel")) - - ((p.required_group + p.required_group) - | Error(r"Expected \stackrel{num}{den}")) - ) - - p.binom <<= Group( - Suppress(Literal(r"\binom")) - - ((p.required_group + p.required_group) - | Error(r"Expected \binom{num}{den}")) - ) - - p.ambi_delim <<= oneOf(list(self._ambi_delim)) - p.left_delim <<= oneOf(list(self._left_delim)) - p.right_delim <<= oneOf(list(self._right_delim)) - p.right_delim_safe <<= oneOf([*(self._right_delim - {'}'}), r'\}']) - - p.genfrac <<= Group( - Suppress(Literal(r"\genfrac")) - - (((p.lbrace - + Optional(p.ambi_delim | p.left_delim, default='') - + p.rbrace) - + (p.lbrace - + Optional(p.ambi_delim | p.right_delim_safe, default='') - + p.rbrace) - + (p.lbrace + p.float_literal + p.rbrace) - + p.simple_group + p.required_group + p.required_group) - | Error("Expected " - r"\genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}")) - ) - - p.sqrt <<= Group( - Suppress(Literal(r"\sqrt")) - - ((Optional(p.lbracket + p.int_literal + p.rbracket, default=None) - + p.required_group) - | Error("Expected \\sqrt{value}")) - ) - - p.overline <<= Group( - Suppress(Literal(r"\overline")) - - (p.required_group | Error("Expected \\overline{value}")) - ) - - p.unknown_symbol <<= Combine(p.bslash + Regex("[A-Za-z]*")) - - p.operatorname <<= Group( - Suppress(Literal(r"\operatorname")) - - ((p.lbrace + ZeroOrMore(p.simple | p.unknown_symbol) + p.rbrace) - | Error("Expected \\operatorname{value}")) - ) - - p.placeable <<= ( - p.accentprefixed # Must be before accent so named symbols that are - # prefixed with an accent name work - | p.accent # Must be before symbol as all accents are symbols - | p.symbol # Must be third to catch all named symbols and single - # chars not in a group - | p.c_over_c - | p.function - | p.group - | p.frac - | p.dfrac - | p.stackrel - | p.binom - | p.genfrac - | p.sqrt - | p.overline - | p.operatorname - ) - - p.simple <<= ( - p.space - | p.customspace - | p.font - | p.subsuper - ) - - p.subsuperop <<= oneOf(["_", "^"]) - - p.subsuper <<= Group( - (Optional(p.placeable) - + OneOrMore(p.subsuperop - p.placeable) - + Optional(p.apostrophe)) - | (p.placeable + Optional(p.apostrophe)) - | p.apostrophe - ) - - p.token <<= ( - p.simple - | p.auto_delim - | p.unknown_symbol # Must be last - ) - - p.auto_delim <<= ( - Suppress(Literal(r"\left")) - - ((p.left_delim | p.ambi_delim) - | Error("Expected a delimiter")) - + Group(ZeroOrMore(p.simple | p.auto_delim)) - + Suppress(Literal(r"\right")) - - ((p.right_delim | p.ambi_delim) - | Error("Expected a delimiter")) - ) - - p.math <<= OneOrMore(p.token) - - p.math_string <<= QuotedString('$', '\\', unquoteResults=False) - - p.non_math <<= Regex(r"(?:(?:\\[$])|[^$])*").leaveWhitespace() - - p.main <<= ( - p.non_math + ZeroOrMore(p.math_string + p.non_math) + StringEnd() - ) - - # Set actions - for key, val in vars(p).items(): - if not key.startswith('_'): - if hasattr(self, key): - val.setParseAction(getattr(self, key)) - - self._expression = p.main - self._math_expression = p.math - - def parse(self, s, fonts_object, fontsize, dpi): - """ - Parse expression *s* using the given *fonts_object* for - output, at the given *fontsize* and *dpi*. - - Returns the parse tree of `Node` instances. - """ - self._state_stack = [ - self.State(fonts_object, 'default', 'rm', fontsize, dpi)] - self._em_width_cache = {} - try: - result = self._expression.parseString(s) - except ParseBaseException as err: - raise ValueError("\n".join(["", - err.line, - " " * (err.column - 1) + "^", - str(err)])) from err - self._state_stack = None - self._em_width_cache = {} - self._expression.resetCache() - return result[0] - - # The state of the parser is maintained in a stack. Upon - # entering and leaving a group { } or math/non-math, the stack - # is pushed and popped accordingly. The current state always - # exists in the top element of the stack. - class State: - """ - Stores the state of the parser. - - States are pushed and popped from a stack as necessary, and - the "current" state is always at the top of the stack. - """ - def __init__(self, font_output, font, font_class, fontsize, dpi): - self.font_output = font_output - self._font = font - self.font_class = font_class - self.fontsize = fontsize - self.dpi = dpi - - def copy(self): - return Parser.State( - self.font_output, - self.font, - self.font_class, - self.fontsize, - self.dpi) - - @property - def font(self): - return self._font - - @font.setter - def font(self, name): - if name in ('rm', 'it', 'bf'): - self.font_class = name - self._font = name - - def get_state(self): - """Get the current `State` of the parser.""" - return self._state_stack[-1] - - def pop_state(self): - """Pop a `State` off of the stack.""" - self._state_stack.pop() - - def push_state(self): - """Push a new `State` onto the stack, copying the current state.""" - self._state_stack.append(self.get_state().copy()) - - def main(self, s, loc, toks): - return [Hlist(toks)] - - def math_string(self, s, loc, toks): - return self._math_expression.parseString(toks[0][1:-1]) - - def math(self, s, loc, toks): - hlist = Hlist(toks) - self.pop_state() - return [hlist] - - def non_math(self, s, loc, toks): - s = toks[0].replace(r'\$', '$') - symbols = [Char(c, self.get_state(), math=False) for c in s] - hlist = Hlist(symbols) - # We're going into math now, so set font to 'it' - self.push_state() - self.get_state().font = rcParams['mathtext.default'] - return [hlist] - - def _make_space(self, percentage): - # All spaces are relative to em width - state = self.get_state() - key = (state.font, state.fontsize, state.dpi) - width = self._em_width_cache.get(key) - if width is None: - metrics = state.font_output.get_metrics( - state.font, rcParams['mathtext.default'], 'm', state.fontsize, - state.dpi) - width = metrics.advance - self._em_width_cache[key] = width - return Kern(width * percentage) - - _space_widths = { - r'\,': 0.16667, # 3/18 em = 3 mu - r'\thinspace': 0.16667, # 3/18 em = 3 mu - r'\/': 0.16667, # 3/18 em = 3 mu - r'\>': 0.22222, # 4/18 em = 4 mu - r'\:': 0.22222, # 4/18 em = 4 mu - r'\;': 0.27778, # 5/18 em = 5 mu - r'\ ': 0.33333, # 6/18 em = 6 mu - r'~': 0.33333, # 6/18 em = 6 mu, nonbreakable - r'\enspace': 0.5, # 9/18 em = 9 mu - r'\quad': 1, # 1 em = 18 mu - r'\qquad': 2, # 2 em = 36 mu - r'\!': -0.16667, # -3/18 em = -3 mu - } - - def space(self, s, loc, toks): - assert len(toks) == 1 - num = self._space_widths[toks[0]] - box = self._make_space(num) - return [box] - - def customspace(self, s, loc, toks): - return [self._make_space(float(toks[0]))] - - def symbol(self, s, loc, toks): - c = toks[0] - try: - char = Char(c, self.get_state()) - except ValueError as err: - raise ParseFatalException(s, loc, - "Unknown symbol: %s" % c) from err - - if c in self._spaced_symbols: - # iterate until we find previous character, needed for cases - # such as ${ -2}$, $ -2$, or $ -2$. - prev_char = next((c for c in s[:loc][::-1] if c != ' '), '') - # Binary operators at start of string should not be spaced - if (c in self._binary_operators and - (len(s[:loc].split()) == 0 or prev_char == '{' or - prev_char in self._left_delim)): - return [char] - else: - return [Hlist([self._make_space(0.2), - char, - self._make_space(0.2)], - do_kern=True)] - elif c in self._punctuation_symbols: - - # Do not space commas between brackets - if c == ',': - prev_char = next((c for c in s[:loc][::-1] if c != ' '), '') - next_char = next((c for c in s[loc + 1:] if c != ' '), '') - if prev_char == '{' and next_char == '}': - return [char] - - # Do not space dots as decimal separators - if c == '.' and s[loc - 1].isdigit() and s[loc + 1].isdigit(): - return [char] - else: - return [Hlist([char, self._make_space(0.2)], do_kern=True)] - return [char] - - accentprefixed = symbol - - def unknown_symbol(self, s, loc, toks): - c = toks[0] - raise ParseFatalException(s, loc, "Unknown symbol: %s" % c) - - _char_over_chars = { - # The first 2 entries in the tuple are (font, char, sizescale) for - # the two symbols under and over. The third element is the space - # (in multiples of underline height) - r'AA': (('it', 'A', 1.0), (None, '\\circ', 0.5), 0.0), - } - - def c_over_c(self, s, loc, toks): - sym = toks[0] - state = self.get_state() - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - - under_desc, over_desc, space = \ - self._char_over_chars.get(sym, (None, None, 0.0)) - if under_desc is None: - raise ParseFatalException("Error parsing symbol") - - over_state = state.copy() - if over_desc[0] is not None: - over_state.font = over_desc[0] - over_state.fontsize *= over_desc[2] - over = Accent(over_desc[1], over_state) - - under_state = state.copy() - if under_desc[0] is not None: - under_state.font = under_desc[0] - under_state.fontsize *= under_desc[2] - under = Char(under_desc[1], under_state) - - width = max(over.width, under.width) - - over_centered = HCentered([over]) - over_centered.hpack(width, 'exactly') - - under_centered = HCentered([under]) - under_centered.hpack(width, 'exactly') - - return Vlist([ - over_centered, - Vbox(0., thickness * space), - under_centered - ]) - - _accent_map = { - r'hat': r'\circumflexaccent', - r'breve': r'\combiningbreve', - r'bar': r'\combiningoverline', - r'grave': r'\combininggraveaccent', - r'acute': r'\combiningacuteaccent', - r'tilde': r'\combiningtilde', - r'dot': r'\combiningdotabove', - r'ddot': r'\combiningdiaeresis', - r'vec': r'\combiningrightarrowabove', - r'"': r'\combiningdiaeresis', - r"`": r'\combininggraveaccent', - r"'": r'\combiningacuteaccent', - r'~': r'\combiningtilde', - r'.': r'\combiningdotabove', - r'^': r'\circumflexaccent', - r'overrightarrow': r'\rightarrow', - r'overleftarrow': r'\leftarrow', - r'mathring': r'\circ', - } - - _wide_accents = set(r"widehat widetilde widebar".split()) - - # make a lambda and call it to get the namespace right - _accentprefixed = (lambda am: [ - p for p in tex2uni - if any(p.startswith(a) and a != p for a in am) - ])(set(_accent_map)) - - def accent(self, s, loc, toks): - assert len(toks) == 1 - state = self.get_state() - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - if len(toks[0]) != 2: - raise ParseFatalException("Error parsing accent") - accent, sym = toks[0] - if accent in self._wide_accents: - accent_box = AutoWidthChar( - '\\' + accent, sym.width, state, char_class=Accent) - else: - accent_box = Accent(self._accent_map[accent], state) - if accent == 'mathring': - accent_box.shrink() - accent_box.shrink() - centered = HCentered([Hbox(sym.width / 4.0), accent_box]) - centered.hpack(sym.width, 'exactly') - return Vlist([ - centered, - Vbox(0., thickness * 2.0), - Hlist([sym]) - ]) - - def function(self, s, loc, toks): - hlist = self.operatorname(s, loc, toks) - hlist.function_name = toks[0] - return hlist - - def operatorname(self, s, loc, toks): - self.push_state() - state = self.get_state() - state.font = 'rm' - hlist_list = [] - # Change the font of Chars, but leave Kerns alone - for c in toks[0]: - if isinstance(c, Char): - c.font = 'rm' - c._update_metrics() - hlist_list.append(c) - elif isinstance(c, str): - hlist_list.append(Char(c, state)) - else: - hlist_list.append(c) - next_char_loc = loc + len(toks[0]) + 1 - if isinstance(toks[0], ParseResults): - next_char_loc += len('operatorname{}') - next_char = next((c for c in s[next_char_loc:] if c != ' '), '') - delimiters = self._left_delim | self._ambi_delim | self._right_delim - delimiters |= {'^', '_'} - if (next_char not in delimiters and - toks[0] not in self._overunder_functions): - # Add thin space except when followed by parenthesis, bracket, etc. - hlist_list += [self._make_space(self._space_widths[r'\,'])] - self.pop_state() - return Hlist(hlist_list) - - def start_group(self, s, loc, toks): - self.push_state() - # Deal with LaTeX-style font tokens - if len(toks): - self.get_state().font = toks[0][4:] - return [] - - def group(self, s, loc, toks): - grp = Hlist(toks[0]) - return [grp] - required_group = simple_group = group - - def end_group(self, s, loc, toks): - self.pop_state() - return [] - - def font(self, s, loc, toks): - assert len(toks) == 1 - name = toks[0] - self.get_state().font = name - return [] - - def is_overunder(self, nucleus): - if isinstance(nucleus, Char): - return nucleus.c in self._overunder_symbols - elif isinstance(nucleus, Hlist) and hasattr(nucleus, 'function_name'): - return nucleus.function_name in self._overunder_functions - return False - - def is_dropsub(self, nucleus): - if isinstance(nucleus, Char): - return nucleus.c in self._dropsub_symbols - return False - - def is_slanted(self, nucleus): - if isinstance(nucleus, Char): - return nucleus.is_slanted() - return False - - def is_between_brackets(self, s, loc): - return False - - def subsuper(self, s, loc, toks): - assert len(toks) == 1 - - nucleus = None - sub = None - super = None - - # Pick all of the apostrophes out, including first apostrophes that - # have been parsed as characters - napostrophes = 0 - new_toks = [] - for tok in toks[0]: - if isinstance(tok, str) and tok not in ('^', '_'): - napostrophes += len(tok) - elif isinstance(tok, Char) and tok.c == "'": - napostrophes += 1 - else: - new_toks.append(tok) - toks = new_toks - - if len(toks) == 0: - assert napostrophes - nucleus = Hbox(0.0) - elif len(toks) == 1: - if not napostrophes: - return toks[0] # .asList() - else: - nucleus = toks[0] - elif len(toks) in (2, 3): - # single subscript or superscript - nucleus = toks[0] if len(toks) == 3 else Hbox(0.0) - op, next = toks[-2:] - if op == '_': - sub = next - else: - super = next - elif len(toks) in (4, 5): - # subscript and superscript - nucleus = toks[0] if len(toks) == 5 else Hbox(0.0) - op1, next1, op2, next2 = toks[-4:] - if op1 == op2: - if op1 == '_': - raise ParseFatalException("Double subscript") - else: - raise ParseFatalException("Double superscript") - if op1 == '_': - sub = next1 - super = next2 - else: - super = next1 - sub = next2 - else: - raise ParseFatalException( - "Subscript/superscript sequence is too long. " - "Use braces { } to remove ambiguity.") - - state = self.get_state() - rule_thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - xHeight = state.font_output.get_xheight( - state.font, state.fontsize, state.dpi) - - if napostrophes: - if super is None: - super = Hlist([]) - for i in range(napostrophes): - super.children.extend(self.symbol(s, loc, ['\\prime'])) - # kern() and hpack() needed to get the metrics right after - # extending - super.kern() - super.hpack() - - # Handle over/under symbols, such as sum or integral - if self.is_overunder(nucleus): - vlist = [] - shift = 0. - width = nucleus.width - if super is not None: - super.shrink() - width = max(width, super.width) - if sub is not None: - sub.shrink() - width = max(width, sub.width) - - if super is not None: - hlist = HCentered([super]) - hlist.hpack(width, 'exactly') - vlist.extend([hlist, Kern(rule_thickness * 3.0)]) - hlist = HCentered([nucleus]) - hlist.hpack(width, 'exactly') - vlist.append(hlist) - if sub is not None: - hlist = HCentered([sub]) - hlist.hpack(width, 'exactly') - vlist.extend([Kern(rule_thickness * 3.0), hlist]) - shift = hlist.height - vlist = Vlist(vlist) - vlist.shift_amount = shift + nucleus.depth - result = Hlist([vlist]) - return [result] - - # We remove kerning on the last character for consistency (otherwise - # it will compute kerning based on non-shrunk characters and may put - # them too close together when superscripted) - # We change the width of the last character to match the advance to - # consider some fonts with weird metrics: e.g. stix's f has a width of - # 7.75 and a kerning of -4.0 for an advance of 3.72, and we want to put - # the superscript at the advance - last_char = nucleus - if isinstance(nucleus, Hlist): - new_children = nucleus.children - if len(new_children): - # remove last kern - if (isinstance(new_children[-1], Kern) and - hasattr(new_children[-2], '_metrics')): - new_children = new_children[:-1] - last_char = new_children[-1] - if hasattr(last_char, '_metrics'): - last_char.width = last_char._metrics.advance - # create new Hlist without kerning - nucleus = Hlist(new_children, do_kern=False) - else: - if isinstance(nucleus, Char): - last_char.width = last_char._metrics.advance - nucleus = Hlist([nucleus]) - - # Handle regular sub/superscripts - constants = _get_font_constant_set(state) - lc_height = last_char.height - lc_baseline = 0 - if self.is_dropsub(last_char): - lc_baseline = last_char.depth - - # Compute kerning for sub and super - superkern = constants.delta * xHeight - subkern = constants.delta * xHeight - if self.is_slanted(last_char): - superkern += constants.delta * xHeight - superkern += (constants.delta_slanted * - (lc_height - xHeight * 2. / 3.)) - if self.is_dropsub(last_char): - subkern = (3 * constants.delta - - constants.delta_integral) * lc_height - superkern = (3 * constants.delta + - constants.delta_integral) * lc_height - else: - subkern = 0 - - if super is None: - # node757 - x = Hlist([Kern(subkern), sub]) - x.shrink() - if self.is_dropsub(last_char): - shift_down = lc_baseline + constants.subdrop * xHeight - else: - shift_down = constants.sub1 * xHeight - x.shift_amount = shift_down - else: - x = Hlist([Kern(superkern), super]) - x.shrink() - if self.is_dropsub(last_char): - shift_up = lc_height - constants.subdrop * xHeight - else: - shift_up = constants.sup1 * xHeight - if sub is None: - x.shift_amount = -shift_up - else: # Both sub and superscript - y = Hlist([Kern(subkern), sub]) - y.shrink() - if self.is_dropsub(last_char): - shift_down = lc_baseline + constants.subdrop * xHeight - else: - shift_down = constants.sub2 * xHeight - # If sub and superscript collide, move super up - clr = (2.0 * rule_thickness - - ((shift_up - x.depth) - (y.height - shift_down))) - if clr > 0.: - shift_up += clr - x = Vlist([ - x, - Kern((shift_up - x.depth) - (y.height - shift_down)), - y]) - x.shift_amount = shift_down - - if not self.is_dropsub(last_char): - x.width += constants.script_space * xHeight - result = Hlist([nucleus, x]) - - return [result] - - def _genfrac(self, ldelim, rdelim, rule, style, num, den): - state = self.get_state() - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - - rule = float(rule) - - # If style != displaystyle == 0, shrink the num and den - if style != self._math_style_dict['displaystyle']: - num.shrink() - den.shrink() - cnum = HCentered([num]) - cden = HCentered([den]) - width = max(num.width, den.width) - cnum.hpack(width, 'exactly') - cden.hpack(width, 'exactly') - vlist = Vlist([cnum, # numerator - Vbox(0, thickness * 2.0), # space - Hrule(state, rule), # rule - Vbox(0, thickness * 2.0), # space - cden # denominator - ]) - - # Shift so the fraction line sits in the middle of the - # equals sign - metrics = state.font_output.get_metrics( - state.font, rcParams['mathtext.default'], - '=', state.fontsize, state.dpi) - shift = (cden.height - - ((metrics.ymax + metrics.ymin) / 2 - - thickness * 3.0)) - vlist.shift_amount = shift - - result = [Hlist([vlist, Hbox(thickness * 2.)])] - if ldelim or rdelim: - if ldelim == '': - ldelim = '.' - if rdelim == '': - rdelim = '.' - return self._auto_sized_delimiter(ldelim, result, rdelim) - return result - - def genfrac(self, s, loc, toks): - assert len(toks) == 1 - assert len(toks[0]) == 6 - - return self._genfrac(*tuple(toks[0])) - - def frac(self, s, loc, toks): - assert len(toks) == 1 - assert len(toks[0]) == 2 - state = self.get_state() - - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - num, den = toks[0] - - return self._genfrac('', '', thickness, - self._math_style_dict['textstyle'], num, den) - - def dfrac(self, s, loc, toks): - assert len(toks) == 1 - assert len(toks[0]) == 2 - state = self.get_state() - - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - num, den = toks[0] - - return self._genfrac('', '', thickness, - self._math_style_dict['displaystyle'], num, den) - - def binom(self, s, loc, toks): - assert len(toks) == 1 - assert len(toks[0]) == 2 - num, den = toks[0] - - return self._genfrac('(', ')', 0.0, - self._math_style_dict['textstyle'], num, den) - - def sqrt(self, s, loc, toks): - root, body = toks[0] - state = self.get_state() - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - - # Determine the height of the body, and add a little extra to - # the height so it doesn't seem cramped - height = body.height - body.shift_amount + thickness * 5.0 - depth = body.depth + body.shift_amount - check = AutoHeightChar(r'\__sqrt__', height, depth, state, always=True) - height = check.height - check.shift_amount - depth = check.depth + check.shift_amount - - # Put a little extra space to the left and right of the body - padded_body = Hlist([Hbox(2 * thickness), body, Hbox(2 * thickness)]) - rightside = Vlist([Hrule(state), Glue('fill'), padded_body]) - # Stretch the glue between the hrule and the body - rightside.vpack(height + (state.fontsize * state.dpi) / (100.0 * 12.0), - 'exactly', depth) - - # Add the root and shift it upward so it is above the tick. - # The value of 0.6 is a hard-coded hack ;) - if root is None: - root = Box(check.width * 0.5, 0., 0.) - else: - root = Hlist([Char(x, state) for x in root]) - root.shrink() - root.shrink() - - root_vlist = Vlist([Hlist([root])]) - root_vlist.shift_amount = -height * 0.6 - - hlist = Hlist([root_vlist, # Root - # Negative kerning to put root over tick - Kern(-check.width * 0.5), - check, # Check - rightside]) # Body - return [hlist] - - def overline(self, s, loc, toks): - assert len(toks) == 1 - assert len(toks[0]) == 1 - - body = toks[0][0] - - state = self.get_state() - thickness = state.font_output.get_underline_thickness( - state.font, state.fontsize, state.dpi) - - height = body.height - body.shift_amount + thickness * 3.0 - depth = body.depth + body.shift_amount - - # Place overline above body - rightside = Vlist([Hrule(state), Glue('fill'), Hlist([body])]) - - # Stretch the glue between the hrule and the body - rightside.vpack(height + (state.fontsize * state.dpi) / (100.0 * 12.0), - 'exactly', depth) - - hlist = Hlist([rightside]) - return [hlist] - - def _auto_sized_delimiter(self, front, middle, back): - state = self.get_state() - if len(middle): - height = max(x.height for x in middle) - depth = max(x.depth for x in middle) - factor = None - else: - height = 0 - depth = 0 - factor = 1.0 - parts = [] - # \left. and \right. aren't supposed to produce any symbols - if front != '.': - parts.append( - AutoHeightChar(front, height, depth, state, factor=factor)) - parts.extend(middle) - if back != '.': - parts.append( - AutoHeightChar(back, height, depth, state, factor=factor)) - hlist = Hlist(parts) - return hlist - - def auto_delim(self, s, loc, toks): - front, middle, back = toks +_log = logging.getLogger(__name__) - return self._auto_sized_delimiter(front, middle.asList(), back) +get_unicode_index.__module__ = __name__ ############################################################################## # MAIN @@ -3315,31 +36,30 @@ def auto_delim(self, s, loc, toks): class MathTextParser: _parser = None - - _backend_mapping = { - 'bitmap': MathtextBackendBitmap, - 'agg': MathtextBackendAgg, - 'ps': MathtextBackendPs, - 'pdf': MathtextBackendPdf, - 'svg': MathtextBackendSvg, - 'path': MathtextBackendPath, - 'cairo': MathtextBackendCairo, - 'macosx': MathtextBackendAgg, - } _font_type_mapping = { - 'cm': BakomaFonts, - 'dejavuserif': DejaVuSerifFonts, - 'dejavusans': DejaVuSansFonts, - 'stix': StixFonts, - 'stixsans': StixSansFonts, - 'custom': UnicodeFonts, + 'cm': _mathtext.BakomaFonts, + 'dejavuserif': _mathtext.DejaVuSerifFonts, + 'dejavusans': _mathtext.DejaVuSansFonts, + 'stix': _mathtext.StixFonts, + 'stixsans': _mathtext.StixSansFonts, + 'custom': _mathtext.UnicodeFonts, } def __init__(self, output): - """Create a MathTextParser for the given backend *output*.""" - self._output = output.lower() + """ + Create a MathTextParser for the given backend *output*. + + Parameters + ---------- + output : {"path", "agg"} + Whether to return a `VectorParse` ("path") or a + `RasterParse` ("agg", or its synonym "macosx"). + """ + self._output_type = _api.check_getitem( + {"path": "vector", "agg": "raster", "macosx": "raster"}, + output=output.lower()) - def parse(self, s, dpi=72, prop=None, *, _force_standard_ps_fonts=False): + def parse(self, s, dpi=72, prop=None, *, antialiased=None): """ Parse the given math expression *s* at the given *dpi*. If *prop* is provided, it is a `.FontProperties` object specifying the "default" @@ -3347,138 +67,46 @@ def parse(self, s, dpi=72, prop=None, *, _force_standard_ps_fonts=False): The results are cached, so multiple calls to `parse` with the same expression should be fast. + + Depending on the *output* type, this returns either a `VectorParse` or + a `RasterParse`. """ - # lru_cache can't decorate parse() directly because the ps.useafm and - # mathtext.fontset rcParams also affect the parse (e.g. by affecting - # the glyph metrics). - return self._parse_cached(s, dpi, prop, _force_standard_ps_fonts) + # lru_cache can't decorate parse() directly because prop is + # mutable, so we key the cache using an internal copy (see + # Text._get_text_metrics_with_cache for a similar case); likewise, + # we need to check the mutable state of the text.antialiased and + # text.hinting rcParams. + prop = prop.copy() if prop is not None else None + antialiased = mpl._val_or_rc(antialiased, 'text.antialiased') + from matplotlib.backends import backend_agg + load_glyph_flags = { + "vector": LoadFlags.NO_HINTING, + "raster": backend_agg.get_hinting_flag(), + }[self._output_type] + return self._parse_cached(s, dpi, prop, antialiased, load_glyph_flags) @functools.lru_cache(50) - def _parse_cached(self, s, dpi, prop, force_standard_ps_fonts): + def _parse_cached(self, s, dpi, prop, antialiased, load_glyph_flags): if prop is None: prop = FontProperties() - - if force_standard_ps_fonts: - font_output = StandardPsFonts(prop) - else: - backend = self._backend_mapping[self._output]() - fontset_class = cbook._check_getitem( - self._font_type_mapping, - fontset=prop.get_math_fontfamily()) - font_output = fontset_class(prop, backend) - + fontset_class = _api.check_getitem( + self._font_type_mapping, fontset=prop.get_math_fontfamily()) + fontset = fontset_class(prop, load_glyph_flags) fontsize = prop.get_size_in_points() - # This is a class variable so we don't rebuild the parser - # with each request. - if self._parser is None: - self.__class__._parser = Parser() - - box = self._parser.parse(s, font_output, fontsize, dpi) - font_output.set_canvas_size(box.width, box.height, box.depth) - return font_output.get_results(box) - - def to_mask(self, texstr, dpi=120, fontsize=14): - r""" - Parameters - ---------- - texstr : str - A valid mathtext string, e.g., r'IQ: $\sigma_i=15$'. - dpi : float - The dots-per-inch setting used to render the text. - fontsize : int - The font size in points - - Returns - ------- - array : 2D uint8 alpha - Mask array of rasterized tex. - depth : int - Offset of the baseline from the bottom of the image, in pixels. - """ - assert self._output == "bitmap" - prop = FontProperties(size=fontsize) - ftimage, depth = self.parse(texstr, dpi=dpi, prop=prop) - return np.asarray(ftimage), depth - - def to_rgba(self, texstr, color='black', dpi=120, fontsize=14): - r""" - Parameters - ---------- - texstr : str - A valid mathtext string, e.g., r'IQ: $\sigma_i=15$'. - color : color - The text color. - dpi : float - The dots-per-inch setting used to render the text. - fontsize : int - The font size in points. - - Returns - ------- - array : (M, N, 4) array - RGBA color values of rasterized tex, colorized with *color*. - depth : int - Offset of the baseline from the bottom of the image, in pixels. - """ - x, depth = self.to_mask(texstr, dpi=dpi, fontsize=fontsize) - - r, g, b, a = mcolors.to_rgba(color) - RGBA = np.zeros((x.shape[0], x.shape[1], 4), dtype=np.uint8) - RGBA[:, :, 0] = 255 * r - RGBA[:, :, 1] = 255 * g - RGBA[:, :, 2] = 255 * b - RGBA[:, :, 3] = x - return RGBA, depth - - def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): - r""" - Render a tex expression to a PNG file. - - Parameters - ---------- - filename - A writable filename or fileobject. - texstr : str - A valid mathtext string, e.g., r'IQ: $\sigma_i=15$'. - color : color - The text color. - dpi : float - The dots-per-inch setting used to render the text. - fontsize : int - The font size in points. - - Returns - ------- - int - Offset of the baseline from the bottom of the image, in pixels. - """ - rgba, depth = self.to_rgba( - texstr, color=color, dpi=dpi, fontsize=fontsize) - Image.fromarray(rgba).save(filename, format="png") - return depth - - def get_depth(self, texstr, dpi=120, fontsize=14): - r""" - Parameters - ---------- - texstr : str - A valid mathtext string, e.g., r'IQ: $\sigma_i=15$'. - dpi : float - The dots-per-inch setting used to render the text. + if self._parser is None: # Cache the parser globally. + self.__class__._parser = _mathtext.Parser() - Returns - ------- - int - Offset of the baseline from the bottom of the image, in pixels. - """ - assert self._output == "bitmap" - prop = FontProperties(size=fontsize) - ftimage, depth = self.parse(texstr, dpi=dpi, prop=prop) - return depth + box = self._parser.parse(s, fontset, fontsize, dpi) + output = _mathtext.ship(box) + if self._output_type == "vector": + return output.to_vector() + elif self._output_type == "raster": + return output.to_raster(antialiased=antialiased) -def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None): +def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None, + *, color=None): """ Given a math expression, renders it in a closely-clipped bounding box to an image file. @@ -3497,20 +125,16 @@ def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None): format : str, optional The output format, e.g., 'svg', 'pdf', 'ps' or 'png'. If not set, the format is determined as for `.Figure.savefig`. + color : str, optional + Foreground color, defaults to :rc:`text.color`. """ from matplotlib import figure - # backend_agg supports all of the core output formats - from matplotlib.backends import backend_agg - - if prop is None: - prop = FontProperties() parser = MathTextParser('path') width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop) fig = figure.Figure(figsize=(width / 72.0, height / 72.0)) - fig.text(0, depth/height, s, fontproperties=prop) - backend_agg.FigureCanvasAgg(fig) + fig.text(0, depth/height, s, fontproperties=prop, color=color) fig.savefig(filename_or_obj, dpi=dpi, format=format) return depth diff --git a/lib/matplotlib/mathtext.pyi b/lib/matplotlib/mathtext.pyi new file mode 100644 index 000000000000..607501a275c6 --- /dev/null +++ b/lib/matplotlib/mathtext.pyi @@ -0,0 +1,33 @@ +import os +from typing import Generic, IO, Literal, TypeVar, overload + +from matplotlib.font_manager import FontProperties +from matplotlib.typing import ColorType + +# Re-exported API from _mathtext. +from ._mathtext import ( + RasterParse as RasterParse, + VectorParse as VectorParse, + get_unicode_index as get_unicode_index, +) + +_ParseType = TypeVar("_ParseType", RasterParse, VectorParse) + +class MathTextParser(Generic[_ParseType]): + @overload + def __init__(self: MathTextParser[VectorParse], output: Literal["path"]) -> None: ... + @overload + def __init__(self: MathTextParser[RasterParse], output: Literal["agg", "raster", "macosx"]) -> None: ... + def parse( + self, s: str, dpi: float = ..., prop: FontProperties | None = ..., *, antialiased: bool | None = ... + ) -> _ParseType: ... + +def math_to_image( + s: str, + filename_or_obj: str | os.PathLike | IO, + prop: FontProperties | None = ..., + dpi: float | None = ..., + format: str | None = ..., + *, + color: ColorType | None = ... +) -> float: ... diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build new file mode 100644 index 000000000000..c4746f332bcb --- /dev/null +++ b/lib/matplotlib/meson.build @@ -0,0 +1,169 @@ +python_sources = [ + '__init__.py', + '_afm.py', + '_animation_data.py', + '_blocking_input.py', + '_cm.py', + '_cm_bivar.py', + '_cm_listed.py', + '_cm_multivar.py', + '_color_data.py', + '_constrained_layout.py', + '_docstring.py', + '_enums.py', + '_fontconfig_pattern.py', + '_internal_utils.py', + '_layoutgrid.py', + '_mathtext.py', + '_mathtext_data.py', + '_pylab_helpers.py', + '_text_helpers.py', + '_tight_bbox.py', + '_tight_layout.py', + '_type1font.py', + 'animation.py', + 'artist.py', + 'axis.py', + 'backend_bases.py', + 'backend_managers.py', + 'backend_tools.py', + 'bezier.py', + 'category.py', + 'cbook.py', + 'cm.py', + 'collections.py', + 'colorbar.py', + 'colorizer.py', + 'colors.py', + 'container.py', + 'contour.py', + 'dates.py', + 'dviread.py', + 'figure.py', + 'font_manager.py', + 'gridspec.py', + 'hatch.py', + 'image.py', + 'inset.py', + 'layout_engine.py', + 'legend_handler.py', + 'legend.py', + 'lines.py', + 'markers.py', + 'mathtext.py', + 'mlab.py', + 'offsetbox.py', + 'patches.py', + 'patheffects.py', + 'path.py', + 'pylab.py', + 'pyplot.py', + 'quiver.py', + 'rcsetup.py', + 'sankey.py', + 'scale.py', + 'spines.py', + 'stackplot.py', + 'streamplot.py', + 'table.py', + 'texmanager.py', + 'textpath.py', + 'text.py', + 'ticker.py', + 'transforms.py', + 'typing.py', + 'units.py', + 'widgets.py', +] + +typing_sources = [ + 'py.typed', + # Compiled extension types. + '_c_internal_utils.pyi', + 'ft2font.pyi', + '_image.pyi', + '_qhull.pyi', + '_tri.pyi', + # Pure Python types. + '__init__.pyi', + '_color_data.pyi', + '_docstring.pyi', + '_enums.pyi', + '_path.pyi', + '_pylab_helpers.pyi', + 'animation.pyi', + 'artist.pyi', + 'axis.pyi', + 'backend_bases.pyi', + 'backend_managers.pyi', + 'backend_tools.pyi', + 'bezier.pyi', + 'cbook.pyi', + 'cm.pyi', + 'collections.pyi', + 'colorbar.pyi', + 'colorizer.pyi', + 'colors.pyi', + 'container.pyi', + 'contour.pyi', + 'dviread.pyi', + 'figure.pyi', + 'font_manager.pyi', + 'gridspec.pyi', + 'hatch.pyi', + 'image.pyi', + 'inset.pyi', + 'layout_engine.pyi', + 'legend_handler.pyi', + 'legend.pyi', + 'lines.pyi', + 'markers.pyi', + 'mathtext.pyi', + 'mlab.pyi', + 'offsetbox.pyi', + 'patches.pyi', + 'patheffects.pyi', + 'path.pyi', + 'quiver.pyi', + 'rcsetup.pyi', + 'sankey.pyi', + 'scale.pyi', + 'spines.pyi', + 'stackplot.pyi', + 'streamplot.pyi', + 'table.pyi', + 'texmanager.pyi', + 'textpath.pyi', + 'text.pyi', + 'ticker.pyi', + 'transforms.pyi', + 'widgets.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib') + +fs = import('fs') +if fs.exists('_version.py') + py3.install_sources('_version.py', subdir: 'matplotlib') +else + cfg = configuration_data() + cfg.set_quoted('VCS_TAG', meson.project_version()) + configure_file( + input: '_version.py.in', output: '_version.py', + configuration: cfg, + install: true, + install_tag: 'python-runtime', + install_dir: py3.get_install_dir() / 'matplotlib') +endif + +subdir('_api') +subdir('axes') +subdir('backends') +subdir('mpl-data') +subdir('projections') +subdir('sphinxext') +subdir('style') +subdir('testing') +subdir('tests') +subdir('tri') diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index cc559bbdddec..f538b79e44f0 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -1,11 +1,14 @@ """ -Numerical python functions written for compatibility with MATLAB -commands with the same names. Most numerical python functions can be found in -the `numpy` and `scipy` libraries. What remains here is code for performing -spectral computations. +Numerical Python functions written for compatibility with MATLAB +commands with the same names. Most numerical Python functions can be found in +the `NumPy`_ and `SciPy`_ libraries. What remains here is code for performing +spectral computations and kernel density estimations. + +.. _NumPy: https://numpy.org +.. _SciPy: https://www.scipy.org Spectral functions -------------------- +------------------ `cohere` Coherence (normalized cross spectral density) @@ -42,15 +45,6 @@ `detrend_none` Return the original line. - -`stride_windows` - Get all windows in an array in a memory-efficient manner - -`stride_repeat` - Repeat an array in a memory-efficient manner - -`apply_window` - Apply a window along a given axis """ import functools @@ -58,13 +52,12 @@ import numpy as np -import matplotlib.cbook as cbook -from matplotlib import docstring +from matplotlib import _api, _docstring, cbook def window_hanning(x): """ - Return x times the hanning window of len(x). + Return *x* times the Hanning (or Hann) window of len(*x*). See Also -------- @@ -75,7 +68,7 @@ def window_hanning(x): def window_none(x): """ - No window function; simply return x. + No window function; simply return *x*. See Also -------- @@ -84,66 +77,9 @@ def window_none(x): return x -@cbook.deprecated("3.2") -def apply_window(x, window, axis=0, return_window=None): - """ - Apply the given window to the given 1D or 2D array along the given axis. - - Parameters - ---------- - x : 1D or 2D array or sequence - Array or sequence containing the data. - - window : function or array. - Either a function to generate a window or an array with length - *x*.shape[*axis*] - - axis : int - The axis over which to do the repetition. - Must be 0 or 1. The default is 0 - - return_window : bool - If true, also return the 1D values of the window that was applied - """ - x = np.asarray(x) - - if x.ndim < 1 or x.ndim > 2: - raise ValueError('only 1D or 2D arrays can be used') - if axis+1 > x.ndim: - raise ValueError('axis(=%s) out of bounds' % axis) - - xshape = list(x.shape) - xshapetarg = xshape.pop(axis) - - if np.iterable(window): - if len(window) != xshapetarg: - raise ValueError('The len(window) must be the same as the shape ' - 'of x for the chosen axis') - windowVals = window - else: - windowVals = window(np.ones(xshapetarg, dtype=x.dtype)) - - if x.ndim == 1: - if return_window: - return windowVals * x, windowVals - else: - return windowVals * x - - xshapeother = xshape.pop() - - otheraxis = (axis+1) % 2 - - windowValsRep = stride_repeat(windowVals, xshapeother, axis=otheraxis) - - if return_window: - return windowValsRep * x, windowVals - else: - return windowValsRep * x - - def detrend(x, key=None, axis=None): """ - Return x with its trend removed. + Return *x* with its trend removed. Parameters ---------- @@ -192,7 +128,7 @@ def detrend(x, key=None, axis=None): def detrend_mean(x, axis=None): """ - Return x minus the mean(x). + Return *x* minus the mean(*x*). Parameters ---------- @@ -201,7 +137,7 @@ def detrend_mean(x, axis=None): Can have any dimensionality axis : int - The axis along which to take the mean. See numpy.mean for a + The axis along which to take the mean. See `numpy.mean` for a description of this argument. See Also @@ -220,7 +156,7 @@ def detrend_mean(x, axis=None): def detrend_none(x, axis=None): """ - Return x: no detrending. + Return *x*: no detrending. Parameters ---------- @@ -242,17 +178,13 @@ def detrend_none(x, axis=None): def detrend_linear(y): """ - Return x minus best fit line; 'linear' detrending. + Return *x* minus best fit line; 'linear' detrending. Parameters ---------- y : 0-D or 1-D array or sequence Array or sequence containing the data - axis : int - The axis along which to take the mean. See numpy.mean for a - description of this argument. - See Also -------- detrend_mean : Another detrend algorithm. @@ -278,130 +210,6 @@ def detrend_linear(y): return y - (b*x + a) -def stride_windows(x, n, noverlap=None, axis=0): - """ - Get all windows of x with length n as a single array, - using strides to avoid data duplication. - - .. warning:: - - It is not safe to write to the output array. Multiple - elements may point to the same piece of memory, - so modifying one value may change others. - - Parameters - ---------- - x : 1D array or sequence - Array or sequence containing the data. - - n : int - The number of data points in each window. - - noverlap : int - The overlap between adjacent windows. - Default is 0 (no overlap) - - axis : int - The axis along which the windows will run. - - References - ---------- - `stackoverflow: Rolling window for 1D arrays in Numpy? - `_ - `stackoverflow: Using strides for an efficient moving average filter - `_ - """ - if noverlap is None: - noverlap = 0 - - if noverlap >= n: - raise ValueError('noverlap must be less than n') - if n < 1: - raise ValueError('n cannot be less than 1') - - x = np.asarray(x) - - if x.ndim != 1: - raise ValueError('only 1-dimensional arrays can be used') - if n == 1 and noverlap == 0: - if axis == 0: - return x[np.newaxis] - else: - return x[np.newaxis].transpose() - if n > x.size: - raise ValueError('n cannot be greater than the length of x') - - # np.lib.stride_tricks.as_strided easily leads to memory corruption for - # non integer shape and strides, i.e. noverlap or n. See #3845. - noverlap = int(noverlap) - n = int(n) - - step = n - noverlap - if axis == 0: - shape = (n, (x.shape[-1]-noverlap)//step) - strides = (x.strides[0], step*x.strides[0]) - else: - shape = ((x.shape[-1]-noverlap)//step, n) - strides = (step*x.strides[0], x.strides[0]) - return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides) - - -@cbook.deprecated("3.2") -def stride_repeat(x, n, axis=0): - """ - Repeat the values in an array in a memory-efficient manner. Array x is - stacked vertically n times. - - .. warning:: - - It is not safe to write to the output array. Multiple - elements may point to the same piece of memory, so - modifying one value may change others. - - Parameters - ---------- - x : 1D array or sequence - Array or sequence containing the data. - - n : int - The number of time to repeat the array. - - axis : int - The axis along which the data will run. - - References - ---------- - `stackoverflow: Repeat NumPy array without replicating data? - `_ - """ - if axis not in [0, 1]: - raise ValueError('axis must be 0 or 1') - x = np.asarray(x) - if x.ndim != 1: - raise ValueError('only 1-dimensional arrays can be used') - - if n == 1: - if axis == 0: - return np.atleast_2d(x) - else: - return np.atleast_2d(x).T - if n < 1: - raise ValueError('n cannot be less than 1') - - # np.lib.stride_tricks.as_strided easily leads to memory corruption for - # non integer shape and strides, i.e. n. See #3845. - n = int(n) - - if axis == 0: - shape = (n, x.size) - strides = (0, x.strides[0]) - else: - shape = (x.size, n) - strides = (x.strides[0], 0) - - return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides) - - def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None, mode=None): @@ -431,9 +239,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, if NFFT is None: NFFT = 256 + if noverlap >= NFFT: + raise ValueError('noverlap must be less than NFFT') + if mode is None or mode == 'default': mode = 'psd' - cbook._check_in_list( + _api.check_in_list( ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'], mode=mode) @@ -451,7 +262,7 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, sides = 'twosided' else: sides = 'onesided' - cbook._check_in_list(['default', 'onesided', 'twosided'], sides=sides) + _api.check_in_list(['default', 'onesided', 'twosided'], sides=sides) # zero pad x and y up to NFFT if they are shorter than NFFT if len(x) < NFFT: @@ -493,7 +304,8 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, raise ValueError( "The window length must match the data's first dimension") - result = stride_windows(x, NFFT, noverlap, axis=0) + result = np.lib.stride_tricks.sliding_window_view( + x, NFFT, axis=0)[::NFFT - noverlap].T result = detrend(result, detrend_func, axis=0) result = result * window.reshape((-1, 1)) result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :] @@ -501,7 +313,8 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, if not same_data: # if same_data is False, mode must be 'psd' - resultY = stride_windows(y, NFFT, noverlap) + resultY = np.lib.stride_tricks.sliding_window_view( + y, NFFT, axis=0)[::NFFT - noverlap].T resultY = detrend(resultY, detrend_func, axis=0) resultY = resultY * window.reshape((-1, 1)) resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :] @@ -509,12 +322,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, elif mode == 'psd': result = np.conj(result) * result elif mode == 'magnitude': - result = np.abs(result) / np.abs(window).sum() + result = np.abs(result) / window.sum() elif mode == 'angle' or mode == 'phase': # we unwrap the phase later to handle the onesided vs. twosided case result = np.angle(result) elif mode == 'complex': - result /= np.abs(window).sum() + result /= window.sum() if mode == 'psd': @@ -538,10 +351,10 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, result /= Fs # Scale the spectrum by the norm of the window to compensate for # windowing loss; see Bendat & Piersol Sec 11.5.2. - result /= (np.abs(window)**2).sum() + result /= (window**2).sum() else: # In this case, preserve power in the segment, not amplitude - result /= np.abs(window).sum()**2 + result /= window.sum()**2 t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs @@ -566,7 +379,7 @@ def _single_spectrum_helper( Private helper implementing the commonality between the complex, magnitude, angle, and phase spectrums. """ - cbook._check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode) + _api.check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode) if pad_to is None: pad_to = len(x) @@ -587,7 +400,7 @@ def _single_spectrum_helper( # Split out these keyword docs so that they can be used elsewhere -docstring.interpd.update( +_docstring.interpd.register( Spectral="""\ Fs : float, default: 2 The sampling frequency (samples per time unit). It is used to calculate @@ -611,8 +424,8 @@ def _single_spectrum_helper( the FFT. While not increasing the actual resolution of the spectrum (the minimum distance between resolvable peaks), this can give more points in the plot, allowing for more detail. This corresponds to the *n* parameter - in the call to fft(). The default is None, which sets *pad_to* equal to - the length of the input signal (i.e. no padding).""", + in the call to `~numpy.fft.fft`. The default is None, which sets *pad_to* + equal to the length of the input signal (i.e. no padding).""", PSD="""\ pad_to : int, optional @@ -621,18 +434,18 @@ def _single_spectrum_helper( of data points used. While not increasing the actual resolution of the spectrum (the minimum distance between resolvable peaks), this can give more points in the plot, allowing for more detail. This corresponds to - the *n* parameter in the call to fft(). The default is None, which sets - *pad_to* equal to *NFFT* + the *n* parameter in the call to `~numpy.fft.fft`. The default is None, + which sets *pad_to* equal to *NFFT* NFFT : int, default: 256 The number of data points used in each block for the FFT. A power 2 is most efficient. This should *NOT* be used to get zero padding, or the scaling of the result will be incorrect; use *pad_to* for this instead. -detrend : {'none', 'mean', 'linear'} or callable, default 'none' +detrend : {'none', 'mean', 'linear'} or callable, default: 'none' The function applied to each segment before fft-ing, designed to remove the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter - is a vector, in Matplotlib is it a function. The :mod:`~matplotlib.mlab` + is a vector, in Matplotlib it is a function. The :mod:`~matplotlib.mlab` module defines `.detrend_none`, `.detrend_mean`, and `.detrend_linear`, but you can use a custom function as well. You can also use a string to choose one of the functions: 'none' calls `.detrend_none`. 'mean' calls @@ -640,12 +453,12 @@ def _single_spectrum_helper( scale_by_freq : bool, default: True Whether the resulting density values should be scaled by the scaling - frequency, which gives density in units of Hz^-1. This allows for + frequency, which gives density in units of 1/Hz. This allows for integration over the returned frequency values. The default is True for MATLAB compatibility.""") -@docstring.dedent_interpd +@_docstring.interpd def psd(x, NFFT=None, Fs=None, detrend=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None): r""" @@ -669,9 +482,8 @@ def psd(x, NFFT=None, Fs=None, detrend=None, window=None, %(PSD)s - noverlap : int + noverlap : int, default: 0 (no overlap) The number of points of overlap between segments. - The default value is 0 (no overlap). Returns ------- @@ -702,7 +514,7 @@ def psd(x, NFFT=None, Fs=None, detrend=None, window=None, return Pxx.real, freqs -@docstring.dedent_interpd +@_docstring.interpd def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None): """ @@ -729,9 +541,8 @@ def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None, %(PSD)s - noverlap : int + noverlap : int, default: 0 (no overlap) The number of points of overlap between segments. - The default value is 0 (no overlap). Returns ------- @@ -808,33 +619,33 @@ def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None, complex_spectrum = functools.partial(_single_spectrum_helper, "complex") complex_spectrum.__doc__ = _single_spectrum_docs.format( quantity="complex-valued frequency spectrum", - **docstring.interpd.params) + **_docstring.interpd.params) magnitude_spectrum = functools.partial(_single_spectrum_helper, "magnitude") magnitude_spectrum.__doc__ = _single_spectrum_docs.format( quantity="magnitude (absolute value) of the frequency spectrum", - **docstring.interpd.params) + **_docstring.interpd.params) angle_spectrum = functools.partial(_single_spectrum_helper, "angle") angle_spectrum.__doc__ = _single_spectrum_docs.format( quantity="angle of the frequency spectrum (wrapped phase spectrum)", - **docstring.interpd.params) + **_docstring.interpd.params) phase_spectrum = functools.partial(_single_spectrum_helper, "phase") phase_spectrum.__doc__ = _single_spectrum_docs.format( quantity="phase of the frequency spectrum (unwrapped phase spectrum)", - **docstring.interpd.params) + **_docstring.interpd.params) -@docstring.dedent_interpd +@_docstring.interpd def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None, mode=None): """ Compute a spectrogram. - Compute and plot a spectrogram of data in x. Data are split into - NFFT length segments and the spectrum of each section is - computed. The windowing function window is applied to each + Compute and plot a spectrogram of data in *x*. Data are split into + *NFFT* length segments and the spectrum of each section is + computed. The windowing function *window* is applied to each segment, and the amount of overlap of each segment is - specified with noverlap. + specified with *noverlap*. Parameters ---------- @@ -845,9 +656,8 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, %(PSD)s - noverlap : int, optional - The number of points of overlap between blocks. The default - value is 128. + noverlap : int, default: 128 + The number of points of overlap between blocks. mode : str, default: 'psd' What sort of spectrum to use: 'psd' @@ -864,7 +674,7 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, Returns ------- spectrum : array-like - 2-D array, columns are the periodograms of successive segments. + 2D array, columns are the periodograms of successive segments. freqs : array-like 1-D array, frequencies corresponding to the rows in *spectrum*. @@ -877,13 +687,13 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, -------- psd : differs in the overlap and in the return values. complex_spectrum : similar, but with complex valued frequencies. - magnitude_spectrum : similar single segment when mode is 'magnitude'. - angle_spectrum : similar to single segment when mode is 'angle'. - phase_spectrum : similar to single segment when mode is 'phase'. + magnitude_spectrum : similar single segment when *mode* is 'magnitude'. + angle_spectrum : similar to single segment when *mode* is 'angle'. + phase_spectrum : similar to single segment when *mode* is 'phase'. Notes ----- - detrend and scale_by_freq only apply when *mode* is set to 'psd'. + *detrend* and *scale_by_freq* only apply when *mode* is set to 'psd'. """ if noverlap is None: @@ -891,9 +701,8 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, if NFFT is None: NFFT = 256 # same default as in _spectral_helper() if len(x) <= NFFT: - cbook._warn_external("Only one segment is calculated since parameter " - "NFFT (=%d) >= signal length (=%d)." % - (NFFT, len(x))) + _api.warn_external("Only one segment is calculated since parameter " + f"NFFT (={NFFT}) >= signal length (={len(x)}).") spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend_func=detrend, window=window, @@ -908,7 +717,7 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, return spec, freqs, t -@docstring.dedent_interpd +@_docstring.interpd def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning, noverlap=0, pad_to=None, sides='default', scale_by_freq=None): r""" @@ -928,9 +737,8 @@ def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning, %(PSD)s - noverlap : int - The number of points of overlap between blocks. The default value - is 0 (no overlap). + noverlap : int, default: 0 (no overlap) + The number of points of overlap between segments. Returns ------- @@ -967,34 +775,27 @@ class GaussianKDE: ---------- dataset : array-like Datapoints to estimate from. In case of univariate data this is a 1-D - array, otherwise a 2-D array with shape (# of dims, # of data). - - bw_method : str, scalar or callable, optional - The method used to calculate the estimator bandwidth. This can be - 'scott', 'silverman', a scalar constant or a callable. If a - scalar, this will be used directly as `kde.factor`. If a + array, otherwise a 2D array with shape (# of dims, # of data). + bw_method : {'scott', 'silverman'} or float or callable, optional + The method used to calculate the estimator bandwidth. If a + float, this will be used directly as `!kde.factor`. If a callable, it should take a `GaussianKDE` instance as only - parameter and return a scalar. If None (default), 'scott' is used. + parameter and return a float. If None (default), 'scott' is used. Attributes ---------- dataset : ndarray - The dataset with which `gaussian_kde` was initialized. - + The dataset passed to the constructor. dim : int Number of dimensions. - num_dp : int Number of datapoints. - factor : float - The bandwidth factor, obtained from `kde.covariance_factor`, with which + The bandwidth factor, obtained from `~GaussianKDE.covariance_factor`, with which the covariance matrix is multiplied. - covariance : ndarray The covariance matrix of *dataset*, scaled by the calculated bandwidth - (`kde.factor`). - + (`!kde.factor`). inv_cov : ndarray The inverse of *covariance*. @@ -1002,10 +803,8 @@ class GaussianKDE: ------- kde.evaluate(points) : ndarray Evaluate the estimated pdf on a provided set of points. - kde(points) : ndarray Same as kde.evaluate(points) - """ # This implementation with minor modification was too good to pass up. @@ -1087,8 +886,8 @@ def evaluate(self, points): dim, num_m = np.array(points).shape if dim != self.dim: - raise ValueError("points have dimension {}, dataset has dimension " - "{}".format(dim, self.dim)) + raise ValueError(f"points have dimension {dim}, dataset has " + f"dimension {self.dim}") result = np.zeros(num_m) diff --git a/lib/matplotlib/mlab.pyi b/lib/matplotlib/mlab.pyi new file mode 100644 index 000000000000..1f23288dd10b --- /dev/null +++ b/lib/matplotlib/mlab.pyi @@ -0,0 +1,100 @@ +from collections.abc import Callable +import functools +from typing import Literal + +import numpy as np +from numpy.typing import ArrayLike + +def window_hanning(x: ArrayLike) -> ArrayLike: ... +def window_none(x: ArrayLike) -> ArrayLike: ... +def detrend( + x: ArrayLike, + key: Literal["default", "constant", "mean", "linear", "none"] + | Callable[[ArrayLike, int | None], ArrayLike] + | None = ..., + axis: int | None = ..., +) -> ArrayLike: ... +def detrend_mean(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ... +def detrend_none(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ... +def detrend_linear(y: ArrayLike) -> ArrayLike: ... +def psd( + x: ArrayLike, + NFFT: int | None = ..., + Fs: float | None = ..., + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike, int | None], ArrayLike] + | None = ..., + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ..., + noverlap: int | None = ..., + pad_to: int | None = ..., + sides: Literal["default", "onesided", "twosided"] | None = ..., + scale_by_freq: bool | None = ..., +) -> tuple[ArrayLike, ArrayLike]: ... +def csd( + x: ArrayLike, + y: ArrayLike | None, + NFFT: int | None = ..., + Fs: float | None = ..., + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike, int | None], ArrayLike] + | None = ..., + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ..., + noverlap: int | None = ..., + pad_to: int | None = ..., + sides: Literal["default", "onesided", "twosided"] | None = ..., + scale_by_freq: bool | None = ..., +) -> tuple[ArrayLike, ArrayLike]: ... + +complex_spectrum = functools.partial(tuple[ArrayLike, ArrayLike]) +magnitude_spectrum = functools.partial(tuple[ArrayLike, ArrayLike]) +angle_spectrum = functools.partial(tuple[ArrayLike, ArrayLike]) +phase_spectrum = functools.partial(tuple[ArrayLike, ArrayLike]) + +def specgram( + x: ArrayLike, + NFFT: int | None = ..., + Fs: float | None = ..., + detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] | None = ..., + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ..., + noverlap: int | None = ..., + pad_to: int | None = ..., + sides: Literal["default", "onesided", "twosided"] | None = ..., + scale_by_freq: bool | None = ..., + mode: Literal["psd", "complex", "magnitude", "angle", "phase"] | None = ..., +) -> tuple[ArrayLike, ArrayLike, ArrayLike]: ... +def cohere( + x: ArrayLike, + y: ArrayLike, + NFFT: int = ..., + Fs: float = ..., + detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] = ..., + window: Callable[[ArrayLike], ArrayLike] | ArrayLike = ..., + noverlap: int = ..., + pad_to: int | None = ..., + sides: Literal["default", "onesided", "twosided"] = ..., + scale_by_freq: bool | None = ..., +) -> tuple[ArrayLike, ArrayLike]: ... + +class GaussianKDE: + dataset: ArrayLike + dim: int + num_dp: int + factor: float + data_covariance: ArrayLike + data_inv_cov: ArrayLike + covariance: ArrayLike + inv_cov: ArrayLike + norm_factor: float + def __init__( + self, + dataset: ArrayLike, + bw_method: Literal["scott", "silverman"] + | float + | Callable[[GaussianKDE], float] + | None = ..., + ) -> None: ... + def scotts_factor(self) -> float: ... + def silverman_factor(self) -> float: ... + def covariance_factor(self) -> float: ... + def evaluate(self, points: ArrayLike) -> np.ndarray: ... + def __call__(self, points: ArrayLike) -> np.ndarray: ... diff --git a/lib/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX b/lib/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX index 12c454a3e104..6034d9474814 100644 --- a/lib/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX +++ b/lib/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX @@ -34,7 +34,7 @@ Portions copyright (c) 1990 by Elsevier, Inc. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL +https://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 diff --git a/lib/matplotlib/mpl-data/fonts/ttf/LastResortHE-Regular.ttf b/lib/matplotlib/mpl-data/fonts/ttf/LastResortHE-Regular.ttf new file mode 100644 index 000000000000..69ad694fa5d2 Binary files /dev/null and b/lib/matplotlib/mpl-data/fonts/ttf/LastResortHE-Regular.ttf differ diff --git a/lib/matplotlib/mpl-data/images/back.gif b/lib/matplotlib/mpl-data/images/back.gif deleted file mode 100644 index b5afd43219ba..000000000000 Binary files a/lib/matplotlib/mpl-data/images/back.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/back.svg b/lib/matplotlib/mpl-data/images/back.svg index a933ef8cdf50..0c2d653cbe8f 100644 --- a/lib/matplotlib/mpl-data/images/back.svg +++ b/lib/matplotlib/mpl-data/images/back.svg @@ -40,7 +40,7 @@ L 33.991875 44.72875 L 60.703125 44.72875 C 63.43375 44.72875 65.144375 42.444375 65.144375 39.8625 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/back_large.gif b/lib/matplotlib/mpl-data/images/back_large.gif deleted file mode 100644 index 63cb49fe2ab5..000000000000 Binary files a/lib/matplotlib/mpl-data/images/back_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/filesave.gif b/lib/matplotlib/mpl-data/images/filesave.gif deleted file mode 100644 index a5fbdcfaf691..000000000000 Binary files a/lib/matplotlib/mpl-data/images/filesave.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/filesave.svg b/lib/matplotlib/mpl-data/images/filesave.svg index ad8372d295de..856721b6b5e2 100644 --- a/lib/matplotlib/mpl-data/images/filesave.svg +++ b/lib/matplotlib/mpl-data/images/filesave.svg @@ -62,7 +62,7 @@ C 6.855625 64.95875 8.491875 66.584375 10.5 66.584375 L 61.5 66.584375 C 63.508125 66.584375 65.144375 64.95875 65.144375 62.94 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/filesave_large.gif b/lib/matplotlib/mpl-data/images/filesave_large.gif deleted file mode 100644 index 5ef195245ee7..000000000000 Binary files a/lib/matplotlib/mpl-data/images/filesave_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/forward.gif b/lib/matplotlib/mpl-data/images/forward.gif deleted file mode 100644 index 5806d72e8bba..000000000000 Binary files a/lib/matplotlib/mpl-data/images/forward.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/forward.svg b/lib/matplotlib/mpl-data/images/forward.svg index 1f4071360680..08b6fe174602 100644 --- a/lib/matplotlib/mpl-data/images/forward.svg +++ b/lib/matplotlib/mpl-data/images/forward.svg @@ -40,7 +40,7 @@ C 30.645 66.4675 31.866875 66.99875 33.1525 66.99875 C 34.438125 66.99875 35.691875 66.4675 36.605625 65.59625 L 61.30875 40.893125 C 62.2225 39.979375 62.71125 38.725625 62.71125 37.44 -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/forward_large.gif b/lib/matplotlib/mpl-data/images/forward_large.gif deleted file mode 100644 index ded4fddf512c..000000000000 Binary files a/lib/matplotlib/mpl-data/images/forward_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/hand.gif b/lib/matplotlib/mpl-data/images/hand.gif deleted file mode 100644 index 68fc7b988a49..000000000000 Binary files a/lib/matplotlib/mpl-data/images/hand.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/hand.svg b/lib/matplotlib/mpl-data/images/hand.svg index f246f51e57d5..28b96a2a9c4a 100644 --- a/lib/matplotlib/mpl-data/images/hand.svg +++ b/lib/matplotlib/mpl-data/images/hand.svg @@ -5,7 +5,7 @@ ]> - + +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/help_large.ppm b/lib/matplotlib/mpl-data/images/help_large.ppm deleted file mode 100644 index 4cf30807b0a1..000000000000 Binary files a/lib/matplotlib/mpl-data/images/help_large.ppm and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/home.gif b/lib/matplotlib/mpl-data/images/home.gif deleted file mode 100644 index 4b057f4f2bf3..000000000000 Binary files a/lib/matplotlib/mpl-data/images/home.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/home.svg b/lib/matplotlib/mpl-data/images/home.svg index 3c4ccce3ed1f..db140d43d156 100644 --- a/lib/matplotlib/mpl-data/images/home.svg +++ b/lib/matplotlib/mpl-data/images/home.svg @@ -53,7 +53,7 @@ C 62.482813 46.47125 62.748438 46.545625 63.056562 46.545625 C 63.088437 46.545625 63.130938 46.545625 63.173438 46.545625 C 63.470938 46.51375 63.779063 46.354375 63.970313 46.13125 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/home_large.gif b/lib/matplotlib/mpl-data/images/home_large.gif deleted file mode 100644 index 5fc08b3e7a46..000000000000 Binary files a/lib/matplotlib/mpl-data/images/home_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib_128.ppm b/lib/matplotlib/mpl-data/images/matplotlib_128.ppm deleted file mode 100644 index d9a647b08a5a..000000000000 --- a/lib/matplotlib/mpl-data/images/matplotlib_128.ppm +++ /dev/null @@ -1,4 +0,0 @@ -P6 -128 128 -255 -ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþðôöØãéÀÒÜ©ÁÏ‘±Ã„§»¥¹}¢·}¢·¥¹„§»‘±Ã©ÁÏÁÓÝØãéðôöþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõøùÉÙáš·Çj•­ +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/move_large.gif b/lib/matplotlib/mpl-data/images/move_large.gif deleted file mode 100644 index e97863ecea94..000000000000 Binary files a/lib/matplotlib/mpl-data/images/move_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg index 0b46bf80923e..02adfbc4ae11 100644 --- a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg +++ b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg @@ -42,7 +42,7 @@ L 63.3275 27.123125 L 67.9175 31.713125 C 68.714375 32.51 70 31.93625 70 30.87375 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/subplots.gif b/lib/matplotlib/mpl-data/images/subplots.gif deleted file mode 100644 index b230b037052f..000000000000 Binary files a/lib/matplotlib/mpl-data/images/subplots.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/subplots.svg b/lib/matplotlib/mpl-data/images/subplots.svg index e87d2c9b1b19..9a0fa90972ff 100644 --- a/lib/matplotlib/mpl-data/images/subplots.svg +++ b/lib/matplotlib/mpl-data/images/subplots.svg @@ -75,7 +75,7 @@ L 32.355625 18.0175 L 32.355625 22.873125 L 65.144375 22.873125 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/subplots_large.gif b/lib/matplotlib/mpl-data/images/subplots_large.gif deleted file mode 100644 index edcf45407201..000000000000 Binary files a/lib/matplotlib/mpl-data/images/subplots_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.gif b/lib/matplotlib/mpl-data/images/zoom_to_rect.gif deleted file mode 100644 index d7ba0e56adca..000000000000 Binary files a/lib/matplotlib/mpl-data/images/zoom_to_rect.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg index f4b69b23c5eb..44e59a8a4fdc 100644 --- a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg +++ b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg @@ -34,7 +34,7 @@ C 36.525937 59.300937 41.838437 57.664687 46.279687 54.594062 L 59.295313 67.577812 C 60.166562 68.480937 61.420313 69.012187 62.716563 69.012187 C 65.372813 69.012187 67.572187 66.812812 67.572187 64.156562 -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect_large.gif b/lib/matplotlib/mpl-data/images/zoom_to_rect_large.gif deleted file mode 100644 index 2bc0b62290b1..000000000000 Binary files a/lib/matplotlib/mpl-data/images/zoom_to_rect_large.gif and /dev/null differ diff --git a/lib/matplotlib/mpl-data/kpsewhich.lua b/lib/matplotlib/mpl-data/kpsewhich.lua new file mode 100644 index 000000000000..8e9172a45082 --- /dev/null +++ b/lib/matplotlib/mpl-data/kpsewhich.lua @@ -0,0 +1,3 @@ +-- see dviread._LuatexKpsewhich +kpse.set_program_name("latex") +while true do print(kpse.lookup(io.read():gsub("\r", ""))); io.flush(); end diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc new file mode 100644 index 000000000000..780dcd377041 --- /dev/null +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -0,0 +1,821 @@ +#### MATPLOTLIBRC FORMAT + +## DO NOT EDIT THIS FILE, MAKE A COPY FIRST +## +## This is a sample Matplotlib configuration file - you can find a copy +## of it on your system in site-packages/matplotlib/mpl-data/matplotlibrc +## (relative to your Python installation location). +## DO NOT EDIT IT! +## +## If you wish to change your default style, copy this file to one of the +## following locations: +## Unix/Linux: +## $HOME/.config/matplotlib/matplotlibrc OR +## $XDG_CONFIG_HOME/matplotlib/matplotlibrc (if $XDG_CONFIG_HOME is set) +## Other platforms: +## $HOME/.matplotlib/matplotlibrc +## and edit that copy. +## +## See https://matplotlib.org/stable/users/explain/customizing.html#customizing-with-matplotlibrc-files +## for more details on the paths which are checked for the configuration file. +## +## Blank lines, or lines starting with a comment symbol, are ignored, as are +## trailing comments. Other lines must have the format: +## key: val # optional comment +## +## Formatting: Use PEP8-like style (as enforced in the rest of the codebase). +## All lines start with an additional '#', so that removing all leading '#'s +## yields a valid style file. +## +## Colors: for the color values below, you can either use +## - a Matplotlib color string, such as r, k, or b +## - an RGB tuple, such as (1.0, 0.5, 0.0) +## - a double-quoted hex string, such as "#ff00ff". +## The unquoted string ff00ff is also supported for backward +## compatibility, but is discouraged. +## - a scalar grayscale intensity such as 0.75 +## - a legal html color name, e.g., red, blue, darkslategray +## +## String values may optionally be enclosed in double quotes, which allows +## using the comment character # in the string. +## +## This file (and other style files) must be encoded as utf-8. +## +## Matplotlib configuration are currently divided into following parts: +## - BACKENDS +## - LINES +## - PATCHES +## - HATCHES +## - BOXPLOT +## - FONT +## - TEXT +## - LaTeX +## - AXES +## - DATES +## - TICKS +## - GRIDS +## - LEGEND +## - FIGURE +## - IMAGES +## - CONTOUR PLOTS +## - ERRORBAR PLOTS +## - HISTOGRAM PLOTS +## - SCATTER PLOTS +## - AGG RENDERING +## - PATHS +## - SAVING FIGURES +## - INTERACTIVE KEYMAPS +## - ANIMATION + +##### CONFIGURATION BEGINS HERE + + +## *************************************************************************** +## * BACKENDS * +## *************************************************************************** +## The default backend. If you omit this parameter, the first working +## backend from the following list is used: +## MacOSX QtAgg Gtk4Agg Gtk3Agg TkAgg WxAgg Agg +## Other choices include: +## QtCairo GTK4Cairo GTK3Cairo TkCairo WxCairo Cairo +## Qt5Agg Qt5Cairo Wx # deprecated. +## PS PDF SVG Template +## You can also deploy your own backend outside of Matplotlib by referring to +## the module name (which must be in the PYTHONPATH) as 'module://my_backend'. +##backend: Agg + +## The port to use for the web server in the WebAgg backend. +#webagg.port: 8988 + +## The address on which the WebAgg web server should be reachable +#webagg.address: 127.0.0.1 + +## If webagg.port is unavailable, a number of other random ports will +## be tried until one that is available is found. +#webagg.port_retries: 50 + +## When True, open the web browser to the plot that is shown +#webagg.open_in_browser: True + +## If you are running pyplot inside a GUI and your backend choice +## conflicts, we will automatically try to find a compatible one for +## you if backend_fallback is True +#backend_fallback: True + +#interactive: False +#figure.hooks: # list of dotted.module.name:dotted.callable.name +#toolbar: toolbar2 # {None, toolbar2, toolmanager} +#timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris + + +## *************************************************************************** +## * LINES * +## *************************************************************************** +## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.lines +## for more information on line properties. +#lines.linewidth: 1.5 # line width in points +#lines.linestyle: - # solid line +#lines.color: C0 # has no affect on plot(); see axes.prop_cycle +#lines.marker: None # the default marker +#lines.markerfacecolor: auto # the default marker face color +#lines.markeredgecolor: auto # the default marker edge color +#lines.markeredgewidth: 1.0 # the line width around the marker symbol +#lines.markersize: 6 # marker size, in points +#lines.dash_joinstyle: round # {miter, round, bevel} +#lines.dash_capstyle: butt # {butt, round, projecting} +#lines.solid_joinstyle: round # {miter, round, bevel} +#lines.solid_capstyle: projecting # {butt, round, projecting} +#lines.antialiased: True # render lines in antialiased (no jaggies) + +## The three standard dash patterns. These are scaled by the linewidth. +#lines.dashed_pattern: 3.7, 1.6 +#lines.dashdot_pattern: 6.4, 1.6, 1, 1.6 +#lines.dotted_pattern: 1, 1.65 +#lines.scale_dashes: True + +#markers.fillstyle: full # {full, left, right, bottom, top, none} + +#pcolor.shading: auto +#pcolormesh.snap: True # Whether to snap the mesh to pixel boundaries. This is + # provided solely to allow old test images to remain + # unchanged. Set to False to obtain the previous behavior. + +## *************************************************************************** +## * PATCHES * +## *************************************************************************** +## Patches are graphical objects that fill 2D space, like polygons or circles. +## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.patches +## for more information on patch properties. +#patch.linewidth: 1.0 # edge width in points. +#patch.facecolor: C0 +#patch.edgecolor: black # By default, Patches and Collections do not draw edges. + # This value is only used if facecolor is "none" + # (an Artist without facecolor and edgecolor would be + # invisible) or if patch.force_edgecolor is True. +#patch.force_edgecolor: False # By default, Patches and Collections do not draw edges. + # Set this to True to draw edges with patch.edgedcolor + # as the default edgecolor. + # This is mainly relevant for styles. +#patch.antialiased: True # render patches in antialiased (no jaggies) + + +## *************************************************************************** +## * HATCHES * +## *************************************************************************** +#hatch.color: edge +#hatch.linewidth: 1.0 + + +## *************************************************************************** +## * BOXPLOT * +## *************************************************************************** +#boxplot.notch: False +#boxplot.vertical: True +#boxplot.whiskers: 1.5 +#boxplot.bootstrap: None +#boxplot.patchartist: False +#boxplot.showmeans: False +#boxplot.showcaps: True +#boxplot.showbox: True +#boxplot.showfliers: True +#boxplot.meanline: False + +#boxplot.flierprops.color: black +#boxplot.flierprops.marker: o +#boxplot.flierprops.markerfacecolor: none +#boxplot.flierprops.markeredgecolor: black +#boxplot.flierprops.markeredgewidth: 1.0 +#boxplot.flierprops.markersize: 6 +#boxplot.flierprops.linestyle: none +#boxplot.flierprops.linewidth: 1.0 + +#boxplot.boxprops.color: black +#boxplot.boxprops.linewidth: 1.0 +#boxplot.boxprops.linestyle: - + +#boxplot.whiskerprops.color: black +#boxplot.whiskerprops.linewidth: 1.0 +#boxplot.whiskerprops.linestyle: - + +#boxplot.capprops.color: black +#boxplot.capprops.linewidth: 1.0 +#boxplot.capprops.linestyle: - + +#boxplot.medianprops.color: C1 +#boxplot.medianprops.linewidth: 1.0 +#boxplot.medianprops.linestyle: - + +#boxplot.meanprops.color: C2 +#boxplot.meanprops.marker: ^ +#boxplot.meanprops.markerfacecolor: C2 +#boxplot.meanprops.markeredgecolor: C2 +#boxplot.meanprops.markersize: 6 +#boxplot.meanprops.linestyle: -- +#boxplot.meanprops.linewidth: 1.0 + + +## *************************************************************************** +## * FONT * +## *************************************************************************** +## The font properties used by `text.Text`. +## See https://matplotlib.org/stable/api/font_manager_api.html for more information +## on font properties. The 6 font properties used for font matching are +## given below with their default values. +## +## The font.family property can take either a single or multiple entries of any +## combination of concrete font names (not supported when rendering text with +## usetex) or the following five generic values: +## - 'serif' (e.g., Times), +## - 'sans-serif' (e.g., Helvetica), +## - 'cursive' (e.g., Zapf-Chancery), +## - 'fantasy' (e.g., Western), and +## - 'monospace' (e.g., Courier). +## Each of these values has a corresponding default list of font names +## (font.serif, etc.); the first available font in the list is used. Note that +## for font.serif, font.sans-serif, and font.monospace, the first element of +## the list (a DejaVu font) will always be used because DejaVu is shipped with +## Matplotlib and is thus guaranteed to be available; the other entries are +## left as examples of other possible values. +## +## The font.style property has three values: normal (or roman), italic +## or oblique. The oblique style will be used for italic, if it is not +## present. +## +## The font.variant property has two values: normal or small-caps. For +## TrueType fonts, which are scalable fonts, small-caps is equivalent +## to using a font size of 'smaller', or about 83 % of the current font +## size. +## +## The font.weight property has effectively 13 values: normal, bold, +## bolder, lighter, 100, 200, 300, ..., 900. Normal is the same as +## 400, and bold is 700. bolder and lighter are relative values with +## respect to the current weight. +## +## The font.stretch property has 11 values: ultra-condensed, +## extra-condensed, condensed, semi-condensed, normal, semi-expanded, +## expanded, extra-expanded, ultra-expanded, wider, and narrower. This +## property is not currently implemented. +## +## The font.size property is the default font size for text, given in points. +## 10 pt is the standard value. +## +## Note that font.size controls default text sizes. To configure +## special text sizes tick labels, axes, labels, title, etc., see the rc +## settings for axes and ticks. Special text sizes can be defined +## relative to font.size, using the following values: xx-small, x-small, +## small, medium, large, x-large, xx-large, larger, or smaller + +#font.family: sans-serif +#font.style: normal +#font.variant: normal +#font.weight: normal +#font.stretch: normal +#font.size: 10.0 + +#font.serif: DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif +#font.sans-serif: DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif +#font.cursive: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, Comic Neue, Comic Sans MS, cursive +#font.fantasy: Chicago, Charcoal, Impact, Western, xkcd script, fantasy +#font.monospace: DejaVu Sans Mono, Bitstream Vera Sans Mono, Computer Modern Typewriter, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace + +## If font.enable_last_resort is True, then Unicode Consortium's Last Resort +## font will be appended to all font selections. This ensures that there will +## always be a glyph displayed. +#font.enable_last_resort: true + + +## *************************************************************************** +## * TEXT * +## *************************************************************************** +## The text properties used by `text.Text`. +## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.text +## for more information on text properties +#text.color: black + +## FreeType hinting flag ("foo" corresponds to FT_LOAD_FOO); may be one of the +## following (Proprietary Matplotlib-specific synonyms are given in parentheses, +## but their use is discouraged): +## - default: Use the font's native hinter if possible, else FreeType's auto-hinter. +## ("either" is a synonym). +## - no_autohint: Use the font's native hinter if possible, else don't hint. +## ("native" is a synonym.) +## - force_autohint: Use FreeType's auto-hinter. ("auto" is a synonym.) +## - no_hinting: Disable hinting. ("none" is a synonym.) +#text.hinting: force_autohint + +#text.hinting_factor: 8 # Specifies the amount of softness for hinting in the + # horizontal direction. A value of 1 will hint to full + # pixels. A value of 2 will hint to half pixels etc. +#text.kerning_factor: 0 # Specifies the scaling factor for kerning values. This + # is provided solely to allow old test images to remain + # unchanged. Set to 6 to obtain previous behavior. + # Values other than 0 or 6 have no defined meaning. +#text.antialiased: True # If True (default), the text will be antialiased. + # This only affects raster outputs. +#text.parse_math: True # Use mathtext if there is an even number of unescaped + # dollar signs. + + +## *************************************************************************** +## * LaTeX * +## *************************************************************************** +## For more information on LaTeX properties, see +## https://matplotlib.org/stable/users/explain/text/usetex.html +#text.usetex: False # use latex for all text handling. The following fonts + # are supported through the usual rc parameter settings: + # new century schoolbook, bookman, times, palatino, + # zapf chancery, charter, serif, sans-serif, helvetica, + # avant garde, courier, monospace, computer modern roman, + # computer modern sans serif, computer modern typewriter +#text.latex.preamble: # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES + # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP + # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. + # text.latex.preamble is a single line of LaTeX code that + # will be passed on to the LaTeX system. It may contain + # any code that is valid for the LaTeX "preamble", i.e. + # between the "\documentclass" and "\begin{document}" + # statements. + # Note that it has to be put on a single line, which may + # become quite long. + # The following packages are always loaded with usetex, + # so beware of package collisions: + # color, fix-cm, geometry, graphicx, textcomp. + # PostScript (PSNFSS) font packages may also be + # loaded, depending on your font settings. + +## The following settings allow you to select the fonts in math mode. +#mathtext.fontset: dejavusans # Should be 'dejavusans' (default), + # 'dejavuserif', 'cm' (Computer Modern), 'stix', + # 'stixsans' or 'custom' +## "mathtext.fontset: custom" is defined by the mathtext.bf, .cal, .it, ... +## settings which map a TeX font name to a fontconfig font pattern. (These +## settings are not used for other font sets.) +#mathtext.bf: sans:bold +#mathtext.bfit: sans:italic:bold +#mathtext.cal: cursive +#mathtext.it: sans:italic +#mathtext.rm: sans +#mathtext.sf: sans +#mathtext.tt: monospace +#mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix' + # 'stixsans'] when a symbol cannot be found in one of the + # custom math fonts. Select 'None' to not perform fallback + # and replace the missing character by a dummy symbol. +#mathtext.default: it # The default font to use for math. + # Can be any of the LaTeX font names, including + # the special name "regular" for the same font + # used in regular text. + + +## *************************************************************************** +## * AXES * +## *************************************************************************** +## Following are default face and edge colors, default tick sizes, +## default font sizes for tick labels, and so on. See +## https://matplotlib.org/stable/api/axes_api.html#module-matplotlib.axes +#axes.facecolor: white # axes background color +#axes.edgecolor: black # axes edge color +#axes.linewidth: 0.8 # edge line width +#axes.grid: False # display grid or not +#axes.grid.axis: both # which axis the grid should apply to +#axes.grid.which: major # grid lines at {major, minor, both} ticks +#axes.titlelocation: center # alignment of the title: {left, right, center} +#axes.titlesize: large # font size of the axes title +#axes.titleweight: normal # font weight of title +#axes.titlecolor: auto # color of the axes title, auto falls back to + # text.color as default value +#axes.titley: None # position title (axes relative units). None implies auto +#axes.titlepad: 6.0 # pad between axes and title in points +#axes.labelsize: medium # font size of the x and y labels +#axes.labelpad: 4.0 # space between label and axis +#axes.labelweight: normal # weight of the x and y labels +#axes.labelcolor: black +#axes.axisbelow: line # draw axis gridlines and ticks: + # - below patches (True) + # - above patches but below lines ('line') + # - above all (False) + +#axes.formatter.limits: -5, 6 # use scientific notation if log10 + # of the axis range is smaller than the + # first or larger than the second +#axes.formatter.use_locale: False # When True, format tick labels + # according to the user's locale. + # For example, use ',' as a decimal + # separator in the fr_FR locale. +#axes.formatter.use_mathtext: False # When True, use mathtext for scientific + # notation. +#axes.formatter.min_exponent: 0 # minimum exponent to format in scientific notation +#axes.formatter.useoffset: True # If True, the tick label formatter + # will default to labeling ticks relative + # to an offset when the data range is + # small compared to the minimum absolute + # value of the data. +#axes.formatter.offset_threshold: 4 # When useoffset is True, the offset + # will be used when it can remove + # at least this number of significant + # digits from tick labels. + +#axes.spines.left: True # display axis spines +#axes.spines.bottom: True +#axes.spines.top: True +#axes.spines.right: True + +#axes.unicode_minus: True # use Unicode for the minus symbol rather than hyphen. See + # https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes +#axes.prop_cycle: cycler(color='tab10') + # color cycle for plot lines as either a named color sequence or a list + # of string color specs: single letter, long name, or web-style hex + # As opposed to all other parameters in this file, the color + # values must be enclosed in quotes for this parameter, + # e.g. '1f77b4', instead of 1f77b4. + # See also https://matplotlib.org/stable/users/explain/artists/color_cycle.html + # for more details on prop_cycle usage. +#axes.xmargin: .05 # x margin. See `axes.Axes.margins` +#axes.ymargin: .05 # y margin. See `axes.Axes.margins` +#axes.zmargin: .05 # z margin. See `axes.Axes.margins` +#axes.autolimit_mode: data # If "data", use axes.xmargin and axes.ymargin as is. + # If "round_numbers", after application of margins, axis + # limits are further expanded to the nearest "round" number. +#polaraxes.grid: True # display grid on polar axes +#axes3d.grid: True # display grid on 3D axes +#axes3d.automargin: False # automatically add margin when manually setting 3D axis limits + +#axes3d.xaxis.panecolor: (0.95, 0.95, 0.95, 0.5) # background pane on 3D axes +#axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes +#axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes + +#axes3d.depthshade: True # depth shade for 3D scatter plots +#axes3d.depthshade_minalpha: 0.3 # minimum alpha value for depth shading + +#axes3d.mouserotationstyle: arcball # {azel, trackball, sphere, arcball} + # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse +#axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox +#axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style) + +## *************************************************************************** +## * AXIS * +## *************************************************************************** +#xaxis.labellocation: center # alignment of the xaxis label: {left, right, center} +#yaxis.labellocation: center # alignment of the yaxis label: {bottom, top, center} + + +## *************************************************************************** +## * DATES * +## *************************************************************************** +## These control the default format strings used in AutoDateFormatter. +## Any valid format datetime format string can be used (see the python +## `datetime` for details). For example, by using: +## - '%x' will use the locale date representation +## - '%X' will use the locale time representation +## - '%c' will use the full locale datetime representation +## These values map to the scales: +## {'year': 365, 'month': 30, 'day': 1, 'hour': 1/24, 'minute': 1 / (24 * 60)} + +#date.autoformatter.year: %Y +#date.autoformatter.month: %Y-%m +#date.autoformatter.day: %Y-%m-%d +#date.autoformatter.hour: %m-%d %H +#date.autoformatter.minute: %d %H:%M +#date.autoformatter.second: %H:%M:%S +#date.autoformatter.microsecond: %M:%S.%f +## The reference date for Matplotlib's internal date representation +## See https://matplotlib.org/stable/gallery/ticks/date_precision_and_epochs.html +#date.epoch: 1970-01-01T00:00:00 +## 'auto', 'concise': +#date.converter: auto +## For auto converter whether to use interval_multiples: +#date.interval_multiples: True + +## *************************************************************************** +## * TICKS * +## *************************************************************************** +## See https://matplotlib.org/stable/api/axis_api.html#matplotlib.axis.Tick +#xtick.top: False # draw ticks on the top side +#xtick.bottom: True # draw ticks on the bottom side +#xtick.labeltop: False # draw label on the top +#xtick.labelbottom: True # draw label on the bottom +#xtick.major.size: 3.5 # major tick size in points +#xtick.minor.size: 2 # minor tick size in points +#xtick.major.width: 0.8 # major tick width in points +#xtick.minor.width: 0.6 # minor tick width in points +#xtick.major.pad: 3.5 # distance to major tick label in points +#xtick.minor.pad: 3.4 # distance to the minor tick label in points +#xtick.color: black # color of the ticks +#xtick.labelcolor: inherit # color of the tick labels or inherit from xtick.color +#xtick.labelsize: medium # font size of the tick labels +#xtick.direction: out # direction: {in, out, inout} +#xtick.minor.visible: False # visibility of minor ticks on x-axis +#xtick.major.top: True # draw x axis top major ticks +#xtick.major.bottom: True # draw x axis bottom major ticks +#xtick.minor.top: True # draw x axis top minor ticks +#xtick.minor.bottom: True # draw x axis bottom minor ticks +#xtick.minor.ndivs: auto # number of minor ticks between the major ticks on x-axis +#xtick.alignment: center # alignment of xticks + +#ytick.left: True # draw ticks on the left side +#ytick.right: False # draw ticks on the right side +#ytick.labelleft: True # draw tick labels on the left side +#ytick.labelright: False # draw tick labels on the right side +#ytick.major.size: 3.5 # major tick size in points +#ytick.minor.size: 2 # minor tick size in points +#ytick.major.width: 0.8 # major tick width in points +#ytick.minor.width: 0.6 # minor tick width in points +#ytick.major.pad: 3.5 # distance to major tick label in points +#ytick.minor.pad: 3.4 # distance to the minor tick label in points +#ytick.color: black # color of the ticks +#ytick.labelcolor: inherit # color of the tick labels or inherit from ytick.color +#ytick.labelsize: medium # font size of the tick labels +#ytick.direction: out # direction: {in, out, inout} +#ytick.minor.visible: False # visibility of minor ticks on y-axis +#ytick.major.left: True # draw y axis left major ticks +#ytick.major.right: True # draw y axis right major ticks +#ytick.minor.left: True # draw y axis left minor ticks +#ytick.minor.right: True # draw y axis right minor ticks +#ytick.minor.ndivs: auto # number of minor ticks between the major ticks on y-axis +#ytick.alignment: center_baseline # alignment of yticks + + +## *************************************************************************** +## * GRIDS * +## *************************************************************************** +#grid.color: "#b0b0b0" # grid color +#grid.linestyle: - # solid +#grid.linewidth: 0.8 # in points +#grid.alpha: 1.0 # transparency, between 0.0 and 1.0 + + +## *************************************************************************** +## * LEGEND * +## *************************************************************************** +#legend.loc: best +#legend.frameon: True # if True, draw the legend on a background patch +#legend.framealpha: 0.8 # legend patch transparency +#legend.facecolor: inherit # inherit from axes.facecolor; or color spec +#legend.edgecolor: 0.8 # background patch boundary color +#legend.fancybox: True # if True, use a rounded box for the + # legend background, else a rectangle +#legend.shadow: False # if True, give background a shadow effect +#legend.numpoints: 1 # the number of marker points in the legend line +#legend.scatterpoints: 1 # number of scatter points +#legend.markerscale: 1.0 # the relative size of legend markers vs. original +#legend.fontsize: medium +#legend.labelcolor: None +#legend.title_fontsize: None # None sets to the same as the default axes. + +## Dimensions as fraction of font size: +#legend.borderpad: 0.4 # border whitespace +#legend.labelspacing: 0.5 # the vertical space between the legend entries +#legend.handlelength: 2.0 # the length of the legend lines +#legend.handleheight: 0.7 # the height of the legend handle +#legend.handletextpad: 0.8 # the space between the legend line and legend text +#legend.borderaxespad: 0.5 # the border between the axes and legend edge +#legend.columnspacing: 2.0 # column separation + + +## *************************************************************************** +## * FIGURE * +## *************************************************************************** +## See https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure +#figure.titlesize: large # size of the figure title (``Figure.suptitle()``) +#figure.titleweight: normal # weight of the figure title +#figure.labelsize: large # size of the figure label (``Figure.sup[x|y]label()``) +#figure.labelweight: normal # weight of the figure label +#figure.figsize: 6.4, 4.8 # figure size in inches +#figure.dpi: 100 # figure dots per inch +#figure.facecolor: white # figure face color +#figure.edgecolor: white # figure edge color +#figure.frameon: True # enable figure frame +#figure.max_open_warning: 20 # The maximum number of figures to open through + # the pyplot interface before emitting a warning. + # If less than one this feature is disabled. +#figure.raise_window : True # Raise the GUI window to front when show() is called. + +## The figure subplot parameters. All dimensions are a fraction of the figure width and height. +#figure.subplot.left: 0.125 # the left side of the subplots of the figure +#figure.subplot.right: 0.9 # the right side of the subplots of the figure +#figure.subplot.bottom: 0.11 # the bottom of the subplots of the figure +#figure.subplot.top: 0.88 # the top of the subplots of the figure +#figure.subplot.wspace: 0.2 # the amount of width reserved for space between subplots, + # expressed as a fraction of the average axis width +#figure.subplot.hspace: 0.2 # the amount of height reserved for space between subplots, + # expressed as a fraction of the average axis height + +## Figure layout +#figure.autolayout: False # When True, automatically adjust subplot + # parameters to make the plot fit the figure + # using `tight_layout` +#figure.constrained_layout.use: False # When True, automatically make plot + # elements fit on the figure. (Not + # compatible with `autolayout`, above). +## Padding (in inches) around axes; defaults to 3/72 inches, i.e. 3 points. +#figure.constrained_layout.h_pad: 0.04167 +#figure.constrained_layout.w_pad: 0.04167 +## Spacing between subplots, relative to the subplot sizes. Much smaller than for +## tight_layout (figure.subplot.hspace, figure.subplot.wspace) as constrained_layout +## already takes surrounding texts (titles, labels, # ticklabels) into account. +#figure.constrained_layout.hspace: 0.02 +#figure.constrained_layout.wspace: 0.02 + + +## *************************************************************************** +## * IMAGES * +## *************************************************************************** +#image.aspect: equal # {equal, auto} or a number +#image.interpolation: auto # see help(imshow) for options +#image.interpolation_stage: auto # see help(imshow) for options +#image.cmap: viridis # A colormap name (plasma, magma, etc.) +#image.lut: 256 # the size of the colormap lookup table +#image.origin: upper # {lower, upper} +#image.resample: True +#image.composite_image: True # When True, all the images on a set of axes are + # combined into a single composite image before + # saving a figure as a vector graphics file, + # such as a PDF. + + +## *************************************************************************** +## * CONTOUR PLOTS * +## *************************************************************************** +#contour.negative_linestyle: dashed # string or on-off ink sequence +#contour.corner_mask: True # {True, False} +#contour.linewidth: None # {float, None} Size of the contour line + # widths. If set to None, it falls back to + # `line.linewidth`. +#contour.algorithm: mpl2014 # {mpl2005, mpl2014, serial, threaded} + + +## *************************************************************************** +## * ERRORBAR PLOTS * +## *************************************************************************** +#errorbar.capsize: 0 # length of end cap on error bars in pixels + + +## *************************************************************************** +## * HISTOGRAM PLOTS * +## *************************************************************************** +#hist.bins: 10 # The default number of histogram bins or 'auto'. + + +## *************************************************************************** +## * SCATTER PLOTS * +## *************************************************************************** +#scatter.marker: o # The default marker type for scatter plots. +#scatter.edgecolors: face # The default edge colors for scatter plots. + + +## *************************************************************************** +## * AGG RENDERING * +## *************************************************************************** +## Warning: experimental, 2008/10/10 +#agg.path.chunksize: 0 # 0 to disable; values in the range + # 10000 to 100000 can improve speed slightly + # and prevent an Agg rendering failure + # when plotting very large data sets, + # especially if they are very gappy. + # It may cause minor artifacts, though. + # A value of 20000 is probably a good + # starting point. + + +## *************************************************************************** +## * PATHS * +## *************************************************************************** +#path.simplify: True # When True, simplify paths by removing "invisible" + # points to reduce file size and increase rendering + # speed +#path.simplify_threshold: 0.111111111111 # The threshold of similarity below + # which vertices will be removed in + # the simplification process. +#path.snap: True # When True, rectilinear axis-aligned paths will be snapped + # to the nearest pixel when certain criteria are met. + # When False, paths will never be snapped. +#path.sketch: None # May be None, or a tuple of the form: + # path.sketch: (scale, length, randomness) + # - *scale* is the amplitude of the wiggle + # perpendicular to the line (in pixels). + # - *length* is the length of the wiggle along the + # line (in pixels). + # - *randomness* is the factor by which the length is + # randomly scaled. +#path.effects: + + +## *************************************************************************** +## * SAVING FIGURES * +## *************************************************************************** +## The default savefig parameters can be different from the display parameters +## e.g., you may want a higher resolution, or to make the figure +## background white +#savefig.dpi: figure # figure dots per inch or 'figure' +#savefig.facecolor: auto # figure face color when saving +#savefig.edgecolor: auto # figure edge color when saving +#savefig.format: png # {png, ps, pdf, svg} +#savefig.bbox: standard # {tight, standard} + # 'tight' is incompatible with generating frames + # for animation +#savefig.pad_inches: 0.1 # padding to be used, when bbox is set to 'tight' +#savefig.directory: ~ # default directory in savefig dialog, gets updated after + # interactive saves, unless set to the empty string (i.e. + # the current directory); use '.' to start at the current + # directory but update after interactive saves +#savefig.transparent: False # whether figures are saved with a transparent + # background by default +#savefig.orientation: portrait # orientation of saved figure, for PostScript output only + +### macosx backend params +#macosx.window_mode : system # How to open new figures (system, tab, window) + # system uses the MacOS system preferences + +### tk backend params +#tk.window_focus: False # Maintain shell focus for TkAgg + +### ps backend params +#ps.papersize: letter # {figure, letter, legal, ledger, A0-A10, B0-B10} +#ps.useafm: False # use AFM fonts, results in small files +#ps.usedistiller: False # {ghostscript, xpdf, None} + # Experimental: may produce smaller files. + # xpdf intended for production of publication quality files, + # but requires ghostscript, xpdf and ps2eps +#ps.distiller.res: 6000 # dpi +#ps.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType) + +### PDF backend params +#pdf.compression: 6 # integer from 0 to 9 + # 0 disables compression (good for debugging) +#pdf.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType) +#pdf.use14corefonts: False +#pdf.inheritcolor: False + +### SVG backend params +#svg.image_inline: True # Write raster image data directly into the SVG file +#svg.fonttype: path # How to handle SVG fonts: + # path: Embed characters as paths -- supported + # by most SVG renderers + # None: Assume fonts are installed on the + # machine where the SVG will be viewed. +#svg.hashsalt: None # If not None, use this string as hash salt instead of uuid4 +#svg.id: None # If not None, use this string as the value for the `id` + # attribute in the top tag + +### pgf parameter +## See https://matplotlib.org/stable/tutorials/text/pgf.html for more information. +#pgf.rcfonts: True +#pgf.preamble: # See text.latex.preamble for documentation +#pgf.texsystem: xelatex + +### docstring params +#docstring.hardcopy: False # set this when you want to generate hardcopy docstring + + +## *************************************************************************** +## * INTERACTIVE KEYMAPS * +## *************************************************************************** +## Event keys to interact with figures/plots via keyboard. +## See https://matplotlib.org/stable/users/explain/interactive.html for more +## details on interactive navigation. Customize these settings according to +## your needs. Leave the field(s) empty if you don't need a key-map. (i.e., +## fullscreen : '') +#keymap.fullscreen: f, ctrl+f # toggling +#keymap.home: h, r, home # home or reset mnemonic +#keymap.back: left, c, backspace, MouseButton.BACK # forward / backward keys +#keymap.forward: right, v, MouseButton.FORWARD # for quick navigation +#keymap.pan: p # pan mnemonic +#keymap.zoom: o # zoom mnemonic +#keymap.save: s, ctrl+s # saving current figure +#keymap.help: f1 # display help about active tools +#keymap.quit: ctrl+w, cmd+w, q # close the current figure +#keymap.quit_all: # close all figures +#keymap.grid: g # switching on/off major grids in current axes +#keymap.grid_minor: G # switching on/off minor grids in current axes +#keymap.yscale: l # toggle scaling of y-axes ('log'/'linear') +#keymap.xscale: k, L # toggle scaling of x-axes ('log'/'linear') +#keymap.copy: ctrl+c, cmd+c # copy figure to clipboard + + +## *************************************************************************** +## * ANIMATION * +## *************************************************************************** +#animation.html: none # How to display the animation as HTML in + # the IPython notebook: + # - 'html5' uses HTML5 video tag + # - 'jshtml' creates a JavaScript animation +#animation.writer: ffmpeg # MovieWriter 'backend' to use +#animation.codec: h264 # Codec to use for writing movie +#animation.bitrate: -1 # Controls size/quality trade-off for movie. + # -1 implies let utility auto-determine +#animation.frame_format: png # Controls frame format used by temp files + +## Path to ffmpeg binary. Unqualified paths are resolved by subprocess.Popen. +#animation.ffmpeg_path: ffmpeg +## Additional arguments to pass to ffmpeg. +#animation.ffmpeg_args: + +## Path to ImageMagick's convert binary. Unqualified paths are resolved by +## subprocess.Popen, except that on Windows, we look up an install of +## ImageMagick in the registry (as convert is also the name of a system tool). +#animation.convert_path: convert +## Additional arguments to pass to convert. +#animation.convert_args: -layers, OptimizePlus +# +#animation.embed_limit: 20.0 # Limit, in MB, of size of base64 encoded + # animation in HTML (i.e. IPython notebook) diff --git a/lib/matplotlib/mpl-data/meson.build b/lib/matplotlib/mpl-data/meson.build new file mode 100644 index 000000000000..00a825fbbbea --- /dev/null +++ b/lib/matplotlib/mpl-data/meson.build @@ -0,0 +1,24 @@ +custom_target('matplotlibrc', + command: [ + find_program(meson.project_source_root() / 'tools/generate_matplotlibrc.py'), + '@INPUT@', + '@OUTPUT@', + get_option('rcParams-backend') + ], + input: 'matplotlibrc', + output: 'matplotlibrc', + install: true, + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) + +install_data( + 'kpsewhich.lua', + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) + +foreach dir : ['fonts', 'images', 'plot_directive', 'sample_data', 'stylelib'] + install_subdir( + dir, + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) +endforeach diff --git a/lib/matplotlib/mpl-data/plot_directive/plot_directive.css b/lib/matplotlib/mpl-data/plot_directive/plot_directive.css new file mode 100644 index 000000000000..d45593c93c95 --- /dev/null +++ b/lib/matplotlib/mpl-data/plot_directive/plot_directive.css @@ -0,0 +1,16 @@ +/* + * plot_directive.css + * ~~~~~~~~~~~~ + * + * Stylesheet controlling images created using the `plot` directive within + * Sphinx. + * + * :copyright: Copyright 2020-* by the Matplotlib development team. + * :license: Matplotlib, see LICENSE for details. + * + */ + +img.plot-directive { + border: 0; + max-width: 100%; +} diff --git a/lib/matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png b/lib/matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png deleted file mode 100644 index 40af4721f206..000000000000 Binary files a/lib/matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png and /dev/null differ diff --git a/lib/matplotlib/mpl-data/sample_data/Stocks.csv b/lib/matplotlib/mpl-data/sample_data/Stocks.csv new file mode 100644 index 000000000000..575d353dffe2 --- /dev/null +++ b/lib/matplotlib/mpl-data/sample_data/Stocks.csv @@ -0,0 +1,526 @@ +# Data source: https://finance.yahoo.com +Date,IBM,AAPL,MSFT,XRX,AMZN,DELL,GOOGL,ADBE,^GSPC,^IXIC +1990-01-01,10.970438003540039,0.24251236021518707,0.40375930070877075,11.202081680297852,,,,1.379060983657837,329.0799865722656,415.79998779296875 +1990-02-01,11.554415702819824,0.24251236021518707,0.43104037642478943,10.39472484588623,,,,1.7790844440460205,331.8900146484375,425.79998779296875 +1990-02-05,,,,,,,,,, +1990-03-01,11.951693534851074,0.28801724314689636,0.4834197461605072,11.394058227539062,,,,2.2348830699920654,339.94000244140625,435.5 +1990-04-01,12.275476455688477,0.2817564308643341,0.5063362717628479,10.139430046081543,,,,2.2531447410583496,330.79998779296875,420.1000061035156 +1990-05-01,13.514284133911133,0.295173317193985,0.6372847557067871,9.704153060913086,,,,2.0985164642333984,361.2300109863281,459.0 +1990-05-04,,,,,,,,,, +1990-06-01,13.380948066711426,0.3211067020893097,0.6634747385978699,9.749448776245117,,,,2.164785146713257,358.0199890136719,462.29998779296875 +1990-07-01,12.697657585144043,0.3013734817504883,0.5805402994155884,9.489465713500977,,,,2.069063901901245,356.1499938964844,438.20001220703125 +1990-08-01,11.601561546325684,0.26549553871154785,0.5368908047676086,8.553519248962402,,,,1.4750508069992065,322.55999755859375,381.20001220703125 +1990-08-06,,,,,,,,,, +1990-09-01,12.251119613647461,0.20872052013874054,0.5499854683876038,7.255113124847412,,,,1.1210384368896484,306.04998779296875,344.5 +1990-10-01,12.15034294128418,0.22131578624248505,0.5565334558486938,6.248927116394043,,,,1.416249394416809,304.0,329.79998779296875 +1990-11-01,13.08609390258789,0.26449888944625854,0.6307373642921448,7.361023426055908,,,,1.4900128841400146,322.2200012207031,359.1000061035156 +1990-11-05,,,,,,,,,, +1990-12-01,13.161062240600586,0.31051647663116455,0.6569272875785828,7.519896507263184,,,,1.7186784744262695,330.2200012207031,373.79998779296875 +1991-01-01,14.762505531311035,0.40078282356262207,0.8566243648529053,10.554410934448242,,,,2.242394208908081,343.92999267578125,414.20001220703125 +1991-02-01,14.995450973510742,0.4134202301502228,0.9057300686836243,12.286416053771973,,,,2.8402483463287354,367.07000732421875,453.1000061035156 +1991-02-04,,,,,,,,,, +1991-03-01,13.39067268371582,0.49208223819732666,0.92646324634552,12.511940002441406,,,,3.1869795322418213,375.2200012207031,482.29998779296875 +1991-04-01,12.111870765686035,0.39800751209259033,0.8642628788948059,12.511940002441406,,,,3.0589873790740967,375.3399963378906,484.7200012207031 +1991-05-01,12.479341506958008,0.34011581540107727,0.9581103920936584,12.73144817352295,,,,2.9850986003875732,389.8299865722656,506.1099853515625 +1991-05-06,,,,,,,,,, +1991-06-01,11.555957794189453,0.30108359456062317,0.89208984375,11.853414535522461,,,,2.5565452575683594,371.1600036621094,475.9200134277344 +1991-07-01,12.04675579071045,0.33554431796073914,0.9624748229980469,12.397871017456055,,,,3.1678967475891113,387.80999755859375,502.0400085449219 +1991-08-01,11.526222229003906,0.3845158815383911,1.1163398027420044,13.037229537963867,,,,3.012463331222534,395.42999267578125,525.6799926757812 +1991-08-06,,,,,,,,,, +1991-09-01,12.478833198547363,0.3599340617656708,1.1654454469680786,13.740047454833984,,,,3.0346662998199463,387.8599853515625,526.8800048828125 +1991-10-01,11.83155345916748,0.37447676062583923,1.2292828559875488,14.41578483581543,,,,3.1431360244750977,392.45001220703125,542.97998046875 +1991-11-01,11.139124870300293,0.36902356147766113,1.2734787464141846,13.965290069580078,,,,2.846613883972168,375.2200012207031,523.9000244140625 +1991-11-04,,,,,,,,,, +1991-12-01,10.851117134094238,0.4109107553958893,1.4568067789077759,15.42939281463623,,,,3.8844411373138428,417.0899963378906,586.3400268554688 +1992-01-01,10.973037719726562,0.4719553291797638,1.5746610164642334,17.584869384765625,,,,3.639812469482422,408.7799987792969,620.2100219726562 +1992-02-01,10.592026710510254,0.49199995398521423,1.61721932888031,18.04088020324707,,,,3.3547844886779785,412.70001220703125,633.469970703125 +1992-02-06,,,,,,,,,, +1992-03-01,10.317353248596191,0.4253714978694916,1.5517452955245972,16.302349090576172,,,,3.043056011199951,403.69000244140625,603.77001953125 +1992-04-01,11.213163375854492,0.43906375765800476,1.4437123537063599,17.062589645385742,,,,2.631263494491577,414.95001220703125,578.6799926757812 +1992-05-01,11.213163375854492,0.4363253116607666,1.5844820737838745,17.263996124267578,,,,2.705594062805176,415.3500061035156,585.3099975585938 +1992-05-07,,,,,,,,,, +1992-06-01,12.25231647491455,0.3505205810070038,1.3749638795852661,16.055517196655273,,,,2.705594062805176,408.1400146484375,563.5999755859375 +1992-07-01,11.861115455627441,0.34207984805107117,1.4289811849594116,17.38025665283203,,,,2.263554334640503,424.2099914550781,580.8300170898438 +1992-08-01,10.84400749206543,0.33659130334854126,1.4633547067642212,17.525571823120117,,,,1.9359338283538818,414.0299987792969,563.1199951171875 +1992-08-06,,,,,,,,,, +1992-09-01,10.243829727172852,0.3310767114162445,1.58120858669281,18.464359283447266,,,,1.6753284931182861,417.79998779296875,583.27001953125 +1992-10-01,8.483662605285645,0.38518592715263367,1.7432584762573242,17.436922073364258,,,,2.12785267829895,418.67999267578125,605.1699829101562 +1992-11-01,8.658098220825195,0.42187052965164185,1.8291932344436646,18.493711471557617,,,,2.030792474746704,431.3500061035156,652.72998046875 +1992-11-05,,,,,,,,,, +1992-12-01,6.505843639373779,0.43931084871292114,1.6769649982452393,18.788379669189453,,,,1.8814688920974731,435.7099914550781,676.9500122070312 +1993-01-01,6.651134490966797,0.43747296929359436,1.6990629434585571,20.329378128051758,,,,2.4787611961364746,438.7799987792969,696.3400268554688 +1993-02-01,7.022435188293457,0.38968151807785034,1.6376806497573853,19.618152618408203,,,,2.670894145965576,443.3800048828125,670.77001953125 +1993-02-04,,,,,,,,,, +1993-03-01,6.6405534744262695,0.37947842478752136,1.8169171810150146,19.766319274902344,,,,2.573633909225464,451.6700134277344,690.1300048828125 +1993-04-01,6.346868991851807,0.3776364326477051,1.6794201135635376,18.451818466186523,,,,3.434307336807251,440.19000244140625,661.4199829101562 +1993-05-01,6.885295867919922,0.4172421991825104,1.8193715810775757,18.12286949157715,,,,3.959200859069824,450.19000244140625,700.530029296875 +1993-05-06,,,,,,,,,, +1993-06-01,6.51603364944458,0.29166528582572937,1.7285257577896118,19.299583435058594,,,,3.7042527198791504,450.5299987792969,703.9500122070312 +1993-07-01,5.872671604156494,0.2049039751291275,1.453533411026001,17.638439178466797,,,,2.9742372035980225,448.1300048828125,704.7000122070312 +1993-08-01,6.037637233734131,0.19567380845546722,1.4756313562393188,17.759246826171875,,,,2.5536391735076904,463.55999755859375,742.8400268554688 +1993-08-05,,,,,,,,,, +1993-09-01,5.574150562286377,0.1733584851026535,1.620492935180664,17.849544525146484,,,,2.1931254863739014,458.92999267578125,762.780029296875 +1993-10-01,6.105024337768555,0.22805523872375488,1.5738422870635986,19.34462547302246,,,,2.6137242317199707,467.8299865722656,779.260009765625 +1993-11-01,7.150176048278809,0.2336171418428421,1.5713871717453003,20.107433319091797,,,,2.786593437194824,461.7900085449219,754.3900146484375 +1993-11-04,,,,,,,,,, +1993-12-01,7.535686016082764,0.21771006286144257,1.5836634635925293,22.01595687866211,,,,2.68115496635437,466.45001220703125,776.7999877929688 +1994-01-01,7.535686016082764,0.24376071989536285,1.672054648399353,24.171361923217773,,,,3.64516544342041,481.6099853515625,800.469970703125 +1994-02-01,7.052198886871338,0.27167221903800964,1.620492935180664,23.894229888916016,,,,3.5310842990875244,467.1400146484375,792.5 +1994-02-04,,,,,,,,,, +1994-03-01,7.31842041015625,0.24837146699428558,1.6646885871887207,23.706161499023438,,,,2.9274797439575195,445.7699890136719,743.4600219726562 +1994-04-01,7.703606128692627,0.22409436106681824,1.8169171810150146,24.543949127197266,,,,3.2354440689086914,450.9100036621094,733.8400268554688 +1994-05-01,8.440468788146973,0.21849241852760315,2.1115520000457764,24.947307586669922,,,,3.4773480892181396,456.5,735.1900024414062 +1994-05-05,,,,,,,,,, +1994-06-01,7.905290126800537,0.19873161613941193,2.028071165084839,24.444643020629883,,,,3.2959203720092773,444.2699890136719,705.9600219726562 +1994-07-01,8.325791358947754,0.2526327669620514,2.023160934448242,25.569963455200195,,,,3.756444215774536,458.260009765625,722.1599731445312 +1994-08-01,9.217235565185547,0.27138155698776245,2.2834222316741943,26.789077758789062,,,,3.847325563430786,475.489990234375,765.6199951171875 +1994-08-04,,,,,,,,,, +1994-09-01,9.405942916870117,0.25350791215896606,2.2048532962799072,26.88198471069336,,,,3.9382081031799316,462.7099914550781,764.2899780273438 +1994-10-01,10.064526557922363,0.324998676776886,2.474935293197632,25.811735153198242,,,,4.368794918060303,472.3500061035156,777.489990234375 +1994-11-01,9.557921409606934,0.28031668066978455,2.470024824142456,24.741479873657227,,,,4.004726409912109,453.69000244140625,750.3200073242188 +1994-11-04,,,,,,,,,, +1994-12-01,9.9633207321167,0.2943686842918396,2.401276111602783,25.12115478515625,,,,3.6103241443634033,459.2699890136719,751.9600219726562 +1995-01-01,9.77692985534668,0.3047473132610321,2.332528591156006,27.753808975219727,,,,3.5117223262786865,470.4200134277344,755.2000122070312 +1995-02-01,10.200541496276855,0.29814332723617554,2.474935293197632,28.13443946838379,,,,4.345465660095215,487.3900146484375,793.72998046875 +1995-02-06,,,,,,,,,, +1995-03-01,11.169903755187988,0.2667955756187439,2.7941226959228516,29.989917755126953,,,,6.016797065734863,500.7099914550781,817.2100219726562 +1995-04-01,12.870043754577637,0.2895018756389618,3.2115228176116943,31.52293586730957,,,,7.08742618560791,514.7100219726562,843.97998046875 +1995-05-01,12.649023056030273,0.31457316875457764,3.326920747756958,28.96788215637207,,,,6.326970100402832,533.4000244140625,864.5800170898438 +1995-05-04,,,,,,,,,, +1995-06-01,13.091790199279785,0.3524453043937683,3.5503528118133545,30.15083122253418,,,,7.057007789611816,544.75,933.4500122070312 +1995-07-01,14.847586631774902,0.3415350615978241,3.5552642345428467,30.697277069091797,,,,7.513277530670166,562.0599975585938,1001.2100219726562 +1995-08-01,14.09753131866455,0.32635587453842163,3.6338343620300293,31.08300018310547,,,,6.210629463195801,561.8800048828125,1020.1099853515625 +1995-08-08,,,,,,,,,, +1995-09-01,12.916881561279297,0.28348639607429504,3.5552642345428467,34.767181396484375,,,,6.301961898803711,584.4099731445312,1043.5400390625 +1995-10-01,13.292764663696289,0.27635207772254944,3.92846941947937,33.5381965637207,,,,6.941293239593506,581.5,1036.06005859375 +1995-11-01,13.207347869873047,0.2901458144187927,3.4226772785186768,35.478694915771484,,,,8.243063926696777,605.3699951171875,1059.199951171875 +1995-11-08,,,,,,,,,, +1995-12-01,12.52143669128418,0.24333631992340088,3.447230815887451,35.68303298950195,,,,7.557409763336182,615.9299926757812,1052.1300048828125 +1996-01-01,14.868143081665039,0.21089188754558563,3.6338343620300293,32.199371337890625,,,,4.14438533782959,636.02001953125,1059.7900390625 +1996-02-01,16.80373764038086,0.2099376916885376,3.876908779144287,33.924922943115234,,,,4.089058876037598,640.4299926757812,1100.050048828125 +1996-02-07,,,,,,,,,, +1996-03-01,15.278267860412598,0.1875123232603073,4.051232814788818,32.900360107421875,,,,3.936483860015869,645.5,1101.4000244140625 +1996-04-01,14.797598838806152,0.1860809624195099,4.448990821838379,38.40559768676758,,,,5.248644828796387,654.1699829101562,1190.52001953125 +1996-05-01,14.660264015197754,0.1994406133890152,4.6650567054748535,41.25652313232422,,,,4.538435459136963,669.1199951171875,1243.4300537109375 +1996-05-08,,,,,,,,,, +1996-06-01,13.641029357910156,0.1603158563375473,4.719073295593262,42.07575225830078,,,,4.385625839233398,670.6300048828125,1185.02001953125 +1996-07-01,14.812233924865723,0.1679503321647644,4.630680561065674,39.836021423339844,,,,3.7132644653320312,639.9500122070312,1080.5899658203125 +1996-08-01,15.759532928466797,0.18512675166130066,4.812370777130127,43.394588470458984,,,,4.269299030303955,651.989990234375,1141.5 +1996-08-07,,,,,,,,,, +1996-09-01,17.209583282470703,0.16938161849975586,5.180670261383057,42.40607833862305,,,,4.5600385665893555,687.3300170898438,1226.9200439453125 +1996-10-01,17.831613540649414,0.17558389902114868,5.391822814941406,36.86507034301758,,,,4.244296073913574,705.27001953125,1221.510009765625 +1996-11-01,22.03032875061035,0.184172585606575,6.162785530090332,38.95176315307617,,,,4.841867923736572,757.02001953125,1292.6099853515625 +1996-11-06,,,,,,,,,, +1996-12-01,20.998197555541992,0.15936164557933807,6.491794109344482,41.83340072631836,,,,4.581387996673584,740.739990234375,1291.030029296875 +1997-01-01,21.743183135986328,0.12691716849803925,8.01407527923584,46.8797492980957,,,,4.642676830291748,786.1599731445312,1379.8499755859375 +1997-02-01,19.924028396606445,0.1240537017583847,7.660515785217285,49.97840881347656,,,,4.47982120513916,790.8200073242188,1309.0 +1997-02-06,,,,,,,,,, +1997-03-01,19.067983627319336,0.13932174444198608,7.203826904296875,45.4803581237793,,,,4.924733638763428,757.1199951171875,1221.699951171875 +1997-04-01,22.298084259033203,0.12977975606918335,9.546177864074707,49.431339263916016,,,,4.807621955871582,801.3400268554688,1260.760009765625 +1997-05-01,24.034704208374023,0.12691716849803925,9.742606163024902,54.45485305786133,,,,5.483453750610352,848.280029296875,1400.3199462890625 +1997-05-07,,,,,,,,,, +1997-05-28,,,,,,,,,, +1997-06-01,25.13742446899414,0.10878564417362213,9.929201126098633,63.396705627441406,0.07708299905061722,,,4.3084282875061035,885.1400146484375,1442.0699462890625 +1997-07-01,29.45465660095215,0.1335965245962143,11.107746124267578,66.42845916748047,0.11979199945926666,,,4.592585563659668,954.3099975585938,1593.81005859375 +1997-08-01,28.23609161376953,0.1660410314798355,10.385887145996094,60.97687911987305,0.11692699790000916,,,4.851738929748535,899.469970703125,1587.3199462890625 +1997-08-07,,,,,,,,,, +1997-09-01,29.579116821289062,0.16556398570537567,10.395710945129395,67.9932632446289,0.21692700684070587,,,6.2071452140808105,947.280029296875,1685.68994140625 +1997-10-01,27.48627281188965,0.13001829385757446,10.214018821716309,64.32245635986328,0.25416699051856995,,,5.889601707458496,914.6199951171875,1593.6099853515625 +1997-11-01,30.555805206298828,0.13550494611263275,11.117568969726562,63.00460433959961,0.20624999701976776,,,5.180382251739502,955.4000244140625,1600.550048828125 +1997-11-06,,,,,,,,,, +1997-12-01,29.25237274169922,0.10019782930612564,10.155089378356934,59.91267013549805,0.2510420083999634,,,5.087874889373779,970.4299926757812,1570.3499755859375 +1998-01-01,27.609769821166992,0.1397988647222519,11.721570014953613,65.44635009765625,0.24583299458026886,,,4.750911235809326,980.280029296875,1619.3599853515625 +1998-02-01,29.19993782043457,0.1803557574748993,13.317505836486816,72.36756134033203,0.3208329975605011,,,5.452751159667969,1049.3399658203125,1770.510009765625 +1998-02-06,,,,,,,,,, +1998-03-01,29.10112953186035,0.2099376916885376,14.06391716003418,86.66805267333984,0.35637998580932617,,,5.576151371002197,1101.75,1835.6800537109375 +1998-04-01,32.4630012512207,0.20898354053497314,14.162128448486328,92.79229736328125,0.3822920024394989,,,6.177727699279785,1111.75,1868.4100341796875 +1998-05-01,32.918251037597656,0.2032574564218521,13.327329635620117,84.10579681396484,0.3671880066394806,,,4.930263519287109,1090.8199462890625,1778.8699951171875 +1998-05-06,,,,,,,,,, +1998-06-01,32.225502014160156,0.2190026193857193,17.029911041259766,83.08383178710938,0.831250011920929,,,5.238887786865234,1133.8399658203125,1894.739990234375 +1998-07-01,37.19002914428711,0.26433059573173523,17.27544403076172,86.6082992553711,0.9239580035209656,,,3.988961935043335,1120.6700439453125,1872.3900146484375 +1998-08-01,31.611520767211914,0.23808826506137848,15.075493812561035,72.04536437988281,0.6979169845581055,,,3.2419238090515137,957.280029296875,1499.25 +1998-08-06,,,,,,,,,, +1998-09-01,36.12905502319336,0.2910497486591339,17.295076370239258,69.53275299072266,0.9302080273628235,,,4.283971786499023,1017.010009765625,1693.8399658203125 +1998-10-01,41.75224685668945,0.2834153473377228,16.637067794799805,80.36154174804688,1.0536459684371948,,,4.59221076965332,1098.6700439453125,1771.3900146484375 +1998-11-01,46.4265251159668,0.2438134402036667,19.170928955078125,88.54693603515625,1.600000023841858,,,5.535391330718994,1163.6300048828125,1949.5400390625 +1998-11-06,,,,,,,,,, +1998-12-01,51.91548538208008,0.3125200569629669,21.793180465698242,97.19573974609375,2.6770830154418945,,,5.782783031463623,1229.22998046875,2192.68994140625 +1999-01-01,51.59874725341797,0.3144294023513794,27.499271392822266,102.47120666503906,2.92343807220459,,,5.912916660308838,1279.6400146484375,2505.889892578125 +1999-02-01,47.797481536865234,0.2657618522644043,23.5904541015625,91.21175384521484,3.203125,,,4.984185218811035,1238.3299560546875,2288.030029296875 +1999-02-08,,,,,,,,,, +1999-03-01,49.97552490234375,0.27435049414634705,28.16712188720703,88.21611785888672,4.304687976837158,,,7.027392387390137,1286.3699951171875,2461.39990234375 +1999-04-01,58.98025894165039,0.35116779804229736,25.554689407348633,97.44929504394531,4.301562786102295,,,7.854666709899902,1335.1800537109375,2542.860107421875 +1999-05-01,65.41220092773438,0.3363769054412842,25.35825538635254,93.19886779785156,2.96875,,,9.187017440795898,1301.8399658203125,2470.52001953125 +1999-05-06,,,,,,,,,, +1999-05-27,,,,,,,,,, +1999-06-01,72.96644592285156,0.35355332493782043,28.343910217285156,97.96764373779297,3.128124952316284,,,10.182408332824707,1372.7099609375,2686.1201171875 +1999-07-01,70.95524597167969,0.4251234233379364,26.96893310546875,81.35432434082031,2.50156307220459,,,10.634023666381836,1328.719970703125,2638.489990234375 +1999-08-01,70.32015991210938,0.49812400341033936,29.090301513671875,79.7938461303711,3.109375,,,12.354691505432129,1320.4100341796875,2739.35009765625 +1999-08-06,,,,,,,,,, +1999-09-01,68.37564849853516,0.48333317041397095,28.46175193786621,69.8066177368164,3.996875047683716,,,14.07535457611084,1282.7099609375,2746.159912109375 +1999-10-01,55.519859313964844,0.6116815209388733,29.090301513671875,47.42917251586914,3.53125,,,17.35419464111328,1362.9300537109375,2966.429931640625 +1999-11-01,58.239349365234375,0.747186541557312,28.613975524902344,45.75767135620117,4.253125190734863,,,17.044021606445312,1388.9100341796875,3336.159912109375 +1999-11-08,,,,,,,,,, +1999-12-01,61.04003143310547,0.7848799824714661,36.6919059753418,37.92245101928711,3.8062500953674316,,,16.687326431274414,1469.25,4069.31005859375 +2000-01-01,63.515560150146484,0.7920366525650024,30.75990104675293,35.14961242675781,3.2281250953674316,,,13.668244361877441,1394.4599609375,3940.35009765625 +2000-02-01,58.14006042480469,0.8750578165054321,28.088550567626953,36.62297058105469,3.4437499046325684,,,25.31960105895996,1366.4200439453125,4696.68994140625 +2000-02-08,,,,,,,,,, +2000-03-01,67.05181884765625,1.0368047952651978,33.39197540283203,43.779178619384766,3.3499999046325684,,,27.631258010864258,1498.5799560546875,4572.830078125 +2000-04-01,63.157623291015625,0.947104275226593,21.920852661132812,45.03520965576172,2.7593750953674316,,,30.027847290039062,1452.4300537109375,3860.659912109375 +2000-05-01,60.785640716552734,0.6412634253501892,19.661985397338867,46.097347259521484,2.4156250953674316,,,27.948402404785156,1420.5999755859375,3400.909912109375 +2000-05-08,,,,,,,,,, +2000-06-01,62.13500213623047,0.7996708750724792,25.142194747924805,35.529056549072266,1.8156249523162842,,,32.277984619140625,1454.5999755859375,3966.110107421875 +2000-07-01,63.65913009643555,0.7758141756057739,21.940492630004883,25.79067039489746,1.506250023841858,,,28.43518829345703,1430.8299560546875,3766.989990234375 +2000-08-01,74.86860656738281,0.9304049015045166,21.940492630004883,27.82395362854004,2.075000047683716,,,32.28449630737305,1517.6800537109375,4206.35009765625 +2000-08-08,,,,,,,,,, +2000-09-01,63.94328689575195,0.3931553065776825,18.95485496520996,25.980575561523438,1.921875,,,38.555137634277344,1436.510009765625,3672.820068359375 +2000-10-01,55.92375183105469,0.29868337512016296,21.645862579345703,14.6140718460083,1.8312499523162842,,,37.785430908203125,1429.4000244140625,3369.6298828125 +2000-11-01,53.08496856689453,0.2519250810146332,18.03167152404785,12.016016960144043,1.234375,,,31.482683181762695,1314.949951171875,2597.929931640625 +2000-11-08,,,,,,,,,, +2000-12-01,48.32046127319336,0.22711420059204102,13.631781578063965,8.067792892456055,0.778124988079071,,,28.90570068359375,1320.280029296875,2470.52001953125 +2001-01-01,63.6693229675293,0.33017459511756897,19.190568923950195,14.251648902893066,0.8656250238418579,,,21.702556610107422,1366.010009765625,2772.72998046875 +2001-02-01,56.790767669677734,0.2786443829536438,18.54237174987793,10.536102294921875,0.5093749761581421,,,14.440549850463867,1239.93994140625,2151.830078125 +2001-02-07,,,,,,,,,, +2001-03-01,54.73835754394531,0.3369686007499695,17.18704605102539,10.535667419433594,0.5115000009536743,,,17.375865936279297,1160.3299560546875,1840.260009765625 +2001-04-01,65.52893829345703,0.38918623328208923,21.292295455932617,15.900238990783691,0.7889999747276306,,,22.32872772216797,1249.4599609375,2116.239990234375 +2001-05-01,63.62804412841797,0.3046000301837921,21.741714477539062,17.430465698242188,0.8345000147819519,,,19.768779754638672,1255.8199462890625,2110.489990234375 +2001-05-08,,,,,,,,,, +2001-06-01,64.6737060546875,0.35498547554016113,22.942251205444336,16.83243751525879,0.7074999809265137,,,23.362648010253906,1224.3800048828125,2160.5400390625 +2001-07-01,59.949947357177734,0.28688937425613403,20.80202293395996,14.035829544067383,0.6244999766349792,,,18.640790939331055,1211.22998046875,2027.1300048828125 +2001-08-01,56.952762603759766,0.2832247018814087,17.929533004760742,16.18165397644043,0.44699999690055847,,,16.711578369140625,1133.5799560546875,1805.4300537109375 +2001-08-08,,,,,,,,,, +2001-09-01,52.33213424682617,0.23680917918682098,16.081575393676758,13.6312894821167,0.2985000014305115,,,11.92334270477295,1040.93994140625,1498.800048828125 +2001-10-01,61.660858154296875,0.26810887455940247,18.275238037109375,12.312134742736816,0.3490000069141388,,,13.133195877075195,1059.780029296875,1690.199951171875 +2001-11-01,65.95150756835938,0.3252120614051819,20.17975616455078,14.774558067321777,0.5659999847412109,,,15.958827018737793,1139.449951171875,1930.5799560546875 +2001-11-07,,,,,,,,,, +2001-12-01,69.1006088256836,0.3343726694583893,20.820878982543945,18.32748794555664,0.5410000085830688,,,15.446427345275879,1148.0799560546875,1950.4000244140625 +2002-01-01,61.63407897949219,0.37742963433265686,20.022619247436523,19.928070068359375,0.7095000147819519,,,16.771486282348633,1130.199951171875,1934.030029296875 +2002-02-01,56.052799224853516,0.3313194811344147,18.334945678710938,17.07868194580078,0.7049999833106995,,,18.105239868164062,1106.72998046875,1731.489990234375 +2002-02-06,,,,,,,,,, +2002-03-01,59.49020767211914,0.3613981306552887,18.95407485961914,18.907917022705078,0.7149999737739563,,,20.051130294799805,1147.3900146484375,1845.3499755859375 +2002-04-01,47.912540435791016,0.3705587685108185,16.424144744873047,15.56605339050293,0.8345000147819519,,,19.893611907958984,1076.9200439453125,1688.22998046875 +2002-05-01,46.01911926269531,0.35574817657470703,15.999865531921387,15.777122497558594,0.9114999771118164,,,17.971961975097656,1067.1400146484375,1615.72998046875 +2002-05-08,,,,,,,,,, +2002-06-01,41.26642990112305,0.2705524265766144,17.190977096557617,10.55325984954834,0.8125,,,14.188389778137207,989.8200073242188,1463.2099609375 +2002-07-01,40.349422454833984,0.23299239575862885,15.079033851623535,12.224191665649414,0.7225000262260437,,,11.933782577514648,911.6199951171875,1328.260009765625 +2002-08-01,43.203697204589844,0.22520573437213898,15.424735069274902,12.329721450805664,0.746999979019165,,,10.01123046875,916.0700073242188,1314.8499755859375 +2002-08-07,,,,,,,,,, +2002-09-01,33.49409484863281,0.22138899564743042,13.746496200561523,8.706437110900879,0.796500027179718,,,9.513158798217773,815.280029296875,1172.06005859375 +2002-10-01,45.344242095947266,0.24535933136940002,16.804426193237305,11.678934097290039,0.9679999947547913,,,11.782204627990723,885.760009765625,1329.75 +2002-11-01,49.92806625366211,0.23665708303451538,18.127525329589844,15.337401390075684,1.1675000190734863,,,14.71778678894043,936.3099975585938,1478.780029296875 +2002-11-06,,,,,,,,,, +2002-12-01,44.5990104675293,0.2187930941581726,16.248149871826172,14.15894889831543,0.9445000290870667,,,12.360349655151367,879.8200073242188,1335.510009765625 +2003-01-01,45.00184631347656,0.21925139427185059,14.91561222076416,15.56605339050293,1.0924999713897705,,,13.167760848999023,855.7000122070312,1320.9100341796875 +2003-02-01,44.85797119140625,0.22917553782463074,14.896756172180176,15.829883575439453,1.1004999876022339,,,13.712512969970703,841.1500244140625,1337.52001953125 +2003-02-06,,,,,,,,,, +2003-03-01,45.22197723388672,0.2158920168876648,15.266251564025879,15.302220344543457,1.3014999628067017,,,15.372973442077637,848.1799926757812,1341.1700439453125 +2003-04-01,48.95253372192383,0.21711383759975433,16.123830795288086,17.342517852783203,1.434499979019165,,,17.22471046447754,916.9199829101562,1464.31005859375 +2003-05-01,50.76301956176758,0.2740640342235565,15.518482208251953,19.224523544311523,1.7944999933242798,,,17.618791580200195,963.5900268554688,1595.9100341796875 +2003-05-07,,,,,,,,,, +2003-06-01,47.655845642089844,0.29101142287254333,16.16796875,18.626508712768555,1.815999984741211,,,15.997578620910645,974.5,1622.800048828125 +2003-07-01,46.933773040771484,0.32185351848602295,16.653514862060547,18.995861053466797,2.0820000171661377,,,16.333431243896484,990.3099975585938,1735.02001953125 +2003-08-01,47.37279510498047,0.34521347284317017,16.722881317138672,18.960683822631836,2.315999984741211,,,19.37755012512207,1008.010009765625,1810.449951171875 +2003-08-06,,,,,,,,,, +2003-09-01,51.1259651184082,0.3163566291332245,17.530014038085938,18.046072006225586,2.4214999675750732,,,19.657007217407227,995.969970703125,1786.93994140625 +2003-10-01,51.79159927368164,0.3494885563850403,16.48325538635254,18.468202590942383,2.7214999198913574,,,21.84463119506836,1050.7099609375,1932.2099609375 +2003-11-01,52.405128479003906,0.3192576766014099,16.303056716918945,21.423110961914062,2.698499917984009,,,20.621614456176758,1058.199951171875,1960.260009765625 +2003-11-06,,,,,,,,,, +2003-12-01,53.74093246459961,0.32628077268600464,17.35569190979004,24.272485733032227,2.63100004196167,,,19.5084171295166,1111.9200439453125,2003.3699951171875 +2004-01-01,57.53900146484375,0.3444499373435974,17.533241271972656,25.74994659423828,2.5199999809265137,,,19.119047164916992,1131.1300048828125,2066.14990234375 +2004-02-01,55.95598602294922,0.36521488428115845,16.82303810119629,24.87051010131836,2.1505000591278076,,,18.600963592529297,1144.93994140625,2029.8199462890625 +2004-02-06,,,,,,,,,, +2004-03-01,53.3401985168457,0.4128514230251312,15.808448791503906,25.6268310546875,2.1640000343322754,,,19.62464141845703,1126.2099609375,1994.219970703125 +2004-04-01,51.208683013916016,0.39361342787742615,16.56939125061035,23.6217041015625,2.180000066757202,,,20.729951858520508,1107.300048828125,1920.1500244140625 +2004-05-01,51.45262145996094,0.4284247159957886,16.632802963256836,23.815183639526367,2.424999952316284,,,22.293439865112305,1120.6800537109375,1986.739990234375 +2004-05-06,,,,,,,,,, +2004-06-01,51.300846099853516,0.4968261420726776,18.110280990600586,25.503700256347656,2.7200000286102295,,,23.227535247802734,1140.8399658203125,2047.7900390625 +2004-07-01,50.672325134277344,0.4937727451324463,18.065902709960938,24.378023147583008,1.9459999799728394,,,21.075881958007812,1101.719970703125,1887.3599853515625 +2004-08-01,49.28722381591797,0.5265995860099792,17.31130027770996,23.6217041015625,1.906999945640564,,,22.91964340209961,1104.239990234375,1838.0999755859375 +2004-08-06,,,,,,,,,, +2004-09-01,50.00397872924805,0.5916416049003601,17.5849609375,24.764976501464844,2.0429999828338623,,64.8648681640625,24.718441009521484,1114.5799560546875,1896.8399658203125 +2004-10-01,52.34260559082031,0.800052285194397,17.788476943969727,25.978591918945312,1.7065000534057617,,95.41541290283203,28.003637313842773,1130.199951171875,1974.989990234375 +2004-11-01,54.96117401123047,1.0237311124801636,17.050731658935547,26.945974349975586,1.9839999675750732,,91.0810775756836,30.26772117614746,1173.8199462890625,2096.81005859375 +2004-11-08,,,,,,,,,, +2004-12-01,57.60344696044922,0.9832708239555359,18.939943313598633,29.918479919433594,2.2144999504089355,,96.49149322509766,31.357276916503906,1211.9200439453125,2175.43994140625 +2005-01-01,54.58831024169922,1.1741228103637695,18.628063201904297,27.930959701538086,2.1610000133514404,,97.90790557861328,28.444419860839844,1181.27001953125,2062.409912109375 +2005-02-01,54.09749221801758,1.3698610067367554,17.83417510986328,27.438472747802734,1.7589999437332153,,94.0890884399414,30.86894416809082,1203.5999755859375,2051.719970703125 +2005-02-08,,,,,,,,,, +2005-03-01,53.49815368652344,1.2724499702453613,17.18527603149414,26.6469669342041,1.7135000228881836,,90.34534454345703,33.57841110229492,1180.5899658203125,1999.22998046875 +2005-04-01,44.7164421081543,1.1011403799057007,17.988739013671875,23.305103302001953,1.6180000305175781,,110.110107421875,29.735000610351562,1156.8499755859375,1921.6500244140625 +2005-05-01,44.23051071166992,1.2141252756118774,18.3442440032959,23.867952346801758,1.7755000591278076,,138.77377319335938,33.119998931884766,1191.5,2068.219970703125 +2005-05-06,,,,,,,,,, +2005-06-01,43.555538177490234,1.124043345451355,17.71768569946289,24.254899978637695,1.6545000076293945,,147.22222900390625,28.610000610351562,1191.3299560546875,2056.9599609375 +2005-07-01,48.991188049316406,1.3023751974105835,18.26690673828125,23.234752655029297,2.257499933242798,,144.02401733398438,29.639999389648438,1234.1800537109375,2184.830078125 +2005-08-01,47.3240966796875,1.431849479675293,19.529403686523438,23.58652687072754,2.134999990463257,,143.1431427001953,27.040000915527344,1220.3299560546875,2152.090087890625 +2005-08-08,,,,,,,,,, +2005-09-01,47.202552795410156,1.6370543241500854,18.40694236755371,24.00865936279297,2.265000104904175,,158.3883819580078,29.850000381469727,1228.81005859375,2151.68994140625 +2005-10-01,48.1793098449707,1.7585887908935547,18.385473251342773,23.867952346801758,1.9930000305175781,,186.25625610351562,32.25,1207.010009765625,2120.300048828125 +2005-11-01,52.309974670410156,2.0709757804870605,19.80194664001465,24.976051330566406,2.4230000972747803,,202.65765380859375,32.61000061035156,1249.47998046875,2232.820068359375 +2005-11-08,,,,,,,,,, +2005-12-01,48.483585357666016,2.1952593326568604,18.762245178222656,25.76755142211914,2.3575000762939453,,207.63763427734375,36.959999084472656,1248.2900390625,2205.320068359375 +2006-01-01,47.95273208618164,2.305800437927246,20.19721794128418,25.169517517089844,2.240999937057495,,216.5465545654297,39.72999954223633,1280.0799560546875,2305.820068359375 +2006-02-01,47.32752227783203,2.0914342403411865,19.27882957458496,26.207246780395508,1.871999979019165,,181.49148559570312,38.54999923706055,1280.6600341796875,2281.389892578125 +2006-02-08,,,,,,,,,, +2006-03-01,48.76497268676758,1.9152405261993408,19.588937759399414,26.734920501708984,1.8265000581741333,,195.1951904296875,34.95000076293945,1294.8699951171875,2339.7900390625 +2006-04-01,48.6880989074707,2.149454355239868,17.385986328125,24.694625854492188,1.7604999542236328,,209.17918395996094,39.20000076293945,1310.6099853515625,2322.570068359375 +2006-05-01,47.24531936645508,1.8251585960388184,16.306108474731445,24.149375915527344,1.7304999828338623,,186.09609985351562,28.6299991607666,1270.0899658203125,2178.8798828125 +2006-05-08,,,,,,,,,, +2006-06-01,45.58832931518555,1.7488168478012085,16.83946990966797,24.465961456298828,1.934000015258789,,209.8748779296875,30.360000610351562,1270.199951171875,2172.090087890625 +2006-07-01,45.938453674316406,2.075251340866089,17.388734817504883,24.78256607055664,1.344499945640564,,193.49349975585938,28.510000228881836,1276.6600341796875,2091.469970703125 +2006-08-01,48.051090240478516,2.0718915462493896,18.574007034301758,26.048952102661133,1.5414999723434448,,189.45445251464844,32.439998626708984,1303.8199462890625,2183.75 +2006-08-08,,,,,,,,,, +2006-09-01,48.820682525634766,2.350689172744751,19.839282989501953,27.368104934692383,1.6059999465942383,,201.15115356445312,37.459999084472656,1335.8499755859375,2258.429931640625 +2006-10-01,55.01115798950195,2.475886821746826,20.82581329345703,29.90088653564453,1.9045000076293945,,238.4334259033203,38.25,1377.93994140625,2366.7099609375 +2006-11-01,54.766883850097656,2.798962354660034,21.29731559753418,29.021446228027344,2.0169999599456787,,242.64764404296875,40.15999984741211,1400.6300048828125,2431.77001953125 +2006-11-08,,,,,,,,,, +2006-12-01,58.07079315185547,2.5907039642333984,21.73406219482422,29.812957763671875,1.9730000495910645,,230.47047424316406,41.119998931884766,1418.300048828125,2415.2900390625 +2007-01-01,59.266300201416016,2.617882013320923,22.461923599243164,30.252676010131836,1.8834999799728394,,251.00100708007812,38.869998931884766,1438.239990234375,2463.929931640625 +2007-02-01,55.55428695678711,2.583681344985962,20.50397491455078,30.37578582763672,1.9570000171661377,,224.949951171875,39.25,1406.8199462890625,2416.14990234375 +2007-02-07,,,,,,,,,, +2007-03-01,56.51309585571289,2.8371317386627197,20.3559513092041,29.707414627075195,1.9895000457763672,,229.30931091308594,41.70000076293945,1420.8599853515625,2421.639892578125 +2007-04-01,61.27952575683594,3.0475287437438965,21.867843627929688,32.539215087890625,3.066499948501587,,235.92591857910156,41.560001373291016,1482.3699951171875,2525.090087890625 +2007-05-01,63.91150665283203,3.700700044631958,22.415639877319336,33.18999099731445,3.4570000171661377,,249.20420837402344,44.060001373291016,1530.6199951171875,2604.52001953125 +2007-05-08,,,,,,,,,, +2007-06-01,63.347747802734375,3.7266552448272705,21.59428596496582,32.5040397644043,3.4205000400543213,,261.6116027832031,40.150001525878906,1503.3499755859375,2603.22998046875 +2007-07-01,66.59786987304688,4.023471355438232,21.242568969726562,30.70998764038086,3.927000045776367,,255.2552490234375,40.290000915527344,1455.27001953125,2546.27001953125 +2007-08-01,70.23324584960938,4.228675365447998,21.052053451538086,30.12954330444336,3.995500087738037,,257.88287353515625,42.75,1473.989990234375,2596.360107421875 +2007-08-08,,,,,,,,,, +2007-09-01,71.1520004272461,4.68641471862793,21.662628173828125,30.49891471862793,4.65749979019165,,283.9189147949219,43.65999984741211,1526.75,2701.5 +2007-10-01,70.13726806640625,5.800380706787109,27.067256927490234,30.674802780151367,4.457499980926514,,353.8538513183594,47.900001525878906,1549.3800048828125,2859.1201171875 +2007-11-01,63.529457092285156,5.564334392547607,24.70686912536621,29.6898193359375,4.5279998779296875,,346.8468322753906,42.13999938964844,1481.1400146484375,2660.9599609375 +2007-11-07,,,,,,,,,, +2007-12-01,65.52474212646484,6.048642158508301,26.26405906677246,28.4762020111084,4.631999969482422,,346.0860900878906,42.72999954223633,1468.3599853515625,2652.280029296875 +2008-01-01,64.92465209960938,4.133399963378906,24.050800323486328,27.21040916442871,3.884999990463257,,282.43243408203125,34.93000030517578,1378.550048828125,2389.860107421875 +2008-02-01,69.0161361694336,3.8176541328430176,20.066930770874023,25.923078536987305,3.2235000133514404,,235.82582092285156,33.650001525878906,1330.6300048828125,2271.47998046875 +2008-02-06,,,,,,,,,, +2008-03-01,70.05885314941406,4.381966590881348,21.01883316040039,26.399211883544922,3.565000057220459,,220.45545959472656,35.59000015258789,1322.699951171875,2279.10009765625 +2008-04-01,73.44194030761719,5.311798572540283,21.122520446777344,24.706567764282227,3.93149995803833,,287.43243408203125,37.290000915527344,1385.5899658203125,2412.800048828125 +2008-05-01,78.75386810302734,5.763737201690674,20.974388122558594,24.016834259033203,4.080999851226807,,293.1932067871094,44.060001373291016,1400.3800048828125,2522.659912109375 +2008-05-07,,,,,,,,,, +2008-06-01,72.41636657714844,5.11300802230835,20.449499130249023,23.981456756591797,3.6665000915527344,,263.4734802246094,39.38999938964844,1280.0,2292.97998046875 +2008-07-01,78.18988800048828,4.853753566741943,19.118900299072266,24.197914123535156,3.816999912261963,,237.1121063232422,41.349998474121094,1267.3800048828125,2325.550048828125 +2008-08-01,74.37141418457031,5.176827907562256,20.285959243774414,24.712379455566406,4.040500164031982,,231.8768768310547,42.83000183105469,1282.8299560546875,2367.52001953125 +2008-08-06,,,,,,,,,, +2008-09-01,71.73551177978516,3.470762014389038,19.91908073425293,20.454687118530273,3.638000011444092,,200.46046447753906,39.470001220703125,1166.3599853515625,2091.8798828125 +2008-10-01,57.02163314819336,3.2854056358337402,16.66515350341797,14.2785062789917,2.861999988555908,,179.85986328125,26.639999389648438,968.75,1720.949951171875 +2008-11-01,50.04803466796875,2.8298041820526123,15.090431213378906,12.444729804992676,2.134999990463257,,146.6266326904297,23.15999984741211,896.239990234375,1535.5699462890625 +2008-11-06,,,,,,,,,, +2008-12-01,51.90673828125,2.6062774658203125,14.606595039367676,14.189484596252441,2.563999891281128,,153.97897338867188,21.290000915527344,903.25,1577.030029296875 +2009-01-01,56.52627944946289,2.7522425651550293,12.848398208618164,11.888165473937988,2.940999984741211,,169.43443298339844,19.309999465942383,825.8800048828125,1476.4200439453125 +2009-02-01,56.76066589355469,2.7272019386291504,12.13459587097168,9.274202346801758,3.239500045776367,,169.16416931152344,16.700000762939453,735.0900268554688,1377.8399658203125 +2009-02-06,,,,,,,,,, +2009-03-01,60.08320999145508,3.209982395172119,13.897279739379883,8.146258354187012,3.671999931335449,,174.20420837402344,21.389999389648438,797.8699951171875,1528.5899658203125 +2009-04-01,64.00231170654297,3.8423893451690674,15.3270902633667,11.028571128845215,4.026000022888184,,198.1831817626953,27.350000381469727,872.8099975585938,1717.300048828125 +2009-05-01,65.90607452392578,4.147140979766846,15.803704261779785,12.274019241333008,3.8994998931884766,,208.82382202148438,28.18000030517578,919.1400146484375,1774.3299560546875 +2009-05-06,,,,,,,,,, +2009-06-01,65.09091186523438,4.349293231964111,18.0966796875,11.69642448425293,4.183000087738037,,211.00601196289062,28.299999237060547,919.3200073242188,1835.0400390625 +2009-07-01,73.51245880126953,4.989336013793945,17.906352996826172,14.879191398620605,4.288000106811523,,221.7467498779297,32.41999816894531,987.47998046875,1978.5 +2009-08-01,73.58724975585938,5.1365203857421875,18.766645431518555,15.714898109436035,4.059500217437744,,231.06607055664062,31.420000076293945,1020.6199951171875,2009.06005859375 +2009-08-06,,,,,,,,,, +2009-09-01,74.90744018554688,5.659914016723633,19.69136619567871,14.061647415161133,4.668000221252441,,248.1731719970703,33.040000915527344,1057.0799560546875,2122.419921875 +2009-10-01,75.53370666503906,5.756102561950684,21.230228424072266,13.72740650177002,5.940499782562256,,268.3283386230469,32.939998626708984,1036.18994140625,2045.1099853515625 +2009-11-01,79.12847900390625,6.1045241355896,22.51645278930664,14.055986404418945,6.795499801635742,,291.7917785644531,35.08000183105469,1095.6300048828125,2144.60009765625 +2009-11-06,,,,,,,,,, +2009-12-01,82.34589385986328,6.434925556182861,23.438796997070312,15.443329811096191,6.72599983215332,,310.30029296875,36.779998779296875,1115.0999755859375,2269.14990234375 +2010-01-01,76.99246215820312,5.86481237411499,21.670120239257812,15.999075889587402,6.270500183105469,,265.2352294921875,32.29999923706055,1073.8699951171875,2147.35009765625 +2010-02-01,79.99315643310547,6.248349666595459,22.04693031311035,17.19167137145996,5.920000076293945,,263.6636657714844,34.650001525878906,1104.489990234375,2238.260009765625 +2010-02-08,,,,,,,,,, +2010-03-01,81.0396728515625,7.176041126251221,22.629026412963867,17.88887596130371,6.78849983215332,,283.8438415527344,35.369998931884766,1169.4300537109375,2397.9599609375 +2010-04-01,81.51359558105469,7.972740650177002,23.594758987426758,20.0878963470459,6.855000019073486,,263.11309814453125,33.599998474121094,1186.68994140625,2461.18994140625 +2010-05-01,79.1503677368164,7.84417724609375,19.932706832885742,17.157638549804688,6.2729997634887695,,243.0580596923828,32.08000183105469,1089.4100341796875,2257.0400390625 +2010-05-06,,,,,,,,,, +2010-06-01,78.42550659179688,7.680809020996094,17.857412338256836,14.817129135131836,5.4629998207092285,,222.69769287109375,26.43000030517578,1030.7099609375,2109.239990234375 +2010-07-01,81.55033874511719,7.855478763580322,20.03040885925293,18.038850784301758,5.894499778747559,,242.66766357421875,28.719999313354492,1101.5999755859375,2254.699951171875 +2010-08-01,78.20323944091797,7.4233856201171875,18.214401245117188,15.64971923828125,6.241499900817871,,225.2352294921875,27.700000762939453,1049.3299560546875,2114.030029296875 +2010-08-06,,,,,,,,,, +2010-09-01,85.6181411743164,8.664690971374512,19.10737419128418,19.168588638305664,7.853000164031982,,263.1581726074219,26.149999618530273,1141.199951171875,2368.6201171875 +2010-10-01,91.65619659423828,9.190831184387207,20.808244705200195,21.757949829101562,8.261500358581543,,307.15716552734375,28.149999618530273,1183.260009765625,2507.409912109375 +2010-11-01,90.290283203125,9.501388549804688,19.70814323425293,21.311634063720703,8.770000457763672,,278.1331481933594,27.799999237060547,1180.550048828125,2498.22998046875 +2010-11-08,,,,,,,,,, +2010-12-01,94.08943176269531,9.84980583190918,21.909496307373047,21.423206329345703,9.0,,297.28228759765625,30.780000686645508,1257.6400146484375,2652.8701171875 +2011-01-01,103.8599853515625,10.361597061157227,21.76820182800293,19.823131561279297,8.482000350952148,,300.48046875,33.04999923706055,1286.1199951171875,2700.080078125 +2011-02-01,103.78302001953125,10.785745620727539,20.865453720092773,20.065792083740234,8.66450023651123,,307.00701904296875,34.5,1327.219970703125,2782.27001953125 +2011-02-08,,,,,,,,,, +2011-03-01,104.95984649658203,10.64222526550293,20.04909324645996,19.879127502441406,9.006500244140625,,293.6736755371094,33.15999984741211,1325.8299560546875,2781.070068359375 +2011-04-01,109.79368591308594,10.691693305969238,20.467607498168945,18.910266876220703,9.790499687194824,,272.32232666015625,33.54999923706055,1363.6099853515625,2873.5400390625 +2011-05-01,108.73165130615234,10.621461868286133,19.7490291595459,19.135168075561523,9.834500312805176,,264.7747802734375,34.630001068115234,1345.199951171875,2835.300048828125 +2011-05-06,,,,,,,,,, +2011-06-01,110.91179656982422,10.25013542175293,20.665348052978516,19.510000228881836,10.224499702453613,,253.4434356689453,31.450000762939453,1320.6400146484375,2773.52001953125 +2011-07-01,117.571044921875,11.923834800720215,21.778095245361328,17.562105178833008,11.12600040435791,,302.14715576171875,27.709999084472656,1292.280029296875,2756.3798828125 +2011-08-01,111.14454650878906,11.75130844116211,21.142244338989258,15.623312950134277,10.761500358581543,,270.7507629394531,25.239999771118164,1218.8900146484375,2579.4599609375 +2011-08-08,,,,,,,,,, +2011-09-01,113.55061340332031,11.644124984741211,19.90796661376953,13.119815826416016,10.81149959564209,,257.77777099609375,24.170000076293945,1131.4200439453125,2415.39990234375 +2011-10-01,119.88819122314453,12.360504150390625,21.299680709838867,15.485393524169922,10.67549991607666,,296.6166076660156,29.40999984741211,1253.300048828125,2684.409912109375 +2011-11-01,122.07645416259766,11.670994758605957,20.459848403930664,15.42860221862793,9.614500045776367,,299.9949951171875,27.420000076293945,1246.9599609375,2620.340087890625 +2011-11-08,,,,,,,,,, +2011-12-01,119.88117218017578,12.367225646972656,20.92014503479004,15.068914413452148,8.654999732971191,,323.2732849121094,28.270000457763672,1257.5999755859375,2605.14990234375 +2012-01-01,125.56623077392578,13.939234733581543,23.79706382751465,14.749091148376465,9.722000122070312,,290.3453369140625,30.950000762939453,1312.4100341796875,2813.840087890625 +2012-02-01,128.25875854492188,16.564136505126953,25.57801628112793,15.662582397460938,8.98449993133545,,309.4344482421875,32.88999938964844,1365.6800537109375,2966.889892578125 +2012-02-08,,,,,,,,,, +2012-03-01,136.5597381591797,18.308074951171875,26.1682071685791,15.377117156982422,10.125499725341797,,320.9409484863281,34.310001373291016,1408.469970703125,3091.570068359375 +2012-04-01,135.53221130371094,17.83262062072754,25.97353172302246,14.882647514343262,11.595000267028809,,302.72772216796875,33.54999923706055,1397.9100341796875,3046.360107421875 +2012-05-01,126.25150299072266,17.64176368713379,23.677928924560547,13.811394691467285,10.645500183105469,,290.7207336425781,31.049999237060547,1310.3299560546875,2827.340087890625 +2012-05-08,,,,,,,,,, +2012-06-01,128.5418243408203,17.83323097229004,24.976381301879883,15.054808616638184,11.417499542236328,,290.3253173828125,32.369998931884766,1362.1600341796875,2935.050048828125 +2012-07-01,128.80467224121094,18.650381088256836,24.06191635131836,13.332178115844727,11.664999961853027,,316.8017883300781,30.8799991607666,1379.3199462890625,2939.52001953125 +2012-08-01,128.06199645996094,20.314008712768555,25.1641845703125,14.178669929504395,12.41349983215332,,342.88787841796875,31.270000457763672,1406.5799560546875,3066.9599609375 +2012-08-08,,,,,,,,,, +2012-09-01,136.92532348632812,20.458269119262695,24.459667205810547,14.120949745178223,12.715999603271484,,377.62762451171875,32.439998626708984,1440.6700439453125,3116.22998046875 +2012-10-01,128.39756774902344,18.256948471069336,23.456958770751953,12.4627103805542,11.644499778747559,,340.490478515625,34.029998779296875,1412.1600341796875,2977.22998046875 +2012-11-01,125.45381927490234,17.94904899597168,21.878910064697266,13.178736686706543,12.602499961853027,,349.5345458984375,34.61000061035156,1416.1800537109375,3010.239990234375 +2012-11-07,,,,,,,,,, +2012-12-01,126.98394775390625,16.39484405517578,22.13326644897461,13.19808578491211,12.543499946594238,,354.0440368652344,37.68000030517578,1426.18994140625,3019.510009765625 +2013-01-01,134.6208953857422,14.03252124786377,22.746475219726562,15.598185539245605,13.274999618530273,,378.2232360839844,37.83000183105469,1498.1099853515625,3142.1298828125 +2013-02-01,133.1359405517578,13.598445892333984,23.036500930786133,15.79291820526123,13.213500022888184,,401.0010070800781,39.310001373291016,1514.6800537109375,3160.18994140625 +2013-02-06,,,,,,,,,, +2013-03-01,141.99786376953125,13.716739654541016,23.903995513916016,16.74711036682129,13.32450008392334,,397.49249267578125,43.52000045776367,1569.18994140625,3267.52001953125 +2013-04-01,134.8347625732422,13.720457077026367,27.655447006225586,16.822824478149414,12.690500259399414,,412.69769287109375,45.08000183105469,1597.5699462890625,3328.7900390625 +2013-05-01,138.48284912109375,13.935823440551758,29.15936851501465,17.234567642211914,13.460000038146973,,436.0460510253906,42.90999984741211,1630.739990234375,3455.909912109375 +2013-05-08,,,,,,,,,, +2013-06-01,127.82191467285156,12.368638038635254,29.060949325561523,17.783567428588867,13.884499549865723,,440.6256408691406,45.560001373291016,1606.280029296875,3403.25 +2013-07-01,130.45040893554688,14.115401268005371,26.789243698120117,19.142587661743164,15.060999870300293,,444.3193054199219,47.279998779296875,1685.72998046875,3626.3701171875 +2013-08-01,121.90938568115234,15.19745922088623,28.101774215698242,19.695158004760742,14.048999786376953,,423.8738708496094,45.75,1632.969970703125,3589.8701171875 +2013-08-07,,,,,,,,,, +2013-09-01,124.47478485107422,14.96906566619873,28.19812774658203,20.306934356689453,15.631999969482422,,438.3934020996094,51.939998626708984,1681.550048828125,3771.47998046875 +2013-10-01,120.46192169189453,16.41180992126465,30.002870559692383,19.72601890563965,18.201499938964844,,515.8057861328125,54.220001220703125,1756.5400390625,3919.7099609375 +2013-11-01,120.77778625488281,17.45956802368164,32.30753707885742,22.58371353149414,19.680999755859375,,530.3253173828125,56.779998779296875,1805.81005859375,4059.889892578125 +2013-11-06,,,,,,,,,, +2013-12-01,126.7584228515625,17.71782684326172,31.937856674194336,24.151473999023438,19.93950080871582,,560.9158935546875,59.880001068115234,1848.3599853515625,4176.58984375 +2014-01-01,119.39906311035156,15.80967903137207,32.304969787597656,21.634523391723633,17.934499740600586,,591.0760498046875,59.189998626708984,1782.5899658203125,4103.8798828125 +2014-02-01,125.13655090332031,16.61942481994629,32.70622634887695,21.913677215576172,18.104999542236328,,608.4334106445312,68.62999725341797,1859.449951171875,4308.1201171875 +2014-02-06,,,,,,,,,, +2014-03-01,130.79644775390625,17.052499771118164,35.256614685058594,22.53180694580078,16.818500518798828,,557.8128051757812,65.73999786376953,1872.3399658203125,4198.990234375 +2014-04-01,133.50091552734375,18.747455596923828,34.7491340637207,24.246538162231445,15.206500053405762,,534.8800048828125,61.689998626708984,1883.949951171875,4114.56005859375 +2014-05-01,125.27215576171875,20.11072540283203,35.21359634399414,24.767969131469727,15.6274995803833,,571.6500244140625,64.54000091552734,1923.5699462890625,4242.6201171875 +2014-05-07,,,,,,,,,, +2014-06-01,123.88963317871094,20.782461166381836,36.12032699584961,24.94846534729004,16.23900032043457,,584.6699829101562,72.36000061035156,1960.22998046875,4408.18017578125 +2014-07-01,130.99761962890625,21.37957000732422,37.38497543334961,26.72607421875,15.649499893188477,,579.5499877929688,69.25,1930.6700439453125,4369.77001953125 +2014-08-01,131.4281463623047,22.922651290893555,39.35124206542969,27.834640502929688,16.95199966430664,,582.3599853515625,71.9000015258789,2003.3699951171875,4580.27001953125 +2014-08-06,,,,,,,,,, +2014-09-01,130.50729370117188,22.643360137939453,40.40761947631836,26.66560935974121,16.121999740600586,,588.4099731445312,69.19000244140625,1972.2900390625,4493.39013671875 +2014-10-01,113.0242691040039,24.27278709411621,40.92185974121094,26.89417266845703,15.27299976348877,,567.8699951171875,70.12000274658203,2018.050048828125,4630.740234375 +2014-11-01,111.49117279052734,26.729284286499023,41.67144012451172,28.27128028869629,16.93199920654297,,549.0800170898438,73.68000030517578,2067.56005859375,4791.6298828125 +2014-11-06,,,,,,,,,, +2014-12-01,111.05672454833984,24.915250778198242,40.74142074584961,28.068769454956055,15.517499923706055,,530.6599731445312,72.69999694824219,2058.89990234375,4736.0498046875 +2015-01-01,106.12132263183594,26.445661544799805,35.434940338134766,26.79075813293457,17.726499557495117,,537.5499877929688,70.12999725341797,1994.989990234375,4635.240234375 +2015-02-01,112.09505462646484,28.996322631835938,38.460933685302734,27.767194747924805,19.007999420166016,,562.6300048828125,79.0999984741211,2104.5,4963.52978515625 +2015-02-06,,,,,,,,,, +2015-03-01,111.87759399414062,28.197509765625,35.91679000854492,26.139816284179688,18.604999542236328,,554.7000122070312,73.94000244140625,2067.889892578125,4900.8798828125 +2015-04-01,119.3988265991211,28.360668182373047,42.96587371826172,23.521541595458984,21.089000701904297,,548.77001953125,76.05999755859375,2085.510009765625,4941.419921875 +2015-05-01,118.25563049316406,29.523195266723633,41.39352035522461,23.3579158782959,21.46150016784668,,545.3200073242188,79.08999633789062,2107.389892578125,5070.02978515625 +2015-05-06,,,,,,,,,, +2015-06-01,114.24130249023438,28.542850494384766,39.253108978271484,21.762542724609375,21.704500198364258,,540.0399780273438,81.01000213623047,2063.110107421875,4986.8701171875 +2015-07-01,113.77072143554688,27.60302734375,41.520286560058594,22.683992385864258,26.8075008392334,,657.5,81.98999786376953,2103.840087890625,5128.27978515625 +2015-08-01,103.86784362792969,25.65966796875,38.692989349365234,20.93431854248047,25.644500732421875,,647.8200073242188,78.56999969482422,1972.1800537109375,4776.509765625 +2015-08-06,,,,,,,,,, +2015-09-01,102.66226959228516,25.21347999572754,39.61040496826172,20.028608322143555,25.594499588012695,,638.3699951171875,82.22000122070312,1920.030029296875,4620.16015625 +2015-10-01,99.1993637084961,27.31651496887207,47.11008071899414,19.46390151977539,31.295000076293945,,737.3900146484375,88.66000366210938,2079.360107421875,5053.75 +2015-11-01,98.7319564819336,27.042200088500977,48.64043045043945,21.868392944335938,33.2400016784668,,762.8499755859375,91.45999908447266,2080.409912109375,5108.669921875 +2015-11-06,,,,,,,,,, +2015-12-01,98.37144470214844,24.16438102722168,49.98638916015625,22.03421974182129,33.794498443603516,,778.010009765625,93.94000244140625,2043.93994140625,5007.41015625 +2016-01-01,89.20050811767578,22.3461971282959,49.63501739501953,20.343839645385742,29.350000381469727,,761.3499755859375,89.12999725341797,1940.239990234375,4613.9501953125 +2016-02-01,93.6608657836914,22.196985244750977,45.841888427734375,20.051719665527344,27.625999450683594,,717.219970703125,85.1500015258789,1932.22998046875,4557.9501953125 +2016-02-08,,,,,,,,,, +2016-03-01,109.36297607421875,25.15644073486328,50.11842727661133,23.285871505737305,29.68199920654297,,762.9000244140625,93.80000305175781,2059.739990234375,4869.85009765625 +2016-04-01,105.3841552734375,21.636524200439453,45.25450134277344,20.178363800048828,32.97949981689453,,707.8800048828125,94.22000122070312,2065.300048828125,4775.35986328125 +2016-05-01,111.01663208007812,23.049110412597656,48.094825744628906,20.956079483032227,36.13949966430664,,748.8499755859375,99.47000122070312,2096.949951171875,4948.0498046875 +2016-05-06,,,,,,,,,, +2016-06-01,110.65898895263672,22.20018196105957,46.75897216796875,19.947153091430664,35.78099822998047,,703.530029296875,95.79000091552734,2098.860107421875,4842.669921875 +2016-07-01,117.10401916503906,24.199600219726562,51.79398727416992,21.839828491210938,37.94049835205078,,791.3400268554688,97.86000061035156,2173.60009765625,5162.1298828125 +2016-08-01,115.83541107177734,24.638490676879883,52.506752014160156,20.885658264160156,38.45800018310547,,789.8499755859375,102.30999755859375,2170.949951171875,5213.22021484375 +2016-08-08,,,,,,,,,, +2016-09-01,116.81381225585938,26.394628524780273,52.96271896362305,21.479366302490234,41.865501403808594,13.321450233459473,804.0599975585938,108.54000091552734,2168.27001953125,5312.0 +2016-10-01,113.019287109375,26.509033203125,55.095947265625,20.878395080566406,39.49100112915039,13.680961608886719,809.9000244140625,107.51000213623047,2126.14990234375,5189.14013671875 +2016-11-01,119.29200744628906,25.803936004638672,55.40858840942383,19.980859756469727,37.528499603271484,14.926712036132812,775.8800048828125,102.80999755859375,2198.81005859375,5323.68017578125 +2016-11-08,,,,,,,,,, +2016-12-01,123.1717300415039,27.180198669433594,57.52321243286133,18.655929565429688,37.493499755859375,15.31966781616211,792.4500122070312,102.94999694824219,2238.830078125,5383.1201171875 +2017-01-01,129.5013427734375,28.47795867919922,59.84672927856445,22.670724868774414,41.17399978637695,17.554771423339844,820.1900024414062,113.37999725341797,2278.8701171875,5614.7900390625 +2017-02-01,133.43417358398438,32.148292541503906,59.22650909423828,24.339130401611328,42.25199890136719,17.69411849975586,844.9299926757812,118.33999633789062,2363.639892578125,5825.43994140625 +2017-02-08,,,,,,,,,, +2017-03-01,130.24111938476562,33.85976028442383,61.33644485473633,24.011991500854492,44.32699966430664,17.858545303344727,847.7999877929688,130.1300048828125,2362.719970703125,5911.740234375 +2017-04-01,119.88253784179688,33.85739517211914,63.75787353515625,23.72492027282715,46.2495002746582,18.70298194885254,924.52001953125,133.74000549316406,2384.199951171875,6047.60986328125 +2017-05-01,114.1535415649414,36.00456237792969,65.0430908203125,23.328956604003906,49.73099899291992,19.338397979736328,987.0900268554688,141.86000061035156,2411.800048828125,6198.52001953125 +2017-05-08,,,,,,,,,, +2017-06-01,116.17494201660156,34.084716796875,64.56354522705078,23.700172424316406,48.400001525878906,17.030832290649414,929.6799926757812,141.44000244140625,2423.409912109375,6140.419921875 +2017-07-01,109.25714874267578,35.19940948486328,68.0947265625,25.520694732666016,49.388999938964844,17.911497116088867,945.5,146.49000549316406,2470.300048828125,6348.1201171875 +2017-08-01,108.01860809326172,38.81330490112305,70.03360748291016,26.852062225341797,49.029998779296875,20.882347106933594,955.239990234375,155.16000366210938,2471.64990234375,6428.66015625 +2017-08-08,,,,,,,,,, +2017-09-01,110.72441864013672,36.6182861328125,70.14307403564453,27.700807571411133,48.067501068115234,21.517763137817383,973.719970703125,149.17999267578125,2519.360107421875,6495.9599609375 +2017-10-01,117.57792663574219,40.163211822509766,78.32596588134766,25.408233642578125,55.263999938964844,23.067289352416992,1033.0400390625,175.16000366210938,2575.260009765625,6727.669921875 +2017-11-01,117.50923919677734,40.83085632324219,79.2582015991211,24.86334991455078,58.837501525878906,21.80481719970703,1036.1700439453125,181.47000122070312,2647.580078125,6873.97021484375 +2017-11-09,,,,,,,,,, +2017-12-01,118.25981140136719,40.3528938293457,80.95279693603516,24.43583106994629,58.4734992980957,22.65203857421875,1053.4000244140625,175.24000549316406,2673.610107421875,6903.39013671875 +2018-01-01,126.18390655517578,39.9236946105957,89.91493225097656,28.855159759521484,72.54450225830078,19.982173919677734,1182.219970703125,199.75999450683594,2823.81005859375,7411.47998046875 +2018-02-01,120.11751556396484,42.472713470458984,88.74141693115234,25.634004592895508,75.62249755859375,20.7039852142334,1103.9200439453125,209.1300048828125,2713.830078125,7273.009765625 +2018-02-08,,,,,,,,,, +2018-03-01,119.43197631835938,40.170265197753906,86.7812271118164,24.33201026916504,72.36699676513672,20.402997970581055,1037.1400146484375,216.0800018310547,2640.8701171875,7063.4501953125 +2018-04-01,112.83880615234375,39.566917419433594,88.92057037353516,26.82056999206543,78.30650329589844,20.00168228149414,1018.5800170898438,221.60000610351562,2648.050048828125,7066.27001953125 +2018-05-01,109.99760437011719,44.7408332824707,93.97891998291016,23.179109573364258,81.48100280761719,22.479249954223633,1100.0,249.27999877929688,2705.27001953125,7442.1201171875 +2018-05-09,,,,,,,,,, +2018-06-01,109.95153045654297,44.4903450012207,94.16664123535156,20.467208862304688,84.98999786376953,23.571720123291016,1129.18994140625,243.80999755859375,2718.3701171875,7510.2998046875 +2018-07-01,114.06781768798828,45.73533630371094,101.30004119873047,22.375450134277344,88.87200164794922,25.784528732299805,1227.219970703125,244.67999267578125,2816.2900390625,7671.7900390625 +2018-08-01,115.2877197265625,54.709842681884766,107.2684097290039,24.00385284423828,100.635498046875,26.8017520904541,1231.800048828125,263.510009765625,2901.52001953125,8109.5400390625 +2018-08-09,,,,,,,,,, +2018-09-01,120.2962646484375,54.445865631103516,109.63678741455078,23.24565315246582,100.1500015258789,27.066509246826172,1207.0799560546875,269.95001220703125,2913.97998046875,8046.35009765625 +2018-10-01,91.83122253417969,52.78649139404297,102.38966369628906,24.236047744750977,79.90049743652344,25.190916061401367,1090.5799560546875,245.75999450683594,2711.739990234375,7305.89990234375 +2018-11-01,98.86396026611328,43.07142639160156,106.3008041381836,23.4099178314209,84.50849914550781,29.396371841430664,1109.6500244140625,250.88999938964844,2760.169921875,7330.5400390625 +2018-11-08,,,,,,,,,, +2018-12-01,91.58279418945312,38.17780685424805,97.78714752197266,17.18350601196289,75.09850311279297,24.59708595275879,1044.9599609375,226.24000549316406,2506.85009765625,6635.27978515625 +2019-01-01,108.30086517333984,40.28346252441406,100.5406265258789,24.84735679626465,85.9365005493164,24.456159591674805,1125.8900146484375,247.82000732421875,2704.10009765625,7281.740234375 +2019-02-01,111.28997039794922,41.90748596191406,107.85758972167969,27.216707229614258,81.99150085449219,28.095136642456055,1126.550048828125,262.5,2784.489990234375,7532.52978515625 +2019-02-07,,,,,,,,,, +2019-03-01,115.00740814208984,46.17075729370117,114.03240966796875,28.167972564697266,89.0374984741211,29.539655685424805,1176.8900146484375,266.489990234375,2834.39990234375,7729.31982421875 +2019-04-01,114.33090209960938,48.77644348144531,126.27296447753906,29.61660385131836,96.32599639892578,33.9285774230957,1198.9599609375,289.25,2945.830078125,8095.39013671875 +2019-05-01,103.50668334960938,42.55390930175781,119.58222198486328,27.175188064575195,88.75350189208984,29.972509384155273,1106.5,270.8999938964844,2752.06005859375,7453.14990234375 +2019-05-09,,,,,,,,,, +2019-06-01,113.73433685302734,48.293270111083984,130.00108337402344,31.43656349182129,94.68150329589844,25.56848907470703,1082.800048828125,294.6499938964844,2941.760009765625,8006.240234375 +2019-07-01,122.26231384277344,51.98261260986328,132.24281311035156,28.701255798339844,93.33899688720703,29.061506271362305,1218.199951171875,298.8599853515625,2980.3798828125,8175.419921875 +2019-08-01,111.7796401977539,50.93339920043945,133.78579711914062,25.92054557800293,88.81449890136719,25.9359073638916,1190.530029296875,284.510009765625,2926.4599609375,7962.8798828125 +2019-08-08,,,,,,,,,, +2019-09-01,121.34967803955078,54.85721969604492,135.37051391601562,26.743131637573242,86.79550170898438,26.10200309753418,1221.1400146484375,276.25,2976.739990234375,7999.33984375 +2019-10-01,111.59463500976562,60.929054260253906,139.59625244140625,30.58913803100586,88.83300018310547,26.620418548583984,1258.800048828125,277.92999267578125,3037.56005859375,8292.3603515625 +2019-11-01,112.19547271728516,65.45783996582031,147.39544677734375,35.09682083129883,90.04000091552734,24.40582847595215,1304.0899658203125,309.5299987792969,3140.97998046875,8665.4697265625 +2019-11-07,,,,,,,,,, +2019-12-01,113.17443084716797,72.13994598388672,154.07154846191406,33.23965072631836,92.39199829101562,25.86544418334961,1339.3900146484375,329.80999755859375,3230.780029296875,8972.599609375 +2020-01-01,121.35601806640625,76.03622436523438,166.31326293945312,32.28398132324219,100.43599700927734,24.546754837036133,1432.780029296875,351.1400146484375,3225.52001953125,9150.9404296875 +2020-02-01,109.88997650146484,67.1553726196289,158.28240966796875,29.225305557250977,94.1875,20.364192962646484,1339.25,345.1199951171875,2954.219970703125,8567.3701171875 +2020-02-07,,,,,,,,,, +2020-03-01,94.6399154663086,62.61878204345703,154.502197265625,17.190288543701172,97.48600006103516,19.90617561340332,1161.949951171875,318.239990234375,2584.590087890625,7700.10009765625 +2020-04-01,107.12150573730469,72.34808349609375,175.5648956298828,16.6003360748291,123.69999694824219,21.486589431762695,1346.699951171875,353.6400146484375,2912.429931640625,8889.5498046875 +2020-05-01,106.55841827392578,78.29256439208984,179.52272033691406,14.412978172302246,122.11849975585938,24.98464012145996,1433.52001953125,386.6000061035156,3044.31005859375,9489.8701171875 +2020-05-07,,,,,,,,,, +2020-06-01,104.41674041748047,90.0749740600586,199.92588806152344,13.877481460571289,137.9409942626953,27.652219772338867,1418.050048828125,435.30999755859375,3100.2900390625,10058.76953125 +2020-07-01,106.29290008544922,104.9491958618164,201.3994598388672,15.366937637329102,158.23399353027344,30.11343765258789,1487.949951171875,444.32000732421875,3271.1201171875,10745.26953125 +2020-08-01,106.61280059814453,127.44819641113281,221.55807495117188,17.406635284423828,172.54800415039062,33.25917053222656,1629.530029296875,513.3900146484375,3500.31005859375,11775.4599609375 +2020-08-07,,,,,,,,,, +2020-09-01,106.57222747802734,114.5876235961914,207.12527465820312,17.323570251464844,157.43649291992188,34.06950759887695,1465.5999755859375,490.42999267578125,3363.0,11167.509765625 +2020-10-01,97.80435180664062,107.71099090576172,199.38504028320312,16.259458541870117,151.8074951171875,30.329862594604492,1616.1099853515625,447.1000061035156,3269.9599609375,10911.58984375 +2020-11-01,108.19266510009766,117.79344177246094,210.8082733154297,20.478687286376953,158.40199279785156,34.74394989013672,1754.4000244140625,478.4700012207031,3621.6298828125,12198.740234375 +2020-11-09,,,,,,,,,, +2020-12-01,111.85865020751953,131.51597595214844,219.60447692871094,21.694869995117188,162.84649658203125,36.88808059692383,1752.6400146484375,500.1199951171875,3756.070068359375,12888.2802734375 +2021-01-01,105.84272766113281,130.7924346923828,229.0237274169922,19.891191482543945,160.30999755859375,36.6867561340332,1827.3599853515625,458.7699890136719,3714.239990234375,13070.6904296875 +2021-02-01,105.68277740478516,120.18710327148438,229.4384002685547,24.100215911865234,154.64649963378906,40.80388259887695,2021.9100341796875,459.6700134277344,3811.14990234375,13192.349609375 +2021-02-09,,,,,,,,,, +2021-03-01,119.9990005493164,121.25013732910156,233.32164001464844,22.955739974975586,154.70399475097656,44.367366790771484,2062.52001953125,475.3699951171875,3972.889892578125,13246.8701171875 +2021-04-01,127.76121520996094,130.49156188964844,249.56121826171875,23.070621490478516,173.37100219726562,49.49113082885742,2353.5,508.3399963378906,4181.169921875,13962.6796875 +2021-05-01,129.4361114501953,123.6920166015625,247.08718872070312,22.41118812561035,161.15350341796875,49.64715576171875,2356.85009765625,504.5799865722656,4204.10986328125,13748.740234375 +2021-05-07,,,,,,,,,, +2021-06-01,133.47738647460938,136.1819610595703,268.70587158203125,22.44941520690918,172.00799560546875,50.16557312011719,2441.7900390625,585.6400146484375,4297.5,14503.9501953125 +2021-07-01,128.35101318359375,145.03138732910156,282.6023864746094,23.305561065673828,166.37950134277344,48.63045883178711,2694.530029296875,621.6300048828125,4395.259765625,14672.6796875 +2021-08-01,127.78646087646484,150.96749877929688,299.4349365234375,21.74091148376465,173.5395050048828,49.053245544433594,2893.949951171875,663.7000122070312,4522.68017578125,15259.240234375 +2021-08-09,,,,,,,,,, +2021-09-01,127.95899963378906,140.906982421875,280.1719665527344,19.48086166381836,164.2519989013672,52.36507034301758,2673.52001953125,575.719970703125,4307.5400390625,14448.580078125 +2021-10-01,115.22111511230469,149.1721954345703,329.56378173828125,17.397972106933594,168.6215057373047,55.35980224609375,2960.919921875,650.3599853515625,4605.3798828125,15498.3896484375 +2021-11-01,112.8140869140625,164.60723876953125,328.5401611328125,18.003969192504883,175.35350036621094,56.077186584472656,2837.949951171875,669.8499755859375,4567.0,15537.6904296875 +2021-11-04,,,,,,,,,, +2021-11-09,,,,,,,,,, +2021-12-01,130.48629760742188,177.08387756347656,334.8461608886719,22.1286563873291,166.7169952392578,55.77927017211914,2897.0400390625,567.0599975585938,4766.18017578125,15644.9697265625 +2022-01-01,130.3984375,174.30149841308594,309.6171875,20.856517791748047,149.57350158691406,56.41482162475586,2706.070068359375,534.2999877929688,4515.5498046875,14239.8798828125 +2022-02-01,119.60104370117188,164.66795349121094,297.4805908203125,19.47332763671875,153.56300354003906,50.60551452636719,2701.139892578125,467.67999267578125,4373.93994140625,13751.400390625 +2022-02-10,,,,,,,,,, +2022-03-01,128.46168518066406,174.3538360595703,307.59356689453125,19.927804946899414,162.99749755859375,49.84086990356445,2781.35009765625,455.6199951171875,4530.41015625,14220.51953125 +2022-04-01,130.6254425048828,157.418701171875,276.8751220703125,17.399999618530273,124.28150177001953,46.68299102783203,2282.18994140625,395.95001220703125,4131.93017578125,12334.6396484375 +2022-05-01,137.1759796142578,148.6216278076172,271.2382507324219,18.81999969482422,120.20950317382812,49.939998626708984,2275.239990234375,416.4800109863281,4132.14990234375,12081.3896484375 +2022-05-09,,,,,,,,,, +2022-06-01,141.86000061035156,137.44000244140625,256.4800109863281,15.819999694824219,107.4000015258789,48.939998626708984,2240.14990234375,365.6300048828125,3821.550048828125,11181.5400390625 +2022-06-28,141.86000061035156,137.44000244140625,256.4800109863281,15.819999694824219,107.4000015258789,48.939998626708984,2240.14990234375,365.6300048828125,3821.550048828125,11181.5400390625 diff --git a/lib/matplotlib/mpl-data/sample_data/aapl.npz b/lib/matplotlib/mpl-data/sample_data/aapl.npz deleted file mode 100644 index 653d6a3c8c27..000000000000 Binary files a/lib/matplotlib/mpl-data/sample_data/aapl.npz and /dev/null differ diff --git a/lib/matplotlib/mpl-data/sample_data/ada.png b/lib/matplotlib/mpl-data/sample_data/ada.png deleted file mode 100644 index ce826b026a61..000000000000 Binary files a/lib/matplotlib/mpl-data/sample_data/ada.png and /dev/null differ diff --git a/lib/matplotlib/mpl-data/sample_data/ct.raw.gz b/lib/matplotlib/mpl-data/sample_data/ct.raw.gz deleted file mode 100644 index c03530b13faa..000000000000 Binary files a/lib/matplotlib/mpl-data/sample_data/ct.raw.gz and /dev/null differ diff --git a/lib/matplotlib/mpl-data/sample_data/demodata.csv b/lib/matplotlib/mpl-data/sample_data/demodata.csv deleted file mode 100644 index c167c4c73479..000000000000 --- a/lib/matplotlib/mpl-data/sample_data/demodata.csv +++ /dev/null @@ -1,11 +0,0 @@ -clientid,date,weekdays,gains,prices,up -0,2008-04-30,Wed,-0.52458192906686452,7791404.0091921333,False -1,2008-05-01,Thu,0.076191536201738269,3167180.7366340165,True -2,2008-05-02,Fri,-0.86850970062880861,9589766.9613829032,False -3,2008-05-03,Sat,-0.42701083852713395,8949415.1867596991,False -4,2008-05-04,Sun,0.2532553652693274,937163.44375252665,True -5,2008-05-05,Mon,-0.68151636911081892,949579.88022264629,False -6,2008-05-06,Tue,0.0071911579626532168,7268426.906552773,True -7,2008-05-07,Wed,0.67449747200412147,7517014.782897247,True -8,2008-05-08,Thu,-1.1841008656818983,1920959.5423492221,False -9,2008-05-09,Fri,-1.5803692595811152,8456240.6198725495,False diff --git a/lib/matplotlib/mpl-data/sample_data/grace_hopper.png b/lib/matplotlib/mpl-data/sample_data/grace_hopper.png deleted file mode 100644 index 4a63320ab28d..000000000000 Binary files a/lib/matplotlib/mpl-data/sample_data/grace_hopper.png and /dev/null differ diff --git a/lib/matplotlib/mpl-data/sample_data/logo2.png b/lib/matplotlib/mpl-data/sample_data/logo2.png index a1adda483eed..72843ab1febb 100644 Binary files a/lib/matplotlib/mpl-data/sample_data/logo2.png and b/lib/matplotlib/mpl-data/sample_data/logo2.png differ diff --git a/lib/matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv b/lib/matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv deleted file mode 100644 index 1e488d0233d1..000000000000 --- a/lib/matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv +++ /dev/null @@ -1,43 +0,0 @@ -Year,Agriculture,Architecture,Art and Performance,Biology,Business,Communications and Journalism,Computer Science,Education,Engineering,English,Foreign Languages,Health Professions,Math and Statistics,Physical Sciences,Psychology,Public Administration,Social Sciences and History -1970,4.22979798,11.92100539,59.7,29.08836297,9.064438975,35.3,13.6,74.53532758,0.8,65.57092343,73.8,77.1,38,13.8,44.4,68.4,36.8 -1971,5.452796685,12.00310559,59.9,29.39440285,9.503186594,35.5,13.6,74.14920369,1,64.55648516,73.9,75.5,39,14.9,46.2,65.5,36.2 -1972,7.42071022,13.21459351,60.4,29.81022105,10.5589621,36.6,14.9,73.55451996,1.2,63.6642632,74.6,76.9,40.2,14.8,47.6,62.6,36.1 -1973,9.653602412,14.7916134,60.2,31.14791477,12.80460152,38.4,16.4,73.50181443,1.6,62.94150212,74.9,77.4,40.9,16.5,50.4,64.3,36.4 -1974,14.07462346,17.44468758,61.9,32.99618284,16.20485038,40.5,18.9,73.33681143,2.2,62.41341209,75.3,77.9,41.8,18.2,52.6,66.1,37.3 -1975,18.33316153,19.13404767,60.9,34.44990213,19.68624931,41.5,19.8,72.80185448,3.2,61.64720641,75,78.9,40.7,19.1,54.5,63,37.7 -1976,22.25276005,21.39449143,61.3,36.07287146,23.4300375,44.3,23.9,72.16652471,4.5,62.14819377,74.4,79.2,41.5,20,56.9,65.6,39.2 -1977,24.6401766,23.74054054,62,38.33138629,27.16342715,46.9,25.7,72.45639481,6.8,62.72306675,74.3,80.5,41.1,21.3,59,69.3,40.5 -1978,27.14619175,25.84923973,62.5,40.11249564,30.52751868,49.9,28.1,73.19282134,8.4,63.61912216,74.3,81.9,41.6,22.5,61.3,71.5,41.8 -1979,29.63336549,27.77047744,63.2,42.06555109,33.62163381,52.3,30.2,73.82114234,9.4,65.08838972,74.2,82.3,42.3,23.7,63.3,73.3,43.6 -1980,30.75938956,28.08038075,63.4,43.99925716,36.76572529,54.7,32.5,74.98103152,10.3,65.28413007,74.1,83.5,42.8,24.6,65.1,74.6,44.2 -1981,31.31865519,29.84169408,63.3,45.24951206,39.26622984,56.4,34.8,75.84512345,11.6,65.83832154,73.9,84.1,43.2,25.7,66.9,74.7,44.6 -1982,32.63666364,34.81624758,63.1,45.96733794,41.94937335,58,36.3,75.84364914,12.4,65.84735212,72.7,84.4,44,27.3,67.5,76.8,44.6 -1983,31.6353471,35.82625735,62.4,46.71313451,43.54206966,58.6,37.1,75.95060123,13.1,65.91837999,71.8,84.6,44.3,27.6,67.9,76.1,44.1 -1984,31.09294748,35.45308311,62.1,47.66908276,45.12403027,59.1,36.8,75.86911601,13.5,65.74986233,72.1,85.1,46.2,28,68.2,75.9,44.1 -1985,31.3796588,36.13334795,61.8,47.9098841,45.747782,59,35.7,75.92343971,13.5,65.79819852,70.8,85.3,46.5,27.5,69,75,43.8 -1986,31.19871923,37.24022346,62.1,48.30067763,46.53291505,60,34.7,76.14301516,13.9,65.98256091,71.2,85.7,46.7,28.4,69,75.7,44 -1987,31.48642948,38.73067535,61.7,50.20987789,46.69046648,60.2,32.4,76.96309168,14,66.70603055,72,85.5,46.5,30.4,70.1,76.4,43.9 -1988,31.08508746,39.3989071,61.7,50.09981147,46.7648277,60.4,30.8,77.62766177,13.9,67.14449816,72.3,85.2,46.2,29.7,70.9,75.6,44.4 -1989,31.6124031,39.09653994,62,50.77471585,46.7815648,60.5,29.9,78.11191872,14.1,67.01707156,72.4,84.6,46.2,31.3,71.6,76,44.2 -1990,32.70344407,40.82404662,62.6,50.81809432,47.20085084,60.8,29.4,78.86685859,14.1,66.92190193,71.2,83.9,47.3,31.6,72.6,77.6,45.1 -1991,34.71183749,33.67988118,62.1,51.46880537,47.22432481,60.8,28.7,78.99124597,14,66.24147465,71.1,83.5,47,32.6,73.2,78.2,45.5 -1992,33.93165961,35.20235628,61,51.34974154,47.21939541,59.7,28.2,78.43518191,14.5,65.62245655,71,83,47.4,32.6,73.2,77.3,45.8 -1993,34.94683208,35.77715877,60.2,51.12484404,47.63933161,58.7,28.5,77.26731199,14.9,65.73095014,70,82.4,46.4,33.6,73.1,78,46.1 -1994,36.03267447,34.43353129,59.4,52.2462176,47.98392441,58.1,28.5,75.81493264,15.7,65.64197772,69.1,81.8,47,34.8,72.9,78.8,46.8 -1995,36.84480747,36.06321839,59.2,52.59940342,48.57318101,58.8,27.5,75.12525621,16.2,65.93694921,69.6,81.5,46.1,35.9,73,78.8,47.9 -1996,38.96977475,35.9264854,58.6,53.78988011,48.6473926,58.7,27.1,75.03519921,16.7,66.43777883,69.7,81.3,46.4,37.3,73.9,79.8,48.7 -1997,40.68568483,35.10193413,58.7,54.99946903,48.56105033,60,26.8,75.1637013,17,66.78635548,70,81.9,47,38.3,74.4,81,49.2 -1998,41.91240333,37.59854457,59.1,56.35124789,49.2585152,60,27,75.48616027,17.8,67.2554484,70.1,82.1,48.3,39.7,75.1,81.3,50.5 -1999,42.88720191,38.63152919,59.2,58.22882288,49.81020815,61.2,28.1,75.83816206,18.6,67.82022113,70.9,83.5,47.8,40.2,76.5,81.1,51.2 -2000,45.05776637,40.02358491,59.2,59.38985737,49.80361649,61.9,27.7,76.69214284,18.4,68.36599498,70.9,83.5,48.2,41,77.5,81.1,51.8 -2001,45.86601517,40.69028156,59.4,60.71233149,50.27514494,63,27.6,77.37522931,19,68.57852029,71.2,85.1,47,42.2,77.5,80.9,51.7 -2002,47.13465821,41.13295053,60.9,61.8951284,50.5523346,63.7,27,78.64424394,18.7,68.82995959,70.5,85.8,45.7,41.1,77.7,81.3,51.5 -2003,47.93518721,42.75854266,61.1,62.1694558,50.34559774,64.6,25.1,78.54494815,18.8,68.89448726,70.6,86.5,46,41.7,77.8,81.5,50.9 -2004,47.88714025,43.46649345,61.3,61.91458697,49.95089449,64.2,22.2,78.65074774,18.2,68.45473436,70.8,86.5,44.7,42.1,77.8,80.7,50.5 -2005,47.67275409,43.10036784,61.4,61.50098432,49.79185139,63.4,20.6,79.06712173,17.9,68.57122114,69.9,86,45.1,41.6,77.5,81.2,50 -2006,46.79029957,44.49933107,61.6,60.17284465,49.21091439,63,18.6,78.68630551,16.8,68.29759443,69.6,85.9,44.1,40.8,77.4,81.2,49.8 -2007,47.60502633,43.10045895,61.4,59.41199314,49.00045935,62.5,17.6,78.72141311,16.8,67.87492278,70.2,85.4,44.1,40.7,77.1,82.1,49.3 -2008,47.570834,42.71173041,60.7,59.30576517,48.88802678,62.4,17.8,79.19632674,16.5,67.59402834,70.2,85.2,43.3,40.7,77.2,81.7,49.4 -2009,48.66722357,43.34892051,61,58.48958333,48.84047414,62.8,18.1,79.5329087,16.8,67.96979204,69.3,85.1,43.3,40.7,77.1,82,49.4 -2010,48.73004227,42.06672091,61.3,59.01025521,48.75798769,62.5,17.6,79.61862451,17.2,67.92810557,69,85,43.1,40.2,77,81.7,49.3 -2011,50.03718193,42.7734375,61.2,58.7423969,48.18041792,62.2,18.2,79.43281184,17.5,68.42673015,69.5,84.8,43.1,40.1,76.7,81.9,49.2 \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle b/lib/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle index 6c7aa7ee1219..418721314335 100644 --- a/lib/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle @@ -1,4 +1,4 @@ -# Solarized color palette taken from http://ethanschoonover.com/solarized +# Solarized color palette taken from https://ethanschoonover.com/solarized/ # Inspired by, and copied from ggthemes https://github.com/jrnold/ggthemes #TODO: diff --git a/lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle b/lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle index 96f62f4ba592..abd972925871 100644 --- a/lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle @@ -4,3 +4,5 @@ text.kerning_factor : 6 ytick.alignment: center_baseline + +hatch.color: edge diff --git a/lib/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle b/lib/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle new file mode 100644 index 000000000000..911658fe8833 --- /dev/null +++ b/lib/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle @@ -0,0 +1,19 @@ +# This style is used for the plot_types gallery. It is considered private. + +axes.grid: False +axes.axisbelow: True + +figure.figsize: 2, 2 +# make it so the axes labels don't show up. Obviously +# not good style for any quantitative analysis: +figure.subplot.left: 0.01 +figure.subplot.right: 0.99 +figure.subplot.bottom: 0.01 +figure.subplot.top: 0.99 + +xtick.major.size: 0.0 +ytick.major.size: 0.0 + +# colors: +image.cmap : Blues +axes.prop_cycle: cycler('color', ['1f77b4', '82bbdb', 'ccdff1']) diff --git a/lib/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle b/lib/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle new file mode 100644 index 000000000000..75c95bf16a1f --- /dev/null +++ b/lib/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle @@ -0,0 +1,19 @@ +# This style is used for the plot_types gallery. It is considered part of the private API. + +axes.grid: True +axes.axisbelow: True + +figure.figsize: 2, 2 +# make it so the axes labels don't show up. Obviously +# not good style for any quantitative analysis: +figure.subplot.left: 0.01 +figure.subplot.right: 0.99 +figure.subplot.bottom: 0.01 +figure.subplot.top: 0.99 + +xtick.major.size: 0.0 +ytick.major.size: 0.0 + +# colors: +image.cmap : Blues +axes.prop_cycle: cycler('color', ['1f77b4', '58a1cf', 'abd0e6']) diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index b5430e554353..92624503f99e 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -2,7 +2,7 @@ ### LINES -# See http://matplotlib.org/api/artist_api.html#module-matplotlib.lines for more +# See https://matplotlib.org/api/artist_api.html#module-matplotlib.lines for more # information on line properties. lines.linewidth : 1.0 # line width in points lines.linestyle : - # solid line @@ -28,7 +28,7 @@ markers.fillstyle: full ### PATCHES # Patches are graphical objects that fill 2D space, like polygons or # circles. See -# http://matplotlib.org/api/artist_api.html#module-matplotlib.patches +# https://matplotlib.org/api/artist_api.html#module-matplotlib.patches # information on patch properties patch.linewidth : 1.0 # edge width in points patch.facecolor : b @@ -44,7 +44,7 @@ hist.bins : 10 ### FONT # # font properties used by text.Text. See -# http://matplotlib.org/api/font_manager_api.html for more +# https://matplotlib.org/api/font_manager_api.html for more # information on font properties. The 6 font properties used for font # matching are given below with their default values. # @@ -91,12 +91,12 @@ font.size : 12.0 font.serif : DejaVu Serif, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif font.sans-serif: DejaVu Sans, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif font.cursive : Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, cursive -font.fantasy : Comic Sans MS, Chicago, Charcoal, ImpactWestern, Humor Sans, fantasy +font.fantasy : Comic Sans MS, Chicago, Charcoal, ImpactWestern, xkcd script, fantasy font.monospace : DejaVu Sans Mono, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace ### TEXT # text properties used by text.Text. See -# http://matplotlib.org/api/artist_api.html#module-matplotlib.text for more +# https://matplotlib.org/api/artist_api.html#module-matplotlib.text for more # information on text properties text.color : k @@ -112,16 +112,20 @@ text.usetex : False # use latex for all text handling. The following fo # LaTeX \usepackage command, please inquire at the # matplotlib mailing list text.latex.preamble : # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES - # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP - # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. - # preamble is a comma separated list of LaTeX statements - # that are included in the LaTeX document preamble. - # An example: - # text.latex.preamble : \usepackage{bm},\usepackage{euler} - # The following packages are always loaded with usetex, so - # beware of package collisions: color, geometry, graphicx, - # type1cm, textcomp. Adobe Postscript (PSSNFS) font packages - # may also be loaded, depending on your font settings + # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP + # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. + # text.latex.preamble is a single line of LaTeX code that + # will be passed on to the LaTeX system. It may contain + # any code that is valid for the LaTeX "preamble", i.e. + # between the "\documentclass" and "\begin{document}" + # statements. + # Note that it has to be put on a single line, which may + # become quite long. + # The following packages are always loaded with usetex, so + # beware of package collisions: + # color, fix-cm, geometry, graphicx, textcomp. + # Adobe Postscript (PSSNFS) font packages may also be + # loaded, depending on your font settings. text.hinting : auto # May be one of the following: # 'none': Perform no hinting @@ -154,7 +158,7 @@ mathtext.sf : sans\-serif mathtext.fontset : cm # Should be 'cm' (Computer Modern), 'stix', # 'stixsans' or 'custom' mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix' - # 'stixsans'] when a symbol can not be found in one of the + # 'stixsans'] when a symbol cannot be found in one of the # custom math fonts. Select 'None' to not perform fallback # and replace the missing character by a dummy. @@ -166,7 +170,7 @@ mathtext.default : it # The default font to use for math. ### AXES # default face and edge color, default tick sizes, # default fontsizes for ticklabels, and so on. See -# http://matplotlib.org/api/axes_api.html#module-matplotlib.axes +# https://matplotlib.org/api/axes_api.html#module-matplotlib.axes axes.facecolor : w # axes background color axes.edgecolor : k # axes edge color axes.linewidth : 1.0 # edge linewidth @@ -203,9 +207,9 @@ axes.formatter.offset_threshold : 2 # When useoffset is True, the offset # at least this number of significant # digits from tick labels. -axes.unicode_minus : True # use unicode for the minus symbol +axes.unicode_minus : True # use Unicode for the minus symbol # rather than hyphen. See - # http://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes + # https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes axes.prop_cycle : cycler('color', 'bgrcmyk') # color cycle for plot lines # as list of string colorspecs: @@ -219,7 +223,10 @@ axes.spines.left : True axes.spines.right : True axes.spines.top : True polaraxes.grid : True # display grid on polar axes -axes3d.grid : True # display grid on 3d axes +axes3d.grid : True # display grid on 3D axes +axes3d.automargin : False # automatically add margin when manually setting 3D axis limits +axes3d.depthshade : False # depth shade for 3D scatter plots +axes3d.depthshade_minalpha : 0.3 # minimum alpha value for depth shading date.autoformatter.year : %Y date.autoformatter.month : %b %Y @@ -228,9 +235,10 @@ date.autoformatter.hour : %H:%M:%S date.autoformatter.minute : %H:%M:%S.%f date.autoformatter.second : %H:%M:%S.%f date.autoformatter.microsecond : %H:%M:%S.%f +date.converter: auto # 'auto', 'concise' ### TICKS -# see http://matplotlib.org/api/axis_api.html#matplotlib.axis.Tick +# see https://matplotlib.org/api/axis_api.html#matplotlib.axis.Tick xtick.top : True # draw ticks on the top side xtick.bottom : True # draw ticks on the bottom side @@ -299,9 +307,11 @@ legend.edgecolor : inherit # legend edge color (when 'inherit' uses axes.e ### FIGURE -# See http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure +# See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure figure.titlesize : medium # size of the figure title figure.titleweight : normal # weight of the figure title +figure.labelsize: medium # size of the figure label +figure.labelweight: normal # weight of the figure label figure.figsize : 8, 6 # figure size in inches figure.dpi : 80 # figure dots per inch figure.facecolor : 0.75 # figure facecolor; 0.75 is scalar gray @@ -324,7 +334,7 @@ figure.subplot.hspace : 0.2 # the amount of height reserved for space betwee ### IMAGES image.aspect : equal # equal | auto | a number image.interpolation : bilinear # see help(imshow) for options -image.cmap : jet # gray | jet etc... +image.cmap : jet # gray | jet | ... image.lut : 256 # the size of the colormap lookup table image.origin : upper # lower | upper image.resample : False @@ -372,7 +382,6 @@ boxplot.showbox: True boxplot.showcaps: True boxplot.showfliers: True boxplot.showmeans: False -boxplot.vertical: True boxplot.whiskerprops.color: b boxplot.whiskerprops.linestyle: -- boxplot.whiskerprops.linewidth: 1.0 diff --git a/lib/matplotlib/mpl-data/stylelib/dark_background.mplstyle b/lib/matplotlib/mpl-data/stylelib/dark_background.mplstyle index c4b7741ae440..61a99f3c0d10 100644 --- a/lib/matplotlib/mpl-data/stylelib/dark_background.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/dark_background.mplstyle @@ -18,9 +18,6 @@ grid.color: white figure.facecolor: black figure.edgecolor: black -savefig.facecolor: black -savefig.edgecolor: black - ### Boxplots boxplot.boxprops.color: white boxplot.capprops.color: white diff --git a/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle b/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle index 738db39f5f80..cd56d404c3b5 100644 --- a/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle @@ -29,10 +29,7 @@ xtick.minor.size: 0 ytick.major.size: 0 ytick.minor.size: 0 -font.size:14.0 - -savefig.edgecolor: f0f0f0 -savefig.facecolor: f0f0f0 +font.size: 14.0 figure.subplot.left: 0.08 figure.subplot.right: 0.95 diff --git a/lib/matplotlib/mpl-data/stylelib/ggplot.mplstyle b/lib/matplotlib/mpl-data/stylelib/ggplot.mplstyle index 67afd83cc42d..d1b3ba2e337b 100644 --- a/lib/matplotlib/mpl-data/stylelib/ggplot.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/ggplot.mplstyle @@ -1,4 +1,4 @@ -# from http://www.huyng.com/posts/sane-color-scheme-for-matplotlib/ +# from https://everyhue.me/posts/sane-color-scheme-for-matplotlib/ patch.linewidth: 0.5 patch.facecolor: 348ABD # blue diff --git a/lib/matplotlib/mpl-data/stylelib/petroff10.mplstyle b/lib/matplotlib/mpl-data/stylelib/petroff10.mplstyle new file mode 100644 index 000000000000..62d1262a09cd --- /dev/null +++ b/lib/matplotlib/mpl-data/stylelib/petroff10.mplstyle @@ -0,0 +1,5 @@ +# Color cycle survey palette from Petroff (2021): +# https://arxiv.org/abs/2107.02270 +# https://github.com/mpetroff/accessible-color-cycles +axes.prop_cycle: cycler('color', ['3f90da', 'ffa90e', 'bd1f01', '94a4a2', '832db6', 'a96b59', 'e76300', 'b9ac70', '717581', '92dadd']) +patch.facecolor: 3f90da diff --git a/lib/matplotlib/mpl-data/stylelib/petroff6.mplstyle b/lib/matplotlib/mpl-data/stylelib/petroff6.mplstyle new file mode 100644 index 000000000000..ff227eba45ba --- /dev/null +++ b/lib/matplotlib/mpl-data/stylelib/petroff6.mplstyle @@ -0,0 +1,5 @@ +# Color cycle survey palette from Petroff (2021): +# https://arxiv.org/abs/2107.02270 +# https://github.com/mpetroff/accessible-color-cycles +axes.prop_cycle: cycler('color', ['5790fc', 'f89c20', 'e42536', '964a8b', '9c9ca1', '7a21dd']) +patch.facecolor: 5790fc diff --git a/lib/matplotlib/mpl-data/stylelib/petroff8.mplstyle b/lib/matplotlib/mpl-data/stylelib/petroff8.mplstyle new file mode 100644 index 000000000000..0228f736ddea --- /dev/null +++ b/lib/matplotlib/mpl-data/stylelib/petroff8.mplstyle @@ -0,0 +1,5 @@ +# Color cycle survey palette from Petroff (2021): +# https://arxiv.org/abs/2107.02270 +# https://github.com/mpetroff/accessible-color-cycles +axes.prop_cycle: cycler('color', ['1845fb', 'ff5e02', 'c91f16', 'c849a9', 'adad7d', '86c8dd', '578dff', '656364']) +patch.facecolor: 1845fb diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-bright.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-bright.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-bright.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-bright.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-colorblind.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-colorblind.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-colorblind.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-colorblind.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-dark-palette.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark-palette.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-dark-palette.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark-palette.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-dark.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-dark.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-darkgrid.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-darkgrid.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-darkgrid.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-darkgrid.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-deep.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-deep.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-deep.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-deep.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-muted.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-muted.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-muted.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-muted.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-notebook.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-notebook.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-notebook.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-notebook.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-paper.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-paper.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-paper.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-paper.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-pastel.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-pastel.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-pastel.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-pastel.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-poster.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-poster.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-poster.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-poster.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-talk.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-talk.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-talk.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-talk.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-ticks.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-ticks.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-ticks.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-ticks.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-white.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-white.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-white.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-white.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn-whitegrid.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-whitegrid.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn-whitegrid.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8-whitegrid.mplstyle diff --git a/lib/matplotlib/mpl-data/stylelib/seaborn.mplstyle b/lib/matplotlib/mpl-data/stylelib/seaborn-v0_8.mplstyle similarity index 100% rename from lib/matplotlib/mpl-data/stylelib/seaborn.mplstyle rename to lib/matplotlib/mpl-data/stylelib/seaborn-v0_8.mplstyle diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index a788d34040bb..1e07125cdc2a 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -22,9 +22,12 @@ Contains a single `.Text` instance. """ +import functools + import numpy as np -from matplotlib import cbook, docstring, rcParams +import matplotlib as mpl +from matplotlib import _api, _docstring import matplotlib.artist as martist import matplotlib.path as mpath import matplotlib.text as mtext @@ -39,27 +42,39 @@ DEBUG = False +def _compat_get_offset(meth): + """ + Decorator for the get_offset method of OffsetBox and subclasses, that + allows supporting both the new signature (self, bbox, renderer) and the old + signature (self, width, height, xdescent, ydescent, renderer). + """ + sigs = [lambda self, width, height, xdescent, ydescent, renderer: locals(), + lambda self, bbox, renderer: locals()] + + @functools.wraps(meth) + def get_offset(self, *args, **kwargs): + params = _api.select_matching_signature(sigs, self, *args, **kwargs) + bbox = (params["bbox"] if "bbox" in params else + Bbox.from_bounds(-params["xdescent"], -params["ydescent"], + params["width"], params["height"])) + return meth(params["self"], bbox, params["renderer"]) + return get_offset + + # for debugging use -def bbox_artist(*args, **kwargs): +def _bbox_artist(*args, **kwargs): if DEBUG: mbbox_artist(*args, **kwargs) -# _get_packed_offsets() and _get_aligned_offsets() are coded assuming -# that we are packing boxes horizontally. But same function will be -# used with vertical packing. - -def _get_packed_offsets(wd_list, total, sep, mode="fixed"): - """ - Given a list of (width, xdescent) of each boxes, calculate the - total width and the x-offset positions of each items according to - *mode*. xdescent is analogous to the usual descent, but along the - x-direction. xdescent values are currently ignored. +def _get_packed_offsets(widths, total, sep, mode="fixed"): + r""" + Pack boxes specified by their *widths*. For simplicity of the description, the terminology used here assumes a horizontal layout, but the function works equally for a vertical layout. - There are three packing modes: + There are three packing *mode*\s: - 'fixed': The elements are packed tight to the left with a spacing of *sep* in between. If *total* is *None* the returned total will be the @@ -82,11 +97,11 @@ def _get_packed_offsets(wd_list, total, sep, mode="fixed"): Parameters ---------- - wd_list : list of (float, float) - (width, xdescent) of boxes to be packed. + widths : list of float + Widths of boxes to be packed. total : float or None Intended total length. *None* if not used. - sep : float + sep : float or None Spacing between boxes. mode : {'fixed', 'expand', 'equal'} The packing mode. @@ -98,11 +113,10 @@ def _get_packed_offsets(wd_list, total, sep, mode="fixed"): offsets : array of float The left offsets of the boxes. """ - w_list, d_list = zip(*wd_list) # d_list is currently not used. - cbook._check_in_list(["fixed", "expand", "equal"], mode=mode) + _api.check_in_list(["fixed", "expand", "equal"], mode=mode) if mode == "fixed": - offsets_ = np.cumsum([0] + [w + sep for w in w_list]) + offsets_ = np.cumsum([0] + [w + sep for w in widths]) offsets = offsets_[:-1] if total is None: total = offsets_[-1] - sep @@ -113,67 +127,76 @@ def _get_packed_offsets(wd_list, total, sep, mode="fixed"): # is None and used in conjugation with tight layout. if total is None: total = 1 - if len(w_list) > 1: - sep = (total - sum(w_list)) / (len(w_list) - 1) + if len(widths) > 1: + sep = (total - sum(widths)) / (len(widths) - 1) else: sep = 0 - offsets_ = np.cumsum([0] + [w + sep for w in w_list]) + offsets_ = np.cumsum([0] + [w + sep for w in widths]) offsets = offsets_[:-1] return total, offsets elif mode == "equal": - maxh = max(w_list) + maxh = max(widths) if total is None: if sep is None: raise ValueError("total and sep cannot both be None when " "using layout mode 'equal'") - total = (maxh + sep) * len(w_list) + total = (maxh + sep) * len(widths) else: - sep = total / len(w_list) - maxh - offsets = (maxh + sep) * np.arange(len(w_list)) + sep = total / len(widths) - maxh + offsets = (maxh + sep) * np.arange(len(widths)) return total, offsets -def _get_aligned_offsets(hd_list, height, align="baseline"): +def _get_aligned_offsets(yspans, height, align="baseline"): """ - Given a list of (height, descent) of each boxes, align the boxes - with *align* and calculate the y-offsets of each boxes. - total width and the offset positions of each items according to - *mode*. xdescent is analogous to the usual descent, but along the - x-direction. xdescent values are currently ignored. + Align boxes each specified by their ``(y0, y1)`` spans. + + For simplicity of the description, the terminology used here assumes a + horizontal layout (i.e., vertical alignment), but the function works + equally for a vertical layout. Parameters ---------- - hd_list - List of (height, xdescent) of boxes to be aligned. + yspans + List of (y0, y1) spans of boxes to be aligned. height : float or None - Intended total length. If None, the maximum of the heights in *hd_list* - is used. + Intended total height. If None, the maximum of the heights + (``y1 - y0``) in *yspans* is used. align : {'baseline', 'left', 'top', 'right', 'bottom', 'center'} - Align mode. + The alignment anchor of the boxes. + + Returns + ------- + (y0, y1) + y range spanned by the packing. If a *height* was originally passed + in, then for all alignments other than "baseline", a span of ``(0, + height)`` is used without checking that it is actually large enough). + descent + The descent of the packing. + offsets + The bottom offsets of the boxes. """ - if height is None: - height = max(h for h, d in hd_list) - cbook._check_in_list( + _api.check_in_list( ["baseline", "left", "top", "right", "bottom", "center"], align=align) + if height is None: + height = max(y1 - y0 for y0, y1 in yspans) if align == "baseline": - height_descent = max(h - d for h, d in hd_list) - descent = max(d for h, d in hd_list) - height = height_descent + descent - offsets = [0. for h, d in hd_list] - elif align in ["left", "top"]: - descent = 0. - offsets = [d for h, d in hd_list] - elif align in ["right", "bottom"]: - descent = 0. - offsets = [height - h + d for h, d in hd_list] + yspan = (min(y0 for y0, y1 in yspans), max(y1 for y0, y1 in yspans)) + offsets = [0] * len(yspans) + elif align in ["left", "bottom"]: + yspan = (0, height) + offsets = [-y0 for y0, y1 in yspans] + elif align in ["right", "top"]: + yspan = (0, height) + offsets = [height - y1 for y0, y1 in yspans] elif align == "center": - descent = 0. - offsets = [(height - h) * .5 + d for h, d in hd_list] + yspan = (0, height) + offsets = [(height - (y1 - y0)) * .5 - y0 for y0, y1 in yspans] - return height, descent, offsets + return yspan, offsets class OffsetBox(martist.Artist): @@ -183,17 +206,15 @@ class OffsetBox(martist.Artist): The child artists are meant to be drawn at a relative position to its parent. - Being an artist itself, all parameters are passed on to `.Artist`. + Being an artist itself, all keyword arguments are passed on to `.Artist`. """ - def __init__(self, *args, **kwargs): - - super().__init__(*args, **kwargs) - - # Clipping has not been implemented in the OffesetBox family, so + def __init__(self, **kwargs): + super().__init__() + self._internal_update(kwargs) + # Clipping has not been implemented in the OffsetBox family, so # disable the clip flag for consistency. It can always be turned back # on to zero effect. self.set_clip_on(False) - self._children = [] self._offset = (0, 0) @@ -226,7 +247,7 @@ def contains(self, mouseevent): Parameters ---------- - mouseevent : `matplotlib.backend_bases.MouseEvent` + mouseevent : `~matplotlib.backend_bases.MouseEvent` Returns ------- @@ -241,9 +262,8 @@ def contains(self, mouseevent): -------- .Artist.contains """ - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} for c in self.get_children(): a, b = c.contains(mouseevent) if a: @@ -268,7 +288,8 @@ def offset(width, height, xdescent, ydescent, renderer) \ self._offset = xy self.stale = True - def get_offset(self, width, height, xdescent, ydescent, renderer): + @_compat_get_offset + def get_offset(self, bbox, renderer): """ Return the offset as a tuple (x, y). @@ -278,14 +299,13 @@ def get_offset(self, width, height, xdescent, ydescent, renderer): Parameters ---------- - width, height, xdescent, ydescent - Extent parameters. + bbox : `.Bbox` renderer : `.RendererBase` subclass - """ - return (self._offset(width, height, xdescent, ydescent, renderer) - if callable(self._offset) - else self._offset) + return ( + self._offset(bbox.width, bbox.height, -bbox.x0, -bbox.y0, renderer) + if callable(self._offset) + else self._offset) def set_width(self, width): """ @@ -317,9 +337,11 @@ def get_children(self): r"""Return a list of the child `.Artist`\s.""" return self._children - def get_extent_offsets(self, renderer): + def _get_bbox_and_child_offsets(self, renderer): """ - Update offset of the children and return the extent of the box. + Return the bbox of the offsetbox and the child offsets. + + The bbox should satisfy ``x0 <= x1 and y0 <= y1``. Parameters ---------- @@ -327,68 +349,66 @@ def get_extent_offsets(self, renderer): Returns ------- - width - height - xdescent - ydescent + bbox list of (xoffset, yoffset) pairs """ raise NotImplementedError( - "get_extent_offsets must be overridden in derived classes.") + "get_bbox_and_offsets must be overridden in derived classes") - def get_extent(self, renderer): - """Return a tuple ``width, height, xdescent, ydescent`` of the box.""" - w, h, xd, yd, offsets = self.get_extent_offsets(renderer) - return w, h, xd, yd + def get_bbox(self, renderer): + """Return the bbox of the offsetbox, ignoring parent offsets.""" + bbox, offsets = self._get_bbox_and_child_offsets(renderer) + return bbox - def get_window_extent(self, renderer): - """Return the bounding box (`.Bbox`) in display space.""" - w, h, xd, yd, offsets = self.get_extent_offsets(renderer) - px, py = self.get_offset(w, h, xd, yd, renderer) - return mtransforms.Bbox.from_bounds(px - xd, py - yd, w, h) + def get_window_extent(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + bbox = self.get_bbox(renderer) + try: # Some subclasses redefine get_offset to take no args. + px, py = self.get_offset(bbox, renderer) + except TypeError: + px, py = self.get_offset() + return bbox.translated(px, py) def draw(self, renderer): """ Update the location of children if necessary and draw them to the given *renderer*. """ - width, height, xdescent, ydescent, offsets = self.get_extent_offsets( - renderer) - - px, py = self.get_offset(width, height, xdescent, ydescent, renderer) - + bbox, offsets = self._get_bbox_and_child_offsets(renderer) + px, py = self.get_offset(bbox, renderer) for c, (ox, oy) in zip(self.get_visible_children(), offsets): c.set_offset((px + ox, py + oy)) c.draw(renderer) - - bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + _bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) self.stale = False class PackerBase(OffsetBox): - def __init__(self, pad=None, sep=None, width=None, height=None, - align=None, mode=None, - children=None): + def __init__(self, pad=0., sep=0., width=None, height=None, + align="baseline", mode="fixed", children=None): """ Parameters ---------- - pad : float, optional + pad : float, default: 0.0 The boundary padding in points. - sep : float, optional + sep : float, default: 0.0 The spacing between items in points. width, height : float, optional Width and height of the container box in pixels, calculated if *None*. - align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'} + align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}, \ +default: 'baseline' Alignment of boxes. - mode : {'fixed', 'expand', 'equal'} + mode : {'fixed', 'expand', 'equal'}, default: 'fixed' The packing mode. - - 'fixed' packs the given `.Artists` tight with *sep* spacing. + - 'fixed' packs the given `.Artist`\\s tight with *sep* spacing. - 'expand' uses the maximal available space to distribute the artists with equal spacing in between. - 'equal': Each artist an equal fraction of the available space @@ -400,64 +420,33 @@ def __init__(self, pad=None, sep=None, width=None, height=None, Notes ----- *pad* and *sep* are in points and will be scaled with the renderer - dpi, while *width* and *height* are in in pixels. + dpi, while *width* and *height* are in pixels. """ super().__init__() - self.height = height self.width = width self.sep = sep self.pad = pad self.mode = mode self.align = align - self._children = children class VPacker(PackerBase): """ - The VPacker has its children packed vertically. It automatically - adjusts the relative positions of children at drawing time. - """ - def __init__(self, pad=None, sep=None, width=None, height=None, - align="baseline", mode="fixed", - children=None): - """ - Parameters - ---------- - pad : float, optional - The boundary padding in points. - - sep : float, optional - The spacing between items in points. - - width, height : float, optional - Width and height of the container box in pixels, calculated if - *None*. - - align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'} - Alignment of boxes. - - mode : {'fixed', 'expand', 'equal'} - The packing mode. + VPacker packs its children vertically, automatically adjusting their + relative positions at draw time. - - 'fixed' packs the given `.Artists` tight with *sep* spacing. - - 'expand' uses the maximal available space to distribute the - artists with equal spacing in between. - - 'equal': Each artist an equal fraction of the available space - and is left-aligned (or top-aligned) therein. - - children : list of `.Artist` - The artists to pack. + .. code-block:: none - Notes - ----- - *pad* and *sep* are in points and will be scaled with the renderer - dpi, while *width* and *height* are in in pixels. - """ - super().__init__(pad, sep, width, height, align, mode, children) + +---------+ + | Child 1 | + | Child 2 | + | Child 3 | + +---------+ + """ - def get_extent_offsets(self, renderer): + def _get_bbox_and_child_offsets(self, renderer): # docstring inherited dpicor = renderer.points_to_pixels(1.) pad = self.pad * dpicor @@ -468,110 +457,53 @@ def get_extent_offsets(self, renderer): if isinstance(c, PackerBase) and c.mode == "expand": c.set_width(self.width) - whd_list = [c.get_extent(renderer) - for c in self.get_visible_children()] - whd_list = [(w, h, xd, (h - yd)) for w, h, xd, yd in whd_list] - - wd_list = [(w, xd) for w, h, xd, yd in whd_list] - width, xdescent, xoffsets = _get_aligned_offsets(wd_list, - self.width, - self.align) - - pack_list = [(h, yd) for w, h, xd, yd in whd_list] - height, yoffsets_ = _get_packed_offsets(pack_list, self.height, - sep, self.mode) - - yoffsets = yoffsets_ + [yd for w, h, xd, yd in whd_list] - ydescent = height - yoffsets[0] - yoffsets = height - yoffsets + bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()] + (x0, x1), xoffsets = _get_aligned_offsets( + [bbox.intervalx for bbox in bboxes], self.width, self.align) + height, yoffsets = _get_packed_offsets( + [bbox.height for bbox in bboxes], self.height, sep, self.mode) + yoffsets = height - (yoffsets + [bbox.y1 for bbox in bboxes]) + ydescent = yoffsets[0] yoffsets = yoffsets - ydescent - return (width + 2 * pad, height + 2 * pad, - xdescent + pad, ydescent + pad, - list(zip(xoffsets, yoffsets))) + return ( + Bbox.from_bounds(x0, -ydescent, x1 - x0, height).padded(pad), + [*zip(xoffsets, yoffsets)]) class HPacker(PackerBase): """ - The HPacker has its children packed horizontally. It automatically - adjusts the relative positions of children at draw time. - """ - def __init__(self, pad=None, sep=None, width=None, height=None, - align="baseline", mode="fixed", - children=None): - """ - Parameters - ---------- - pad : float, optional - The boundary padding in points. - - sep : float, optional - The spacing between items in points. + HPacker packs its children horizontally, automatically adjusting their + relative positions at draw time. - width, height : float, optional - Width and height of the container box in pixels, calculated if - *None*. - - align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'} - Alignment of boxes. - - mode : {'fixed', 'expand', 'equal'} - The packing mode. + .. code-block:: none - - 'fixed' packs the given `.Artists` tight with *sep* spacing. - - 'expand' uses the maximal available space to distribute the - artists with equal spacing in between. - - 'equal': Each artist an equal fraction of the available space - and is left-aligned (or top-aligned) therein. - - children : list of `.Artist` - The artists to pack. - - Notes - ----- - *pad* and *sep* are in points and will be scaled with the renderer - dpi, while *width* and *height* are in in pixels. - """ - super().__init__(pad, sep, width, height, align, mode, children) + +-------------------------------+ + | Child 1 Child 2 Child 3 | + +-------------------------------+ + """ - def get_extent_offsets(self, renderer): + def _get_bbox_and_child_offsets(self, renderer): # docstring inherited dpicor = renderer.points_to_pixels(1.) pad = self.pad * dpicor sep = self.sep * dpicor - whd_list = [c.get_extent(renderer) - for c in self.get_visible_children()] + bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()] + if not bboxes: + return Bbox.from_bounds(0, 0, 0, 0).padded(pad), [] - if not whd_list: - return 2 * pad, 2 * pad, pad, pad, [] + (y0, y1), yoffsets = _get_aligned_offsets( + [bbox.intervaly for bbox in bboxes], self.height, self.align) + width, xoffsets = _get_packed_offsets( + [bbox.width for bbox in bboxes], self.width, sep, self.mode) - if self.height is None: - height_descent = max(h - yd for w, h, xd, yd in whd_list) - ydescent = max(yd for w, h, xd, yd in whd_list) - height = height_descent + ydescent - else: - height = self.height - 2 * pad # width w/o pad + x0 = bboxes[0].x0 + xoffsets -= ([bbox.x0 for bbox in bboxes] - x0) - hd_list = [(h, yd) for w, h, xd, yd in whd_list] - height, ydescent, yoffsets = _get_aligned_offsets(hd_list, - self.height, - self.align) - - pack_list = [(w, xd) for w, h, xd, yd in whd_list] - - width, xoffsets_ = _get_packed_offsets(pack_list, self.width, - sep, self.mode) - - xoffsets = xoffsets_ + [xd for w, h, xd, yd in whd_list] - - xdescent = whd_list[0][2] - xoffsets = xoffsets - xdescent - - return (width + 2 * pad, height + 2 * pad, - xdescent + pad, ydescent + pad, - list(zip(xoffsets, yoffsets))) + return (Bbox.from_bounds(x0, y0, width, y1 - y0).padded(pad), + [*zip(xoffsets, yoffsets)]) class PaddedBox(OffsetBox): @@ -580,16 +512,37 @@ class PaddedBox(OffsetBox): The `.PaddedBox` contains a `.FancyBboxPatch` that is used to visualize it when rendering. + + .. code-block:: none + + +----------------------------+ + | | + | | + | | + | <--pad--> Artist | + | ^ | + | pad | + | v | + +----------------------------+ + + Attributes + ---------- + pad : float + The padding in points. + patch : `.FancyBboxPatch` + When *draw_frame* is True, this `.FancyBboxPatch` is made visible and + creates a border around the box. """ - def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None): + + def __init__(self, child, pad=0., *, draw_frame=False, patch_attrs=None): """ Parameters ---------- child : `~matplotlib.artist.Artist` The contained `.Artist`. - pad : float + pad : float, default: 0.0 The padding in points. This will be scaled with the renderer dpi. - In contrast *width* and *height* are in *pixels* and thus not + In contrast, *width* and *height* are in *pixels* and thus not scaled. draw_frame : bool Whether to draw the contained `.FancyBboxPatch`. @@ -610,21 +563,15 @@ def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None): if patch_attrs is not None: self.patch.update(patch_attrs) - def get_extent_offsets(self, renderer): + def _get_bbox_and_child_offsets(self, renderer): # docstring inherited. - dpicor = renderer.points_to_pixels(1.) - pad = self.pad * dpicor - w, h, xd, yd = self._children[0].get_extent(renderer) - return (w + 2 * pad, h + 2 * pad, xd + pad, yd + pad, - [(0, 0)]) + pad = self.pad * renderer.points_to_pixels(1.) + return (self._children[0].get_bbox(renderer).padded(pad), [(0, 0)]) def draw(self, renderer): # docstring inherited - width, height, xdescent, ydescent, offsets = self.get_extent_offsets( - renderer) - - px, py = self.get_offset(width, height, xdescent, ydescent, renderer) - + bbox, offsets = self._get_bbox_and_child_offsets(renderer) + px, py = self.get_offset(bbox, renderer) for c, (ox, oy) in zip(self.get_visible_children(), offsets): c.set_offset((px + ox, py + oy)) @@ -633,11 +580,10 @@ def draw(self, renderer): for c in self.get_visible_children(): c.draw(renderer) - #bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) self.stale = False def update_frame(self, bbox, fontsize=None): - self.patch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) + self.patch.set_bounds(bbox.bounds) if fontsize: self.patch.set_mutation_scale(fontsize) self.stale = True @@ -656,8 +602,7 @@ class DrawingArea(OffsetBox): boundaries of the parent. """ - def __init__(self, width, height, xdescent=0., - ydescent=0., clip=False): + def __init__(self, width, height, xdescent=0., ydescent=0., clip=False): """ Parameters ---------- @@ -719,18 +664,12 @@ def get_offset(self): """Return offset of the container.""" return self._offset - def get_window_extent(self, renderer): - """Return the bounding box in display space.""" - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() # w, h, xd, yd) - - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def get_extent(self, renderer): - """Return width, height, xdescent, ydescent of box.""" + def get_bbox(self, renderer): + # docstring inherited dpi_cor = renderer.points_to_pixels(1.) - return (self.width * dpi_cor, self.height * dpi_cor, - self.xdescent * dpi_cor, self.ydescent * dpi_cor) + return Bbox.from_bounds( + -self.xdescent * dpi_cor, -self.ydescent * dpi_cor, + self.width * dpi_cor, self.height * dpi_cor) def add_artist(self, a): """Add an `.Artist` to the container box.""" @@ -739,7 +678,7 @@ def add_artist(self, a): a.set_transform(self.get_transform()) if self.axes is not None: a.axes = self.axes - fig = self.figure + fig = self.get_figure(root=False) if fig is not None: a.set_figure(fig) @@ -763,7 +702,7 @@ def draw(self, renderer): c.set_clip_path(tpath) c.draw(renderer) - bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + _bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) self.stale = False @@ -775,31 +714,26 @@ class TextArea(OffsetBox): width and height of the TextArea instance is the width and height of its child text. """ + def __init__(self, s, + *, textprops=None, - multilinebaseline=None, - minimumdescent=True, + multilinebaseline=False, ): """ Parameters ---------- s : str The text to be displayed. - - textprops : dict, optional - Dictionary of keyword parameters to be passed to the - `~matplotlib.text.Text` instance contained inside TextArea. - - multilinebaseline : bool, optional - If `True`, baseline for multiline text is adjusted so that it is - (approximately) center-aligned with singleline text. - - minimumdescent : bool, optional - If `True`, the box has a minimum descent of "p". + textprops : dict, default: {} + Dictionary of keyword parameters to be passed to the `.Text` + instance in the TextArea. + multilinebaseline : bool, default: False + Whether the baseline for multiline text is adjusted so that it + is (approximately) center-aligned with single-line text. """ if textprops is None: textprops = {} - textprops.setdefault("va", "baseline") self._text = mtext.Text(0, 0, s, **textprops) super().__init__() self._children = [self._text] @@ -808,7 +742,6 @@ def __init__(self, s, self._text.set_transform(self.offset_transform + self._baseline_transform) self._multilinebaseline = multilinebaseline - self._minimumdescent = minimumdescent def set_text(self, s): """Set the text of this area as a string.""" @@ -823,8 +756,10 @@ def set_multilinebaseline(self, t): """ Set multilinebaseline. - If True, baseline for multiline text is adjusted so that it is - (approximately) center-aligned with single-line text. + If True, the baseline for multiline text is adjusted so that it is + (approximately) center-aligned with single-line text. This is used + e.g. by the legend implementation so that single-line labels are + baseline-aligned, but multiline labels are "center"-aligned with them. """ self._multilinebaseline = t self.stale = True @@ -835,22 +770,6 @@ def get_multilinebaseline(self): """ return self._multilinebaseline - def set_minimumdescent(self, t): - """ - Set minimumdescent. - - If True, extent of the single line text is adjusted so that - it has minimum descent of "p" - """ - self._minimumdescent = t - self.stale = True - - def get_minimumdescent(self): - """ - Get minimumdescent. - """ - return self._minimumdescent - def set_transform(self, t): """ set_transform is ignored. @@ -874,18 +793,14 @@ def get_offset(self): """Return offset of the container.""" return self._offset - def get_window_extent(self, renderer): - """Return the bounding box in display space.""" - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def get_extent(self, renderer): - _, h_, d_ = renderer.get_text_width_height_descent( - "lp", self._text._fontproperties, ismath=False) + def get_bbox(self, renderer): + _, h_, d_ = mtext._get_text_metrics_with_cache( + renderer, "lp", self._text._fontproperties, + ismath="TeX" if self._text.get_usetex() else False, + dpi=self.get_figure(root=True).dpi) bbox, info, yd = self._text._get_layout(renderer) - w, h = bbox.width, bbox.height + w, h = bbox.size self._baseline_transform.clear() @@ -893,32 +808,19 @@ def get_extent(self, renderer): yd_new = 0.5 * h - 0.5 * (h_ - d_) self._baseline_transform.translate(0, yd - yd_new) yd = yd_new - else: # single line - h_d = max(h_ - d_, h - yd) - - if self.get_minimumdescent(): - # To have a minimum descent, i.e., "l" and "p" have same - # descents. - yd = max(yd, d_) - h = h_d + yd ha = self._text.get_horizontalalignment() - if ha == 'left': - xd = 0 - elif ha == 'center': - xd = w / 2 - elif ha == 'right': - xd = w + x0 = {"left": 0, "center": -w / 2, "right": -w}[ha] - return w, h, xd, yd + return Bbox.from_bounds(x0, -yd, w, h) def draw(self, renderer): # docstring inherited self._text.draw(renderer) - bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + _bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) self.stale = False @@ -982,32 +884,25 @@ def get_offset(self): """Return offset of the container.""" return self._offset - def get_window_extent(self, renderer): - """Return the bounding box in display space.""" - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() # w, h, xd, yd) - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def get_extent(self, renderer): + def get_bbox(self, renderer): # clear the offset transforms _off = self.offset_transform.get_matrix() # to be restored later self.ref_offset_transform.clear() self.offset_transform.clear() # calculate the extent bboxes = [c.get_window_extent(renderer) for c in self._children] - ub = mtransforms.Bbox.union(bboxes) + ub = Bbox.union(bboxes) # adjust ref_offset_transform self.ref_offset_transform.translate(-ub.x0, -ub.y0) - # restor offset transform + # restore offset transform self.offset_transform.set_matrix(_off) - - return ub.width, ub.height, 0., 0. + return Bbox.from_bounds(0, 0, ub.width, ub.height) def draw(self, renderer): # docstring inherited for c in self._children: c.draw(renderer) - bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + _bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) self.stale = False @@ -1017,7 +912,7 @@ class AnchoredOffsetbox(OffsetBox): AnchoredOffsetbox has a single child. When multiple children are needed, use an extra OffsetBox to enclose them. By default, the offset box is - anchored against its parent axes. You may explicitly specify the + anchored against its parent Axes. You may explicitly specify the *bbox_to_anchor*. """ zorder = 5 # zorder of the legend @@ -1035,7 +930,7 @@ class AnchoredOffsetbox(OffsetBox): 'center': 10, } - def __init__(self, loc, + def __init__(self, loc, *, pad=0.4, borderpad=0.5, child=None, prop=None, frameon=True, bbox_to_anchor=None, @@ -1045,43 +940,27 @@ def __init__(self, loc, Parameters ---------- loc : str - The box location. Supported values: - - - 'upper right' - - 'upper left' - - 'lower left' - - 'lower right' - - 'center left' - - 'center right' - - 'lower center' - - 'upper center' - - 'center' - + The box location. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. For backward compatibility, numeric values are accepted as well. See the parameter *loc* of `.Legend` for details. - pad : float, default: 0.4 Padding around the child as fraction of the fontsize. - borderpad : float, default: 0.5 Padding between the offsetbox frame and the *bbox_to_anchor*. - child : `.OffsetBox` The box that will be anchored. - prop : `.FontProperties` This is only used as a reference for paddings. If not given, :rc:`legend.fontsize` is used. - frameon : bool Whether to draw a frame around the box. - bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats Box that is used to position the legend in conjunction with *loc*. - bbox_transform : None or :class:`matplotlib.transforms.Transform` The transform for the bounding box (*bbox_to_anchor*). - **kwargs All other parameters are passed on to `.OffsetBox`. @@ -1095,18 +974,18 @@ def __init__(self, loc, self.set_child(child) if isinstance(loc, str): - loc = cbook._check_getitem(self.codes, loc=loc) + loc = _api.check_getitem(self.codes, loc=loc) self.loc = loc self.borderpad = borderpad self.pad = pad if prop is None: - self.prop = FontProperties(size=rcParams["legend.fontsize"]) + self.prop = FontProperties(size=mpl.rcParams["legend.fontsize"]) else: self.prop = FontProperties._from_any(prop) if isinstance(prop, dict) and "size" not in prop: - self.prop.set_size(rcParams["legend.fontsize"]) + self.prop.set_size(mpl.rcParams["legend.fontsize"]) self.patch = FancyBboxPatch( xy=(0.0, 0.0), width=1., height=1., @@ -1132,17 +1011,11 @@ def get_children(self): """Return the list of children.""" return [self._child] - def get_extent(self, renderer): - """ - Return the extent of the box as (width, height, x, y). - - This is the extent of the child plus the padding. - """ - w, h, xd, yd = self.get_child().get_extent(renderer) + def get_bbox(self, renderer): + # docstring inherited fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) pad = self.pad * fontsize - - return w + 2 * pad, h + 2 * pad, xd + pad, yd + pad + return self.get_child().get_bbox(renderer).padded(pad) def get_bbox_to_anchor(self): """Return the bbox that the box is anchored to.""" @@ -1153,8 +1026,7 @@ def get_bbox_to_anchor(self): if transform is None: return self._bbox_to_anchor else: - return TransformedBbox(self._bbox_to_anchor, - transform) + return TransformedBbox(self._bbox_to_anchor, transform) def set_bbox_to_anchor(self, bbox, transform=None): """ @@ -1171,8 +1043,7 @@ def set_bbox_to_anchor(self, bbox, transform=None): try: l = len(bbox) except TypeError as err: - raise ValueError("Invalid argument for bbox : %s" % - str(bbox)) from err + raise ValueError(f"Invalid bbox: {bbox}") from err if l == 2: bbox = [bbox[0], bbox[1], 0, 0] @@ -1182,37 +1053,19 @@ def set_bbox_to_anchor(self, bbox, transform=None): self._bbox_to_anchor_transform = transform self.stale = True - def get_window_extent(self, renderer): - """Return the bounding box in display space.""" - self._update_offset_func(renderer) - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset(w, h, xd, yd, renderer) - return Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def _update_offset_func(self, renderer, fontsize=None): - """ - Update the offset func which depends on the dpi of the - renderer (because of the padding). - """ - if fontsize is None: - fontsize = renderer.points_to_pixels( - self.prop.get_size_in_points()) - - def _offset(w, h, xd, yd, renderer, fontsize=fontsize, self=self): - bbox = Bbox.from_bounds(0, 0, w, h) - borderpad = self.borderpad * fontsize - bbox_to_anchor = self.get_bbox_to_anchor() - - x0, y0 = self._get_anchored_bbox(self.loc, - bbox, - bbox_to_anchor, - borderpad) - return x0 + xd, y0 + yd - - self.set_offset(_offset) + @_compat_get_offset + def get_offset(self, bbox, renderer): + # docstring inherited + pad = (self.borderpad + * renderer.points_to_pixels(self.prop.get_size_in_points())) + bbox_to_anchor = self.get_bbox_to_anchor() + x0, y0 = _get_anchored_bbox( + self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height), + bbox_to_anchor, pad) + return x0 - bbox.x0, y0 - bbox.y0 def update_frame(self, bbox, fontsize=None): - self.patch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) + self.patch.set_bounds(bbox.bounds) if fontsize: self.patch.set_mutation_scale(fontsize) @@ -1221,47 +1074,28 @@ def draw(self, renderer): if not self.get_visible(): return - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - self._update_offset_func(renderer, fontsize) - # update the location and size of the legend bbox = self.get_window_extent(renderer) + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) self.update_frame(bbox, fontsize) self.patch.draw(renderer) - width, height, xdescent, ydescent = self.get_extent(renderer) - - px, py = self.get_offset(width, height, xdescent, ydescent, renderer) - + px, py = self.get_offset(self.get_bbox(renderer), renderer) self.get_child().set_offset((px, py)) self.get_child().draw(renderer) self.stale = False - def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad): - """ - Return the position of the bbox anchored at the parentbbox - with the loc code, with the borderpad. - """ - assert loc in range(1, 11) # called only internally - - BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11) - - anchor_coefs = {UR: "NE", - UL: "NW", - LL: "SW", - LR: "SE", - R: "E", - CL: "W", - CR: "E", - LC: "S", - UC: "N", - C: "C"} - - c = anchor_coefs[loc] - container = parentbbox.padded(-borderpad) - anchored_box = bbox.anchored(c, container=container) - return anchored_box.x0, anchored_box.y0 +def _get_anchored_bbox(loc, bbox, parentbbox, borderpad): + """ + Return the (x, y) position of the *bbox* anchored at the *parentbbox* with + the *loc* code with the *borderpad*. + """ + # This is only called internally and *loc* should already have been + # validated. If 0 (None), we just let ``bbox.anchored`` raise. + c = [None, "NE", "NW", "SW", "SE", "E", "W", "E", "S", "N", "C"][loc] + container = parentbbox.padded(-borderpad) + return bbox.anchored(c, container=container).p0 class AnchoredText(AnchoredOffsetbox): @@ -1269,7 +1103,7 @@ class AnchoredText(AnchoredOffsetbox): AnchoredOffsetbox with Text. """ - def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs): + def __init__(self, s, loc, *, pad=0.4, borderpad=0.5, prop=None, **kwargs): """ Parameters ---------- @@ -1300,7 +1134,7 @@ def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs): raise ValueError( 'Mixing verticalalignment with AnchoredText is not supported.') - self.txt = TextArea(s, textprops=prop, minimumdescent=False) + self.txt = TextArea(s, textprops=prop) fp = self.txt._text.get_fontproperties() super().__init__( loc, pad=pad, borderpad=borderpad, child=self.txt, prop=fp, @@ -1308,7 +1142,8 @@ def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs): class OffsetImage(OffsetBox): - def __init__(self, arr, + + def __init__(self, arr, *, zoom=1, cmap=None, norm=None, @@ -1362,24 +1197,13 @@ def get_offset(self): def get_children(self): return [self.image] - def get_window_extent(self, renderer): - """Return the bounding box in display space.""" - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def get_extent(self, renderer): - if self._dpi_cor: # True, do correction - dpi_cor = renderer.points_to_pixels(1.) - else: - dpi_cor = 1. - + def get_bbox(self, renderer): + dpi_cor = renderer.points_to_pixels(1.) if self._dpi_cor else 1. zoom = self.get_zoom() data = self.get_data() ny, nx = data.shape[:2] w, h = dpi_cor * nx * zoom, dpi_cor * ny * zoom - - return w, h, 0, 0 + return Bbox.from_bounds(0, 0, w, h) def draw(self, renderer): # docstring inherited @@ -1400,13 +1224,10 @@ class AnnotationBbox(martist.Artist, mtext._AnnotationBase): zorder = 3 def __str__(self): - return "AnnotationBbox(%g,%g)" % (self.xy[0], self.xy[1]) + return f"AnnotationBbox({self.xy[0]:g},{self.xy[1]:g})" - @docstring.dedent_interpd - def __init__(self, offsetbox, xy, - xybox=None, - xycoords='data', - boxcoords=None, + @_docstring.interpd + def __init__(self, offsetbox, xy, xybox=None, xycoords='data', boxcoords=None, *, frameon=True, pad=0.4, # FancyBboxPatch boxstyle. annotation_clip=None, box_alignment=(0.5, 0.5), @@ -1427,18 +1248,30 @@ def __init__(self, offsetbox, xy, The position *(x, y)* to place the text at. The coordinate system is determined by *boxcoords*. - xycoords : str or `.Artist` or `.Transform` or callable or \ -(float, float), default: 'data' + xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \ +callable, default: 'data' The coordinate system that *xy* is given in. See the parameter *xycoords* in `.Annotation` for a detailed description. - boxcoords : str or `.Artist` or `.Transform` or callable or \ -(float, float), default: value of *xycoords* + boxcoords : single or two-tuple of str or `.Artist` or `.Transform` \ +or callable, default: value of *xycoords* The coordinate system that *xybox* is given in. See the parameter *textcoords* in `.Annotation` for a detailed description. frameon : bool, default: True - Whether to draw a frame around the box. + By default, the text is surrounded by a white `.FancyBboxPatch` + (accessible as the ``patch`` attribute of the `.AnnotationBbox`). + If *frameon* is set to False, this patch is made invisible. + + annotation_clip: bool or None, default: None + Whether to clip (i.e. not draw) the annotation when the annotation + point *xy* is outside the Axes area. + + - If *True*, the annotation will be clipped when *xy* is outside + the Axes. + - If *False*, the annotation will always be drawn. + - If *None*, the annotation will be clipped when *xy* is outside + the Axes and *xycoords* is 'data'. pad : float, default: 0.4 Padding around the offsetbox. @@ -1448,31 +1281,37 @@ def __init__(self, offsetbox, xy, the offset box w.r.t. the *boxcoords*. The lower-left corner is (0, 0) and upper-right corner is (1, 1). + bboxprops : dict, optional + A dictionary of properties to set for the annotation bounding box, + for example *boxstyle* and *alpha*. See `.FancyBboxPatch` for + details. + + arrowprops: dict, optional + Arrow properties, see `.Annotation` for description. + + fontsize: float or str, optional + Translated to points and passed as *mutation_scale* into + `.FancyBboxPatch` to scale attributes of the box style (e.g. pad + or rounding_size). The name is chosen in analogy to `.Text` where + *fontsize* defines the mutation scale as well. If not given, + :rc:`legend.fontsize` is used. See `.Text.set_fontsize` for valid + values. + **kwargs - Other parameters are identical to `.Annotation`. + Other `AnnotationBbox` properties. See `.AnnotationBbox.set` for + a list. """ - martist.Artist.__init__(self, **kwargs) - mtext._AnnotationBase.__init__(self, - xy, - xycoords=xycoords, - annotation_clip=annotation_clip) + martist.Artist.__init__(self) + mtext._AnnotationBase.__init__( + self, xy, xycoords=xycoords, annotation_clip=annotation_clip) self.offsetbox = offsetbox - - self.arrowprops = arrowprops - + self.arrowprops = arrowprops.copy() if arrowprops is not None else None self.set_fontsize(fontsize) - - if xybox is None: - self.xybox = xy - else: - self.xybox = xybox - - if boxcoords is None: - self.boxcoords = xycoords - else: - self.boxcoords = boxcoords + self.xybox = xybox if xybox is not None else xy + self.boxcoords = boxcoords if boxcoords is not None else xycoords + self._box_alignment = box_alignment if arrowprops is not None: self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5)) @@ -1482,10 +1321,7 @@ def __init__(self, offsetbox, xy, self._arrow_relpos = None self.arrow_patch = None - self._box_alignment = box_alignment - - # frame - self.patch = FancyBboxPatch( + self.patch = FancyBboxPatch( # frame xy=(0.0, 0.0), width=1., height=1., facecolor='w', edgecolor='k', mutation_scale=self.prop.get_size_in_points(), @@ -1496,6 +1332,8 @@ def __init__(self, offsetbox, xy, if bboxprops: self.patch.set(**bboxprops) + self._internal_update(kwargs) + @property def xyann(self): return self.xybox @@ -1515,15 +1353,11 @@ def anncoords(self, coords): self.stale = True def contains(self, mouseevent): - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} if not self._check_xy(None): return False, {} return self.offsetbox.contains(mouseevent) - #if self.arrow_patch is not None: - # a, ainfo=self.arrow_patch.contains(event) - # t = t or a # self.arrow_patch is currently not checked as this can be a line - JJ def get_children(self): @@ -1544,118 +1378,80 @@ def set_fontsize(self, s=None): If *s* is not given, reset to :rc:`legend.fontsize`. """ - if s is None: - s = rcParams["legend.fontsize"] - + s = mpl._val_or_rc(s, "legend.fontsize") self.prop = FontProperties(size=s) self.stale = True - @cbook._delete_parameter("3.3", "s") - def get_fontsize(self, s=None): + def get_fontsize(self): """Return the fontsize in points.""" return self.prop.get_size_in_points() - def get_window_extent(self, renderer): - """ - get the bounding box in display space. - """ - bboxes = [child.get_window_extent(renderer) - for child in self.get_children()] - - return Bbox.union(bboxes) - - def get_tightbbox(self, renderer): - """ - get tight bounding box in display space. - """ - bboxes = [child.get_tightbbox(renderer) - for child in self.get_children()] + def get_window_extent(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + self.update_positions(renderer) + return Bbox.union([child.get_window_extent(renderer) + for child in self.get_children()]) - return Bbox.union(bboxes) + def get_tightbbox(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + self.update_positions(renderer) + return Bbox.union([child.get_tightbbox(renderer) + for child in self.get_children()]) def update_positions(self, renderer): - """ - Update the pixel positions of the annotated point and the text. - """ - xy_pixel = self._get_position_xy(renderer) - self._update_position_xybox(renderer, xy_pixel) - - mutation_scale = renderer.points_to_pixels(self.get_fontsize()) - self.patch.set_mutation_scale(mutation_scale) - - if self.arrow_patch: - self.arrow_patch.set_mutation_scale(mutation_scale) - - def _update_position_xybox(self, renderer, xy_pixel): - """ - Update the pixel positions of the annotation text and the arrow patch. - """ - - x, y = self.xybox - if isinstance(self.boxcoords, tuple): - xcoord, ycoord = self.boxcoords - x1, y1 = self._get_xy(renderer, x, y, xcoord) - x2, y2 = self._get_xy(renderer, x, y, ycoord) - ox0, oy0 = x1, y2 - else: - ox0, oy0 = self._get_xy(renderer, x, y, self.boxcoords) + """Update pixel positions for the annotated point, the text, and the arrow.""" - w, h, xd, yd = self.offsetbox.get_extent(renderer) + ox0, oy0 = self._get_xy(renderer, self.xybox, self.boxcoords) + bbox = self.offsetbox.get_bbox(renderer) + fw, fh = self._box_alignment + self.offsetbox.set_offset( + (ox0 - fw*bbox.width - bbox.x0, oy0 - fh*bbox.height - bbox.y0)) - _fw, _fh = self._box_alignment - self.offsetbox.set_offset((ox0 - _fw * w + xd, oy0 - _fh * h + yd)) - - # update patch position bbox = self.offsetbox.get_window_extent(renderer) - #self.offsetbox.set_offset((ox0-_fw*w, oy0-_fh*h)) - self.patch.set_bounds(bbox.x0, bbox.y0, - bbox.width, bbox.height) - - x, y = xy_pixel + self.patch.set_bounds(bbox.bounds) - ox1, oy1 = x, y + mutation_scale = renderer.points_to_pixels(self.get_fontsize()) + self.patch.set_mutation_scale(mutation_scale) if self.arrowprops: - d = self.arrowprops.copy() - # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key. - # adjust the starting point of the arrow relative to - # the textbox. - # TODO : Rotation needs to be accounted. - relpos = self._arrow_relpos - - ox0 = bbox.x0 + bbox.width * relpos[0] - oy0 = bbox.y0 + bbox.height * relpos[1] - - # The arrow will be drawn from (ox0, oy0) to (ox1, - # oy1). It will be first clipped by patchA and patchB. - # Then it will be shrunk by shrinkA and shrinkB - # (in points). If patch A is not set, self.bbox_patch - # is used. - - self.arrow_patch.set_positions((ox0, oy0), (ox1, oy1)) - fs = self.prop.get_size_in_points() - mutation_scale = d.pop("mutation_scale", fs) - mutation_scale = renderer.points_to_pixels(mutation_scale) + # Adjust the starting point of the arrow relative to the textbox. + # TODO: Rotation needs to be accounted. + arrow_begin = bbox.p0 + bbox.size * self._arrow_relpos + arrow_end = self._get_position_xy(renderer) + # The arrow (from arrow_begin to arrow_end) will be first clipped + # by patchA and patchB, then shrunk by shrinkA and shrinkB (in + # points). If patch A is not set, self.bbox_patch is used. + self.arrow_patch.set_positions(arrow_begin, arrow_end) + + if "mutation_scale" in self.arrowprops: + mutation_scale = renderer.points_to_pixels( + self.arrowprops["mutation_scale"]) + # Else, use fontsize-based mutation_scale defined above. self.arrow_patch.set_mutation_scale(mutation_scale) - patchA = d.pop("patchA", self.patch) + patchA = self.arrowprops.get("patchA", self.patch) self.arrow_patch.set_patchA(patchA) def draw(self, renderer): # docstring inherited - if renderer is not None: - self._renderer = renderer if not self.get_visible() or not self._check_xy(renderer): return + renderer.open_group(self.__class__.__name__, gid=self.get_gid()) self.update_positions(renderer) if self.arrow_patch is not None: - if self.arrow_patch.figure is None and self.figure is not None: - self.arrow_patch.figure = self.figure + if (self.arrow_patch.get_figure(root=False) is None and + (fig := self.get_figure(root=False)) is not None): + self.arrow_patch.set_figure(fig) self.arrow_patch.draw(renderer) self.patch.draw(renderer) self.offsetbox.draw(renderer) + renderer.close_group(self.__class__.__name__) self.stale = False @@ -1690,22 +1486,32 @@ def finalize_offset(self): def __init__(self, ref_artist, use_blit=False): self.ref_artist = ref_artist + if not ref_artist.pickable(): + ref_artist.set_picker(self._picker) self.got_artist = False - - self.canvas = self.ref_artist.figure.canvas self._use_blit = use_blit and self.canvas.supports_blit + callbacks = self.canvas.callbacks + self._disconnectors = [ + functools.partial( + callbacks.disconnect, callbacks._connect_picklable(name, func)) + for name, func in [ + ("pick_event", self.on_pick), + ("button_release_event", self.on_release), + ("motion_notify_event", self.on_motion), + ] + ] + + @staticmethod + def _picker(artist, mouseevent): + # A custom picker to prevent dragging on mouse scroll events + if mouseevent.name == "scroll_event": + return False, {} + return artist.contains(mouseevent) - c2 = self.canvas.mpl_connect('pick_event', self.on_pick) - c3 = self.canvas.mpl_connect('button_release_event', self.on_release) - - if not ref_artist.pickable(): - ref_artist.set_picker(True) - overridden_picker = cbook._deprecate_method_override( - __class__.artist_picker, self, since="3.3", - addendum="Directly set the artist's picker if desired.") - if overridden_picker is not None: - ref_artist.set_picker(overridden_picker) - self.cids = [c2, c3] + # A property, not an attribute, to maintain picklability. + canvas = property(lambda self: self.ref_artist.get_figure(root=True).canvas) + cids = property(lambda self: [ + disconnect.args[0] for disconnect in self._disconnectors[:2]]) def on_motion(self, evt): if self._check_still_parented() and self.got_artist: @@ -1714,48 +1520,39 @@ def on_motion(self, evt): self.update_offset(dx, dy) if self._use_blit: self.canvas.restore_region(self.background) - self.ref_artist.draw(self.ref_artist.figure._cachedRenderer) + self.ref_artist.draw( + self.ref_artist.get_figure(root=True)._get_renderer()) self.canvas.blit() else: self.canvas.draw() - @cbook.deprecated("3.3", alternative="self.on_motion") - def on_motion_blit(self, evt): - if self._check_still_parented() and self.got_artist: - dx = evt.x - self.mouse_x - dy = evt.y - self.mouse_y - self.update_offset(dx, dy) - self.canvas.restore_region(self.background) - self.ref_artist.draw(self.ref_artist.figure._cachedRenderer) - self.canvas.blit() - def on_pick(self, evt): - if self._check_still_parented() and evt.artist == self.ref_artist: - self.mouse_x = evt.mouseevent.x - self.mouse_y = evt.mouseevent.y - self.got_artist = True - if self._use_blit: + if self._check_still_parented(): + if evt.artist == self.ref_artist: + self.mouse_x = evt.mouseevent.x + self.mouse_y = evt.mouseevent.y + self.save_offset() + self.got_artist = True + if self.got_artist and self._use_blit: self.ref_artist.set_animated(True) self.canvas.draw() - self.background = \ - self.canvas.copy_from_bbox(self.ref_artist.figure.bbox) - self.ref_artist.draw(self.ref_artist.figure._cachedRenderer) + fig = self.ref_artist.get_figure(root=False) + self.background = self.canvas.copy_from_bbox(fig.bbox) + self.ref_artist.draw(fig._get_renderer()) self.canvas.blit() - self._c1 = self.canvas.mpl_connect( - "motion_notify_event", self.on_motion) - self.save_offset() def on_release(self, event): if self._check_still_parented() and self.got_artist: self.finalize_offset() self.got_artist = False - self.canvas.mpl_disconnect(self._c1) - if self._use_blit: + self.canvas.restore_region(self.background) + self.ref_artist.draw(self.ref_artist.figure._get_renderer()) + self.canvas.blit() self.ref_artist.set_animated(False) def _check_still_parented(self): - if self.ref_artist.figure is None: + if self.ref_artist.get_figure(root=False) is None: self.disconnect() return False else: @@ -1763,18 +1560,8 @@ def _check_still_parented(self): def disconnect(self): """Disconnect the callbacks.""" - for cid in self.cids: - self.canvas.mpl_disconnect(cid) - try: - c1 = self._c1 - except AttributeError: - pass - else: - self.canvas.mpl_disconnect(c1) - - @cbook.deprecated("3.3", alternative="self.ref_artist.contains") - def artist_picker(self, artist, evt): - return self.ref_artist.contains(evt) + for disconnector in self._disconnectors: + disconnector() def save_offset(self): pass @@ -1793,9 +1580,8 @@ def __init__(self, ref_artist, offsetbox, use_blit=False): def save_offset(self): offsetbox = self.offsetbox - renderer = offsetbox.figure._cachedRenderer - w, h, xd, yd = offsetbox.get_extent(renderer) - offset = offsetbox.get_offset(w, h, xd, yd, renderer) + renderer = offsetbox.get_figure(root=True)._get_renderer() + offset = offsetbox.get_offset(offsetbox.get_bbox(renderer), renderer) self.offsetbox_x, self.offsetbox_y = offset self.offsetbox.set_offset(offset) @@ -1805,10 +1591,10 @@ def update_offset(self, dx, dy): def get_loc_in_canvas(self): offsetbox = self.offsetbox - renderer = offsetbox.figure._cachedRenderer - w, h, xd, yd = offsetbox.get_extent(renderer) + renderer = offsetbox.get_figure(root=True)._get_renderer() + bbox = offsetbox.get_bbox(renderer) ox, oy = offsetbox._offset - loc_in_canvas = (ox - xd, oy - yd) + loc_in_canvas = (ox + bbox.x0, oy + bbox.y0) return loc_in_canvas diff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi new file mode 100644 index 000000000000..8a2016c0320a --- /dev/null +++ b/lib/matplotlib/offsetbox.pyi @@ -0,0 +1,298 @@ +import matplotlib.artist as martist +from matplotlib.backend_bases import RendererBase, Event, FigureCanvasBase +from matplotlib.colors import Colormap, Normalize +import matplotlib.text as mtext +from matplotlib.figure import Figure, SubFigure +from matplotlib.font_manager import FontProperties +from matplotlib.image import BboxImage +from matplotlib.patches import FancyArrowPatch, FancyBboxPatch +from matplotlib.transforms import Bbox, BboxBase, Transform +from matplotlib.typing import CoordsType + +import numpy as np +from numpy.typing import ArrayLike +from collections.abc import Callable, Sequence +from typing import Any, Literal, overload + +DEBUG: bool + +def _get_packed_offsets( + widths: Sequence[float], + total: float | None, + sep: float | None, + mode: Literal["fixed", "expand", "equal"] = ..., +) -> tuple[float, np.ndarray]: ... + +class OffsetBox(martist.Artist): + width: float | None + height: float | None + def __init__(self, **kwargs) -> None: ... + def set_figure(self, fig: Figure | SubFigure) -> None: ... + def set_offset( + self, + xy: tuple[float, float] + | Callable[[float, float, float, float, RendererBase], tuple[float, float]], + ) -> None: ... + + @overload + def get_offset(self, bbox: Bbox, renderer: RendererBase) -> tuple[float, float]: ... + @overload + def get_offset( + self, + width: float, + height: float, + xdescent: float, + ydescent: float, + renderer: RendererBase + ) -> tuple[float, float]: ... + + def set_width(self, width: float) -> None: ... + def set_height(self, height: float) -> None: ... + def get_visible_children(self) -> list[martist.Artist]: ... + def get_children(self) -> list[martist.Artist]: ... + def get_bbox(self, renderer: RendererBase) -> Bbox: ... + def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... + +class PackerBase(OffsetBox): + height: float | None + width: float | None + sep: float | None + pad: float | None + mode: Literal["fixed", "expand", "equal"] + align: Literal["top", "bottom", "left", "right", "center", "baseline"] + def __init__( + self, + pad: float | None = ..., + sep: float | None = ..., + width: float | None = ..., + height: float | None = ..., + align: Literal["top", "bottom", "left", "right", "center", "baseline"] = ..., + mode: Literal["fixed", "expand", "equal"] = ..., + children: list[martist.Artist] | None = ..., + ) -> None: ... + +class VPacker(PackerBase): ... +class HPacker(PackerBase): ... + +class PaddedBox(OffsetBox): + pad: float | None + patch: FancyBboxPatch + def __init__( + self, + child: martist.Artist, + pad: float | None = ..., + *, + draw_frame: bool = ..., + patch_attrs: dict[str, Any] | None = ..., + ) -> None: ... + def update_frame(self, bbox: Bbox, fontsize: float | None = ...) -> None: ... + def draw_frame(self, renderer: RendererBase) -> None: ... + +class DrawingArea(OffsetBox): + width: float + height: float + xdescent: float + ydescent: float + offset_transform: Transform + dpi_transform: Transform + def __init__( + self, + width: float, + height: float, + xdescent: float = ..., + ydescent: float = ..., + clip: bool = ..., + ) -> None: ... + @property + def clip_children(self) -> bool: ... + @clip_children.setter + def clip_children(self, val: bool) -> None: ... + def get_transform(self) -> Transform: ... + + # does not accept all options of superclass + def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override] + def get_offset(self) -> tuple[float, float]: ... # type: ignore[override] + def add_artist(self, a: martist.Artist) -> None: ... + +class TextArea(OffsetBox): + offset_transform: Transform + def __init__( + self, + s: str, + *, + textprops: dict[str, Any] | None = ..., + multilinebaseline: bool = ..., + ) -> None: ... + def set_text(self, s: str) -> None: ... + def get_text(self) -> str: ... + def set_multilinebaseline(self, t: bool) -> None: ... + def get_multilinebaseline(self) -> bool: ... + + # does not accept all options of superclass + def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override] + def get_offset(self) -> tuple[float, float]: ... # type: ignore[override] + +class AuxTransformBox(OffsetBox): + aux_transform: Transform + offset_transform: Transform + ref_offset_transform: Transform + def __init__(self, aux_transform: Transform) -> None: ... + def add_artist(self, a: martist.Artist) -> None: ... + def get_transform(self) -> Transform: ... + + # does not accept all options of superclass + def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override] + def get_offset(self) -> tuple[float, float]: ... # type: ignore[override] + +class AnchoredOffsetbox(OffsetBox): + zorder: float + codes: dict[str, int] + loc: int + borderpad: float + pad: float + prop: FontProperties + patch: FancyBboxPatch + def __init__( + self, + loc: str, + *, + pad: float = ..., + borderpad: float = ..., + child: OffsetBox | None = ..., + prop: FontProperties | None = ..., + frameon: bool = ..., + bbox_to_anchor: BboxBase + | tuple[float, float] + | tuple[float, float, float, float] + | None = ..., + bbox_transform: Transform | None = ..., + **kwargs + ) -> None: ... + def set_child(self, child: OffsetBox | None) -> None: ... + def get_child(self) -> OffsetBox | None: ... + def get_children(self) -> list[martist.Artist]: ... + def get_bbox_to_anchor(self) -> Bbox: ... + def set_bbox_to_anchor( + self, bbox: BboxBase, transform: Transform | None = ... + ) -> None: ... + def update_frame(self, bbox: Bbox, fontsize: float | None = ...) -> None: ... + +class AnchoredText(AnchoredOffsetbox): + txt: TextArea + def __init__( + self, + s: str, + loc: str, + *, + pad: float = ..., + borderpad: float = ..., + prop: dict[str, Any] | None = ..., + **kwargs + ) -> None: ... + +class OffsetImage(OffsetBox): + image: BboxImage + def __init__( + self, + arr: ArrayLike, + *, + zoom: float = ..., + cmap: Colormap | str | None = ..., + norm: Normalize | str | None = ..., + interpolation: str | None = ..., + origin: Literal["upper", "lower"] | None = ..., + filternorm: bool = ..., + filterrad: float = ..., + resample: bool = ..., + dpi_cor: bool = ..., + **kwargs + ) -> None: ... + stale: bool + def set_data(self, arr: ArrayLike | None) -> None: ... + def get_data(self) -> ArrayLike | None: ... + def set_zoom(self, zoom: float) -> None: ... + def get_zoom(self) -> float: ... + def get_children(self) -> list[martist.Artist]: ... + def get_offset(self) -> tuple[float, float]: ... # type: ignore[override] + +class AnnotationBbox(martist.Artist, mtext._AnnotationBase): + zorder: float + offsetbox: OffsetBox + arrowprops: dict[str, Any] | None + xybox: tuple[float, float] + boxcoords: CoordsType + arrow_patch: FancyArrowPatch | None + patch: FancyBboxPatch + prop: FontProperties + def __init__( + self, + offsetbox: OffsetBox, + xy: tuple[float, float], + xybox: tuple[float, float] | None = ..., + xycoords: CoordsType = ..., + boxcoords: CoordsType | None = ..., + *, + frameon: bool = ..., + pad: float = ..., + annotation_clip: bool | None = ..., + box_alignment: tuple[float, float] = ..., + bboxprops: dict[str, Any] | None = ..., + arrowprops: dict[str, Any] | None = ..., + fontsize: float | str | None = ..., + **kwargs + ) -> None: ... + @property + def xyann(self) -> tuple[float, float]: ... + @xyann.setter + def xyann(self, xyann: tuple[float, float]) -> None: ... + @property + def anncoords( + self, + ) -> CoordsType: ... + @anncoords.setter + def anncoords( + self, + coords: CoordsType, + ) -> None: ... + def get_children(self) -> list[martist.Artist]: ... + def set_figure(self, fig: Figure | SubFigure) -> None: ... + def set_fontsize(self, s: str | float | None = ...) -> None: ... + def get_fontsize(self) -> float: ... + def get_tightbbox(self, renderer: RendererBase | None = ...) -> Bbox: ... + def update_positions(self, renderer: RendererBase) -> None: ... + +class DraggableBase: + ref_artist: martist.Artist + got_artist: bool + mouse_x: int + mouse_y: int + background: Any + + @property + def canvas(self) -> FigureCanvasBase: ... + @property + def cids(self) -> list[int]: ... + + def __init__(self, ref_artist: martist.Artist, use_blit: bool = ...) -> None: ... + def on_motion(self, evt: Event) -> None: ... + def on_pick(self, evt: Event) -> None: ... + def on_release(self, event: Event) -> None: ... + def disconnect(self) -> None: ... + def save_offset(self) -> None: ... + def update_offset(self, dx: float, dy: float) -> None: ... + def finalize_offset(self) -> None: ... + +class DraggableOffsetBox(DraggableBase): + offsetbox: OffsetBox + def __init__( + self, ref_artist: martist.Artist, offsetbox: OffsetBox, use_blit: bool = ... + ) -> None: ... + def save_offset(self) -> None: ... + def update_offset(self, dx: float, dy: float) -> None: ... + def get_loc_in_canvas(self) -> tuple[float, float]: ... + +class DraggableAnnotation(DraggableBase): + annotation: mtext.Annotation + def __init__(self, annotation: mtext.Annotation, use_blit: bool = ...) -> None: ... + def save_offset(self) -> None: ... + def update_offset(self, dx: float, dy: float) -> None: ... diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 859d495111cb..63453d416b99 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1,23 +1,31 @@ -import contextlib +r""" +Patches are `.Artist`\s with a face color and an edge color. +""" + import functools import inspect import math -from numbers import Number +from numbers import Number, Real import textwrap +from types import SimpleNamespace +from collections import namedtuple +from matplotlib.transforms import Affine2D import numpy as np import matplotlib as mpl -from . import (artist, cbook, colors, docstring, hatch as mhatch, +from . import (_api, artist, cbook, colors, _docstring, hatch as mhatch, lines as mlines, transforms) from .bezier import ( NonIntersectingPathException, get_cos_sin, get_intersection, get_parallels, inside_circle, make_wedged_bezier2, split_bezier_intersecting_with_closedpath, split_path_inout) from .path import Path +from ._enums import JoinStyle, CapStyle -@cbook._define_aliases({ +@_docstring.interpd +@_api.define_aliases({ "antialiased": ["aa"], "edgecolor": ["ec"], "facecolor": ["fc"], @@ -32,14 +40,12 @@ class Patch(artist.Artist): are *None*, they default to their rc params setting. """ zorder = 1 - validCap = mlines.Line2D.validCap - validJoin = mlines.Line2D.validJoin # Whether to draw an edge by default. Set on a # subclass-by-subclass basis. _edge_default = False - def __init__(self, + def __init__(self, *, edgecolor=None, facecolor=None, color=None, @@ -50,41 +56,39 @@ def __init__(self, fill=True, capstyle=None, joinstyle=None, + hatchcolor=None, **kwargs): """ The following kwarg properties are supported - %(Patch)s + %(Patch:kwdoc)s """ super().__init__() - if linewidth is None: - linewidth = mpl.rcParams['patch.linewidth'] if linestyle is None: linestyle = "solid" if capstyle is None: - capstyle = 'butt' + capstyle = CapStyle.butt if joinstyle is None: - joinstyle = 'miter' - if antialiased is None: - antialiased = mpl.rcParams['patch.antialiased'] + joinstyle = JoinStyle.miter - self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color']) - self._fill = True # needed for set_facecolor call + self._hatch_linewidth = mpl.rcParams['hatch.linewidth'] + self._fill = bool(fill) # needed for set_facecolor call if color is not None: if edgecolor is not None or facecolor is not None: - cbook._warn_external( + _api.warn_external( "Setting the 'color' property will override " "the edgecolor or facecolor properties.") self.set_color(color) else: self.set_edgecolor(edgecolor) + self.set_hatchcolor(hatchcolor) self.set_facecolor(facecolor) - # unscaled dashes. Needed to scale dash patterns by lw - self._us_dashes = None + self._linewidth = 0 + self._unscaled_dash_pattern = (0, None) # offset, dash + self._dash_pattern = (0, None) # offset, dash (scaled by linewidth) - self.set_fill(fill) self.set_linestyle(linestyle) self.set_linewidth(linewidth) self.set_antialiased(antialiased) @@ -93,13 +97,13 @@ def __init__(self, self.set_joinstyle(joinstyle) if len(kwargs): - self.update(kwargs) + self._internal_update(kwargs) def get_verts(self): """ Return a copy of the vertices used in this patch. - If the patch contains Bezier curves, the curves will be interpolated by + If the patch contains Bézier curves, the curves will be interpolated by line segments. To access the curves as curves, use `get_path`. """ trans = self.get_transform() @@ -125,13 +129,35 @@ def contains(self, mouseevent, radius=None): """ Test whether the mouse event occurred in the patch. + Parameters + ---------- + mouseevent : `~matplotlib.backend_bases.MouseEvent` + Where the user clicked. + + radius : float, optional + Additional margin on the patch in target coordinates of + `.Patch.get_transform`. See `.Path.contains_point` for further + details. + + If `None`, the default value depends on the state of the object: + + - If `.Artist.get_picker` is a number, the default + is that value. This is so that picking works as expected. + - Otherwise if the edge color has a non-zero alpha, the default + is half of the linewidth. This is so that all the colored + pixels are "in" the patch. + - Finally, if the edge has 0 alpha, the default is 0. This is + so that patches without a stroked edge do not have points + outside of the filled region report as "in" due to an + invisible edge. + + Returns ------- (bool, empty dict) """ - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} radius = self._process_radius(radius) codes = self.get_path().codes if codes is not None: @@ -159,13 +185,25 @@ def contains_point(self, point, radius=None): ---------- point : (float, float) The point (x, y) to check, in target coordinates of - ``self.get_transform()``. These are display coordinates for patches - that are added to a figure or axes. + ``.Patch.get_transform()``. These are display coordinates for patches + that are added to a figure or Axes. radius : float, optional - Add an additional margin on the patch in target coordinates of - ``self.get_transform()``. See `.Path.contains_point` for further + Additional margin on the patch in target coordinates of + `.Patch.get_transform`. See `.Path.contains_point` for further details. + If `None`, the default value depends on the state of the object: + + - If `.Artist.get_picker` is a number, the default + is that value. This is so that picking works as expected. + - Otherwise if the edge color has a non-zero alpha, the default + is half of the linewidth. This is so that all the colored + pixels are "in" the patch. + - Finally, if the edge has 0 alpha, the default is 0. This is + so that patches without a stroked edge do not have points + outside of the filled region report as "in" due to an + invisible edge. + Returns ------- bool @@ -189,10 +227,10 @@ def contains_point(self, point, radius=None): transform them first: >>> center = 0, 0 - >>> c = Circle(center, radius=1) + >>> c = Circle(center, radius=3) >>> plt.gca().add_patch(c) - >>> transformed_center = c.get_transform().transform(center) - >>> c.contains_point(transformed_center) + >>> transformed_interior_point = c.get_data_transform().transform((0, 2)) + >>> c.contains_point(transformed_interior_point) True """ @@ -210,12 +248,24 @@ def contains_points(self, points, radius=None): points : (N, 2) array The points to check, in target coordinates of ``self.get_transform()``. These are display coordinates for patches - that are added to a figure or axes. Columns contain x and y values. + that are added to a figure or Axes. Columns contain x and y values. radius : float, optional - Add an additional margin on the patch in target coordinates of - ``self.get_transform()``. See `.Path.contains_point` for further + Additional margin on the patch in target coordinates of + `.Patch.get_transform`. See `.Path.contains_point` for further details. + If `None`, the default value depends on the state of the object: + + - If `.Artist.get_picker` is a number, the default + is that value. This is so that picking works as expected. + - Otherwise if the edge color has a non-zero alpha, the default + is half of the linewidth. This is so that all the colored + pixels are "in" the patch. + - Finally, if the edge has 0 alpha, the default is 0. This is + so that patches without a stroked edge do not have points + outside of the filled region report as "in" due to an + invisible edge. + Returns ------- length-N bool array @@ -242,9 +292,9 @@ def update_from(self, other): self._fill = other._fill self._hatch = other._hatch self._hatch_color = other._hatch_color - # copy the unscaled dash pattern - self._us_dashes = other._us_dashes - self.set_linewidth(other._linewidth) # also sets dash properties + self._original_hatchcolor = other._original_hatchcolor + self._unscaled_dash_pattern = other._unscaled_dash_pattern + self.set_linewidth(other._linewidth) # also sets scaled dashes self.set_transform(other.get_data_transform()) # If the transform of other needs further initialization, then it will # be the case for this artist too. @@ -290,6 +340,14 @@ def get_facecolor(self): """Return the face color.""" return self._facecolor + def get_hatchcolor(self): + """Return the hatch color.""" + if self._hatch_color == 'edge': + if self._edgecolor[3] == 0: # fully transparent + return colors.to_rgba(mpl.rcParams['patch.edgecolor']) + return self.get_edgecolor() + return self._hatch_color + def get_linewidth(self): """Return the line width in points.""" return self._linewidth @@ -304,26 +362,20 @@ def set_antialiased(self, aa): Parameters ---------- - b : bool or None + aa : bool or None """ - if aa is None: - aa = mpl.rcParams['patch.antialiased'] - self._antialiased = aa + self._antialiased = mpl._val_or_rc(aa, 'patch.antialiased') self.stale = True def _set_edgecolor(self, color): - set_hatch_color = True if color is None: if (mpl.rcParams['patch.force_edgecolor'] or not self._fill or self._edge_default): color = mpl.rcParams['patch.edgecolor'] else: color = 'none' - set_hatch_color = False self._edgecolor = colors.to_rgba(color, self._alpha) - if set_hatch_color: - self._hatch_color = self._edgecolor self.stale = True def set_edgecolor(self, color): @@ -332,14 +384,13 @@ def set_edgecolor(self, color): Parameters ---------- - color : color or None or 'auto' + color : :mpltype:`color` or None """ self._original_edgecolor = color self._set_edgecolor(color) def _set_facecolor(self, color): - if color is None: - color = mpl.rcParams['patch.facecolor'] + color = mpl._val_or_rc(color, 'patch.facecolor') alpha = self._alpha if self._fill else 0 self._facecolor = colors.to_rgba(color, alpha) self.stale = True @@ -350,7 +401,7 @@ def set_facecolor(self, color): Parameters ---------- - color : color or None + color : :mpltype:`color` or None """ self._original_facecolor = color self._set_facecolor(color) @@ -361,21 +412,42 @@ def set_color(self, c): Parameters ---------- - c : color + c : :mpltype:`color` See Also -------- Patch.set_facecolor, Patch.set_edgecolor For setting the edge or face color individually. """ - self.set_facecolor(c) self.set_edgecolor(c) + self.set_hatchcolor(c) + self.set_facecolor(c) + + def _set_hatchcolor(self, color): + color = mpl._val_or_rc(color, 'hatch.color') + if cbook._str_equal(color, 'edge'): + self._hatch_color = 'edge' + else: + self._hatch_color = colors.to_rgba(color, self._alpha) + self.stale = True + + def set_hatchcolor(self, color): + """ + Set the patch hatch color. + + Parameters + ---------- + color : :mpltype:`color` or 'edge' or None + """ + self._original_hatchcolor = color + self._set_hatchcolor(color) def set_alpha(self, alpha): # docstring inherited super().set_alpha(alpha) self._set_facecolor(self._original_facecolor) self._set_edgecolor(self._original_edgecolor) + self._set_hatchcolor(self._original_hatchcolor) # stale is already True def set_linewidth(self, w): @@ -386,30 +458,24 @@ def set_linewidth(self, w): ---------- w : float or None """ - if w is None: - w = mpl.rcParams['patch.linewidth'] - if w is None: - w = mpl.rcParams['axes.linewidth'] - + w = mpl._val_or_rc(w, 'patch.linewidth') self._linewidth = float(w) - # scale the dash pattern by the linewidth - offset, ls = self._us_dashes - self._dashoffset, self._dashes = mlines._scale_dashes( - offset, ls, self._linewidth) + self._dash_pattern = mlines._scale_dashes(*self._unscaled_dash_pattern, w) self.stale = True def set_linestyle(self, ls): """ Set the patch linestyle. - =========================== ================= - linestyle description - =========================== ================= - ``'-'`` or ``'solid'`` solid line - ``'--'`` or ``'dashed'`` dashed line - ``'-.'`` or ``'dashdot'`` dash-dotted line - ``':'`` or ``'dotted'`` dotted line - =========================== ================= + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ Alternatively a dash tuple of the following form can be provided:: @@ -424,12 +490,12 @@ def set_linestyle(self, ls): """ if ls is None: ls = "solid" + if ls in [' ', '', 'none']: + ls = 'None' self._linestyle = ls - # get the unscaled dash pattern - offset, ls = self._us_dashes = mlines._get_dash_pattern(ls) - # scale the dash pattern by the linewidth - self._dashoffset, self._dashes = mlines._scale_dashes( - offset, ls, self._linewidth) + self._unscaled_dash_pattern = mlines._get_dash_pattern(ls) + self._dash_pattern = mlines._scale_dashes( + *self._unscaled_dash_pattern, self._linewidth) self.stale = True def set_fill(self, b): @@ -443,6 +509,7 @@ def set_fill(self, b): self._fill = bool(b) self._set_facecolor(self._original_facecolor) self._set_edgecolor(self._original_edgecolor) + self._set_hatchcolor(self._original_hatchcolor) self.stale = True def get_fill(self): @@ -454,37 +521,45 @@ def get_fill(self): # attribute. fill = property(get_fill, set_fill) + @_docstring.interpd def set_capstyle(self, s): """ - Set the capstyle. + Set the `.CapStyle`. + + The default capstyle is 'round' for `.FancyArrowPatch` and 'butt' for + all other patches. Parameters ---------- - s : {'butt', 'round', 'projecting'} + s : `.CapStyle` or %(CapStyle)s """ - mpl.rcsetup.validate_capstyle(s) - self._capstyle = s + cs = CapStyle(s) + self._capstyle = cs self.stale = True def get_capstyle(self): """Return the capstyle.""" - return self._capstyle + return self._capstyle.name + @_docstring.interpd def set_joinstyle(self, s): """ - Set the joinstyle. + Set the `.JoinStyle`. + + The default joinstyle is 'round' for `.FancyArrowPatch` and 'miter' for + all other patches. Parameters ---------- - s : {'miter', 'round', 'bevel'} + s : `.JoinStyle` or %(JoinStyle)s """ - mpl.rcsetup.validate_joinstyle(s) - self._joinstyle = s + js = JoinStyle(s) + self._joinstyle = js self.stale = True def get_joinstyle(self): """Return the joinstyle.""" - return self._joinstyle + return self._joinstyle.name def set_hatch(self, hatch): r""" @@ -507,9 +582,6 @@ def set_hatch(self, hatch): hatchings are done. If same letter repeats, it increases the density of hatching of that pattern. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. - Parameters ---------- hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} @@ -523,15 +595,23 @@ def get_hatch(self): """Return the hatching pattern.""" return self._hatch - @contextlib.contextmanager - def _bind_draw_path_function(self, renderer): + def set_hatch_linewidth(self, lw): + """Set the hatch linewidth.""" + self._hatch_linewidth = lw + + def get_hatch_linewidth(self): + """Return the hatch linewidth.""" + return self._hatch_linewidth + + def _draw_paths_with_artist_properties( + self, renderer, draw_path_args_list): """ ``draw()`` helper factored out for sharing with `FancyArrowPatch`. - Yields a callable ``dp`` such that calling ``dp(*args, **kwargs)`` is - equivalent to calling ``renderer1.draw_path(gc, *args, **kwargs)`` - where ``renderer1`` and ``gc`` have been suitably set from ``renderer`` - and the artist's properties. + Configure *renderer* and the associated graphics context *gc* + from the artist properties, then repeatedly call + ``renderer.draw_path(gc, *draw_path_args)`` for each tuple + *draw_path_args* in *draw_path_args_list*. """ renderer.open_group('patch', self.get_gid()) @@ -540,10 +620,10 @@ def _bind_draw_path_function(self, renderer): gc.set_foreground(self._edgecolor, isRGBA=True) lw = self._linewidth - if self._edgecolor[3] == 0: + if self._edgecolor[3] == 0 or self._linestyle == 'None': lw = 0 gc.set_linewidth(lw) - gc.set_dashes(self._dashoffset, self._dashes) + gc.set_dashes(*self._dash_pattern) gc.set_capstyle(self._capstyle) gc.set_joinstyle(self._joinstyle) @@ -556,7 +636,8 @@ def _bind_draw_path_function(self, renderer): if self._hatch: gc.set_hatch(self._hatch) - gc.set_hatch_color(self._hatch_color) + gc.set_hatch_color(self.get_hatchcolor()) + gc.set_hatch_linewidth(self._hatch_linewidth) if self.get_sketch_params() is not None: gc.set_sketch_params(*self.get_sketch_params()) @@ -565,11 +646,8 @@ def _bind_draw_path_function(self, renderer): from matplotlib.patheffects import PathEffectRenderer renderer = PathEffectRenderer(self.get_path_effects(), renderer) - # In `with _bind_draw_path_function(renderer) as draw_path: ...` - # (in the implementations of `draw()` below), calls to `draw_path(...)` - # will occur as if they took place here with `gc` inserted as - # additional first argument. - yield functools.partial(renderer.draw_path, gc) + for draw_path_args in draw_path_args_list: + renderer.draw_path(gc, *draw_path_args) gc.restore() renderer.close_group('patch') @@ -580,18 +658,17 @@ def draw(self, renderer): # docstring inherited if not self.get_visible(): return - # Patch has traditionally ignored the dashoffset. - with cbook._setattr_cm(self, _dashoffset=0), \ - self._bind_draw_path_function(renderer) as draw_path: - path = self.get_path() - transform = self.get_transform() - tpath = transform.transform_path_non_affine(path) - affine = transform.get_affine() - draw_path(tpath, affine, - # Work around a bug in the PDF and SVG renderers, which - # do not draw the hatches if the facecolor is fully - # transparent, but do if it is None. - self._facecolor if self._facecolor[3] else None) + path = self.get_path() + transform = self.get_transform() + tpath = transform.transform_path_non_affine(path) + affine = transform.get_affine() + self._draw_paths_with_artist_properties( + renderer, + [(tpath, affine, + # Work around a bug in the PDF and SVG renderers, which + # do not draw the hatches if the facecolor is fully + # transparent, but do if it is None. + self._facecolor if self._facecolor[3] else None)]) def get_path(self): """Return the path of this patch.""" @@ -607,68 +684,49 @@ def _convert_xy_units(self, xy): return x, y -patchdoc = artist.kwdoc(Patch) -for k in ['Rectangle', 'Circle', 'RegularPolygon', 'Polygon', 'Wedge', 'Arrow', - 'FancyArrow', 'CirclePolygon', 'Ellipse', 'Arc', 'FancyBboxPatch', - 'Patch']: - docstring.interpd.update({k: patchdoc}) - -# define Patch.__init__ docstring after the class has been added to interpd -docstring.dedent_interpd(Patch.__init__) - - class Shadow(Patch): def __str__(self): - return "Shadow(%s)" % (str(self.patch)) + return f"Shadow({self.patch})" - @cbook._delete_parameter("3.3", "props") - @docstring.dedent_interpd - def __init__(self, patch, ox, oy, props=None, **kwargs): + @_docstring.interpd + def __init__(self, patch, ox, oy, *, shade=0.7, **kwargs): """ Create a shadow of the given *patch*. By default, the shadow will have the same face color as the *patch*, - but darkened. + but darkened. The darkness can be controlled by *shade*. Parameters ---------- - patch : `.Patch` + patch : `~matplotlib.patches.Patch` The patch to create the shadow for. ox, oy : float The shift of the shadow in data coordinates, scaled by a factor of dpi/72. - props : dict - *deprecated (use kwargs instead)* Properties of the shadow patch. + shade : float, default: 0.7 + How the darkness of the shadow relates to the original color. If 1, the + shadow is black, if 0, the shadow has the same color as the *patch*. + + .. versionadded:: 3.8 + **kwargs Properties of the shadow patch. Supported keys are: - %(Patch)s + %(Patch:kwdoc)s """ super().__init__() self.patch = patch - # Note: when removing props, we can directly pass kwargs to _update() - # and remove self._props - if props is None: - color = .3 * np.asarray(colors.to_rgb(self.patch.get_facecolor())) - props = { - 'facecolor': color, - 'edgecolor': color, - 'alpha': 0.5, - } - self._props = {**props, **kwargs} self._ox, self._oy = ox, oy self._shadow_transform = transforms.Affine2D() - self._update() - - props = cbook._deprecate_privatize_attribute("3.3") - def _update(self): self.update_from(self.patch) - - # Place the shadow patch directly behind the inherited patch. - self.set_zorder(np.nextafter(self.patch.zorder, -np.inf)) - - self.update(self._props) + if not 0 <= shade <= 1: + raise ValueError("shade must be between 0 and 1.") + color = (1 - shade) * np.asarray(colors.to_rgb(self.patch.get_facecolor())) + self.update({'facecolor': color, 'edgecolor': color, 'alpha': 0.5, + # Place shadow patch directly behind the inherited patch. + 'zorder': np.nextafter(self.patch.zorder, -np.inf), + **kwargs}) def _update_transform(self, renderer): ox = renderer.points_to_pixels(self._ox) @@ -700,7 +758,7 @@ class Rectangle(Patch): : (xy)---- width -----+ One may picture *xy* as the bottom left corner, but which corner *xy* is - actually depends on the the direction of the axis and the sign of *width* + actually depends on the direction of the axis and the sign of *width* and *height*; e.g. *xy* would be the bottom right corner if the x-axis was inverted or if *width* was negative. """ @@ -710,8 +768,9 @@ def __str__(self): fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)" return fmt % pars - @docstring.dedent_interpd - def __init__(self, xy, width, height, angle=0.0, **kwargs): + @_docstring.interpd + def __init__(self, xy, width, height, *, + angle=0.0, rotation_point='xy', **kwargs): """ Parameters ---------- @@ -722,66 +781,81 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs): height : float Rectangle height. angle : float, default: 0 - Rotation in degrees anti-clockwise about *xy*. + Rotation in degrees anti-clockwise about the rotation point. + rotation_point : {'xy', 'center', (number, number)}, default: 'xy' + If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate + around the center. If 2-tuple of number, rotate around this + coordinate. Other Parameters ---------------- - **kwargs : `.Patch` properties - %(Patch)s + **kwargs : `~matplotlib.patches.Patch` properties + %(Patch:kwdoc)s """ - super().__init__(**kwargs) - self._x0 = xy[0] self._y0 = xy[1] - self._width = width self._height = height - - self._x1 = self._x0 + self._width - self._y1 = self._y0 + self._height - self.angle = float(angle) - # Note: This cannot be calculated until this is added to an Axes - self._rect_transform = transforms.IdentityTransform() + self.rotation_point = rotation_point + # Required for RectangleSelector with axes aspect ratio != 1 + # The patch is defined in data coordinates and when changing the + # selector with square modifier and not in data coordinates, we need + # to correct for the aspect ratio difference between the data and + # display coordinate systems. Its value is typically provide by + # Axes._get_aspect_ratio() + self._aspect_ratio_correction = 1.0 + self._convert_units() # Validate the inputs. def get_path(self): """Return the vertices of the rectangle.""" return Path.unit_rectangle() - def _update_patch_transform(self): - """ - Notes - ----- - This cannot be called until after this has been added to an Axes, - otherwise unit conversion will fail. This makes it very important to - call the accessor method and not directly access the transformation - member variable. - """ - x0, y0, x1, y1 = self._convert_units() - bbox = transforms.Bbox.from_extents(x0, y0, x1, y1) - rot_trans = transforms.Affine2D() - rot_trans.rotate_deg_around(x0, y0, self.angle) - self._rect_transform = transforms.BboxTransformTo(bbox) - self._rect_transform += rot_trans - - def _update_x1(self): - self._x1 = self._x0 + self._width - - def _update_y1(self): - self._y1 = self._y0 + self._height - def _convert_units(self): """Convert bounds of the rectangle.""" x0 = self.convert_xunits(self._x0) y0 = self.convert_yunits(self._y0) - x1 = self.convert_xunits(self._x1) - y1 = self.convert_yunits(self._y1) + x1 = self.convert_xunits(self._x0 + self._width) + y1 = self.convert_yunits(self._y0 + self._height) return x0, y0, x1, y1 def get_patch_transform(self): - self._update_patch_transform() - return self._rect_transform + # Note: This cannot be called until after this has been added to + # an Axes, otherwise unit conversion will fail. This makes it very + # important to call the accessor method and not directly access the + # transformation member variable. + bbox = self.get_bbox() + if self.rotation_point == 'center': + width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0 + rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2. + elif self.rotation_point == 'xy': + rotation_point = bbox.x0, bbox.y0 + else: + rotation_point = self.rotation_point + return transforms.BboxTransformTo(bbox) \ + + transforms.Affine2D() \ + .translate(-rotation_point[0], -rotation_point[1]) \ + .scale(1, self._aspect_ratio_correction) \ + .rotate_deg(self.angle) \ + .scale(1, 1 / self._aspect_ratio_correction) \ + .translate(*rotation_point) + + @property + def rotation_point(self): + """The rotation point of the patch.""" + return self._rotation_point + + @rotation_point.setter + def rotation_point(self, value): + if value in ['center', 'xy'] or ( + isinstance(value, tuple) and len(value) == 2 and + isinstance(value[0], Real) and isinstance(value[1], Real) + ): + self._rotation_point = value + else: + raise ValueError("`rotation_point` must be one of " + "{'xy', 'center', (number, number)}.") def get_x(self): """Return the left coordinate of the rectangle.""" @@ -795,6 +869,18 @@ def get_xy(self): """Return the left and bottom coords of the rectangle as a tuple.""" return self._x0, self._y0 + def get_corners(self): + """ + Return the corners of the rectangle, moving anti-clockwise from + (x0, y0). + """ + return self.get_patch_transform().transform( + [(0, 0), (1, 0), (1, 1), (0, 1)]) + + def get_center(self): + """Return the centre of the rectangle.""" + return self.get_patch_transform().transform((0.5, 0.5)) + def get_width(self): """Return the width of the rectangle.""" return self._width @@ -803,16 +889,27 @@ def get_height(self): """Return the height of the rectangle.""" return self._height + def get_angle(self): + """Get the rotation angle in degrees.""" + return self.angle + def set_x(self, x): """Set the left coordinate of the rectangle.""" self._x0 = x - self._update_x1() self.stale = True def set_y(self, y): """Set the bottom coordinate of the rectangle.""" self._y0 = y - self._update_y1() + self.stale = True + + def set_angle(self, angle): + """ + Set the rotation angle in degrees. + + The rotation is performed anti-clockwise around *xy*. + """ + self.angle = angle self.stale = True def set_xy(self, xy): @@ -824,20 +921,16 @@ def set_xy(self, xy): xy : (float, float) """ self._x0, self._y0 = xy - self._update_x1() - self._update_y1() self.stale = True def set_width(self, w): """Set the width of the rectangle.""" self._width = w - self._update_x1() self.stale = True def set_height(self, h): """Set the height of the rectangle.""" self._height = h - self._update_y1() self.stale = True def set_bounds(self, *args): @@ -859,14 +952,11 @@ def set_bounds(self, *args): self._y0 = b self._width = w self._height = h - self._update_x1() - self._update_y1() self.stale = True def get_bbox(self): """Return the `.Bbox`.""" - x0, y0, x1, y1 = self._convert_units() - return transforms.Bbox.from_extents(x0, y0, x1, y1) + return transforms.Bbox.from_extents(*self._convert_units()) xy = property(get_xy, set_xy) @@ -876,12 +966,12 @@ class RegularPolygon(Patch): def __str__(self): s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)" - return s % (self._xy[0], self._xy[1], self._numVertices, self._radius, - self._orientation) + return s % (self.xy[0], self.xy[1], self.numvertices, self.radius, + self.orientation) - @docstring.dedent_interpd - def __init__(self, xy, numVertices, radius=5, orientation=0, - **kwargs): + @_docstring.interpd + def __init__(self, xy, numVertices, *, + radius=5, orientation=0, **kwargs): """ Parameters ---------- @@ -900,65 +990,24 @@ def __init__(self, xy, numVertices, radius=5, orientation=0, **kwargs `Patch` properties: - %(Patch)s + %(Patch:kwdoc)s """ - self._xy = xy - self._numVertices = numVertices - self._orientation = orientation - self._radius = radius + self.xy = xy + self.numvertices = numVertices + self.orientation = orientation + self.radius = radius self._path = Path.unit_regular_polygon(numVertices) - self._poly_transform = transforms.Affine2D() - self._update_transform() - + self._patch_transform = transforms.Affine2D() super().__init__(**kwargs) - def _update_transform(self): - self._poly_transform.clear() \ - .scale(self.radius) \ - .rotate(self.orientation) \ - .translate(*self.xy) - - @property - def xy(self): - return self._xy - - @xy.setter - def xy(self, xy): - self._xy = xy - self._update_transform() - - @property - def orientation(self): - return self._orientation - - @orientation.setter - def orientation(self, orientation): - self._orientation = orientation - self._update_transform() - - @property - def radius(self): - return self._radius - - @radius.setter - def radius(self, radius): - self._radius = radius - self._update_transform() - - @property - def numvertices(self): - return self._numVertices - - @numvertices.setter - def numvertices(self, numVertices): - self._numVertices = numVertices - def get_path(self): return self._path def get_patch_transform(self): - self._update_transform() - return self._poly_transform + return self._patch_transform.clear() \ + .scale(self.radius) \ + .rotate(self.orientation) \ + .translate(*self.xy) class PathPatch(Patch): @@ -970,14 +1019,14 @@ def __str__(self): s = "PathPatch%d((%g, %g) ...)" return s % (len(self._path.vertices), *tuple(self._path.vertices[0])) - @docstring.dedent_interpd + @_docstring.interpd def __init__(self, path, **kwargs): """ - *path* is a `~.path.Path` object. + *path* is a `.Path` object. Valid keyword arguments are: - %(Patch)s + %(Patch:kwdoc)s """ super().__init__(**kwargs) self._path = path @@ -989,24 +1038,138 @@ def set_path(self, path): self._path = path +class StepPatch(PathPatch): + """ + A path patch describing a stepwise constant function. + + By default, the path is not closed and starts and stops at + baseline value. + """ + + _edge_default = False + + @_docstring.interpd + def __init__(self, values, edges, *, + orientation='vertical', baseline=0, **kwargs): + """ + Parameters + ---------- + values : array-like + The step heights. + + edges : array-like + The edge positions, with ``len(edges) == len(vals) + 1``, + between which the curve takes on vals values. + + orientation : {'vertical', 'horizontal'}, default: 'vertical' + The direction of the steps. Vertical means that *values* are + along the y-axis, and edges are along the x-axis. + + baseline : float, array-like or None, default: 0 + The bottom value of the bounding edges or when + ``fill=True``, position of lower edge. If *fill* is + True or an array is passed to *baseline*, a closed + path is drawn. + + **kwargs + `Patch` properties: + + %(Patch:kwdoc)s + """ + self.orientation = orientation + self._edges = np.asarray(edges) + self._values = np.asarray(values) + self._baseline = np.asarray(baseline) if baseline is not None else None + self._update_path() + super().__init__(self._path, **kwargs) + + def _update_path(self): + if np.isnan(np.sum(self._edges)): + raise ValueError('Nan values in "edges" are disallowed') + if self._edges.size - 1 != self._values.size: + raise ValueError('Size mismatch between "values" and "edges". ' + "Expected `len(values) + 1 == len(edges)`, but " + f"`len(values) = {self._values.size}` and " + f"`len(edges) = {self._edges.size}`.") + # Initializing with empty arrays allows supporting empty stairs. + verts, codes = [np.empty((0, 2))], [np.empty(0, dtype=Path.code_type)] + + _nan_mask = np.isnan(self._values) + if self._baseline is not None: + _nan_mask |= np.isnan(self._baseline) + for idx0, idx1 in cbook.contiguous_regions(~_nan_mask): + x = np.repeat(self._edges[idx0:idx1+1], 2) + y = np.repeat(self._values[idx0:idx1], 2) + if self._baseline is None: + y = np.concatenate([y[:1], y, y[-1:]]) + elif self._baseline.ndim == 0: # single baseline value + y = np.concatenate([[self._baseline], y, [self._baseline]]) + elif self._baseline.ndim == 1: # baseline array + base = np.repeat(self._baseline[idx0:idx1], 2)[::-1] + x = np.concatenate([x, x[::-1]]) + y = np.concatenate([base[-1:], y, base[:1], + base[:1], base, base[-1:]]) + else: # no baseline + raise ValueError('Invalid `baseline` specified') + if self.orientation == 'vertical': + xy = np.column_stack([x, y]) + else: + xy = np.column_stack([y, x]) + verts.append(xy) + codes.append([Path.MOVETO] + [Path.LINETO]*(len(xy)-1)) + self._path = Path(np.concatenate(verts), np.concatenate(codes)) + + def get_data(self): + """Get `.StepPatch` values, edges and baseline as namedtuple.""" + StairData = namedtuple('StairData', 'values edges baseline') + return StairData(self._values, self._edges, self._baseline) + + def set_data(self, values=None, edges=None, baseline=None): + """ + Set `.StepPatch` values, edges and baseline. + + Parameters + ---------- + values : 1D array-like or None + Will not update values, if passing None + edges : 1D array-like, optional + baseline : float, 1D array-like or None + """ + if values is None and edges is None and baseline is None: + raise ValueError("Must set *values*, *edges* or *baseline*.") + if values is not None: + self._values = np.asarray(values) + if edges is not None: + self._edges = np.asarray(edges) + if baseline is not None: + self._baseline = np.asarray(baseline) + self._update_path() + self.stale = True + + class Polygon(Patch): """A general polygon patch.""" def __str__(self): - s = "Polygon%d((%g, %g) ...)" - return s % (len(self._path.vertices), *tuple(self._path.vertices[0])) + if len(self._path.vertices): + s = "Polygon%d((%g, %g) ...)" + return s % (len(self._path.vertices), *self._path.vertices[0]) + else: + return "Polygon0()" - @docstring.dedent_interpd - def __init__(self, xy, closed=True, **kwargs): + @_docstring.interpd + def __init__(self, xy, *, closed=True, **kwargs): """ - *xy* is a numpy array with shape Nx2. - - If *closed* is *True*, the polygon will be closed so the - starting and ending points are the same. + Parameters + ---------- + xy : (N, 2) array - Valid keyword arguments are: + closed : bool, default: True + Whether the polygon is closed (i.e., has identical start and end + points). - %(Patch)s + **kwargs + %(Patch:kwdoc)s """ super().__init__(**kwargs) self._closed = closed @@ -1027,7 +1190,7 @@ def set_closed(self, closed): Parameters ---------- closed : bool - True if the polygon is closed + True if the polygon is closed """ if self._closed == bool(closed): return @@ -1041,7 +1204,7 @@ def get_xy(self): Returns ------- - (N, 2) numpy array + (N, 2) array The coordinates of the vertices. """ return self._path.vertices @@ -1057,7 +1220,7 @@ def set_xy(self, xy): Notes ----- - Unlike `~.path.Path`, we do not ignore the last input vertex. If the + Unlike `.Path`, we do not ignore the last input vertex. If the polygon is meant to be closed, and the last point of the polygon is not equal to the first, we assume that the user has not explicitly passed a ``CLOSEPOLY`` vertex, and add it ourselves. @@ -1073,14 +1236,14 @@ def set_xy(self, xy): xy = np.concatenate([xy, [xy[0]]]) else: # if we aren't closed, and the last vertex matches the first, then - # we assume we have an unecessary CLOSEPOLY vertex and remove it + # we assume we have an unnecessary CLOSEPOLY vertex and remove it if nverts > 2 and (xy[0] == xy[-1]).all(): xy = xy[:-1] self._path = Path(xy, closed=self._closed) self.stale = True xy = property(get_xy, set_xy, - doc='The vertices of the path as (N, 2) numpy array.') + doc='The vertices of the path as a (N, 2) array.') class Wedge(Patch): @@ -1092,8 +1255,8 @@ def __str__(self): fmt = "Wedge(center=(%g, %g), r=%g, theta1=%g, theta2=%g, width=%s)" return fmt % pars - @docstring.dedent_interpd - def __init__(self, center, r, theta1, theta2, width=None, **kwargs): + @_docstring.interpd + def __init__(self, center, r, theta1, theta2, *, width=None, **kwargs): """ A wedge centered at *x*, *y* center with radius *r* that sweeps *theta1* to *theta2* (in degrees). If *width* is given, @@ -1102,7 +1265,7 @@ def __init__(self, center, r, theta1, theta2, width=None, **kwargs): Valid keyword arguments are: - %(Patch)s + %(Patch:kwdoc)s """ super().__init__(**kwargs) self.center = center @@ -1128,18 +1291,15 @@ def _recompute_path(self): # followed by a reversed and scaled inner ring v1 = arc.vertices v2 = arc.vertices[::-1] * (self.r - self.width) / self.r - v = np.vstack([v1, v2, v1[0, :], (0, 0)]) - c = np.hstack([arc.codes, arc.codes, connector, Path.CLOSEPOLY]) - c[len(arc.codes)] = connector + v = np.concatenate([v1, v2, [(0, 0)]]) + c = [*arc.codes, connector, *arc.codes[1:], Path.CLOSEPOLY] else: # Wedge doesn't need an inner ring - v = np.vstack([arc.vertices, [(0, 0), arc.vertices[0, :], (0, 0)]]) - c = np.hstack([arc.codes, [connector, connector, Path.CLOSEPOLY]]) + v = np.concatenate([arc.vertices, [(0, 0), (0, 0)]]) + c = [*arc.codes, connector, Path.CLOSEPOLY] # Shift and scale the wedge to the final location. - v *= self.r - v += np.asarray(self.center) - self._path = Path(v, c) + self._path = Path(v * self.r + self.center, c) def set_center(self, center): self._path = None @@ -1179,14 +1339,12 @@ class Arrow(Patch): def __str__(self): return "Arrow()" - _path = Path([[0.0, 0.1], [0.0, -0.1], - [0.8, -0.1], [0.8, -0.3], - [1.0, 0.0], [0.8, 0.3], - [0.8, 0.1], [0.0, 0.1]], - closed=True) + _path = Path._create_closed([ + [0.0, 0.1], [0.0, -0.1], [0.8, -0.1], [0.8, -0.3], [1.0, 0.0], + [0.8, 0.3], [0.8, 0.1]]) - @docstring.dedent_interpd - def __init__(self, x, y, dx, dy, width=1.0, **kwargs): + @_docstring.interpd + def __init__(self, x, y, dx, dy, *, width=1.0, **kwargs): """ Draws an arrow from (*x*, *y*) to (*x* + *dx*, *y* + *dy*). The width of the arrow is scaled by *width*. @@ -1207,7 +1365,7 @@ def __init__(self, x, y, dx, dy, width=1.0, **kwargs): **kwargs Keyword arguments control the `Patch` properties: - %(Patch)s + %(Patch:kwdoc)s See Also -------- @@ -1216,12 +1374,7 @@ def __init__(self, x, y, dx, dy, width=1.0, **kwargs): properties. """ super().__init__(**kwargs) - self._patch_transform = ( - transforms.Affine2D() - .scale(np.hypot(dx, dy), width) - .rotate(np.arctan2(dy, dx)) - .translate(x, y) - .frozen()) + self.set_data(x, y, dx, dy, width) def get_path(self): return self._path @@ -1229,6 +1382,39 @@ def get_path(self): def get_patch_transform(self): return self._patch_transform + def set_data(self, x=None, y=None, dx=None, dy=None, width=None): + """ + Set `.Arrow` x, y, dx, dy and width. + Values left as None will not be updated. + + Parameters + ---------- + x, y : float or None, default: None + The x and y coordinates of the arrow base. + + dx, dy : float or None, default: None + The length of the arrow along x and y direction. + + width : float or None, default: None + Width of full arrow tail. + """ + if x is not None: + self._x = x + if y is not None: + self._y = y + if dx is not None: + self._dx = dx + if dy is not None: + self._dy = dy + if width is not None: + self._width = width + self._patch_transform = ( + transforms.Affine2D() + .scale(np.hypot(self._dx, self._dy), self._width) + .rotate(np.arctan2(self._dy, self._dx)) + .translate(self._x, self._y) + .frozen()) + class FancyArrow(Polygon): """ @@ -1240,57 +1426,124 @@ class FancyArrow(Polygon): def __str__(self): return "FancyArrow()" - @docstring.dedent_interpd - def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, - head_width=None, head_length=None, shape='full', overhang=0, + @_docstring.interpd + def __init__(self, x, y, dx, dy, *, + width=0.001, length_includes_head=False, head_width=None, + head_length=None, shape='full', overhang=0, head_starts_at_zero=False, **kwargs): """ Parameters ---------- - width: float, default: 0.001 + x, y : float + The x and y coordinates of the arrow base. + + dx, dy : float + The length of the arrow along x and y direction. + + width : float, default: 0.001 Width of full arrow tail. - length_includes_head: bool, default: False + length_includes_head : bool, default: False True if head is to be counted in calculating the length. - head_width: float or None, default: 3*width + head_width : float or None, default: 3*width Total width of the full arrow head. - head_length: float or None, default: 1.5*head_width + head_length : float or None, default: 1.5*head_width Length of arrow head. - shape: ['full', 'left', 'right'], default: 'full' + shape : {'full', 'left', 'right'}, default: 'full' Draw the left-half, right-half, or full arrow. - overhang: float, default: 0 + overhang : float, default: 0 Fraction that the arrow is swept back (0 overhang means triangular shape). Can be negative or greater than one. - head_starts_at_zero: bool, default: False + head_starts_at_zero : bool, default: False If True, the head starts being drawn at coordinate 0 instead of ending at coordinate 0. **kwargs `.Patch` properties: - %(Patch)s + %(Patch:kwdoc)s + """ + self._x = x + self._y = y + self._dx = dx + self._dy = dy + self._width = width + self._length_includes_head = length_includes_head + self._head_width = head_width + self._head_length = head_length + self._shape = shape + self._overhang = overhang + self._head_starts_at_zero = head_starts_at_zero + self._make_verts() + super().__init__(self.verts, closed=True, **kwargs) + + def set_data(self, *, x=None, y=None, dx=None, dy=None, width=None, + head_width=None, head_length=None): + """ + Set `.FancyArrow` x, y, dx, dy, width, head_with, and head_length. + Values left as None will not be updated. + + Parameters + ---------- + x, y : float or None, default: None + The x and y coordinates of the arrow base. + + dx, dy : float or None, default: None + The length of the arrow along x and y direction. + + width : float or None, default: None + Width of full arrow tail. + + head_width : float or None, default: None + Total width of the full arrow head. + + head_length : float or None, default: None + Length of arrow head. """ - if head_width is None: - head_width = 3 * width - if head_length is None: + if x is not None: + self._x = x + if y is not None: + self._y = y + if dx is not None: + self._dx = dx + if dy is not None: + self._dy = dy + if width is not None: + self._width = width + if head_width is not None: + self._head_width = head_width + if head_length is not None: + self._head_length = head_length + self._make_verts() + self.set_xy(self.verts) + + def _make_verts(self): + if self._head_width is None: + head_width = 3 * self._width + else: + head_width = self._head_width + if self._head_length is None: head_length = 1.5 * head_width + else: + head_length = self._head_length - distance = np.hypot(dx, dy) + distance = np.hypot(self._dx, self._dy) - if length_includes_head: + if self._length_includes_head: length = distance else: length = distance + head_length if not length: - verts = np.empty([0, 2]) # display nothing if empty + self.verts = np.empty([0, 2]) # display nothing if empty else: # start by drawing horizontal arrow, point at (0, 0) - hw, hl, hs, lw = head_width, head_length, overhang, width + hw, hl = head_width, head_length + hs, lw = self._overhang, self._width left_half_arrow = np.array([ [0.0, 0.0], # tip [-hl, -hw / 2], # leftmost @@ -1299,40 +1552,42 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, [-length, 0], ]) # if we're not including the head, shift up by head length - if not length_includes_head: + if not self._length_includes_head: left_half_arrow += [head_length, 0] # if the head starts at 0, shift up by another head length - if head_starts_at_zero: + if self._head_starts_at_zero: left_half_arrow += [head_length / 2, 0] # figure out the shape, and complete accordingly - if shape == 'left': + if self._shape == 'left': coords = left_half_arrow else: right_half_arrow = left_half_arrow * [1, -1] - if shape == 'right': + if self._shape == 'right': coords = right_half_arrow - elif shape == 'full': + elif self._shape == 'full': # The half-arrows contain the midpoint of the stem, # which we can omit from the full arrow. Including it # twice caused a problem with xpdf. coords = np.concatenate([left_half_arrow[:-1], right_half_arrow[-2::-1]]) else: - raise ValueError("Got unknown shape: %s" % shape) + raise ValueError(f"Got unknown shape: {self._shape!r}") if distance != 0: - cx = dx / distance - sx = dy / distance + cx = self._dx / distance + sx = self._dy / distance else: # Account for division by zero cx, sx = 0, 1 M = [[cx, sx], [-sx, cx]] - verts = np.dot(coords, M) + (x + dx, y + dy) - - super().__init__(verts, closed=True, **kwargs) + self.verts = np.dot(coords, M) + [ + self._x + self._dx, + self._y + self._dy, + ] -docstring.interpd.update( - FancyArrow="\n".join(inspect.getdoc(FancyArrow.__init__).splitlines()[2:])) +_docstring.interpd.register( + FancyArrow="\n".join( + (inspect.getdoc(FancyArrow.__init__) or "").splitlines()[2:])) class CirclePolygon(RegularPolygon): @@ -1340,10 +1595,10 @@ class CirclePolygon(RegularPolygon): def __str__(self): s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)" - return s % (self._xy[0], self._xy[1], self._radius, self._numVertices) + return s % (self.xy[0], self.xy[1], self.radius, self.numvertices) - @docstring.dedent_interpd - def __init__(self, xy, radius=5, + @_docstring.interpd + def __init__(self, xy, radius=5, *, resolution=20, # the number of vertices ** kwargs): """ @@ -1354,9 +1609,10 @@ def __init__(self, xy, radius=5, Valid keyword arguments are: - %(Patch)s + %(Patch:kwdoc)s """ - super().__init__(xy, resolution, radius, orientation=0, **kwargs) + super().__init__( + xy, resolution, radius=radius, orientation=0, **kwargs) class Ellipse(Patch): @@ -1368,8 +1624,8 @@ def __str__(self): fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)" return fmt % pars - @docstring.dedent_interpd - def __init__(self, xy, width, height, angle=0, **kwargs): + @_docstring.interpd + def __init__(self, xy, width, height, *, angle=0, **kwargs): """ Parameters ---------- @@ -1386,7 +1642,7 @@ def __init__(self, xy, width, height, angle=0, **kwargs): ----- Valid keyword arguments are: - %(Patch)s + %(Patch:kwdoc)s """ super().__init__(**kwargs) @@ -1394,6 +1650,12 @@ def __init__(self, xy, width, height, angle=0, **kwargs): self._width, self._height = width, height self._angle = angle self._path = Path.unit_circle() + # Required for EllipseSelector with axes aspect ratio != 1 + # The patch is defined in data coordinates and when changing the + # selector with square modifier and not in data coordinates, we need + # to correct for the aspect ratio difference between the data and + # display coordinate systems. + self._aspect_ratio_correction = 1.0 # Note: This cannot be calculated until this is added to an Axes self._patch_transform = transforms.IdentityTransform() @@ -1411,8 +1673,9 @@ def _recompute_transform(self): width = self.convert_xunits(self._width) height = self.convert_yunits(self._height) self._patch_transform = transforms.Affine2D() \ - .scale(width * 0.5, height * 0.5) \ + .scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \ .rotate_deg(self.angle) \ + .scale(1, 1 / self._aspect_ratio_correction) \ .translate(*center) def get_path(self): @@ -1493,117 +1756,340 @@ def get_angle(self): angle = property(get_angle, set_angle) - -class Circle(Ellipse): - """A circle patch.""" - - def __str__(self): - pars = self.center[0], self.center[1], self.radius - fmt = "Circle(xy=(%g, %g), radius=%g)" - return fmt % pars - - @docstring.dedent_interpd - def __init__(self, xy, radius=5, **kwargs): + def get_corners(self): """ - Create a true circle at center *xy* = (*x*, *y*) with given *radius*. - - Unlike `CirclePolygon` which is a polygonal approximation, this uses - Bezier splines and is much closer to a scale-free circle. + Return the corners of the ellipse bounding box. - Valid keyword arguments are: + The bounding box orientation is moving anti-clockwise from the + lower left corner defined before rotation. + """ + return self.get_patch_transform().transform( + [(-1, -1), (1, -1), (1, 1), (-1, 1)]) - %(Patch)s + def get_vertices(self): """ - super().__init__(xy, radius * 2, radius * 2, **kwargs) - self.radius = radius + Return the vertices coordinates of the ellipse. - def set_radius(self, radius): + The definition can be found `here `_ + + .. versionadded:: 3.8 """ - Set the radius of the circle. + if self.width < self.height: + ret = self.get_patch_transform().transform([(0, 1), (0, -1)]) + else: + ret = self.get_patch_transform().transform([(1, 0), (-1, 0)]) + return [tuple(x) for x in ret] - Parameters - ---------- - radius : float + def get_co_vertices(self): """ - self.width = self.height = 2 * radius - self.stale = True + Return the co-vertices coordinates of the ellipse. - def get_radius(self): - """Return the radius of the circle.""" - return self.width / 2. + The definition can be found `here `_ - radius = property(get_radius, set_radius) + .. versionadded:: 3.8 + """ + if self.width < self.height: + ret = self.get_patch_transform().transform([(1, 0), (-1, 0)]) + else: + ret = self.get_patch_transform().transform([(0, 1), (0, -1)]) + return [tuple(x) for x in ret] -class Arc(Ellipse): +class Annulus(Patch): """ - An elliptical arc, i.e. a segment of an ellipse. - - Due to internal optimizations, there are certain restrictions on using Arc: - - - The arc cannot be filled. - - - The arc must be used in an `~.axes.Axes` instance. It can not be added - directly to a `.Figure` because it is optimized to only render the - segments that are inside the axes bounding box with high resolution. + An elliptical annulus. """ - def __str__(self): - pars = (self.center[0], self.center[1], self.width, - self.height, self.angle, self.theta1, self.theta2) - fmt = ("Arc(xy=(%g, %g), width=%g, " - "height=%g, angle=%g, theta1=%g, theta2=%g)") - return fmt % pars - @docstring.dedent_interpd - def __init__(self, xy, width, height, angle=0.0, - theta1=0.0, theta2=360.0, **kwargs): + @_docstring.interpd + def __init__(self, xy, r, width, angle=0.0, **kwargs): """ Parameters ---------- xy : (float, float) - The center of the ellipse. + xy coordinates of annulus centre. + r : float or (float, float) + The radius, or semi-axes: + - If float: radius of the outer circle. + - If two floats: semi-major and -minor axes of outer ellipse. width : float - The length of the horizontal axis. + Width (thickness) of the annular ring. The width is measured inward + from the outer ellipse so that for the inner ellipse the semi-axes + are given by ``r - width``. *width* must be less than or equal to + the semi-minor axis. + angle : float, default: 0 + Rotation angle in degrees (anti-clockwise from the positive + x-axis). Ignored for circular annuli (i.e., if *r* is a scalar). + **kwargs + Keyword arguments control the `Patch` properties: - height : float - The length of the vertical axis. + %(Patch:kwdoc)s + """ + super().__init__(**kwargs) - angle : float - Rotation of the ellipse in degrees (counterclockwise). + self.set_radii(r) + self.center = xy + self.width = width + self.angle = angle + self._path = None - theta1, theta2 : float, default: 0, 360 - Starting and ending angles of the arc in degrees. These values - are relative to *angle*, e.g. if *angle* = 45 and *theta1* = 90 - the absolute starting angle is 135. - Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse. - The arc is drawn in the counterclockwise direction. - Angles greater than or equal to 360, or smaller than 0, are - represented by an equivalent angle in the range [0, 360), by - taking the input value mod 360. + def __str__(self): + if self.a == self.b: + r = self.a + else: + r = (self.a, self.b) - Other Parameters - ---------------- - **kwargs : `.Patch` properties - Most `.Patch` properties are supported as keyword arguments, - with the exception of *fill* and *facecolor* because filling is - not supported. + return "Annulus(xy=(%s, %s), r=%s, width=%s, angle=%s)" % \ + (*self.center, r, self.width, self.angle) - %(Patch)s + def set_center(self, xy): """ - fill = kwargs.setdefault('fill', False) - if fill: - raise ValueError("Arc objects can not be filled") - - super().__init__(xy, width, height, angle, **kwargs) - - self.theta1 = theta1 - self.theta2 = theta2 + Set the center of the annulus. - @artist.allow_rasterization - def draw(self, renderer): + Parameters + ---------- + xy : (float, float) """ - Draw the arc to the given *renderer*. + self._center = xy + self._path = None + self.stale = True + + def get_center(self): + """Return the center of the annulus.""" + return self._center + + center = property(get_center, set_center) + + def set_width(self, width): + """ + Set the width (thickness) of the annulus ring. + + The width is measured inwards from the outer ellipse. + + Parameters + ---------- + width : float + """ + if width > min(self.a, self.b): + raise ValueError( + 'Width of annulus must be less than or equal to semi-minor axis') + + self._width = width + self._path = None + self.stale = True + + def get_width(self): + """Return the width (thickness) of the annulus ring.""" + return self._width + + width = property(get_width, set_width) + + def set_angle(self, angle): + """ + Set the tilt angle of the annulus. + + Parameters + ---------- + angle : float + """ + self._angle = angle + self._path = None + self.stale = True + + def get_angle(self): + """Return the angle of the annulus.""" + return self._angle + + angle = property(get_angle, set_angle) + + def set_semimajor(self, a): + """ + Set the semi-major axis *a* of the annulus. + + Parameters + ---------- + a : float + """ + self.a = float(a) + self._path = None + self.stale = True + + def set_semiminor(self, b): + """ + Set the semi-minor axis *b* of the annulus. + + Parameters + ---------- + b : float + """ + self.b = float(b) + self._path = None + self.stale = True + + def set_radii(self, r): + """ + Set the semi-major (*a*) and semi-minor radii (*b*) of the annulus. + + Parameters + ---------- + r : float or (float, float) + The radius, or semi-axes: + + - If float: radius of the outer circle. + - If two floats: semi-major and -minor axes of outer ellipse. + """ + if np.shape(r) == (2,): + self.a, self.b = r + elif np.shape(r) == (): + self.a = self.b = float(r) + else: + raise ValueError("Parameter 'r' must be one or two floats.") + + self._path = None + self.stale = True + + def get_radii(self): + """Return the semi-major and semi-minor radii of the annulus.""" + return self.a, self.b + + radii = property(get_radii, set_radii) + + def _transform_verts(self, verts, a, b): + return transforms.Affine2D() \ + .scale(*self._convert_xy_units((a, b))) \ + .rotate_deg(self.angle) \ + .translate(*self._convert_xy_units(self.center)) \ + .transform(verts) + + def _recompute_path(self): + # circular arc + arc = Path.arc(0, 360) + + # annulus needs to draw an outer ring + # followed by a reversed and scaled inner ring + a, b, w = self.a, self.b, self.width + v1 = self._transform_verts(arc.vertices, a, b) + v2 = self._transform_verts(arc.vertices[::-1], a - w, b - w) + v = np.vstack([v1, v2, v1[0, :], (0, 0)]) + c = np.hstack([arc.codes, Path.MOVETO, + arc.codes[1:], Path.MOVETO, + Path.CLOSEPOLY]) + self._path = Path(v, c) + + def get_path(self): + if self._path is None: + self._recompute_path() + return self._path + + +class Circle(Ellipse): + """ + A circle patch. + """ + def __str__(self): + pars = self.center[0], self.center[1], self.radius + fmt = "Circle(xy=(%g, %g), radius=%g)" + return fmt % pars + + @_docstring.interpd + def __init__(self, xy, radius=5, **kwargs): + """ + Create a true circle at center *xy* = (*x*, *y*) with given *radius*. + + Unlike `CirclePolygon` which is a polygonal approximation, this uses + Bezier splines and is much closer to a scale-free circle. + + Valid keyword arguments are: + + %(Patch:kwdoc)s + """ + super().__init__(xy, radius * 2, radius * 2, **kwargs) + self.radius = radius + + def set_radius(self, radius): + """ + Set the radius of the circle. + + Parameters + ---------- + radius : float + """ + self.width = self.height = 2 * radius + self.stale = True + + def get_radius(self): + """Return the radius of the circle.""" + return self.width / 2. + + radius = property(get_radius, set_radius) + + +class Arc(Ellipse): + """ + An elliptical arc, i.e. a segment of an ellipse. + + Due to internal optimizations, the arc cannot be filled. + """ + + def __str__(self): + pars = (self.center[0], self.center[1], self.width, + self.height, self.angle, self.theta1, self.theta2) + fmt = ("Arc(xy=(%g, %g), width=%g, " + "height=%g, angle=%g, theta1=%g, theta2=%g)") + return fmt % pars + + @_docstring.interpd + def __init__(self, xy, width, height, *, + angle=0.0, theta1=0.0, theta2=360.0, **kwargs): + """ + Parameters + ---------- + xy : (float, float) + The center of the ellipse. + + width : float + The length of the horizontal axis. + + height : float + The length of the vertical axis. + + angle : float + Rotation of the ellipse in degrees (counterclockwise). + + theta1, theta2 : float, default: 0, 360 + Starting and ending angles of the arc in degrees. These values + are relative to *angle*, e.g. if *angle* = 45 and *theta1* = 90 + the absolute starting angle is 135. + Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse. + The arc is drawn in the counterclockwise direction. + Angles greater than or equal to 360, or smaller than 0, are + represented by an equivalent angle in the range [0, 360), by + taking the input value mod 360. + + Other Parameters + ---------------- + **kwargs : `~matplotlib.patches.Patch` properties + Most `.Patch` properties are supported as keyword arguments, + except *fill* and *facecolor* because filling is not supported. + + %(Patch:kwdoc)s + """ + fill = kwargs.setdefault('fill', False) + if fill: + raise ValueError("Arc objects cannot be filled") + + super().__init__(xy, width, height, angle=angle, **kwargs) + + self.theta1 = theta1 + self.theta2 = theta2 + (self._theta1, self._theta2, self._stretched_width, + self._stretched_height) = self._theta_stretch() + self._path = Path.arc(self._theta1, self._theta2) + + @artist.allow_rasterization + def draw(self, renderer): + """ + Draw the arc to the given *renderer*. Notes ----- @@ -1628,12 +2114,11 @@ def draw(self, renderer): with each visible arc using a fixed number of spline segments (8). The algorithm proceeds as follows: - 1. The points where the ellipse intersects the axes bounding - box are located. (This is done be performing an inverse - transformation on the axes bbox such that it is relative - to the unit circle -- this makes the intersection - calculation much easier than doing rotated ellipse - intersection directly). + 1. The points where the ellipse intersects the axes (or figure) + bounding box are located. (This is done by performing an inverse + transformation on the bbox such that it is relative to the unit + circle -- this makes the intersection calculation much easier than + doing rotated ellipse intersection directly.) This uses the "line intersecting a circle" algorithm from: @@ -1647,43 +2132,12 @@ def draw(self, renderer): pairs of vertices are drawn using the Bezier arc approximation technique implemented in `.Path.arc`. """ - if not hasattr(self, 'axes'): - raise RuntimeError('Arcs can only be used in Axes instances') if not self.get_visible(): return self._recompute_transform() - width = self.convert_xunits(self.width) - height = self.convert_yunits(self.height) - - # If the width and height of ellipse are not equal, take into account - # stretching when calculating angles to draw between - def theta_stretch(theta, scale): - theta = np.deg2rad(theta) - x = np.cos(theta) - y = np.sin(theta) - stheta = np.rad2deg(np.arctan2(scale * y, x)) - # arctan2 has the range [-pi, pi], we expect [0, 2*pi] - return (stheta + 360) % 360 - - theta1 = self.theta1 - theta2 = self.theta2 - - if ( - # if we need to stretch the angles because we are distorted - width != height - # and we are not doing a full circle. - # - # 0 and 360 do not exactly round-trip through the angle - # stretching (due to both float precision limitations and - # the difference between the range of arctan2 [-pi, pi] and - # this method [0, 360]) so avoid doing it if we don't have to. - and not (theta1 != theta2 and theta1 % 360 == theta2 % 360) - ): - theta1 = theta_stretch(self.theta1, width / height) - theta2 = theta_stretch(self.theta2, width / height) - + self._update_path() # Get width and height in pixels we need to use # `self.get_data_transform` rather than `self.get_transform` # because we want the transform from dataspace to the @@ -1692,12 +2146,13 @@ def theta_stretch(theta, scale): # `self.get_transform()` goes from an idealized unit-radius # space to screen space). data_to_screen_trans = self.get_data_transform() - pwidth, pheight = (data_to_screen_trans.transform((width, height)) - - data_to_screen_trans.transform((0, 0))) + pwidth, pheight = ( + data_to_screen_trans.transform((self._stretched_width, + self._stretched_height)) - + data_to_screen_trans.transform((0, 0))) inv_error = (1.0 / 1.89818e-6) * 0.5 if pwidth < inv_error and pheight < inv_error: - self._path = Path.arc(theta1, theta2) return Patch.draw(self, renderer) def line_circle_intersect(x0, y0, x1, y1): @@ -1735,10 +2190,12 @@ def segment_circle_intersect(x0, y0, x1, y1): & (y0e - epsilon < ys) & (ys < y1e + epsilon) ] - # Transforms the axes box_path so that it is relative to the unit - # circle in the same way that it is relative to the desired ellipse. - box_path_transform = (transforms.BboxTransformTo(self.axes.bbox) - + self.get_transform().inverted()) + # Transform the Axes (or figure) box_path so that it is relative to + # the unit circle in the same way that it is relative to the desired + # ellipse. + box_path_transform = ( + transforms.BboxTransformTo((self.axes or self.get_figure(root=False)).bbox) + - self.get_transform()) box_path = Path.unit_rectangle().transformed(box_path_transform) thetas = set() @@ -1749,10 +2206,11 @@ def segment_circle_intersect(x0, y0, x1, y1): # arctan2 return [-pi, pi), the rest of our angles are in # [0, 360], adjust as needed. theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360 - thetas.update(theta[(theta1 < theta) & (theta < theta2)]) - thetas = sorted(thetas) + [theta2] - last_theta = theta1 - theta1_rad = np.deg2rad(theta1) + thetas.update( + theta[(self._theta1 < theta) & (theta < self._theta2)]) + thetas = sorted(thetas) + [self._theta2] + last_theta = self._theta1 + theta1_rad = np.deg2rad(self._theta1) inside = box_path.contains_point( (np.cos(theta1_rad), np.sin(theta1_rad)) ) @@ -1771,6 +2229,46 @@ def segment_circle_intersect(x0, y0, x1, y1): # restore original path self._path = path_original + def _update_path(self): + # Compute new values and update and set new _path if any value changed + stretched = self._theta_stretch() + if any(a != b for a, b in zip( + stretched, (self._theta1, self._theta2, self._stretched_width, + self._stretched_height))): + (self._theta1, self._theta2, self._stretched_width, + self._stretched_height) = stretched + self._path = Path.arc(self._theta1, self._theta2) + + def _theta_stretch(self): + # If the width and height of ellipse are not equal, take into account + # stretching when calculating angles to draw between + def theta_stretch(theta, scale): + theta = np.deg2rad(theta) + x = np.cos(theta) + y = np.sin(theta) + stheta = np.rad2deg(np.arctan2(scale * y, x)) + # arctan2 has the range [-pi, pi], we expect [0, 2*pi] + return (stheta + 360) % 360 + + width = self.convert_xunits(self.width) + height = self.convert_yunits(self.height) + if ( + # if we need to stretch the angles because we are distorted + width != height + # and we are not doing a full circle. + # + # 0 and 360 do not exactly round-trip through the angle + # stretching (due to both float precision limitations and + # the difference between the range of arctan2 [-pi, pi] and + # this method [0, 360]) so avoid doing it if we don't have to. + and not (self.theta1 != self.theta2 and + self.theta1 % 360 == self.theta2 % 360) + ): + theta1 = theta_stretch(self.theta1, width / height) + theta2 = theta_stretch(self.theta2, width / height) + return theta1, theta2, width, height + return self.theta1, self.theta2, width, height + def bbox_artist(artist, renderer, props=None, fill=True): """ @@ -1801,50 +2299,56 @@ def draw_bbox(bbox, renderer, color='k', trans=None): box returned by an artist's `.Artist.get_window_extent` to test whether the artist is returning the correct bbox. """ - r = Rectangle(xy=(bbox.x0, bbox.y0), width=bbox.width, height=bbox.height, + r = Rectangle(xy=bbox.p0, width=bbox.width, height=bbox.height, edgecolor=color, fill=False, clip_on=False) if trans is not None: r.set_transform(trans) r.draw(renderer) -def _simpleprint_styles(_styles): - """ - A helper function for the _Style class. Given the dictionary of - {stylename: styleclass}, return a string rep of the list of keys. - Used to update the documentation. - """ - return "[{}]".format("|".join(map(" '{}' ".format, sorted(_styles)))) - - class _Style: """ A base class for the Styles. It is meant to be a container class, where actual styles are declared as subclass of it, and it provides some helper functions. """ - def __new__(cls, stylename, **kw): - """Return the instance of the subclass with the given style name.""" + def __init_subclass__(cls): + # Automatically perform docstring interpolation on the subclasses: + # This allows listing the supported styles via + # - %(BoxStyle:table)s + # - %(ConnectionStyle:table)s + # - %(ArrowStyle:table)s + # and additionally adding .. ACCEPTS: blocks via + # - %(BoxStyle:table_and_accepts)s + # - %(ConnectionStyle:table_and_accepts)s + # - %(ArrowStyle:table_and_accepts)s + _docstring.interpd.register(**{ + f"{cls.__name__}:table": cls.pprint_styles(), + f"{cls.__name__}:table_and_accepts": ( + cls.pprint_styles() + + "\n\n .. ACCEPTS: [" + + "|".join(map(" '{}' ".format, cls._style_list)) + + "]") + }) + + def __new__(cls, stylename, **kwargs): + """Return the instance of the subclass with the given style name.""" # The "class" should have the _style_list attribute, which is a mapping # of style names to style classes. - _list = stylename.replace(" ", "").split(",") _name = _list[0].lower() try: _cls = cls._style_list[_name] except KeyError as err: - raise ValueError("Unknown style : %s" % stylename) from err - + raise ValueError(f"Unknown style: {stylename!r}") from err try: _args_pair = [cs.split("=") for cs in _list[1:]] _args = {k: float(v) for k, v in _args_pair} except ValueError as err: - raise ValueError("Incorrect style argument : %s" % - stylename) from err - _args.update(kw) - - return _cls(**_args) + raise ValueError( + f"Incorrect style argument: {stylename!r}") from err + return _cls(**{**_args, **kwargs}) @classmethod def get_styles(cls): @@ -1854,13 +2358,13 @@ def get_styles(cls): @classmethod def pprint_styles(cls): """Return the available styles as pretty-printed string.""" - table = [('Class', 'Name', 'Attrs'), + table = [('Class', 'Name', 'Parameters'), *[(cls.__name__, # Add backquotes, as - and | have special meaning in reST. f'``{name}``', # [1:-1] drops the surrounding parentheses. str(inspect.signature(cls))[1:-1] or 'None') - for name, cls in sorted(cls._style_list.items())]] + for name, cls in cls._style_list.items()]] # Convert to rst table. col_len = [max(len(cell) for cell in column) for column in zip(*table)] table_formatstr = ' '.join('=' * cl for cl in col_len) @@ -1872,16 +2376,19 @@ def pprint_styles(cls): *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len)) for row in table[1:]], table_formatstr, - '', ]) - return textwrap.indent(rst_table, prefix=' ' * 2) + return textwrap.indent(rst_table, prefix=' ' * 4) @classmethod + @_api.deprecated( + '3.10.0', + message="This method is never used internally.", + alternative="No replacement. Please open an issue if you use this." + ) def register(cls, name, style): """Register a new style.""" if not issubclass(style, cls._Base): - raise ValueError("%s must be a subclass of %s" % (style, - cls._Base)) + raise ValueError(f"{style} must be a subclass of {cls._Base}") cls._style_list[name] = style @@ -1893,6 +2400,7 @@ def _register_style(style_list, cls=None, *, name=None): return cls +@_docstring.interpd class BoxStyle(_Style): """ `BoxStyle` is a container class which defines several @@ -1912,140 +2420,104 @@ class BoxStyle(_Style): The following boxstyle classes are defined. - %(AvailableBoxstyles)s + %(BoxStyle:table)s - An instance of any boxstyle class is an callable object, - whose call signature is:: + An instance of a boxstyle class is a callable object, with the signature :: - __call__(self, x0, y0, width, height, mutation_size, aspect_ratio=1.) + __call__(self, x0, y0, width, height, mutation_size) -> Path - and returns a `.Path` instance. *x0*, *y0*, *width* and - *height* specify the location and size of the box to be - drawn. *mutation_scale* determines the overall size of the - mutation (by which I mean the transformation of the rectangle to - the fancy box). *mutation_aspect* determines the aspect-ratio of - the mutation. + *x0*, *y0*, *width* and *height* specify the location and size of the box + to be drawn; *mutation_size* scales the outline properties such as padding. """ _style_list = {} - class _Base: - """ - Abstract base class for styling of `.FancyBboxPatch`. - - This class is not an artist itself. The `__call__` method returns the - `~matplotlib.path.Path` for outlining the fancy box. The actual drawing - is handled in `.FancyBboxPatch`. - - Subclasses may only use parameters with default values in their - ``__init__`` method because they must be able to be initialized - without arguments. - - Subclasses must implement the `transmute` method. It receives the - enclosing rectangle *x0, y0, width, height* as well as the - *mutation_size*, which scales the outline properties such as padding. - It returns the outline of the fancy box as `.path.Path`. - """ - - def transmute(self, x0, y0, width, height, mutation_size): - """Return the `~.path.Path` outlining the given rectangle.""" - raise NotImplementedError('Derived must override') + @_register_style(_style_list) + class Square: + """A square box.""" - def __call__(self, x0, y0, width, height, mutation_size, - aspect_ratio=1.): + def __init__(self, pad=0.3): """ - Given the location and size of the box, return the path of - the box around it. - Parameters ---------- - x0, y0, width, height : float - Location and size of the box. - mutation_size : float - A reference scale for the mutation. - aspect_ratio : float, default: 1 - Aspect-ratio for the mutation. - - Returns - ------- - `~matplotlib.path.Path` + pad : float, default: 0.3 + The amount of padding around the original box. """ - # The __call__ method is a thin wrapper around the transmute method - # and takes care of the aspect. + self.pad = pad - if aspect_ratio is not None: - # Squeeze the given height by the aspect_ratio - y0, height = y0 / aspect_ratio, height / aspect_ratio - # call transmute method with squeezed height. - path = self.transmute(x0, y0, width, height, mutation_size) - vertices, codes = path.vertices, path.codes - # Restore the height - vertices[:, 1] = vertices[:, 1] * aspect_ratio - return Path(vertices, codes) - else: - return self.transmute(x0, y0, width, height, mutation_size) + def __call__(self, x0, y0, width, height, mutation_size): + pad = mutation_size * self.pad + # width and height with padding added. + width, height = width + 2 * pad, height + 2 * pad + # boundary of the padded box + x0, y0 = x0 - pad, y0 - pad + x1, y1 = x0 + width, y0 + height + return Path._create_closed( + [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) @_register_style(_style_list) - class Square(_Base): - """ - A square box. + class Circle: + """A circular box.""" - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - """ def __init__(self, pad=0.3): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + """ self.pad = pad - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad - # width and height with padding added. width, height = width + 2 * pad, height + 2 * pad # boundary of the padded box x0, y0 = x0 - pad, y0 - pad - x1, y1 = x0 + width, y0 + height - return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], - closed=True) + return Path.circle((x0 + width / 2, y0 + height / 2), + max(width, height) / 2) @_register_style(_style_list) - class Circle(_Base): + class Ellipse: """ - A circular box. + An elliptical box. - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. + .. versionadded:: 3.7 """ + def __init__(self, pad=0.3): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + """ self.pad = pad - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad width, height = width + 2 * pad, height + 2 * pad # boundary of the padded box x0, y0 = x0 - pad, y0 - pad - return Path.circle((x0 + width / 2, y0 + height / 2), - max(width, height) / 2) + a = width / math.sqrt(2) + b = height / math.sqrt(2) + trans = Affine2D().scale(a, b).translate(x0 + width / 2, + y0 + height / 2) + return trans.transform_path(Path.unit_circle()) @_register_style(_style_list) - class LArrow(_Base): - """ - A box in the shape of a left-pointing arrow. + class LArrow: + """A box in the shape of a left-pointing arrow.""" - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - """ def __init__(self, pad=0.3): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + """ self.pad = pad - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad # width and height with padding added. @@ -2058,49 +2530,37 @@ def transmute(self, x0, y0, width, height, mutation_size): dxx = dx / 2 x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) - return Path([(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), - (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), - (x0 + dxx, y0 - dxx), # arrow - (x0 + dxx, y0), (x0 + dxx, y0)], - closed=True) + return Path._create_closed( + [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), + (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), + (x0 + dxx, y0 - dxx), # arrow + (x0 + dxx, y0)]) @_register_style(_style_list) class RArrow(LArrow): - """ - A box in the shape of a right-pointing arrow. - - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - """ - def __init__(self, pad=0.3): - super().__init__(pad) + """A box in the shape of a right-pointing arrow.""" - def transmute(self, x0, y0, width, height, mutation_size): - p = BoxStyle.LArrow.transmute(self, x0, y0, - width, height, mutation_size) + def __call__(self, x0, y0, width, height, mutation_size): + p = BoxStyle.LArrow.__call__( + self, x0, y0, width, height, mutation_size) p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0] return p @_register_style(_style_list) - class DArrow(_Base): - """ - A box in the shape of a two-way arrow. - - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - """ - # This source is copied from LArrow, - # modified to add a right arrow to the bbox. + class DArrow: + """A box in the shape of a two-way arrow.""" + # Modified from LArrow to add a right arrow to the bbox. def __init__(self, pad=0.3): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + """ self.pad = pad - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad # width and height with padding added. @@ -2114,33 +2574,32 @@ def transmute(self, x0, y0, width, height, mutation_size): dxx = dx / 2 x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) - return Path([(x0 + dxx, y0), (x1, y0), # bot-segment - (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx), - (x1, y1 + dxx), # right-arrow - (x1, y1), (x0 + dxx, y1), # top-segment - (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), - (x0 + dxx, y0 - dxx), # left-arrow - (x0 + dxx, y0), (x0 + dxx, y0)], # close-poly - closed=True) + return Path._create_closed([ + (x0 + dxx, y0), (x1, y0), # bot-segment + (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx), + (x1, y1 + dxx), # right-arrow + (x1, y1), (x0 + dxx, y1), # top-segment + (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), + (x0 + dxx, y0 - dxx), # left-arrow + (x0 + dxx, y0)]) @_register_style(_style_list) - class Round(_Base): - """ - A box with round corners. + class Round: + """A box with round corners.""" - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - rounding_size : float, default: *pad* - Radius of the corners. - """ def __init__(self, pad=0.3, rounding_size=None): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + rounding_size : float, default: *pad* + Radius of the corners. + """ self.pad = pad self.rounding_size = rounding_size - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad @@ -2180,28 +2639,25 @@ def transmute(self, x0, y0, width, height, mutation_size): Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY] - path = Path(cp, com) - - return path + return Path(cp, com) @_register_style(_style_list) - class Round4(_Base): - """ - A box with rounded edges. + class Round4: + """A box with rounded edges.""" - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - rounding_size : float, default: *pad*/2 - Rounding of edges. - """ def __init__(self, pad=0.3, rounding_size=None): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + rounding_size : float, default: *pad*/2 + Rounding of edges. + """ self.pad = pad self.rounding_size = rounding_size - super().__init__() - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad @@ -2232,26 +2688,23 @@ def transmute(self, x0, y0, width, height, mutation_size): Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CLOSEPOLY] - path = Path(cp, com) - - return path + return Path(cp, com) @_register_style(_style_list) - class Sawtooth(_Base): - """ - A box with a sawtooth outline. + class Sawtooth: + """A box with a sawtooth outline.""" - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - tooth_size : float, default: *pad*/2 - Size of the sawtooth. - """ def __init__(self, pad=0.3, tooth_size=None): + """ + Parameters + ---------- + pad : float, default: 0.3 + The amount of padding around the original box. + tooth_size : float, default: *pad*/2 + Size of the sawtooth. + """ self.pad = pad self.tooth_size = tooth_size - super().__init__() def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size): @@ -2264,91 +2717,43 @@ def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size): else: tooth_size = self.tooth_size * mutation_size - tooth_size2 = tooth_size / 2 + hsz = tooth_size / 2 width = width + 2 * pad - tooth_size height = height + 2 * pad - tooth_size # the sizes of the vertical and horizontal sawtooth are # separately adjusted to fit the given box size. - dsx_n = int(round((width - tooth_size) / (tooth_size * 2))) * 2 - dsx = (width - tooth_size) / dsx_n - dsy_n = int(round((height - tooth_size) / (tooth_size * 2))) * 2 - dsy = (height - tooth_size) / dsy_n + dsx_n = round((width - tooth_size) / (tooth_size * 2)) * 2 + dsy_n = round((height - tooth_size) / (tooth_size * 2)) * 2 - x0, y0 = x0 - pad + tooth_size2, y0 - pad + tooth_size2 + x0, y0 = x0 - pad + hsz, y0 - pad + hsz x1, y1 = x0 + width, y0 + height - bottom_saw_x = [ - x0, - *(x0 + tooth_size2 + dsx * .5 * np.arange(dsx_n * 2)), - x1 - tooth_size2, + xs = [ + x0, *np.linspace(x0 + hsz, x1 - hsz, 2 * dsx_n + 1), # bottom + *([x1, x1 + hsz, x1, x1 - hsz] * dsy_n)[:2*dsy_n+2], # right + x1, *np.linspace(x1 - hsz, x0 + hsz, 2 * dsx_n + 1), # top + *([x0, x0 - hsz, x0, x0 + hsz] * dsy_n)[:2*dsy_n+2], # left ] - bottom_saw_y = [ - y0, - *([y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n), - y0 - tooth_size2, - ] - right_saw_x = [ - x1, - *([x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n), - x1 + tooth_size2, - ] - right_saw_y = [ - y0, - *(y0 + tooth_size2 + dsy * .5 * np.arange(dsy_n * 2)), - y1 - tooth_size2, - ] - top_saw_x = [ - x1, - *(x1 - tooth_size2 - dsx * .5 * np.arange(dsx_n * 2)), - x0 + tooth_size2, - ] - top_saw_y = [ - y1, - *([y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n), - y1 + tooth_size2, - ] - left_saw_x = [ - x0, - *([x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n), - x0 - tooth_size2, - ] - left_saw_y = [ - y1, - *(y1 - tooth_size2 - dsy * .5 * np.arange(dsy_n * 2)), - y0 + tooth_size2, + ys = [ + *([y0, y0 - hsz, y0, y0 + hsz] * dsx_n)[:2*dsx_n+2], # bottom + y0, *np.linspace(y0 + hsz, y1 - hsz, 2 * dsy_n + 1), # right + *([y1, y1 + hsz, y1, y1 - hsz] * dsx_n)[:2*dsx_n+2], # top + y1, *np.linspace(y1 - hsz, y0 + hsz, 2 * dsy_n + 1), # left ] - saw_vertices = [*zip(bottom_saw_x, bottom_saw_y), - *zip(right_saw_x, right_saw_y), - *zip(top_saw_x, top_saw_y), - *zip(left_saw_x, left_saw_y), - (bottom_saw_x[0], bottom_saw_y[0])] + return [*zip(xs, ys), (xs[0], ys[0])] - return saw_vertices - - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size) - path = Path(saw_vertices, closed=True) - return path + return Path(saw_vertices, closed=True) @_register_style(_style_list) class Roundtooth(Sawtooth): - """ - A box with a rounded sawtooth outline. - - Parameters - ---------- - pad : float, default: 0.3 - The amount of padding around the original box. - tooth_size : float, default: *pad*/2 - Size of the sawtooth. - """ - def __init__(self, pad=0.3, tooth_size=None): - super().__init__(pad, tooth_size) + """A box with a rounded sawtooth outline.""" - def transmute(self, x0, y0, width, height, mutation_size): + def __call__(self, x0, y0, width, height, mutation_size): saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size) @@ -2360,6 +2765,7 @@ def transmute(self, x0, y0, width, height, mutation_size): return Path(saw_vertices, codes) +@_docstring.interpd class ConnectionStyle(_Style): """ `ConnectionStyle` is a container class which defines @@ -2380,9 +2786,9 @@ class ConnectionStyle(_Style): The following classes are defined - %(AvailableConnectorstyles)s + %(ConnectionStyle:table)s - An instance of any connection style class is an callable object, + An instance of any connection style class is a callable object, whose call signature is:: __call__(self, posA, posB, @@ -2411,60 +2817,30 @@ class _Base: points. This base class defines a __call__ method, and a few helper methods. """ - - class SimpleEvent: - def __init__(self, xy): - self.x, self.y = xy - - def _clip(self, path, patchA, patchB): + def _in_patch(self, patch): """ - Clip the path to the boundary of the patchA and patchB. - The starting point of the path needed to be inside of the - patchA and the end point inside the patch B. The *contains* - methods of each patch object is utilized to test if the point - is inside the path. + Return a predicate function testing whether a point *xy* is + contained in *patch*. """ + return lambda xy: patch.contains( + SimpleNamespace(x=xy[0], y=xy[1]))[0] - if patchA: - def insideA(xy_display): - xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) - return patchA.contains(xy_event)[0] - - try: - left, right = split_path_inout(path, insideA) - except ValueError: - right = path - - path = right - - if patchB: - def insideB(xy_display): - xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) - return patchB.contains(xy_event)[0] - - try: - left, right = split_path_inout(path, insideB) - except ValueError: - left = path - - path = left - - return path - - def _shrink(self, path, shrinkA, shrinkB): + def _clip(self, path, in_start, in_stop): """ - Shrink the path by fixed size (in points) with shrinkA and shrinkB. + Clip *path* at its start by the region where *in_start* returns + True, and at its stop by the region where *in_stop* returns True. + + The original path is assumed to start in the *in_start* region and + to stop in the *in_stop* region. """ - if shrinkA: - insideA = inside_circle(*path.vertices[0], shrinkA) + if in_start: try: - left, path = split_path_inout(path, insideA) + _, path = split_path_inout(path, in_start) except ValueError: pass - if shrinkB: - insideB = inside_circle(*path.vertices[-1], shrinkB) + if in_stop: try: - path, right = split_path_inout(path, insideB) + path, _ = split_path_inout(path, in_stop) except ValueError: pass return path @@ -2476,14 +2852,22 @@ def __call__(self, posA, posB, *posB*; then clip and shrink the path. """ path = self.connect(posA, posB) - clipped_path = self._clip(path, patchA, patchB) - shrunk_path = self._shrink(clipped_path, shrinkA, shrinkB) - return shrunk_path + path = self._clip( + path, + self._in_patch(patchA) if patchA else None, + self._in_patch(patchB) if patchB else None, + ) + path = self._clip( + path, + inside_circle(*path.vertices[0], shrinkA) if shrinkA else None, + inside_circle(*path.vertices[-1], shrinkB) if shrinkB else None + ) + return path @_register_style(_style_list) class Arc3(_Base): """ - Creates a simple quadratic Bezier curve between two + Creates a simple quadratic Bézier curve between two points. The curve is created so that the middle control point (C1) is located at the same distance from the start (C0) and end points(C2) and the distance of the C1 to the line @@ -2492,8 +2876,10 @@ class Arc3(_Base): def __init__(self, rad=0.): """ - *rad* - curvature of the curve. + Parameters + ---------- + rad : float + Curvature of the curve. """ self.rad = rad @@ -2519,19 +2905,21 @@ def connect(self, posA, posB): @_register_style(_style_list) class Angle3(_Base): """ - Creates a simple quadratic Bezier curve between two - points. The middle control points is placed at the - intersecting point of two lines which cross the start and - end point, and have a slope of angleA and angleB, respectively. + Creates a simple quadratic Bézier curve between two points. The middle + control point is placed at the intersecting point of two lines which + cross the start and end point, and have a slope of *angleA* and + *angleB*, respectively. """ def __init__(self, angleA=90, angleB=0): """ - *angleA* - starting angle of the path + Parameters + ---------- + angleA : float + Starting angle of the path. - *angleB* - ending angle of the path + angleB : float + Ending angle of the path. """ self.angleA = angleA @@ -2557,23 +2945,25 @@ def connect(self, posA, posB): @_register_style(_style_list) class Angle(_Base): """ - Creates a piecewise continuous quadratic Bezier path between - two points. The path has a one passing-through point placed at - the intersecting point of two lines which cross the start - and end point, and have a slope of angleA and angleB, respectively. + Creates a piecewise continuous quadratic Bézier path between two + points. The path has a one passing-through point placed at the + intersecting point of two lines which cross the start and end point, + and have a slope of *angleA* and *angleB*, respectively. The connecting edges are rounded with *rad*. """ def __init__(self, angleA=90, angleB=0, rad=0.): """ - *angleA* - starting angle of the path + Parameters + ---------- + angleA : float + Starting angle of the path. - *angleB* - ending angle of the path + angleB : float + Ending angle of the path. - *rad* - rounding radius of the edge + rad : float + Rounding radius of the edge. """ self.angleA = angleA @@ -2619,29 +3009,31 @@ def connect(self, posA, posB): @_register_style(_style_list) class Arc(_Base): """ - Creates a piecewise continuous quadratic Bezier path between - two points. The path can have two passing-through points, a - point placed at the distance of armA and angle of angleA from + Creates a piecewise continuous quadratic Bézier path between two + points. The path can have two passing-through points, a + point placed at the distance of *armA* and angle of *angleA* from point A, another point with respect to point B. The edges are rounded with *rad*. """ def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.): """ - *angleA* : - starting angle of the path + Parameters + ---------- + angleA : float + Starting angle of the path. - *angleB* : - ending angle of the path + angleB : float + Ending angle of the path. - *armA* : - length of the starting arm + armA : float or None + Length of the starting arm. - *armB* : - length of the ending arm + armB : float or None + Length of the ending arm. - *rad* : - rounding radius of the edges + rad : float + Rounding radius of the edges. """ self.angleA = angleA @@ -2713,10 +3105,10 @@ def connect(self, posA, posB): @_register_style(_style_list) class Bar(_Base): """ - A line with *angle* between A and B with *armA* and - *armB*. One of the arms is extended so that they are connected in - a right angle. The length of armA is determined by (*armA* - + *fraction* x AB distance). Same for armB. + A line with *angle* between A and B with *armA* and *armB*. One of the + arms is extended so that they are connected in a right angle. The + length of *armA* is determined by (*armA* + *fraction* x AB distance). + Same for *armB*. """ def __init__(self, armA=0., armB=0., fraction=0.3, angle=None): @@ -2724,18 +3116,17 @@ def __init__(self, armA=0., armB=0., fraction=0.3, angle=None): Parameters ---------- armA : float - minimum length of armA + Minimum length of armA. armB : float - minimum length of armB + Minimum length of armB. fraction : float - a fraction of the distance between two points that - will be added to armA and armB. + A fraction of the distance between two points that will be + added to armA and armB. angle : float or None - angle of the connecting line (if None, parallel - to A and B) + Angle of the connecting line (if None, parallel to A and B). """ self.armA = armA self.armB = armB @@ -2796,13 +3187,14 @@ def _point_along_a_line(x0, y0, x1, y1, d): return x2, y2 +@_docstring.interpd class ArrowStyle(_Style): """ `ArrowStyle` is a container class which defines several arrowstyle classes, which is used to create an arrow path along a given path. These are mainly used with `FancyArrowPatch`. - A arrowstyle object can be either created as:: + An arrowstyle object can be either created as:: ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4) @@ -2816,7 +3208,10 @@ class ArrowStyle(_Style): The following classes are defined - %(AvailableArrowstyles)s + %(ArrowStyle:table)s + + For an overview of the visual appearance, see + :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`. An instance of any arrow style class is a callable object, whose call signature is:: @@ -2830,6 +3225,14 @@ class ArrowStyle(_Style): stroked. This is meant to be used to correct the location of the head so that it does not overshoot the destination point, but not all classes support it. + + Notes + ----- + *angleA* and *angleB* specify the orientation of the bracket, as either a + clockwise or counterclockwise angle depending on the arrow type. 0 degrees + means perpendicular to the line connecting the arrow's head and tail. + + .. plot:: gallery/text_labels_and_annotations/angles_on_bracket_arrows.py """ _style_list = {} @@ -2844,7 +3247,6 @@ class _Base: value indicating the path is open therefore is not fillable. This class is not an artist and actual drawing of the fancy arrow is done by the FancyArrowPatch class. - """ # The derived classes are required to be able to be initialized @@ -2854,10 +3256,11 @@ class is not an artist and actual drawing of the fancy arrow is @staticmethod def ensure_quadratic_bezier(path): """ - Some ArrowStyle class only works with a simple quadratic Bezier - curve (created with Arc3Connection or Angle3Connector). This static - method is to check if the provided path is a simple quadratic - Bezier curve and returns its control points if true. + Some ArrowStyle classes only works with a simple quadratic + Bézier curve (created with `.ConnectionStyle.Arc3` or + `.ConnectionStyle.Angle3`). This static method checks if the + provided path is a simple quadratic Bézier curve and returns its + control points if true. """ segments = list(path.iter_segments()) if (len(segments) != 2 or segments[0][1] != Path.MOVETO or @@ -2869,14 +3272,14 @@ def ensure_quadratic_bezier(path): def transmute(self, path, mutation_size, linewidth): """ The transmute method is the very core of the ArrowStyle class and - must be overridden in the subclasses. It receives the path object - along which the arrow will be drawn, and the mutation_size, with - which the arrow head etc. will be scaled. The linewidth may be - used to adjust the path so that it does not pass beyond the given - points. It returns a tuple of a Path instance and a boolean. The - boolean value indicate whether the path can be filled or not. The - return value can also be a list of paths and list of booleans of a - same length. + must be overridden in the subclasses. It receives the *path* + object along which the arrow will be drawn, and the + *mutation_size*, with which the arrow head etc. will be scaled. + The *linewidth* may be used to adjust the path so that it does not + pass beyond the given points. It returns a tuple of a `.Path` + instance and a boolean. The boolean value indicate whether the + path can be filled or not. The return value can also be a list of + paths and list of booleans of the same length. """ raise NotImplementedError('Derived must override') @@ -2896,11 +3299,9 @@ def __call__(self, path, mutation_size, linewidth, mutation_size, linewidth) if np.iterable(fillable): - path_list = [] - for p in path_mutated: - # Restore the height - path_list.append( - Path(p.vertices * [1, aspect_ratio], p.codes)) + # Restore the height + path_list = [Path(p.vertices * [1, aspect_ratio], p.codes) + for p in path_mutated] return path_list, fillable else: return path_mutated, fillable @@ -2910,24 +3311,75 @@ def __call__(self, path, mutation_size, linewidth, class _Curve(_Base): """ A simple arrow which will work with any path instance. The - returned path is simply concatenation of the original path + at - most two paths representing the arrow head at the begin point and the - at the end point. The arrow heads can be either open or closed. + returned path is the concatenation of the original path, and at + most two paths representing the arrow head or bracket at the start + point and at the end point. The arrow heads can be either open + or closed. """ - def __init__(self, beginarrow=None, endarrow=None, - fillbegin=False, fillend=False, - head_length=.2, head_width=.1): + arrow = "-" + fillbegin = fillend = False # Whether arrows are filled. + + def __init__(self, head_length=.4, head_width=.2, widthA=1., widthB=1., + lengthA=0.2, lengthB=0.2, angleA=0, angleB=0, scaleA=None, + scaleB=None): """ - The arrows are drawn if *beginarrow* and/or *endarrow* are - true. *head_length* and *head_width* determines the size - of the arrow relative to the *mutation scale*. The - arrowhead at the begin (or end) is closed if fillbegin (or - fillend) is True. + Parameters + ---------- + head_length : float, default: 0.4 + Length of the arrow head, relative to *mutation_size*. + head_width : float, default: 0.2 + Width of the arrow head, relative to *mutation_size*. + widthA, widthB : float, default: 1.0 + Width of the bracket. + lengthA, lengthB : float, default: 0.2 + Length of the bracket. + angleA, angleB : float, default: 0 + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. + scaleA, scaleB : float, default: *mutation_size* + The scale of the brackets. """ - self.beginarrow, self.endarrow = beginarrow, endarrow + self.head_length, self.head_width = head_length, head_width - self.fillbegin, self.fillend = fillbegin, fillend + self.widthA, self.widthB = widthA, widthB + self.lengthA, self.lengthB = lengthA, lengthB + self.angleA, self.angleB = angleA, angleB + self.scaleA, self.scaleB = scaleA, scaleB + + self._beginarrow_head = False + self._beginarrow_bracket = False + self._endarrow_head = False + self._endarrow_bracket = False + + if "-" not in self.arrow: + raise ValueError("arrow must have the '-' between " + "the two heads") + + beginarrow, endarrow = self.arrow.split("-", 1) + + if beginarrow == "<": + self._beginarrow_head = True + self._beginarrow_bracket = False + elif beginarrow == "<|": + self._beginarrow_head = True + self._beginarrow_bracket = False + self.fillbegin = True + elif beginarrow in ("]", "|"): + self._beginarrow_head = False + self._beginarrow_bracket = True + + if endarrow == ">": + self._endarrow_head = True + self._endarrow_bracket = False + elif endarrow == "|>": + self._endarrow_head = True + self._endarrow_bracket = False + self.fillend = True + elif endarrow in ("[", "|"): + self._endarrow_head = False + self._endarrow_bracket = True + super().__init__() def _get_arrow_wedge(self, x0, y0, x1, y1, @@ -2944,226 +3396,38 @@ def _get_arrow_wedge(self, x0, y0, x1, y1, cp_distance = np.hypot(dx, dy) - # pad_projected : amount of pad to account the - # overshooting of the projection of the wedge - pad_projected = (.5 * linewidth / sin_t) - - # Account for division by zero - if cp_distance == 0: - cp_distance = 1 - - # apply pad for projected edge - ddx = pad_projected * dx / cp_distance - ddy = pad_projected * dy / cp_distance - - # offset for arrow wedge - dx = dx / cp_distance * head_dist - dy = dy / cp_distance * head_dist - - dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy - dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy - - vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1), - (x1 + ddx, y1 + ddy), - (x1 + ddx + dx2, y1 + ddy + dy2)] - codes_arrow = [Path.MOVETO, - Path.LINETO, - Path.LINETO] - - return vertices_arrow, codes_arrow, ddx, ddy - - def transmute(self, path, mutation_size, linewidth): - - head_length = self.head_length * mutation_size - head_width = self.head_width * mutation_size - head_dist = np.hypot(head_length, head_width) - cos_t, sin_t = head_length / head_dist, head_width / head_dist - - # begin arrow - x0, y0 = path.vertices[0] - x1, y1 = path.vertices[1] - - # If there is no room for an arrow and a line, then skip the arrow - has_begin_arrow = self.beginarrow and (x0, y0) != (x1, y1) - verticesA, codesA, ddxA, ddyA = ( - self._get_arrow_wedge(x1, y1, x0, y0, - head_dist, cos_t, sin_t, linewidth) - if has_begin_arrow - else ([], [], 0, 0) - ) - - # end arrow - x2, y2 = path.vertices[-2] - x3, y3 = path.vertices[-1] - - # If there is no room for an arrow and a line, then skip the arrow - has_end_arrow = self.endarrow and (x2, y2) != (x3, y3) - verticesB, codesB, ddxB, ddyB = ( - self._get_arrow_wedge(x2, y2, x3, y3, - head_dist, cos_t, sin_t, linewidth) - if has_end_arrow - else ([], [], 0, 0) - ) - - # This simple code will not work if ddx, ddy is greater than the - # separation between vertices. - _path = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], - path.vertices[1:-1], - [(x3 + ddxB, y3 + ddyB)]]), - path.codes)] - _fillable = [False] - - if has_begin_arrow: - if self.fillbegin: - p = np.concatenate([verticesA, [verticesA[0], - verticesA[0]], ]) - c = np.concatenate([codesA, [Path.LINETO, Path.CLOSEPOLY]]) - _path.append(Path(p, c)) - _fillable.append(True) - else: - _path.append(Path(verticesA, codesA)) - _fillable.append(False) - - if has_end_arrow: - if self.fillend: - _fillable.append(True) - p = np.concatenate([verticesB, [verticesB[0], - verticesB[0]], ]) - c = np.concatenate([codesB, [Path.LINETO, Path.CLOSEPOLY]]) - _path.append(Path(p, c)) - else: - _fillable.append(False) - _path.append(Path(verticesB, codesB)) - - return _path, _fillable - - @_register_style(_style_list, name="-") - class Curve(_Curve): - """A simple curve without any arrow head.""" - - def __init__(self): - super().__init__(beginarrow=False, endarrow=False) - - @_register_style(_style_list, name="<-") - class CurveA(_Curve): - """An arrow with a head at its begin point.""" - - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. - - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=True, endarrow=False, - head_length=head_length, head_width=head_width) - - @_register_style(_style_list, name="->") - class CurveB(_Curve): - """An arrow with a head at its end point.""" - - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. - - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=False, endarrow=True, - head_length=head_length, head_width=head_width) - - @_register_style(_style_list, name="<->") - class CurveAB(_Curve): - """An arrow with heads both at the begin and the end point.""" - - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. - - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=True, endarrow=True, - head_length=head_length, head_width=head_width) - - @_register_style(_style_list, name="<|-") - class CurveFilledA(_Curve): - """An arrow with filled triangle head at the begin.""" - - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. - - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=True, endarrow=False, - fillbegin=True, fillend=False, - head_length=head_length, head_width=head_width) + # pad_projected : amount of pad to account the + # overshooting of the projection of the wedge + pad_projected = (.5 * linewidth / sin_t) - @_register_style(_style_list, name="-|>") - class CurveFilledB(_Curve): - """An arrow with filled triangle head at the end.""" + # Account for division by zero + if cp_distance == 0: + cp_distance = 1 - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. + # apply pad for projected edge + ddx = pad_projected * dx / cp_distance + ddy = pad_projected * dy / cp_distance - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=False, endarrow=True, - fillbegin=False, fillend=True, - head_length=head_length, head_width=head_width) + # offset for arrow wedge + dx = dx / cp_distance * head_dist + dy = dy / cp_distance * head_dist - @_register_style(_style_list, name="<|-|>") - class CurveFilledAB(_Curve): - """An arrow with filled triangle heads at both ends.""" + dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy + dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy - def __init__(self, head_length=.4, head_width=.2): - """ - Parameters - ---------- - head_length : float, default: 0.4 - Length of the arrow head. + vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1), + (x1 + ddx, y1 + ddy), + (x1 + ddx + dx2, y1 + ddy + dy2)] + codes_arrow = [Path.MOVETO, + Path.LINETO, + Path.LINETO] - head_width : float, default: 0.2 - Width of the arrow head. - """ - super().__init__(beginarrow=True, endarrow=True, - fillbegin=True, fillend=True, - head_length=head_length, head_width=head_width) - - class _Bracket(_Base): - - def __init__(self, bracketA=None, bracketB=None, - widthA=1., widthB=1., - lengthA=0.2, lengthB=0.2, - angleA=None, angleB=None, - scaleA=None, scaleB=None): - self.bracketA, self.bracketB = bracketA, bracketB - self.widthA, self.widthB = widthA, widthB - self.lengthA, self.lengthB = lengthA, lengthB - self.angleA, self.angleB = angleA, angleB - self.scaleA, self.scaleB = scaleA, scaleB + return vertices_arrow, codes_arrow, ddx, ddy def _get_bracket(self, x0, y0, - cos_t, sin_t, width, length): + x1, y1, width, length, angle): + + cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) # arrow from x0, y0 to x1, y1 from matplotlib.bezier import get_normal_points @@ -3180,153 +3444,261 @@ def _get_bracket(self, x0, y0, Path.LINETO, Path.LINETO] + if angle: + trans = transforms.Affine2D().rotate_deg_around(x0, y0, angle) + vertices_arrow = trans.transform(vertices_arrow) + return vertices_arrow, codes_arrow def transmute(self, path, mutation_size, linewidth): + # docstring inherited + if self._beginarrow_head or self._endarrow_head: + head_length = self.head_length * mutation_size + head_width = self.head_width * mutation_size + head_dist = np.hypot(head_length, head_width) + cos_t, sin_t = head_length / head_dist, head_width / head_dist - if self.scaleA is None: - scaleA = mutation_size - else: - scaleA = self.scaleA + scaleA = mutation_size if self.scaleA is None else self.scaleA + scaleB = mutation_size if self.scaleB is None else self.scaleB - if self.scaleB is None: - scaleB = mutation_size - else: - scaleB = self.scaleB + # begin arrow + x0, y0 = path.vertices[0] + x1, y1 = path.vertices[1] + + # If there is no room for an arrow and a line, then skip the arrow + has_begin_arrow = self._beginarrow_head and (x0, y0) != (x1, y1) + verticesA, codesA, ddxA, ddyA = ( + self._get_arrow_wedge(x1, y1, x0, y0, + head_dist, cos_t, sin_t, linewidth) + if has_begin_arrow + else ([], [], 0, 0) + ) + + # end arrow + x2, y2 = path.vertices[-2] + x3, y3 = path.vertices[-1] + + # If there is no room for an arrow and a line, then skip the arrow + has_end_arrow = self._endarrow_head and (x2, y2) != (x3, y3) + verticesB, codesB, ddxB, ddyB = ( + self._get_arrow_wedge(x2, y2, x3, y3, + head_dist, cos_t, sin_t, linewidth) + if has_end_arrow + else ([], [], 0, 0) + ) - vertices_list, codes_list = [], [] + # This simple code will not work if ddx, ddy is greater than the + # separation between vertices. + paths = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], + path.vertices[1:-1], + [(x3 + ddxB, y3 + ddyB)]]), + path.codes)] + fills = [False] - if self.bracketA: + if has_begin_arrow: + if self.fillbegin: + paths.append( + Path([*verticesA, (0, 0)], [*codesA, Path.CLOSEPOLY])) + fills.append(True) + else: + paths.append(Path(verticesA, codesA)) + fills.append(False) + elif self._beginarrow_bracket: x0, y0 = path.vertices[0] x1, y1 = path.vertices[1] - cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) - verticesA, codesA = self._get_bracket(x0, y0, cos_t, sin_t, + verticesA, codesA = self._get_bracket(x0, y0, x1, y1, self.widthA * scaleA, - self.lengthA * scaleA) - vertices_list.append(verticesA) - codes_list.append(codesA) + self.lengthA * scaleA, + self.angleA) - vertices_list.append(path.vertices) - codes_list.append(path.codes) + paths.append(Path(verticesA, codesA)) + fills.append(False) - if self.bracketB: + if has_end_arrow: + if self.fillend: + fills.append(True) + paths.append( + Path([*verticesB, (0, 0)], [*codesB, Path.CLOSEPOLY])) + else: + fills.append(False) + paths.append(Path(verticesB, codesB)) + elif self._endarrow_bracket: x0, y0 = path.vertices[-1] x1, y1 = path.vertices[-2] - cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) - verticesB, codesB = self._get_bracket(x0, y0, cos_t, sin_t, + verticesB, codesB = self._get_bracket(x0, y0, x1, y1, self.widthB * scaleB, - self.lengthB * scaleB) - vertices_list.append(verticesB) - codes_list.append(codesB) + self.lengthB * scaleB, + self.angleB) - vertices = np.concatenate(vertices_list) - codes = np.concatenate(codes_list) + paths.append(Path(verticesB, codesB)) + fills.append(False) - p = Path(vertices, codes) + return paths, fills - return p, False + @_register_style(_style_list, name="-") + class Curve(_Curve): + """A simple curve without any arrow head.""" - @_register_style(_style_list, name="]-[") - class BracketAB(_Bracket): - """An arrow with outward square brackets at both ends.""" + def __init__(self): # hide head_length, head_width + # These attributes (whose values come from backcompat) only matter + # if someone modifies beginarrow/etc. on an ArrowStyle instance. + super().__init__(head_length=.2, head_width=.1) - def __init__(self, - widthA=1., lengthA=0.2, angleA=None, - widthB=1., lengthB=0.2, angleB=None): - """ - Parameters - ---------- - widthA : float, default: 1.0 - Width of the bracket. + @_register_style(_style_list, name="<-") + class CurveA(_Curve): + """An arrow with a head at its start point.""" + arrow = "<-" - lengthA : float, default: 0.2 - Length of the bracket. + @_register_style(_style_list, name="->") + class CurveB(_Curve): + """An arrow with a head at its end point.""" + arrow = "->" - angleA : float, default: None - Angle between the bracket and the line. + @_register_style(_style_list, name="<->") + class CurveAB(_Curve): + """An arrow with heads both at the start and the end point.""" + arrow = "<->" - widthB : float, default: 1.0 - Width of the bracket. + @_register_style(_style_list, name="<|-") + class CurveFilledA(_Curve): + """An arrow with filled triangle head at the start.""" + arrow = "<|-" - lengthB : float, default: 0.2 - Length of the bracket. + @_register_style(_style_list, name="-|>") + class CurveFilledB(_Curve): + """An arrow with filled triangle head at the end.""" + arrow = "-|>" - angleB : float, default: None - Angle between the bracket and the line. - """ - super().__init__(True, True, - widthA=widthA, lengthA=lengthA, angleA=angleA, - widthB=widthB, lengthB=lengthB, angleB=angleB) + @_register_style(_style_list, name="<|-|>") + class CurveFilledAB(_Curve): + """An arrow with filled triangle heads at both ends.""" + arrow = "<|-|>" @_register_style(_style_list, name="]-") - class BracketA(_Bracket): + class BracketA(_Curve): """An arrow with an outward square bracket at its start.""" + arrow = "]-" - def __init__(self, widthA=1., lengthA=0.2, angleA=None): + def __init__(self, widthA=1., lengthA=0.2, angleA=0): """ Parameters ---------- widthA : float, default: 1.0 Width of the bracket. - lengthA : float, default: 0.2 Length of the bracket. - - angleA : float, default: None - Angle between the bracket and the line. + angleA : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. """ - super().__init__(True, None, - widthA=widthA, lengthA=lengthA, angleA=angleA) + super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) @_register_style(_style_list, name="-[") - class BracketB(_Bracket): + class BracketB(_Curve): """An arrow with an outward square bracket at its end.""" + arrow = "-[" - def __init__(self, widthB=1., lengthB=0.2, angleB=None): + def __init__(self, widthB=1., lengthB=0.2, angleB=0): """ Parameters ---------- widthB : float, default: 1.0 Width of the bracket. - lengthB : float, default: 0.2 Length of the bracket. + angleB : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. + """ + super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB) - angleB : float, default: None - Angle between the bracket and the line. + @_register_style(_style_list, name="]-[") + class BracketAB(_Curve): + """An arrow with outward square brackets at both ends.""" + arrow = "]-[" + + def __init__(self, + widthA=1., lengthA=0.2, angleA=0, + widthB=1., lengthB=0.2, angleB=0): + """ + Parameters + ---------- + widthA, widthB : float, default: 1.0 + Width of the bracket. + lengthA, lengthB : float, default: 0.2 + Length of the bracket. + angleA, angleB : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. """ - super().__init__(None, True, + super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA, widthB=widthB, lengthB=lengthB, angleB=angleB) @_register_style(_style_list, name="|-|") - class BarAB(_Bracket): + class BarAB(_Curve): """An arrow with vertical bars ``|`` at both ends.""" + arrow = "|-|" - def __init__(self, - widthA=1., angleA=None, - widthB=1., angleB=None): + def __init__(self, widthA=1., angleA=0, widthB=1., angleB=0): + """ + Parameters + ---------- + widthA, widthB : float, default: 1.0 + Width of the bracket. + angleA, angleB : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. + """ + super().__init__(widthA=widthA, lengthA=0, angleA=angleA, + widthB=widthB, lengthB=0, angleB=angleB) + + @_register_style(_style_list, name=']->') + class BracketCurve(_Curve): + """ + An arrow with an outward square bracket at its start and a head at + the end. + """ + arrow = "]->" + + def __init__(self, widthA=1., lengthA=0.2, angleA=None): """ Parameters ---------- widthA : float, default: 1.0 Width of the bracket. + lengthA : float, default: 0.2 + Length of the bracket. + angleA : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. + """ + super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) - angleA : float, default: None - Angle between the bracket and the line. + @_register_style(_style_list, name='<-[') + class CurveBracket(_Curve): + """ + An arrow with an outward square bracket at its end and a head at + the start. + """ + arrow = "<-[" + def __init__(self, widthB=1., lengthB=0.2, angleB=None): + """ + Parameters + ---------- widthB : float, default: 1.0 Width of the bracket. - - angleB : float, default: None - Angle between the bracket and the line. + lengthB : float, default: 0.2 + Length of the bracket. + angleB : float, default: 0 degrees + Orientation of the bracket, as a counterclockwise angle. + 0 degrees means perpendicular to the line. """ - super().__init__(True, True, - widthA=widthA, lengthA=0, angleA=angleA, - widthB=widthB, lengthB=0, angleB=angleB) + super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB) @_register_style(_style_list) class Simple(_Base): - """A simple arrow. Only works with a quadratic Bezier curve.""" + """A simple arrow. Only works with a quadratic Bézier curve.""" def __init__(self, head_length=.5, head_width=.5, tail_width=.2): """ @@ -3346,7 +3718,7 @@ def __init__(self, head_length=.5, head_width=.5, tail_width=.2): super().__init__() def transmute(self, path, mutation_size, linewidth): - + # docstring inherited x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) # divide the path into a head and a tail @@ -3356,8 +3728,7 @@ def transmute(self, path, mutation_size, linewidth): try: arrow_out, arrow_in = \ - split_bezier_intersecting_with_closedpath( - arrow_path, in_f, tolerance=0.01) + split_bezier_intersecting_with_closedpath(arrow_path, in_f) except NonIntersectingPathException: # if this happens, make a straight line of the head_length # long. @@ -3406,7 +3777,7 @@ def transmute(self, path, mutation_size, linewidth): @_register_style(_style_list) class Fancy(_Base): - """A fancy arrow. Only works with a quadratic Bezier curve.""" + """A fancy arrow. Only works with a quadratic Bézier curve.""" def __init__(self, head_length=.4, head_width=.4, tail_width=.4): """ @@ -3426,7 +3797,7 @@ def __init__(self, head_length=.4, head_width=.4, tail_width=.4): super().__init__() def transmute(self, path, mutation_size, linewidth): - + # docstring inherited x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) # divide the path into a head and a tail @@ -3437,7 +3808,7 @@ def transmute(self, path, mutation_size, linewidth): in_f = inside_circle(x2, y2, head_length) try: path_out, path_in = split_bezier_intersecting_with_closedpath( - arrow_path, in_f, tolerance=0.01) + arrow_path, in_f) except NonIntersectingPathException: # if this happens, make a straight line of the head_length # long. @@ -3451,7 +3822,7 @@ def transmute(self, path, mutation_size, linewidth): # path for head in_f = inside_circle(x2, y2, head_length * .8) path_out, path_in = split_bezier_intersecting_with_closedpath( - arrow_path, in_f, tolerance=0.01) + arrow_path, in_f) path_tail = path_out # head @@ -3469,7 +3840,7 @@ def transmute(self, path, mutation_size, linewidth): # path for head in_f = inside_circle(x0, y0, tail_width * .3) path_in, path_out = split_bezier_intersecting_with_closedpath( - arrow_path, in_f, tolerance=0.01) + arrow_path, in_f) tail_start = path_in[-1] head_right, head_left = head_r, head_l @@ -3495,9 +3866,9 @@ def transmute(self, path, mutation_size, linewidth): @_register_style(_style_list) class Wedge(_Base): """ - Wedge(?) shape. Only works with a quadratic Bezier curve. The - begin point has a width of the tail_width and the end point has a - width of 0. At the middle, the width is shrink_factor*tail_width. + Wedge(?) shape. Only works with a quadratic Bézier curve. The + start point has a width of the *tail_width* and the end point has a + width of 0. At the middle, the width is *shrink_factor*x*tail_width*. """ def __init__(self, tail_width=.3, shrink_factor=0.5): @@ -3515,7 +3886,7 @@ def __init__(self, tail_width=.3, shrink_factor=0.5): super().__init__() def transmute(self, path, mutation_size, linewidth): - + # docstring inherited x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) arrow_path = [(x0, y0), (x1, y1), (x2, y2)] @@ -3537,17 +3908,6 @@ def transmute(self, path, mutation_size, linewidth): return path, True -docstring.interpd.update( - AvailableBoxstyles=BoxStyle.pprint_styles(), - ListBoxstyles=_simpleprint_styles(BoxStyle._style_list), - AvailableArrowstyles=ArrowStyle.pprint_styles(), - AvailableConnectorstyles=ConnectionStyle.pprint_styles(), -) -docstring.dedent_interpd(BoxStyle) -docstring.dedent_interpd(ArrowStyle) -docstring.dedent_interpd(ConnectionStyle) - - class FancyBboxPatch(Patch): """ A fancy box around a rectangle with lower left at *xy* = (*x*, *y*) @@ -3564,16 +3924,13 @@ def __str__(self): s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)" return s % (self._x, self._y, self._width, self._height) - @docstring.dedent_interpd - @cbook._delete_parameter("3.4", "bbox_transmuter", alternative="boxstyle") - def __init__(self, xy, width, height, - boxstyle="round", bbox_transmuter=None, - mutation_scale=1, mutation_aspect=1, - **kwargs): + @_docstring.interpd + def __init__(self, xy, width, height, boxstyle="round", *, + mutation_scale=1, mutation_aspect=1, **kwargs): """ Parameters ---------- - xy : float, float + xy : (float, float) The lower left corner of the box. width : float @@ -3582,16 +3939,16 @@ def __init__(self, xy, width, height, height : float The height of the box. - boxstyle : str or `matplotlib.patches.BoxStyle` + boxstyle : str or `~matplotlib.patches.BoxStyle` The style of the fancy box. This can either be a `.BoxStyle` instance or a string of the style name and optionally comma - seprarated attributes (e.g. "Round, pad=0.2"). This string is + separated attributes (e.g. "Round, pad=0.2"). This string is passed to `.BoxStyle` to construct a `.BoxStyle` object. See there for a full documentation. The following box styles are available: - %(AvailableBoxstyles)s + %(BoxStyle:table)s mutation_scale : float, default: 1 Scaling factor applied to the attributes of the box style @@ -3605,41 +3962,25 @@ def __init__(self, xy, width, height, Other Parameters ---------------- - **kwargs : `.Patch` properties + **kwargs : `~matplotlib.patches.Patch` properties - %(Patch)s + %(Patch:kwdoc)s """ super().__init__(**kwargs) - - self._x = xy[0] - self._y = xy[1] + self._x, self._y = xy self._width = width self._height = height - - if boxstyle == "custom": - cbook._warn_deprecated( - "3.4", message="Support for boxstyle='custom' is deprecated " - "since %(since)s and will be removed %(removal)s; directly " - "pass a boxstyle instance as the boxstyle parameter instead.") - if bbox_transmuter is None: - raise ValueError("bbox_transmuter argument is needed with " - "custom boxstyle") - self._bbox_transmuter = bbox_transmuter - else: - self.set_boxstyle(boxstyle) - + self.set_boxstyle(boxstyle) self._mutation_scale = mutation_scale self._mutation_aspect = mutation_aspect - self.stale = True - @docstring.dedent_interpd + @_docstring.interpd def set_boxstyle(self, boxstyle=None, **kwargs): """ - Set the box style. + Set the box style, possibly with further attributes. - Most box styles can be further configured using attributes. Attributes from the previous box style are not reused. Without argument (or with ``boxstyle=None``), the available box styles @@ -3647,18 +3988,15 @@ def set_boxstyle(self, boxstyle=None, **kwargs): Parameters ---------- - boxstyle : str or `matplotlib.patches.BoxStyle` - The style of the fancy box. This can either be a `.BoxStyle` - instance or a string of the style name and optionally comma - seprarated attributes (e.g. "Round, pad=0.2"). This string is - passed to `.BoxStyle` to construct a `.BoxStyle` object. See - there for a full documentation. + boxstyle : str or `~matplotlib.patches.BoxStyle` + The style of the box: either a `.BoxStyle` instance, or a string, + which is the style name and optionally comma separated attributes + (e.g. "Round,pad=0.2"). Such a string is used to construct a + `.BoxStyle` object, as documented in that class. The following box styles are available: - %(AvailableBoxstyles)s - - .. ACCEPTS: %(ListBoxstyles)s + %(BoxStyle:table_and_accepts)s **kwargs Additional attributes for the box style. See the table above for @@ -3668,19 +4006,20 @@ def set_boxstyle(self, boxstyle=None, **kwargs): -------- :: - set_boxstyle("round,pad=0.2") + set_boxstyle("Round,pad=0.2") set_boxstyle("round", pad=0.2) - """ if boxstyle is None: return BoxStyle.pprint_styles() - - if isinstance(boxstyle, BoxStyle._Base) or callable(boxstyle): - self._bbox_transmuter = boxstyle - else: - self._bbox_transmuter = BoxStyle(boxstyle, **kwargs) + self._bbox_transmuter = ( + BoxStyle(boxstyle, **kwargs) + if isinstance(boxstyle, str) else boxstyle) self.stale = True + def get_boxstyle(self): + """Return the boxstyle object.""" + return self._bbox_transmuter + def set_mutation_scale(self, scale): """ Set the mutation scale. @@ -3712,17 +4051,15 @@ def get_mutation_aspect(self): return (self._mutation_aspect if self._mutation_aspect is not None else 1) # backcompat. - def get_boxstyle(self): - """Return the boxstyle object.""" - return self._bbox_transmuter - def get_path(self): """Return the mutated path of the rectangle.""" - _path = self.get_boxstyle()(self._x, self._y, - self._width, self._height, - self.get_mutation_scale(), - self.get_mutation_aspect()) - return _path + boxstyle = self.get_boxstyle() + m_aspect = self.get_mutation_aspect() + # Call boxstyle with y, height squeezed by aspect_ratio. + path = boxstyle(self._x, self._y / m_aspect, + self._width, self._height / m_aspect, + self.get_mutation_scale()) + return Path(path.vertices * [1, m_aspect], path.codes) # Unsqueeze y. # Following methods are borrowed from the Rectangle class. @@ -3820,7 +4157,11 @@ def get_bbox(self): class FancyArrowPatch(Patch): """ - A fancy arrow patch. It draws an arrow using the `ArrowStyle`. + A fancy arrow patch. + + It draws an arrow using the `ArrowStyle`. It is primarily used by the + `~.axes.Axes.annotate` method. For most purposes, use the annotate method for + drawing arrows. The head and tail positions are fixed at the specified start and end points of the arrow, but the size and shape (in display coordinates) of the arrow @@ -3835,59 +4176,96 @@ def __str__(self): else: return f"{type(self).__name__}({self._path_original})" - @docstring.dedent_interpd - @cbook._delete_parameter("3.4", "dpi_cor") - def __init__(self, posA=None, posB=None, path=None, - arrowstyle="simple", connectionstyle="arc3", - patchA=None, patchB=None, - shrinkA=2, shrinkB=2, - mutation_scale=1, mutation_aspect=1, - dpi_cor=1, - **kwargs): + @_docstring.interpd + def __init__(self, posA=None, posB=None, *, + path=None, arrowstyle="simple", connectionstyle="arc3", + patchA=None, patchB=None, shrinkA=2, shrinkB=2, + mutation_scale=1, mutation_aspect=1, **kwargs): """ - There are two ways for defining an arrow: + **Defining the arrow position and path** + + There are two ways to define the arrow position and path: + + - **Start, end and connection**: + The typical approach is to define the start and end points of the + arrow using *posA* and *posB*. The curve between these two can + further be configured using *connectionstyle*. + + If given, the arrow curve is clipped by *patchA* and *patchB*, + allowing it to start/end at the border of these patches. + Additionally, the arrow curve can be shortened by *shrinkA* and *shrinkB* + to create a margin between start/end (after possible clipping) and the + drawn arrow. - - If *posA* and *posB* are given, a path connecting two points is - created according to *connectionstyle*. The path will be - clipped with *patchA* and *patchB* and further shrunken by - *shrinkA* and *shrinkB*. An arrow is drawn along this - resulting path using the *arrowstyle* parameter. + - **path**: Alternatively if *path* is provided, an arrow is drawn along + this Path. In this case, *connectionstyle*, *patchA*, *patchB*, + *shrinkA*, and *shrinkB* are ignored. - - Alternatively if *path* is provided, an arrow is drawn along this - path and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored. + **Styling** + + The *arrowstyle* defines the styling of the arrow head, tail and shaft. + The resulting arrows can be styled further by setting the `.Patch` + properties such as *linewidth*, *color*, *facecolor*, *edgecolor* + etc. via keyword arguments. Parameters ---------- - posA, posB : (float, float), default: None - (x, y) coordinates of arrow tail and arrow head respectively. + posA, posB : (float, float), optional + (x, y) coordinates of start and end point of the arrow. + The actually drawn start and end positions may be modified + through *patchA*, *patchB*, *shrinkA*, and *shrinkB*. + + *posA*, *posB* are exclusive of *path*. - path : `~matplotlib.path.Path`, default: None + path : `~matplotlib.path.Path`, optional If provided, an arrow is drawn along this path and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored. + *path* is exclusive of *posA*, *posB*. + arrowstyle : str or `.ArrowStyle`, default: 'simple' - The `.ArrowStyle` with which the fancy arrow is drawn. If a - string, it should be one of the available arrowstyle names, with - optional comma-separated attributes. The optional attributes are - meant to be scaled with the *mutation_scale*. The following arrow - styles are available: + The styling of arrow head, tail and shaft. This can be + + - `.ArrowStyle` or one of its subclasses + - The shorthand string name (e.g. "->") as given in the table below, + optionally containing a comma-separated list of style parameters, + e.g. "->, head_length=10, head_width=5". - %(AvailableArrowstyles)s + The style parameters are scaled by *mutation_scale*. + + The following arrow styles are available. See also + :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`. + + %(ArrowStyle:table)s + + Only the styles ``<|-``, ``-|>``, ``<|-|>`` ``simple``, ``fancy`` + and ``wedge`` contain closed paths and can be filled. connectionstyle : str or `.ConnectionStyle` or None, optional, \ default: 'arc3' - The `.ConnectionStyle` with which *posA* and *posB* are connected. - If a string, it should be one of the available connectionstyle - names, with optional comma-separated attributes. The following - connection styles are available: + `.ConnectionStyle` with which *posA* and *posB* are connected. + This can be + + - `.ConnectionStyle` or one of its subclasses + - The shorthand string name as given in the table below, e.g. "arc3". - %(AvailableConnectorstyles)s + %(ConnectionStyle:table)s - patchA, patchB : `.Patch`, default: None - Head and tail patches, respectively. + Ignored if *path* is provided. + + patchA, patchB : `~matplotlib.patches.Patch`, default: None + Optional Patches at *posA* and *posB*, respectively. If given, + the arrow path is clipped by these patches such that head and tail + are at the border of the patches. + + Ignored if *path* is provided. shrinkA, shrinkB : float, default: 2 - Shrinking factor of the tail and head of the arrow respectively. + Shorten the arrow path at *posA* and *posB* by this amount in points. + This allows to add a margin between the intended start/end points and + the arrow. + + Ignored if *path* is provided. mutation_scale : float, default: 1 Value with which attributes of *arrowstyle* (e.g., *head_length*) @@ -3898,23 +4276,19 @@ def __init__(self, posA=None, posB=None, path=None, the mutation and the mutated box will be stretched by the inverse of it. - dpi_cor : float, default: 1 - dpi_cor is currently used for linewidth-related things and shrink - factor. Mutation scale is affected by this. Deprecated. - Other Parameters ---------------- - **kwargs : `.Patch` properties, optional + **kwargs : `~matplotlib.patches.Patch` properties, optional Here is a list of available `.Patch` properties: - %(Patch)s + %(Patch:kwdoc)s In contrast to other patches, the default ``capstyle`` and ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``. """ # Traditionally, the cap- and joinstyle for FancyArrowPatch are round - kwargs.setdefault("joinstyle", "round") - kwargs.setdefault("capstyle", "round") + kwargs.setdefault("joinstyle", JoinStyle.round) + kwargs.setdefault("capstyle", CapStyle.round) super().__init__(**kwargs) @@ -3942,36 +4316,11 @@ def __init__(self, posA=None, posB=None, path=None, self._mutation_scale = mutation_scale self._mutation_aspect = mutation_aspect - self._dpi_cor = dpi_cor - - @cbook.deprecated("3.4") - def set_dpi_cor(self, dpi_cor): - """ - dpi_cor is currently used for linewidth-related things and - shrink factor. Mutation scale is affected by this. - - Parameters - ---------- - dpi_cor : float - """ - self._dpi_cor = dpi_cor - self.stale = True - - @cbook.deprecated("3.4") - def get_dpi_cor(self): - """ - dpi_cor is currently used for linewidth-related things and - shrink factor. Mutation scale is affected by this. - - Returns - ------- - scalar - """ - return self._dpi_cor + self._dpi_cor = 1.0 def set_positions(self, posA, posB): """ - Set the begin and end positions of the connecting path. + Set the start and end positions of the connecting path. Parameters ---------- @@ -4007,67 +4356,88 @@ def set_patchB(self, patchB): self.patchB = patchB self.stale = True - def set_connectionstyle(self, connectionstyle, **kw): + @_docstring.interpd + def set_connectionstyle(self, connectionstyle=None, **kwargs): """ - Set the connection style. Old attributes are forgotten. + Set the connection style, possibly with further attributes. + + Attributes from the previous connection style are not reused. + + Without argument (or with ``connectionstyle=None``), the available box + styles are returned as a human-readable string. Parameters ---------- - connectionstyle : str or `.ConnectionStyle` or None, optional - Can be a string with connectionstyle name with - optional comma-separated attributes, e.g.:: + connectionstyle : str or `~matplotlib.patches.ConnectionStyle` + The style of the connection: either a `.ConnectionStyle` instance, + or a string, which is the style name and optionally comma separated + attributes (e.g. "Arc,armA=30,rad=10"). Such a string is used to + construct a `.ConnectionStyle` object, as documented in that class. - set_connectionstyle("arc,angleA=0,armA=30,rad=10") + The following connection styles are available: - Alternatively, the attributes can be provided as keywords, e.g.:: + %(ConnectionStyle:table_and_accepts)s - set_connectionstyle("arc", angleA=0,armA=30,rad=10) + **kwargs + Additional attributes for the connection style. See the table above + for supported parameters. - Without any arguments (or with ``connectionstyle=None``), return - available styles as a list of strings. - """ + Examples + -------- + :: + set_connectionstyle("Arc,armA=30,rad=10") + set_connectionstyle("arc", armA=30, rad=10) + """ if connectionstyle is None: return ConnectionStyle.pprint_styles() - - if (isinstance(connectionstyle, ConnectionStyle._Base) or - callable(connectionstyle)): - self._connector = connectionstyle - else: - self._connector = ConnectionStyle(connectionstyle, **kw) + self._connector = ( + ConnectionStyle(connectionstyle, **kwargs) + if isinstance(connectionstyle, str) else connectionstyle) self.stale = True def get_connectionstyle(self): """Return the `ConnectionStyle` used.""" return self._connector - def set_arrowstyle(self, arrowstyle=None, **kw): + @_docstring.interpd + def set_arrowstyle(self, arrowstyle=None, **kwargs): """ - Set the arrow style. Old attributes are forgotten. Without arguments - (or with ``arrowstyle=None``) returns available box styles as a list of - strings. + Set the arrow style, possibly with further attributes. + + Attributes from the previous arrow style are not reused. + + Without argument (or with ``arrowstyle=None``), the available box + styles are returned as a human-readable string. Parameters ---------- - arrowstyle : None or ArrowStyle or str, default: None - Can be a string with arrowstyle name with optional comma-separated - attributes, e.g.:: + arrowstyle : str or `~matplotlib.patches.ArrowStyle` + The style of the arrow: either a `.ArrowStyle` instance, or a + string, which is the style name and optionally comma separated + attributes (e.g. "Fancy,head_length=0.2"). Such a string is used to + construct a `.ArrowStyle` object, as documented in that class. - set_arrowstyle("Fancy,head_length=0.2") + The following arrow styles are available: - Alternatively attributes can be provided as keywords, e.g.:: + %(ArrowStyle:table_and_accepts)s - set_arrowstyle("fancy", head_length=0.2) + **kwargs + Additional attributes for the arrow style. See the table above for + supported parameters. - """ + Examples + -------- + :: + set_arrowstyle("Fancy,head_length=0.2") + set_arrowstyle("fancy", head_length=0.2) + """ if arrowstyle is None: return ArrowStyle.pprint_styles() - - if isinstance(arrowstyle, ArrowStyle._Base): - self._arrow_transmuter = arrowstyle - else: - self._arrow_transmuter = ArrowStyle(arrowstyle, **kw) + self._arrow_transmuter = ( + ArrowStyle(arrowstyle, **kwargs) + if isinstance(arrowstyle, str) else arrowstyle) self.stale = True def get_arrowstyle(self): @@ -4112,17 +4482,15 @@ def get_mutation_aspect(self): else 1) # backcompat. def get_path(self): - """ - Return the path of the arrow in the data coordinates. Use - get_path_in_displaycoord() method to retrieve the arrow path - in display coordinates. - """ - _path, fillable = self.get_path_in_displaycoord() + """Return the path of the arrow in the data coordinates.""" + # The path is generated in display coordinates, then converted back to + # data coordinates. + _path, fillable = self._get_path_in_displaycoord() if np.iterable(fillable): _path = Path.make_compound_path(*_path) return self.get_transform().inverted().transform_path(_path) - def get_path_in_displaycoord(self): + def _get_path_in_displaycoord(self): """Return the mutated path of the arrow in display coordinates.""" dpi_cor = self._dpi_cor @@ -4145,46 +4513,39 @@ def get_path_in_displaycoord(self): self.get_linewidth() * dpi_cor, self.get_mutation_aspect()) - # if not fillable: - # self._fill = False - return _path, fillable def draw(self, renderer): if not self.get_visible(): return - with self._bind_draw_path_function(renderer) as draw_path: + # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There + # could be room for improvement. Maybe _get_path_in_displaycoord could + # take a renderer argument, but get_path should be adapted too. + self._dpi_cor = renderer.points_to_pixels(1.) + path, fillable = self._get_path_in_displaycoord() - # FIXME : dpi_cor is for the dpi-dependency of the linewidth. There - # could be room for improvement. Maybe get_path_in_displaycoord - # could take a renderer argument, but get_path should be adapted - # too. - self._dpi_cor = renderer.points_to_pixels(1.) - path, fillable = self.get_path_in_displaycoord() + if not np.iterable(fillable): + path = [path] + fillable = [fillable] - if not np.iterable(fillable): - path = [path] - fillable = [fillable] + affine = transforms.IdentityTransform() - affine = transforms.IdentityTransform() - - for p, f in zip(path, fillable): - draw_path( - p, affine, - self._facecolor if f and self._facecolor[3] else None) + self._draw_paths_with_artist_properties( + renderer, + [(p, affine, self._facecolor if f and self._facecolor[3] else None) + for p, f in zip(path, fillable)]) class ConnectionPatch(FancyArrowPatch): - """A patch that connects two points (possibly in different axes).""" + """A patch that connects two points (possibly in different Axes).""" def __str__(self): return "ConnectionPatch((%g, %g), (%g, %g))" % \ (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1]) - @docstring.dedent_interpd - @cbook._delete_parameter("3.4", "dpi_cor") - def __init__(self, xyA, xyB, coordsA, coordsB=None, + @_docstring.interpd + def __init__(self, xyA, xyB, coordsA, coordsB=None, *, axesA=None, axesB=None, arrowstyle="-", connectionstyle="arc3", @@ -4195,7 +4556,6 @@ def __init__(self, xyA, xyB, coordsA, coordsB=None, mutation_scale=10., mutation_aspect=None, clip_on=False, - dpi_cor=1., **kwargs): """ Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*. @@ -4220,34 +4580,43 @@ def __init__(self, xyA, xyB, coordsA, coordsB=None, *coordsA* and *coordsB* are strings that indicate the coordinates of *xyA* and *xyB*. - ================= =================================================== - Property Description - ================= =================================================== - 'figure points' points from the lower left corner of the figure - 'figure pixels' pixels from the lower left corner of the figure - 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper right - 'axes points' points from lower left corner of axes - 'axes pixels' pixels from lower left corner of axes - 'axes fraction' 0, 0 is lower left of axes and 1, 1 is upper right - 'data' use the coordinate system of the object being - annotated (default) - 'offset points' offset (in points) from the *xy* value - 'polar' you can specify *theta*, *r* for the annotation, - even in cartesian plots. Note that if you are using - a polar axes, you do not need to specify polar for - the coordinate system since that is the native - "data" coordinate system. - ================= =================================================== + ==================== ================================================== + Property Description + ==================== ================================================== + 'figure points' points from the lower left corner of the figure + 'figure pixels' pixels from the lower left corner of the figure + 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper + right + 'subfigure points' points from the lower left corner of the subfigure + 'subfigure pixels' pixels from the lower left corner of the subfigure + 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left. + 'axes points' points from lower left corner of the Axes + 'axes pixels' pixels from lower left corner of the Axes + 'axes fraction' 0, 0 is lower left of Axes and 1, 1 is upper right + 'data' use the coordinate system of the object being + annotated (default) + 'offset points' offset (in points) from the *xy* value + 'polar' you can specify *theta*, *r* for the annotation, + even in cartesian plots. Note that if you are + using a polar Axes, you do not need to specify + polar for the coordinate system since that is the + native "data" coordinate system. + ==================== ================================================== Alternatively they can be set to any valid `~matplotlib.transforms.Transform`. + Note that 'subfigure pixels' and 'figure pixels' are the same + for the parent figure, so users who want code that is usable in + a subfigure can use 'subfigure pixels'. + .. note:: Using `ConnectionPatch` across two `~.axes.Axes` instances - is not directly compatible with :doc:`constrained layout - `. Add the artist - directly to the `.Figure` instead of adding it to a specific Axes. + is not directly compatible with :ref:`constrained layout + `. Add the artist + directly to the `.Figure` instead of adding it to a specific Axes, + or exclude it from the layout using ``con.set_in_layout(False)``. .. code-block:: default @@ -4276,9 +4645,7 @@ def __init__(self, xyA, xyB, coordsA, coordsB=None, mutation_aspect=mutation_aspect, clip_on=clip_on, **kwargs) - self._dpi_cor = dpi_cor - - # if True, draw annotation only if self.xy is inside the axes + # if True, draw annotation only if self.xy is inside the Axes self._annotation_clip = None def _get_xy(self, xy, s, axes=None): @@ -4286,27 +4653,34 @@ def _get_xy(self, xy, s, axes=None): s0 = s # For the error message, if needed. if axes is None: axes = self.axes - xy = np.array(xy) + + # preserve mixed type input (such as str, int) + x = np.array(xy[0]) + y = np.array(xy[1]) + + fig = self.get_figure(root=False) if s in ["figure points", "axes points"]: - xy *= self.figure.dpi / 72 + x = x * fig.dpi / 72 + y = y * fig.dpi / 72 s = s.replace("points", "pixels") elif s == "figure fraction": - s = self.figure.transFigure + s = fig.transFigure + elif s == "subfigure fraction": + s = fig.transSubfigure elif s == "axes fraction": s = axes.transAxes - x, y = xy if s == 'data': trans = axes.transData - x = float(self.convert_xunits(x)) - y = float(self.convert_yunits(y)) + x = cbook._to_unmasked_float_array(axes.xaxis.convert_units(x)) + y = cbook._to_unmasked_float_array(axes.yaxis.convert_units(y)) return trans.transform((x, y)) elif s == 'offset points': if self.xycoords == 'offset points': # prevent recursion return self._get_xy(self.xy, 'data') return ( self._get_xy(self.xy, self.xycoords) # converted data point - + xy * self.figure.dpi / 72) # converted offset + + xy * self.get_figure(root=True).dpi / 72) # converted offset elif s == 'polar': theta, r = x, y x = r * np.cos(theta) @@ -4315,12 +4689,18 @@ def _get_xy(self, xy, s, axes=None): return trans.transform((x, y)) elif s == 'figure pixels': # pixels from the lower left corner of the figure - bb = self.figure.bbox + bb = self.get_figure(root=False).figbbox + x = bb.x0 + x if x >= 0 else bb.x1 + x + y = bb.y0 + y if y >= 0 else bb.y1 + y + return x, y + elif s == 'subfigure pixels': + # pixels from the lower left corner of the figure + bb = self.get_figure(root=False).bbox x = bb.x0 + x if x >= 0 else bb.x1 + x y = bb.y0 + y if y >= 0 else bb.y1 + y return x, y elif s == 'axes pixels': - # pixels from the lower left corner of the axes + # pixels from the lower left corner of the Axes bb = axes.bbox x = bb.x0 + x if x >= 0 else bb.x1 + x y = bb.y0 + y if y >= 0 else bb.y1 + y @@ -4332,18 +4712,16 @@ def _get_xy(self, xy, s, axes=None): def set_annotation_clip(self, b): """ - Set the clipping behavior. + Set the annotation's clipping behavior. Parameters ---------- b : bool or None - - - *False*: The annotation will always be drawn regardless of its - position. - - *True*: The annotation will only be drawn if ``self.xy`` is - inside the axes. - - *None*: The annotation will only be drawn if ``self.xy`` is - inside the axes and ``self.xycoords == "data"``. + - True: The annotation will be clipped when ``self.xy`` is + outside the Axes. + - False: The annotation will always be drawn. + - None: The annotation will be clipped when ``self.xy`` is + outside the Axes and ``self.xycoords == "data"``. """ self._annotation_clip = b self.stale = True @@ -4356,7 +4734,7 @@ def get_annotation_clip(self): """ return self._annotation_clip - def get_path_in_displaycoord(self): + def _get_path_in_displaycoord(self): """Return the mutated path of the arrow in display coordinates.""" dpi_cor = self._dpi_cor posA = self._get_xy(self.xy1, self.coords1, self.axesA) @@ -4400,8 +4778,6 @@ def _check_xy(self, renderer): return True def draw(self, renderer): - if renderer is not None: - self._renderer = renderer if not self.get_visible() or not self._check_xy(renderer): return super().draw(renderer) diff --git a/lib/matplotlib/patches.pyi b/lib/matplotlib/patches.pyi new file mode 100644 index 000000000000..c95f20e35812 --- /dev/null +++ b/lib/matplotlib/patches.pyi @@ -0,0 +1,760 @@ +from . import artist +from .axes import Axes +from .backend_bases import RendererBase, MouseEvent +from .path import Path +from .transforms import Transform, Bbox + +from typing import Any, Literal, overload + +import numpy as np +from numpy.typing import ArrayLike +from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType + +class Patch(artist.Artist): + zorder: float + def __init__( + self, + *, + edgecolor: ColorType | None = ..., + facecolor: ColorType | None = ..., + color: ColorType | None = ..., + linewidth: float | None = ..., + linestyle: LineStyleType | None = ..., + antialiased: bool | None = ..., + hatch: str | None = ..., + fill: bool = ..., + capstyle: CapStyleType | None = ..., + joinstyle: JoinStyleType | None = ..., + hatchcolor: ColorType | Literal["edge"] | None = ..., + **kwargs, + ) -> None: ... + def get_verts(self) -> ArrayLike: ... + def contains(self, mouseevent: MouseEvent, radius: float | None = None) -> tuple[bool, dict[Any, Any]]: ... + def contains_point( + self, point: tuple[float, float], radius: float | None = ... + ) -> bool: ... + def contains_points( + self, points: ArrayLike, radius: float | None = ... + ) -> np.ndarray: ... + def get_extents(self) -> Bbox: ... + def get_transform(self) -> Transform: ... + def get_data_transform(self) -> Transform: ... + def get_patch_transform(self) -> Transform: ... + def get_antialiased(self) -> bool: ... + def get_edgecolor(self) -> ColorType: ... + def get_facecolor(self) -> ColorType: ... + def get_hatchcolor(self) -> ColorType: ... + def get_linewidth(self) -> float: ... + def get_linestyle(self) -> LineStyleType: ... + def set_antialiased(self, aa: bool | None) -> None: ... + def set_edgecolor(self, color: ColorType | None) -> None: ... + def set_facecolor(self, color: ColorType | None) -> None: ... + def set_color(self, c: ColorType | None) -> None: ... + def set_hatchcolor(self, color: ColorType | Literal["edge"] | None) -> None: ... + def set_alpha(self, alpha: float | None) -> None: ... + def set_linewidth(self, w: float | None) -> None: ... + def set_linestyle(self, ls: LineStyleType | None) -> None: ... + def set_fill(self, b: bool) -> None: ... + def get_fill(self) -> bool: ... + fill = property(get_fill, set_fill) + def set_capstyle(self, s: CapStyleType) -> None: ... + def get_capstyle(self) -> Literal["butt", "projecting", "round"]: ... + def set_joinstyle(self, s: JoinStyleType) -> None: ... + def get_joinstyle(self) -> Literal["miter", "round", "bevel"]: ... + def set_hatch(self, hatch: str) -> None: ... + def set_hatch_linewidth(self, lw: float) -> None: ... + def get_hatch_linewidth(self) -> float: ... + def get_hatch(self) -> str: ... + def get_path(self) -> Path: ... + +class Shadow(Patch): + patch: Patch + def __init__(self, patch: Patch, ox: float, oy: float, *, shade: float = ..., **kwargs) -> None: ... + +class Rectangle(Patch): + angle: float + def __init__( + self, + xy: tuple[float, float], + width: float, + height: float, + *, + angle: float = ..., + rotation_point: Literal["xy", "center"] | tuple[float, float] = ..., + **kwargs, + ) -> None: ... + @property + def rotation_point(self) -> Literal["xy", "center"] | tuple[float, float]: ... + @rotation_point.setter + def rotation_point( + self, value: Literal["xy", "center"] | tuple[float, float] + ) -> None: ... + def get_x(self) -> float: ... + def get_y(self) -> float: ... + def get_xy(self) -> tuple[float, float]: ... + def get_corners(self) -> np.ndarray: ... + def get_center(self) -> np.ndarray: ... + def get_width(self) -> float: ... + def get_height(self) -> float: ... + def get_angle(self) -> float: ... + def set_x(self, x: float) -> None: ... + def set_y(self, y: float) -> None: ... + def set_angle(self, angle: float) -> None: ... + def set_xy(self, xy: tuple[float, float]) -> None: ... + def set_width(self, w: float) -> None: ... + def set_height(self, h: float) -> None: ... + @overload + def set_bounds(self, args: tuple[float, float, float, float], /) -> None: ... + @overload + def set_bounds( + self, left: float, bottom: float, width: float, height: float, / + ) -> None: ... + def get_bbox(self) -> Bbox: ... + xy = property(get_xy, set_xy) + +class RegularPolygon(Patch): + xy: tuple[float, float] + numvertices: int + orientation: float + radius: float + def __init__( + self, + xy: tuple[float, float], + numVertices: int, + *, + radius: float = ..., + orientation: float = ..., + **kwargs, + ) -> None: ... + +class PathPatch(Patch): + def __init__(self, path: Path, **kwargs) -> None: ... + def set_path(self, path: Path) -> None: ... + +class StepPatch(PathPatch): + orientation: Literal["vertical", "horizontal"] + def __init__( + self, + values: ArrayLike, + edges: ArrayLike, + *, + orientation: Literal["vertical", "horizontal"] = ..., + baseline: float = ..., + **kwargs, + ) -> None: ... + + # NamedTuple StairData, defined in body of method + def get_data(self) -> tuple[np.ndarray, np.ndarray, float]: ... + def set_data( + self, + values: ArrayLike | None = ..., + edges: ArrayLike | None = ..., + baseline: float | None = ..., + ) -> None: ... + +class Polygon(Patch): + def __init__(self, xy: ArrayLike, *, closed: bool = ..., **kwargs) -> None: ... + def get_closed(self) -> bool: ... + def set_closed(self, closed: bool) -> None: ... + def get_xy(self) -> np.ndarray: ... + def set_xy(self, xy: ArrayLike) -> None: ... + xy = property(get_xy, set_xy) + +class Wedge(Patch): + center: tuple[float, float] + r: float + theta1: float + theta2: float + width: float | None + def __init__( + self, + center: tuple[float, float], + r: float, + theta1: float, + theta2: float, + *, + width: float | None = ..., + **kwargs, + ) -> None: ... + def set_center(self, center: tuple[float, float]) -> None: ... + def set_radius(self, radius: float) -> None: ... + def set_theta1(self, theta1: float) -> None: ... + def set_theta2(self, theta2: float) -> None: ... + def set_width(self, width: float | None) -> None: ... + +class Arrow(Patch): + def __init__( + self, x: float, y: float, dx: float, dy: float, *, width: float = ..., **kwargs + ) -> None: ... + def set_data( + self, + x: float | None = ..., + y: float | None = ..., + dx: float | None = ..., + dy: float | None = ..., + width: float | None = ..., + ) -> None: ... +class FancyArrow(Polygon): + def __init__( + self, + x: float, + y: float, + dx: float, + dy: float, + *, + width: float = ..., + length_includes_head: bool = ..., + head_width: float | None = ..., + head_length: float | None = ..., + shape: Literal["full", "left", "right"] = ..., + overhang: float = ..., + head_starts_at_zero: bool = ..., + **kwargs, + ) -> None: ... + def set_data( + self, + *, + x: float | None = ..., + y: float | None = ..., + dx: float | None = ..., + dy: float | None = ..., + width: float | None = ..., + head_width: float | None = ..., + head_length: float | None = ..., + ) -> None: ... + +class CirclePolygon(RegularPolygon): + def __init__( + self, + xy: tuple[float, float], + radius: float = ..., + *, + resolution: int = ..., + **kwargs, + ) -> None: ... + +class Ellipse(Patch): + def __init__( + self, + xy: tuple[float, float], + width: float, + height: float, + *, + angle: float = ..., + **kwargs, + ) -> None: ... + def set_center(self, xy: tuple[float, float]) -> None: ... + def get_center(self) -> float: ... + center = property(get_center, set_center) + + def set_width(self, width: float) -> None: ... + def get_width(self) -> float: ... + width = property(get_width, set_width) + + def set_height(self, height: float) -> None: ... + def get_height(self) -> float: ... + height = property(get_height, set_height) + + def set_angle(self, angle: float) -> None: ... + def get_angle(self) -> float: ... + angle = property(get_angle, set_angle) + + def get_corners(self) -> np.ndarray: ... + + def get_vertices(self) -> list[tuple[float, float]]: ... + def get_co_vertices(self) -> list[tuple[float, float]]: ... + + +class Annulus(Patch): + a: float + b: float + def __init__( + self, + xy: tuple[float, float], + r: float | tuple[float, float], + width: float, + angle: float = ..., + **kwargs, + ) -> None: ... + def set_center(self, xy: tuple[float, float]) -> None: ... + def get_center(self) -> tuple[float, float]: ... + center = property(get_center, set_center) + + def set_width(self, width: float) -> None: ... + def get_width(self) -> float: ... + width = property(get_width, set_width) + + def set_angle(self, angle: float) -> None: ... + def get_angle(self) -> float: ... + angle = property(get_angle, set_angle) + + def set_semimajor(self, a: float) -> None: ... + def set_semiminor(self, b: float) -> None: ... + def set_radii(self, r: float | tuple[float, float]) -> None: ... + def get_radii(self) -> tuple[float, float]: ... + radii = property(get_radii, set_radii) + +class Circle(Ellipse): + def __init__( + self, xy: tuple[float, float], radius: float = ..., **kwargs + ) -> None: ... + def set_radius(self, radius: float) -> None: ... + def get_radius(self) -> float: ... + radius = property(get_radius, set_radius) + +class Arc(Ellipse): + theta1: float + theta2: float + def __init__( + self, + xy: tuple[float, float], + width: float, + height: float, + *, + angle: float = ..., + theta1: float = ..., + theta2: float = ..., + **kwargs, + ) -> None: ... + +def bbox_artist( + artist: artist.Artist, + renderer: RendererBase, + props: dict[str, Any] | None = ..., + fill: bool = ..., +) -> None: ... +def draw_bbox( + bbox: Bbox, + renderer: RendererBase, + color: ColorType = ..., + trans: Transform | None = ..., +) -> None: ... + +class _Style: + def __new__(cls, stylename, **kwargs): ... + @classmethod + def get_styles(cls) -> dict[str, type]: ... + @classmethod + def pprint_styles(cls) -> str: ... + @classmethod + def register(cls, name: str, style: type) -> None: ... + +class BoxStyle(_Style): + class Square(BoxStyle): + pad: float + def __init__(self, pad: float = ...) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Circle(BoxStyle): + pad: float + def __init__(self, pad: float = ...) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Ellipse(BoxStyle): + pad: float + def __init__(self, pad: float = ...) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class LArrow(BoxStyle): + pad: float + def __init__(self, pad: float = ...) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class RArrow(LArrow): + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class DArrow(BoxStyle): + pad: float + def __init__(self, pad: float = ...) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Round(BoxStyle): + pad: float + rounding_size: float | None + def __init__( + self, pad: float = ..., rounding_size: float | None = ... + ) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Round4(BoxStyle): + pad: float + rounding_size: float | None + def __init__( + self, pad: float = ..., rounding_size: float | None = ... + ) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Sawtooth(BoxStyle): + pad: float + tooth_size: float | None + def __init__( + self, pad: float = ..., tooth_size: float | None = ... + ) -> None: ... + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + + class Roundtooth(Sawtooth): + def __call__( + self, + x0: float, + y0: float, + width: float, + height: float, + mutation_size: float, + ) -> Path: ... + +class ConnectionStyle(_Style): + class _Base(ConnectionStyle): + def __call__( + self, + posA: tuple[float, float], + posB: tuple[float, float], + shrinkA: float = ..., + shrinkB: float = ..., + patchA: Patch | None = ..., + patchB: Patch | None = ..., + ) -> Path: ... + + class Arc3(_Base): + rad: float + def __init__(self, rad: float = ...) -> None: ... + def connect( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> Path: ... + + class Angle3(_Base): + angleA: float + angleB: float + def __init__(self, angleA: float = ..., angleB: float = ...) -> None: ... + def connect( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> Path: ... + + class Angle(_Base): + angleA: float + angleB: float + rad: float + def __init__( + self, angleA: float = ..., angleB: float = ..., rad: float = ... + ) -> None: ... + def connect( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> Path: ... + + class Arc(_Base): + angleA: float + angleB: float + armA: float | None + armB: float | None + rad: float + def __init__( + self, + angleA: float = ..., + angleB: float = ..., + armA: float | None = ..., + armB: float | None = ..., + rad: float = ..., + ) -> None: ... + def connect( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> Path: ... + + class Bar(_Base): + armA: float + armB: float + fraction: float + angle: float | None + def __init__( + self, + armA: float = ..., + armB: float = ..., + fraction: float = ..., + angle: float | None = ..., + ) -> None: ... + def connect( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> Path: ... + +class ArrowStyle(_Style): + class _Base(ArrowStyle): + @staticmethod + def ensure_quadratic_bezier(path: Path) -> list[float]: ... + def transmute( + self, path: Path, mutation_size: float, linewidth: float + ) -> tuple[Path, bool]: ... + def __call__( + self, + path: Path, + mutation_size: float, + linewidth: float, + aspect_ratio: float = ..., + ) -> tuple[Path, bool]: ... + + class _Curve(_Base): + arrow: str + fillbegin: bool + fillend: bool + def __init__( + self, + head_length: float = ..., + head_width: float = ..., + widthA: float = ..., + widthB: float = ..., + lengthA: float = ..., + lengthB: float = ..., + angleA: float | None = ..., + angleB: float | None = ..., + scaleA: float | None = ..., + scaleB: float | None = ..., + ) -> None: ... + + class Curve(_Curve): + def __init__(self) -> None: ... + + class CurveA(_Curve): + arrow: str + + class CurveB(_Curve): + arrow: str + + class CurveAB(_Curve): + arrow: str + + class CurveFilledA(_Curve): + arrow: str + + class CurveFilledB(_Curve): + arrow: str + + class CurveFilledAB(_Curve): + arrow: str + + class BracketA(_Curve): + arrow: str + def __init__( + self, widthA: float = ..., lengthA: float = ..., angleA: float = ... + ) -> None: ... + + class BracketB(_Curve): + arrow: str + def __init__( + self, widthB: float = ..., lengthB: float = ..., angleB: float = ... + ) -> None: ... + + class BracketAB(_Curve): + arrow: str + def __init__( + self, + widthA: float = ..., + lengthA: float = ..., + angleA: float = ..., + widthB: float = ..., + lengthB: float = ..., + angleB: float = ..., + ) -> None: ... + + class BarAB(_Curve): + arrow: str + def __init__( + self, + widthA: float = ..., + angleA: float = ..., + widthB: float = ..., + angleB: float = ..., + ) -> None: ... + + class BracketCurve(_Curve): + arrow: str + def __init__( + self, widthA: float = ..., lengthA: float = ..., angleA: float | None = ... + ) -> None: ... + + class CurveBracket(_Curve): + arrow: str + def __init__( + self, widthB: float = ..., lengthB: float = ..., angleB: float | None = ... + ) -> None: ... + + class Simple(_Base): + def __init__( + self, + head_length: float = ..., + head_width: float = ..., + tail_width: float = ..., + ) -> None: ... + + class Fancy(_Base): + def __init__( + self, + head_length: float = ..., + head_width: float = ..., + tail_width: float = ..., + ) -> None: ... + + class Wedge(_Base): + tail_width: float + shrink_factor: float + def __init__( + self, tail_width: float = ..., shrink_factor: float = ... + ) -> None: ... + +class FancyBboxPatch(Patch): + def __init__( + self, + xy: tuple[float, float], + width: float, + height: float, + boxstyle: str | BoxStyle = ..., + *, + mutation_scale: float = ..., + mutation_aspect: float = ..., + **kwargs, + ) -> None: ... + def set_boxstyle(self, boxstyle: str | BoxStyle | None = ..., **kwargs) -> None: ... + def get_boxstyle(self) -> BoxStyle: ... + def set_mutation_scale(self, scale: float) -> None: ... + def get_mutation_scale(self) -> float: ... + def set_mutation_aspect(self, aspect: float) -> None: ... + def get_mutation_aspect(self) -> float: ... + def get_x(self) -> float: ... + def get_y(self) -> float: ... + def get_width(self) -> float: ... + def get_height(self) -> float: ... + def set_x(self, x: float) -> None: ... + def set_y(self, y: float) -> None: ... + def set_width(self, w: float) -> None: ... + def set_height(self, h: float) -> None: ... + @overload + def set_bounds(self, args: tuple[float, float, float, float], /) -> None: ... + @overload + def set_bounds( + self, left: float, bottom: float, width: float, height: float, / + ) -> None: ... + def get_bbox(self) -> Bbox: ... + +class FancyArrowPatch(Patch): + patchA: Patch + patchB: Patch + shrinkA: float + shrinkB: float + def __init__( + self, + posA: tuple[float, float] | None = ..., + posB: tuple[float, float] | None = ..., + *, + path: Path | None = ..., + arrowstyle: str | ArrowStyle = ..., + connectionstyle: str | ConnectionStyle = ..., + patchA: Patch | None = ..., + patchB: Patch | None = ..., + shrinkA: float = ..., + shrinkB: float = ..., + mutation_scale: float = ..., + mutation_aspect: float | None = ..., + **kwargs, + ) -> None: ... + def set_positions( + self, posA: tuple[float, float], posB: tuple[float, float] + ) -> None: ... + def set_patchA(self, patchA: Patch) -> None: ... + def set_patchB(self, patchB: Patch) -> None: ... + def set_connectionstyle(self, connectionstyle: str | ConnectionStyle | None = ..., **kwargs) -> None: ... + def get_connectionstyle(self) -> ConnectionStyle: ... + def set_arrowstyle(self, arrowstyle: str | ArrowStyle | None = ..., **kwargs) -> None: ... + def get_arrowstyle(self) -> ArrowStyle: ... + def set_mutation_scale(self, scale: float) -> None: ... + def get_mutation_scale(self) -> float: ... + def set_mutation_aspect(self, aspect: float | None) -> None: ... + def get_mutation_aspect(self) -> float: ... + +class ConnectionPatch(FancyArrowPatch): + xy1: tuple[float, float] + xy2: tuple[float, float] + coords1: str | Transform + coords2: str | Transform | None + axesA: Axes | None + axesB: Axes | None + def __init__( + self, + xyA: tuple[float, float], + xyB: tuple[float, float], + coordsA: str | Transform, + coordsB: str | Transform | None = ..., + *, + axesA: Axes | None = ..., + axesB: Axes | None = ..., + arrowstyle: str | ArrowStyle = ..., + connectionstyle: str | ConnectionStyle = ..., + patchA: Patch | None = ..., + patchB: Patch | None = ..., + shrinkA: float = ..., + shrinkB: float = ..., + mutation_scale: float = ..., + mutation_aspect: float | None = ..., + clip_on: bool = ..., + **kwargs, + ) -> None: ... + def set_annotation_clip(self, b: bool | None) -> None: ... + def get_annotation_clip(self) -> bool | None: ... diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index e1daf35eed45..a021706fb1e5 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -9,13 +9,14 @@ visualisation. """ +import copy from functools import lru_cache from weakref import WeakValueDictionary import numpy as np import matplotlib as mpl -from . import _path, cbook +from . import _api, _path from .cbook import _to_unmasked_float_array, simple_linear_interpolation from .bezier import BezierSegment @@ -27,38 +28,38 @@ class Path: The underlying storage is made up of two parallel numpy arrays: - - *vertices*: an Nx2 float array of vertices - - *codes*: an N-length uint8 array of vertex types, or None + - *vertices*: an (N, 2) float array of vertices + - *codes*: an N-length `numpy.uint8` array of path codes, or None These two arrays always have the same length in the first dimension. For example, to represent a cubic curve, you must - provide three vertices as well as three codes ``CURVE3``. + provide three vertices and three `CURVE4` codes. The code types are: - - ``STOP`` : 1 vertex (ignored) + - `STOP` : 1 vertex (ignored) A marker for the end of the entire path (currently not required and ignored) - - ``MOVETO`` : 1 vertex + - `MOVETO` : 1 vertex Pick up the pen and move to the given vertex. - - ``LINETO`` : 1 vertex + - `LINETO` : 1 vertex Draw a line from the current position to the given vertex. - - ``CURVE3`` : 1 control point, 1 endpoint - Draw a quadratic Bezier curve from the current position, with the given + - `CURVE3` : 1 control point, 1 endpoint + Draw a quadratic Bézier curve from the current position, with the given control point, to the given end point. - - ``CURVE4`` : 2 control points, 1 endpoint - Draw a cubic Bezier curve from the current position, with the given + - `CURVE4` : 2 control points, 1 endpoint + Draw a cubic Bézier curve from the current position, with the given control points, to the given end point. - - ``CLOSEPOLY`` : 1 vertex (ignored) + - `CLOSEPOLY` : 1 vertex (ignored) Draw a line segment to the start point of the current polyline. - If *codes* is None, it is interpreted as a ``MOVETO`` followed by a series - of ``LINETO``. + If *codes* is None, it is interpreted as a `MOVETO` followed by a series + of `LINETO`. Users of Path objects should not access the vertices and codes arrays directly. Instead, they should use `iter_segments` or `cleaned` to get the @@ -75,7 +76,6 @@ class Path: made up front in the constructor that will not change when the data changes. """ - code_type = np.uint8 # Path codes @@ -102,16 +102,13 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, Parameters ---------- - vertices : array-like - The ``(N, 2)`` float array, masked array or sequence of pairs - representing the vertices of the path. - - If *vertices* contains masked values, they will be converted - to NaNs which are then handled correctly by the Agg - PathIterator and other consumers of path data, such as - :meth:`iter_segments`. + vertices : (N, 2) array-like + The path vertices, as an array, masked array or sequence of pairs. + Masked values, if any, will be converted to NaNs, which are then + handled correctly by the Agg PathIterator and other consumers of + path data, such as :meth:`iter_segments`. codes : array-like or None, optional - n-length array integers representing the codes of the path. + N-length array of integers representing the codes of the path. If not None, codes must be the same length as vertices. If None, *vertices* will be treated as a series of line segments. _interpolation_steps : int, optional @@ -123,15 +120,15 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, If *codes* is None and closed is True, vertices will be treated as line segments of a closed polygon. Note that the last vertex will then be ignored (as the corresponding code will be set to - CLOSEPOLY). + `CLOSEPOLY`). readonly : bool, optional Makes the path behave in an immutable way and sets the vertices and codes as read-only arrays. """ vertices = _to_unmasked_float_array(vertices) - cbook._check_shape((None, 2), vertices=vertices) + _api.check_shape((None, 2), vertices=vertices) - if codes is not None: + if codes is not None and len(vertices): codes = np.asarray(codes, self.code_type) if codes.ndim != 1 or len(codes) != len(vertices): raise ValueError("'codes' must be a 1D list or array with the " @@ -164,12 +161,12 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, @classmethod def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None): """ - Creates a Path instance without the expense of calling the constructor. + Create a Path instance without the expense of calling the constructor. Parameters ---------- - verts : numpy array - codes : numpy array + verts : array-like + codes : array internals_from : Path or None If not None, another `Path` from which the attributes ``should_simplify``, ``simplify_threshold``, and @@ -190,6 +187,17 @@ def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None): pth._interpolation_steps = 1 return pth + @classmethod + def _create_closed(cls, vertices): + """ + Create a closed polygonal path going through *vertices*. + + Unlike ``Path(..., closed=True)``, *vertices* should **not** end with + an entry for the CLOSEPATH; this entry is added by `._create_closed`. + """ + v = _to_unmasked_float_array(vertices) + return cls(np.concatenate([v, v[:1]]), closed=True) + def _update_values(self): self._simplify_threshold = mpl.rcParams['path.simplify_threshold'] self._should_simplify = ( @@ -201,9 +209,7 @@ def _update_values(self): @property def vertices(self): - """ - The list of vertices in the `Path` as an Nx2 numpy array. - """ + """The vertices of the `Path` as an (N, 2) array.""" return self._vertices @vertices.setter @@ -216,12 +222,12 @@ def vertices(self, vertices): @property def codes(self): """ - The list of codes in the `Path` as a 1-D numpy array. Each - code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4` - or `CLOSEPOLY`. For codes that correspond to more than one - vertex (`CURVE3` and `CURVE4`), that code will be repeated so - that the length of `self.vertices` and `self.codes` is always - the same. + The list of codes in the `Path` as a 1D array. + + Each code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4` or + `CLOSEPOLY`. For codes that correspond to more than one vertex + (`CURVE3` and `CURVE4`), that code will be repeated so that the length + of `vertices` and `codes` is always the same. """ return self._codes @@ -262,43 +268,37 @@ def readonly(self): """ return self._readonly - def __copy__(self): + def copy(self): """ Return a shallow copy of the `Path`, which will share the vertices and codes with the source `Path`. """ - import copy return copy.copy(self) - copy = __copy__ - def __deepcopy__(self, memo=None): """ Return a deepcopy of the `Path`. The `Path` will not be readonly, even if the source `Path` is. """ - try: - codes = self.codes.copy() - except AttributeError: - codes = None - return self.__class__( - self.vertices.copy(), codes, - _interpolation_steps=self._interpolation_steps) + # Deepcopying arrays (vertices, codes) strips the writeable=False flag. + p = copy.deepcopy(super(), memo) + p._readonly = False + return p deepcopy = __deepcopy__ @classmethod def make_compound_path_from_polys(cls, XY): """ - Make a compound path object to draw a number - of polygons with equal numbers of sides XY is a (numpolys x - numsides x 2) numpy array of vertices. Return object is a - :class:`Path` + Make a compound `Path` object to draw a number of polygons with equal + numbers of sides. .. plot:: gallery/misc/histogram_path.py + Parameters + ---------- + XY : (numpolys, numsides, 2) array """ - # for each poly: 1 for the MOVETO, (numsides-1) for the LINETO, 1 for # the CLOSEPOLY; the vert for the closepoly is ignored but we still # need it to keep the codes aligned with the vertices @@ -313,37 +313,32 @@ def make_compound_path_from_polys(cls, XY): codes[numsides::stride] = cls.CLOSEPOLY for i in range(numsides): verts[i::stride] = XY[:, i] - return cls(verts, codes) @classmethod def make_compound_path(cls, *args): + r""" + Concatenate a list of `Path`\s into a single `Path`, removing all `STOP`\s. """ - Make a compound path from a list of Path objects. Blindly removes all - Path.STOP control points. - """ - # Handle an empty list in args (i.e. no args). if not args: return Path(np.empty([0, 2], dtype=np.float32)) - vertices = np.concatenate([x.vertices for x in args]) + vertices = np.concatenate([path.vertices for path in args]) codes = np.empty(len(vertices), dtype=cls.code_type) i = 0 for path in args: + size = len(path.vertices) if path.codes is None: - codes[i] = cls.MOVETO - codes[i + 1:i + len(path.vertices)] = cls.LINETO + if size: + codes[i] = cls.MOVETO + codes[i+1:i+size] = cls.LINETO else: - codes[i:i + len(path.codes)] = path.codes - i += len(path.vertices) - # remove STOP's, since internal STOPs are a bug - not_stop_mask = codes != cls.STOP - vertices = vertices[not_stop_mask, :] - codes = codes[not_stop_mask] - - return cls(vertices, codes) + codes[i:i+size] = path.codes + i += size + not_stop_mask = codes != cls.STOP # Remove STOPs, as internal STOPs are a bug. + return cls(vertices[not_stop_mask], codes[not_stop_mask]) def __repr__(self): - return "Path(%r, %r)" % (self.vertices, self.codes) + return f"Path({self.vertices!r}, {self.codes!r})" def __len__(self): return len(self.vertices) @@ -416,7 +411,7 @@ def iter_segments(self, transform=None, remove_nans=True, clip=None, def iter_bezier(self, **kwargs): """ - Iterate over each bezier curve (lines included) in a Path. + Iterate over each Bézier curve (lines included) in a `Path`. Parameters ---------- @@ -425,15 +420,15 @@ def iter_bezier(self, **kwargs): Yields ------ - B : matplotlib.bezier.BezierSegment - The bezier curves that make up the current path. Note in particular - that freestanding points are bezier curves of order 0, and lines - are bezier curves of order 1 (with two control points). - code : Path.code_type + B : `~matplotlib.bezier.BezierSegment` + The Bézier curves that make up the current path. Note in particular + that freestanding points are Bézier curves of order 0, and lines + are Bézier curves of order 1 (with two control points). + code : `~matplotlib.path.Path.code_type` The code describing what kind of curve is being returned. - Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE4 correspond to - bezier curves with 1, 2, 3, and 4 control points (respectively). - Path.CLOSEPOLY is a Path.LINETO with the control points correctly + `MOVETO`, `LINETO`, `CURVE3`, and `CURVE4` correspond to + Bézier curves with 1, 2, 3, and 4 control points (respectively). + `CLOSEPOLY` is a `LINETO` with the control points correctly chosen based on the start/end points of the current stroke. """ first_vert = None @@ -458,15 +453,24 @@ def iter_bezier(self, **kwargs): elif code == Path.STOP: return else: - raise ValueError("Invalid Path.code_type: " + str(code)) + raise ValueError(f"Invalid Path.code_type: {code}") prev_vert = verts[-2:] - @cbook._delete_parameter("3.3", "quantize") + def _iter_connected_components(self): + """Return subpaths split at MOVETOs.""" + if self.codes is None: + yield self + else: + idxs = np.append((self.codes == Path.MOVETO).nonzero()[0], len(self.codes)) + for sl in map(slice, idxs, idxs[1:]): + yield Path._fast_from_codes_and_verts( + self.vertices[sl], self.codes[sl], self) + def cleaned(self, transform=None, remove_nans=False, clip=None, - quantize=False, simplify=False, curves=False, + *, simplify=False, curves=False, stroke_width=1.0, snap=False, sketch=None): """ - Return a new Path with vertices and codes cleaned according to the + Return a new `Path` with vertices and codes cleaned according to the parameters. See Also @@ -496,18 +500,22 @@ def transformed(self, transform): def contains_point(self, point, transform=None, radius=0.0): """ - Return whether the (closed) path contains the given point. + Return whether the area enclosed by the path contains the given point. + + The path is always treated as closed; i.e. if the last code is not + `CLOSEPOLY` an implicit segment connecting the last vertex to the first + vertex is assumed. Parameters ---------- point : (float, float) The point (x, y) to check. - transform : `matplotlib.transforms.Transform`, optional + transform : `~matplotlib.transforms.Transform`, optional If not ``None``, *point* will be compared to ``self`` transformed by *transform*; i.e. for a correct check, *transform* should transform the path into the coordinate system of *point*. radius : float, default: 0 - Add an additional margin on the path in coordinates of *point*. + Additional margin on the path in coordinates of *point*. The path is extended tangentially by *radius/2*; i.e. if you would draw the path with a linewidth of *radius*, all points on the line would still be considered to be contained in the area. Conversely, @@ -517,6 +525,17 @@ def contains_point(self, point, transform=None, radius=0.0): Returns ------- bool + + Notes + ----- + The current algorithm has some limitations: + + - The result is undefined for points exactly at the boundary + (i.e. at the path shifted by *radius/2*). + - The result is undefined if there is no enclosed area, i.e. all + vertices are on a straight line. + - If bounding lines start to cross each other due to *radius* shift, + the result is not guaranteed to be correct. """ if transform is not None: transform = transform.frozen() @@ -531,18 +550,22 @@ def contains_point(self, point, transform=None, radius=0.0): def contains_points(self, points, transform=None, radius=0.0): """ - Return whether the (closed) path contains the given point. + Return whether the area enclosed by the path contains the given points. + + The path is always treated as closed; i.e. if the last code is not + `CLOSEPOLY` an implicit segment connecting the last vertex to the first + vertex is assumed. Parameters ---------- points : (N, 2) array The points to check. Columns contain x and y values. - transform : `matplotlib.transforms.Transform`, optional + transform : `~matplotlib.transforms.Transform`, optional If not ``None``, *points* will be compared to ``self`` transformed by *transform*; i.e. for a correct check, *transform* should transform the path into the coordinate system of *points*. - radius : float, default: 0. - Add an additional margin on the path in coordinates of *points*. + radius : float, default: 0 + Additional margin on the path in coordinates of *points*. The path is extended tangentially by *radius/2*; i.e. if you would draw the path with a linewidth of *radius*, all points on the line would still be considered to be contained in the area. Conversely, @@ -552,6 +575,17 @@ def contains_points(self, points, transform=None, radius=0.0): Returns ------- length-N bool array + + Notes + ----- + The current algorithm has some limitations: + + - The result is undefined for points exactly at the boundary + (i.e. at the path shifted by *radius/2*). + - The result is undefined if there is no enclosed area, i.e. all + vertices are on a straight line. + - If bounding lines start to cross each other due to *radius* shift, + the result is not guaranteed to be correct. """ if transform is not None: transform = transform.frozen() @@ -575,7 +609,7 @@ def get_extents(self, transform=None, **kwargs): Parameters ---------- - transform : matplotlib.transforms.Transform, optional + transform : `~matplotlib.transforms.Transform`, optional Transform to apply to path before computing extents, if any. **kwargs Forwarded to `.iter_bezier`. @@ -591,7 +625,12 @@ def get_extents(self, transform=None, **kwargs): if self.codes is None: xys = self.vertices elif len(np.intersect1d(self.codes, [Path.CURVE3, Path.CURVE4])) == 0: - xys = self.vertices[self.codes != Path.CLOSEPOLY] + # Optimization for the straight line case. + # Instead of iterating through each curve, consider + # each line segment's end-points + # (recall that STOP and CLOSEPOLY vertices are ignored) + xys = self.vertices[np.isin(self.codes, + [Path.MOVETO, Path.LINETO])] else: xys = [] for curve, code in self.iter_bezier(**kwargs): @@ -628,14 +667,35 @@ def intersects_bbox(self, bbox, filled=True): def interpolated(self, steps): """ - Return a new path resampled to length N x steps. + Return a new path with each segment divided into *steps* parts. + + Codes other than `LINETO`, `MOVETO`, and `CLOSEPOLY` are not handled correctly. + + Parameters + ---------- + steps : int + The number of segments in the new path for each in the original. - Codes other than LINETO are not handled correctly. + Returns + ------- + Path + The interpolated path. """ - if steps == 1: + if steps == 1 or len(self) == 0: return self - vertices = simple_linear_interpolation(self.vertices, steps) + if self.codes is not None and self.MOVETO in self.codes[1:]: + return self.make_compound_path( + *(p.interpolated(steps) for p in self._iter_connected_components())) + + if self.codes is not None and self.CLOSEPOLY in self.codes and not np.all( + self.vertices[self.codes == self.CLOSEPOLY] == self.vertices[0]): + vertices = self.vertices.copy() + vertices[self.codes == self.CLOSEPOLY] = vertices[0] + else: + vertices = self.vertices + + vertices = simple_linear_interpolation(vertices, steps) codes = self.codes if codes is not None: new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO, @@ -648,15 +708,18 @@ def interpolated(self, steps): def to_polygons(self, transform=None, width=0, height=0, closed_only=True): """ Convert this path to a list of polygons or polylines. Each - polygon/polyline is an Nx2 array of vertices. In other words, - each polygon has no ``MOVETO`` instructions or curves. This + polygon/polyline is an (N, 2) array of vertices. In other words, + each polygon has no `MOVETO` instructions or curves. This is useful for displaying in backends that do not support - compound paths or Bezier curves. + compound paths or Bézier curves. If *width* and *height* are both non-zero then the lines will be simplified so that vertices outside of (0, 0), (width, height) will be clipped. + The resulting polygons will be simplified if the + :attr:`Path.should_simplify` attribute of the path is `True`. + If *closed_only* is `True` (default), only closed polygons, with the last point being the same as the first point, will be returned. Any unclosed polylines in the path will be @@ -790,7 +853,7 @@ def circle(cls, center=(0., 0.), radius=1., readonly=False): Notes ----- - The circle is approximated using 8 cubic Bezier curves, as described in + The circle is approximated using 8 cubic Bézier curves, as described in Lancaster, Don. `Approximating a Circle or an Ellipse Using Four Bezier Cubic Splines `_. @@ -888,8 +951,8 @@ def unit_circle_righthalf(cls): @classmethod def arc(cls, theta1, theta2, n=None, is_wedge=False): """ - Return the unit circle arc from angles *theta1* to *theta2* (in - degrees). + Return a `Path` for the unit circle arc from angles *theta1* to + *theta2* (in degrees). *theta2* is unwrapped to produce the shortest arc within 360 degrees. That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to @@ -901,7 +964,7 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False): Masionobe, L. 2003. `Drawing an elliptical arc using polylines, quadratic or cubic Bezier curves - `_. + `_. """ halfpi = np.pi * 0.5 @@ -967,8 +1030,8 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False): @classmethod def wedge(cls, theta1, theta2, n=None): """ - Return the unit circle wedge from angles *theta1* to *theta2* (in - degrees). + Return a `Path` for the unit circle wedge from angles *theta1* to + *theta2* (in degrees). *theta2* is unwrapped to produce the shortest wedge within 360 degrees. That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1* @@ -986,7 +1049,7 @@ def wedge(cls, theta1, theta2, n=None): @lru_cache(8) def hatch(hatchpattern, density=6): """ - Given a hatch specifier, *hatchpattern*, generates a Path that + Given a hatch specifier, *hatchpattern*, generates a `Path` that can be used in a repeated hatching pattern. *density* is the number of lines per unit square. """ @@ -1004,7 +1067,6 @@ def clip_to_bbox(self, bbox, inside=True): If *inside* is `True`, clip to the inside of the box, otherwise to the outside of the box. """ - # Use make_compound_path_from_polys verts = _path.clip_path_to_rect(self, bbox, inside) paths = [Path(poly) for poly in verts] return self.make_compound_path(*paths) @@ -1013,32 +1075,39 @@ def clip_to_bbox(self, bbox, inside=True): def get_path_collection_extents( master_transform, paths, transforms, offsets, offset_transform): r""" - Given a sequence of `Path`\s, `~.Transform`\s objects, and offsets, as - found in a `~.PathCollection`, returns the bounding box that encapsulates - all of them. + Get bounding box of a `.PathCollection`\s internal objects. + + That is, given a sequence of `Path`\s, `.Transform`\s objects, and offsets, as found + in a `.PathCollection`, return the bounding box that encapsulates all of them. Parameters ---------- - master_transform : `~.Transform` + master_transform : `~matplotlib.transforms.Transform` Global transformation applied to all paths. paths : list of `Path` - transforms : list of `~.Affine2D` + transforms : list of `~matplotlib.transforms.Affine2DBase` + If non-empty, this overrides *master_transform*. offsets : (N, 2) array-like - offset_transform : `~.Affine2D` + offset_transform : `~matplotlib.transforms.Affine2DBase` Transform applied to the offsets before offsetting the path. Notes ----- - The way that *paths*, *transforms* and *offsets* are combined - follows the same method as for collections: Each is iterated over - independently, so if you have 3 paths, 2 transforms and 1 offset, - their combinations are as follows: - - (A, A, A), (B, B, A), (C, A, A) + The way that *paths*, *transforms* and *offsets* are combined follows the same + method as for collections: each is iterated over independently, so if you have 3 + paths (A, B, C), 2 transforms (α, β) and 1 offset (O), their combinations are as + follows: + + - (A, α, O) + - (B, β, O) + - (C, α, O) """ from .transforms import Bbox if len(paths) == 0: raise ValueError("No paths provided") - return Bbox.from_extents(*_path.get_path_collection_extents( + if len(offsets) == 0: + raise ValueError("No offsets provided") + extents, minpos = _path.get_path_collection_extents( master_transform, paths, np.atleast_3d(transforms), - offsets, offset_transform)) + offsets, offset_transform) + return Bbox.from_extents(*extents, minpos=minpos) diff --git a/lib/matplotlib/path.pyi b/lib/matplotlib/path.pyi new file mode 100644 index 000000000000..464fc6d9a912 --- /dev/null +++ b/lib/matplotlib/path.pyi @@ -0,0 +1,140 @@ +from .bezier import BezierSegment +from .transforms import Affine2D, Transform, Bbox +from collections.abc import Generator, Iterable, Sequence + +import numpy as np +from numpy.typing import ArrayLike + +from typing import Any, overload + +class Path: + code_type: type[np.uint8] + STOP: np.uint8 + MOVETO: np.uint8 + LINETO: np.uint8 + CURVE3: np.uint8 + CURVE4: np.uint8 + CLOSEPOLY: np.uint8 + NUM_VERTICES_FOR_CODE: dict[np.uint8, int] + + def __init__( + self, + vertices: ArrayLike, + codes: ArrayLike | None = ..., + _interpolation_steps: int = ..., + closed: bool = ..., + readonly: bool = ..., + ) -> None: ... + @property + def vertices(self) -> ArrayLike: ... + @vertices.setter + def vertices(self, vertices: ArrayLike) -> None: ... + @property + def codes(self) -> ArrayLike | None: ... + @codes.setter + def codes(self, codes: ArrayLike) -> None: ... + @property + def simplify_threshold(self) -> float: ... + @simplify_threshold.setter + def simplify_threshold(self, threshold: float) -> None: ... + @property + def should_simplify(self) -> bool: ... + @should_simplify.setter + def should_simplify(self, should_simplify: bool) -> None: ... + @property + def readonly(self) -> bool: ... + def copy(self) -> Path: ... + def __deepcopy__(self, memo: dict[int, Any] | None = ...) -> Path: ... + deepcopy = __deepcopy__ + + @classmethod + def make_compound_path_from_polys(cls, XY: ArrayLike) -> Path: ... + @classmethod + def make_compound_path(cls, *args: Path) -> Path: ... + def __len__(self) -> int: ... + def iter_segments( + self, + transform: Transform | None = ..., + remove_nans: bool = ..., + clip: tuple[float, float, float, float] | None = ..., + snap: bool | None = ..., + stroke_width: float = ..., + simplify: bool | None = ..., + curves: bool = ..., + sketch: tuple[float, float, float] | None = ..., + ) -> Generator[tuple[np.ndarray, np.uint8], None, None]: ... + def iter_bezier(self, **kwargs) -> Generator[BezierSegment, None, None]: ... + def cleaned( + self, + transform: Transform | None = ..., + remove_nans: bool = ..., + clip: tuple[float, float, float, float] | None = ..., + *, + simplify: bool | None = ..., + curves: bool = ..., + stroke_width: float = ..., + snap: bool | None = ..., + sketch: tuple[float, float, float] | None = ... + ) -> Path: ... + def transformed(self, transform: Transform) -> Path: ... + def contains_point( + self, + point: tuple[float, float], + transform: Transform | None = ..., + radius: float = ..., + ) -> bool: ... + def contains_points( + self, points: ArrayLike, transform: Transform | None = ..., radius: float = ... + ) -> np.ndarray: ... + def contains_path(self, path: Path, transform: Transform | None = ...) -> bool: ... + def get_extents(self, transform: Transform | None = ..., **kwargs) -> Bbox: ... + def intersects_path(self, other: Path, filled: bool = ...) -> bool: ... + def intersects_bbox(self, bbox: Bbox, filled: bool = ...) -> bool: ... + def interpolated(self, steps: int) -> Path: ... + def to_polygons( + self, + transform: Transform | None = ..., + width: float = ..., + height: float = ..., + closed_only: bool = ..., + ) -> list[ArrayLike]: ... + @classmethod + def unit_rectangle(cls) -> Path: ... + @classmethod + def unit_regular_polygon(cls, numVertices: int) -> Path: ... + @classmethod + def unit_regular_star(cls, numVertices: int, innerCircle: float = ...) -> Path: ... + @classmethod + def unit_regular_asterisk(cls, numVertices: int) -> Path: ... + @classmethod + def unit_circle(cls) -> Path: ... + @classmethod + def circle( + cls, + center: tuple[float, float] = ..., + radius: float = ..., + readonly: bool = ..., + ) -> Path: ... + @classmethod + def unit_circle_righthalf(cls) -> Path: ... + @classmethod + def arc( + cls, theta1: float, theta2: float, n: int | None = ..., is_wedge: bool = ... + ) -> Path: ... + @classmethod + def wedge(cls, theta1: float, theta2: float, n: int | None = ...) -> Path: ... + @overload + @staticmethod + def hatch(hatchpattern: str, density: float = ...) -> Path: ... + @overload + @staticmethod + def hatch(hatchpattern: None, density: float = ...) -> None: ... + def clip_to_bbox(self, bbox: Bbox, inside: bool = ...) -> Path: ... + +def get_path_collection_extents( + master_transform: Transform, + paths: Sequence[Path], + transforms: Iterable[Affine2D], + offsets: ArrayLike, + offset_transform: Affine2D, +) -> Bbox: ... diff --git a/lib/matplotlib/patheffects.py b/lib/matplotlib/patheffects.py index a5f5b7c42e3b..e12713e2796e 100644 --- a/lib/matplotlib/patheffects.py +++ b/lib/matplotlib/patheffects.py @@ -1,9 +1,9 @@ """ -Defines classes for path effects. The path effects are supported in `~.Text`, -`~.Line2D` and `~.Patch`. +Defines classes for path effects. The path effects are supported in `.Text`, +`.Line2D` and `.Patch`. .. seealso:: - :doc:`/tutorials/advanced/patheffects_guide` + :ref:`patheffects_guide` """ from matplotlib.backend_bases import RendererBase @@ -52,7 +52,7 @@ def _update_gc(self, gc, new_gc_dict): for k, v in new_gc_dict.items(): set_method = getattr(gc, 'set_' + k, None) if not callable(set_method): - raise AttributeError('Unknown property {0}'.format(k)) + raise AttributeError(f'Unknown property {k}') set_method(v) return gc @@ -87,7 +87,7 @@ def __init__(self, path_effects, renderer): ---------- path_effects : iterable of :class:`AbstractPathEffect` The path effects which this renderer represents. - renderer : `matplotlib.backend_bases.RendererBase` subclass + renderer : `~matplotlib.backend_bases.RendererBase` subclass """ self._path_effects = path_effects @@ -96,6 +96,13 @@ def __init__(self, path_effects, renderer): def copy_with_path_effect(self, path_effects): return self.__class__(path_effects, self._renderer) + def __getattribute__(self, name): + if name in ['flipy', 'get_canvas_width_height', 'new_gc', + 'points_to_pixels', '_text2path', 'height', 'width']: + return getattr(self._renderer, name) + else: + return object.__getattribute__(self, name) + def draw_path(self, gc, tpath, affine, rgbFace=None): for path_effect in self._path_effects: path_effect.draw_path(self._renderer, gc, tpath, affine, @@ -137,20 +144,11 @@ def draw_path_collection(self, gc, master_transform, paths, *args, renderer.draw_path_collection(gc, master_transform, paths, *args, **kwargs) - def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): - # Implements the naive text drawing as is found in RendererBase. - path, transform = self._get_text_path_transform(x, y, s, prop, - angle, ismath) - color = gc.get_rgb() - gc.set_linewidth(0.0) - self.draw_path(gc, path, transform, rgbFace=color) + def open_group(self, s, gid=None): + return self._renderer.open_group(s, gid) - def __getattribute__(self, name): - if name in ['flipy', 'get_canvas_width_height', 'new_gc', - 'points_to_pixels', '_text2path', 'height', 'width']: - return getattr(self._renderer, name) - else: - return object.__getattribute__(self, name) + def close_group(self, s): + return self._renderer.close_group(s) class Normal(AbstractPathEffect): @@ -180,12 +178,12 @@ def draw_path(self, renderer, gc, tpath, affine, rgbFace): With this class you can use :: - artist.set_path_effects([path_effects.with{effect_class.__name__}()]) + artist.set_path_effects([patheffects.with{effect_class.__name__}()]) as a shortcut for :: - artist.set_path_effects([path_effects.{effect_class.__name__}(), - path_effects.Normal()]) + artist.set_path_effects([patheffects.{effect_class.__name__}(), + patheffects.Normal()]) """ # Docstring inheritance doesn't work for locally-defined subclasses. withEffect.draw_path.__doc__ = effect_class.draw_path.__doc__ @@ -228,17 +226,16 @@ def __init__(self, offset=(2, -2), ---------- offset : (float, float), default: (2, -2) The (x, y) offset of the shadow in points. - shadow_rgbFace : color + shadow_rgbFace : :mpltype:`color` The shadow color. alpha : float, default: 0.3 The alpha transparency of the created shadow patch. - http://matplotlib.1069221.n5.nabble.com/path-effects-question-td27630.html rho : float, default: 0.3 - A scale factor to apply to the rgbFace color if `shadow_rgbFace` + A scale factor to apply to the rgbFace color if *shadow_rgbFace* is not specified. **kwargs Extra keywords are stored and passed through to - :meth:`AbstractPathEffect._update_gc`. + :meth:`!AbstractPathEffect._update_gc`. """ super().__init__(offset) @@ -296,18 +293,18 @@ def __init__(self, offset=(2, -2), ---------- offset : (float, float), default: (2, -2) The (x, y) offset to apply to the path, in points. - shadow_color : color, default: 'black' + shadow_color : :mpltype:`color`, default: 'black' The shadow color. A value of ``None`` takes the original artist's color with a scale factor of *rho*. alpha : float, default: 0.3 The alpha transparency of the created shadow patch. rho : float, default: 0.3 - A scale factor to apply to the rgbFace color if `shadow_rgbFace` + A scale factor to apply to the rgbFace color if *shadow_color* is ``None``. **kwargs Extra keywords are stored and passed through to - :meth:`AbstractPathEffect._update_gc`. + :meth:`!AbstractPathEffect._update_gc`. """ super().__init__(offset) if shadow_color is None: @@ -369,7 +366,7 @@ def draw_path(self, renderer, gc, tpath, affine, rgbFace): self.patch.set_transform(affine + self._offset_transform(renderer)) self.patch.set_clip_box(gc.get_clip_rectangle()) clip_path = gc.get_clip_path() - if clip_path: + if clip_path and self.patch.get_clip_path() is None: self.patch.set_clip_path(*clip_path) self.patch.draw(renderer) @@ -387,11 +384,7 @@ class TickedStroke(AbstractPathEffect): This line style is sometimes referred to as a hatched line. - See also the :doc:`contour demo example - `. - - See also the :doc:`contours in optimization example - `. + See also the :doc:`/gallery/misc/tickedstroke_demo` example. """ def __init__(self, offset=(0, 0), @@ -408,14 +401,15 @@ def __init__(self, offset=(0, 0), The angle between the path and the tick in degrees. The angle is measured as if you were an ant walking along the curve, with zero degrees pointing directly ahead, 90 to your left, -90 - to your right, and 180 behind you. + to your right, and 180 behind you. To change side of the ticks, + change sign of the angle. length : float, default: 1.414 The length of the tick relative to spacing. Recommended length = 1.414 (sqrt(2)) when angle=45, length=1.0 when angle=90 and length=2.0 when angle=60. **kwargs Extra keywords are stored and passed through to - :meth:`AbstractPathEffect._update_gc`. + :meth:`!AbstractPathEffect._update_gc`. Examples -------- diff --git a/lib/matplotlib/patheffects.pyi b/lib/matplotlib/patheffects.pyi new file mode 100644 index 000000000000..2c1634ca9314 --- /dev/null +++ b/lib/matplotlib/patheffects.pyi @@ -0,0 +1,106 @@ +from collections.abc import Iterable, Sequence +from typing import Any + +from matplotlib.backend_bases import RendererBase, GraphicsContextBase +from matplotlib.path import Path +from matplotlib.patches import Patch +from matplotlib.transforms import Transform + +from matplotlib.typing import ColorType + +class AbstractPathEffect: + def __init__(self, offset: tuple[float, float] = ...) -> None: ... + def draw_path( + self, + renderer: RendererBase, + gc: GraphicsContextBase, + tpath: Path, + affine: Transform, + rgbFace: ColorType | None = ..., + ) -> None: ... + +class PathEffectRenderer(RendererBase): + def __init__( + self, path_effects: Iterable[AbstractPathEffect], renderer: RendererBase + ) -> None: ... + def copy_with_path_effect(self, path_effects: Iterable[AbstractPathEffect]) -> PathEffectRenderer: ... + def draw_path( + self, + gc: GraphicsContextBase, + tpath: Path, + affine: Transform, + rgbFace: ColorType | None = ..., + ) -> None: ... + def draw_markers( + self, + gc: GraphicsContextBase, + marker_path: Path, + marker_trans: Transform, + path: Path, + *args, + **kwargs + ) -> None: ... + def draw_path_collection( + self, + gc: GraphicsContextBase, + master_transform: Transform, + paths: Sequence[Path], + *args, + **kwargs + ) -> None: ... + def __getattribute__(self, name: str) -> Any: ... + +class Normal(AbstractPathEffect): ... + +class Stroke(AbstractPathEffect): + def __init__(self, offset: tuple[float, float] = ..., **kwargs) -> None: ... + # rgbFace becomes non-optional + def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override] + +class withStroke(Stroke): ... + +class SimplePatchShadow(AbstractPathEffect): + def __init__( + self, + offset: tuple[float, float] = ..., + shadow_rgbFace: ColorType | None = ..., + alpha: float | None = ..., + rho: float = ..., + **kwargs + ) -> None: ... + # rgbFace becomes non-optional + def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override] + +class withSimplePatchShadow(SimplePatchShadow): ... + +class SimpleLineShadow(AbstractPathEffect): + def __init__( + self, + offset: tuple[float, float] = ..., + shadow_color: ColorType = ..., + alpha: float = ..., + rho: float = ..., + **kwargs + ) -> None: ... + # rgbFace becomes non-optional + def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override] + +class PathPatchEffect(AbstractPathEffect): + patch: Patch + def __init__(self, offset: tuple[float, float] = ..., **kwargs) -> None: ... + # rgbFace becomes non-optional + def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override] + +class TickedStroke(AbstractPathEffect): + def __init__( + self, + offset: tuple[float, float] = ..., + spacing: float = ..., + angle: float = ..., + length: float = ..., + **kwargs + ) -> None: ... + # rgbFace becomes non-optional + def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override] + +class withTickedStroke(TickedStroke): ... diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 556273803665..f7b46192a84e 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -1,7 +1,69 @@ -from .. import axes, docstring +""" +Non-separable transforms that map from data space to screen space. + +Projections are defined as `~.axes.Axes` subclasses. They include the +following elements: + +- A transformation from data coordinates into display coordinates. + +- An inverse of that transformation. This is used, for example, to convert + mouse positions from screen space back into data space. + +- Transformations for the gridlines, ticks and ticklabels. Custom projections + will often need to place these elements in special locations, and Matplotlib + has a facility to help with doing so. + +- Setting up default values (overriding `~.axes.Axes.cla`), since the defaults + for a rectilinear Axes may not be appropriate. + +- Defining the shape of the Axes, for example, an elliptical Axes, that will be + used to draw the background of the plot and for clipping any data elements. + +- Defining custom locators and formatters for the projection. For example, in + a geographic projection, it may be more convenient to display the grid in + degrees, even if the data is in radians. + +- Set up interactive panning and zooming. This is left as an "advanced" + feature left to the reader, but there is an example of this for polar plots + in `matplotlib.projections.polar`. + +- Any additional methods for additional convenience or features. + +Once the projection Axes is defined, it can be used in one of two ways: + +- By defining the class attribute ``name``, the projection Axes can be + registered with `matplotlib.projections.register_projection` and subsequently + simply invoked by name:: + + fig.add_subplot(projection="my_proj_name") + +- For more complex, parameterisable projections, a generic "projection" object + may be defined which includes the method ``_as_mpl_axes``. ``_as_mpl_axes`` + should take no arguments and return the projection's Axes subclass and a + dictionary of additional arguments to pass to the subclass' ``__init__`` + method. Subsequently a parameterised projection can be initialised with:: + + fig.add_subplot(projection=MyProjection(param1=param1_value)) + + where MyProjection is an object which implements a ``_as_mpl_axes`` method. + +A full-fledged and heavily annotated example is in +:doc:`/gallery/misc/custom_projection`. The polar plot functionality in +`matplotlib.projections.polar` may also be of interest. +""" + +from .. import axes, _docstring from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes from .polar import PolarAxes -from mpl_toolkits.mplot3d import Axes3D + +try: + from mpl_toolkits.mplot3d import Axes3D +except Exception: + import warnings + warnings.warn("Unable to import Axes3D. This may be due to multiple versions of " + "Matplotlib being installed (e.g. as a system package and as a pip " + "package). As a result, the 3D projection is not available.") + Axes3D = None class ProjectionRegistry: @@ -33,8 +95,12 @@ def get_projection_names(self): HammerAxes, LambertAxes, MollweideAxes, - Axes3D, ) +if Axes3D is not None: + projection_registry.register(Axes3D) +else: + # remove from namespace if not importable + del Axes3D def register_projection(cls): @@ -57,4 +123,4 @@ def get_projection_class(projection=None): get_projection_names = projection_registry.get_projection_names -docstring.interpd.update(projection_names=get_projection_names()) +_docstring.interpd.register(projection_names=get_projection_names()) diff --git a/lib/matplotlib/projections/__init__.pyi b/lib/matplotlib/projections/__init__.pyi new file mode 100644 index 000000000000..4e0d210f1c9e --- /dev/null +++ b/lib/matplotlib/projections/__init__.pyi @@ -0,0 +1,20 @@ +from .geo import ( + AitoffAxes as AitoffAxes, + HammerAxes as HammerAxes, + LambertAxes as LambertAxes, + MollweideAxes as MollweideAxes, +) +from .polar import PolarAxes as PolarAxes +from ..axes import Axes + +class ProjectionRegistry: + def __init__(self) -> None: ... + def register(self, *projections: type[Axes]) -> None: ... + def get_projection_class(self, name: str) -> type[Axes]: ... + def get_projection_names(self) -> list[str]: ... + +projection_registry: ProjectionRegistry + +def register_projection(cls: type[Axes]) -> None: ... +def get_projection_class(projection: str | None = ...) -> type[Axes]: ... +def get_projection_names() -> list[str]: ... diff --git a/lib/matplotlib/projections/geo.py b/lib/matplotlib/projections/geo.py index b658caa2bda4..d5ab3c746dea 100644 --- a/lib/matplotlib/projections/geo.py +++ b/lib/matplotlib/projections/geo.py @@ -1,6 +1,7 @@ import numpy as np -from matplotlib import cbook, rcParams +import matplotlib as mpl +from matplotlib import _api from matplotlib.axes import Axes import matplotlib.axis as maxis from matplotlib.patches import Circle @@ -29,15 +30,13 @@ def __call__(self, x, pos=None): RESOLUTION = 75 def _init_axis(self): - self.xaxis = maxis.XAxis(self) - self.yaxis = maxis.YAxis(self) - # Do not register xaxis or yaxis with spines -- as done in - # Axes._init_axis() -- until GeoAxes.xaxis.cla() works. - # self.spines['geo'].register_axis(self.yaxis) - self._update_transScale() + self.xaxis = maxis.XAxis(self, clear=False) + self.yaxis = maxis.YAxis(self, clear=False) + self.spines['geo'].register_axis(self.yaxis) - def cla(self): - super().cla() + def clear(self): + # docstring inherited + super().clear() self.set_longitude_grid(30) self.set_latitude_grid(15) @@ -50,7 +49,7 @@ def cla(self): # Why do we need to turn on yaxis tick labels, but # xaxis tick labels are already on? - self.grid(rcParams['axes.grid']) + self.grid(mpl.rcParams['axes.grid']) Axes.set_xlim(self, -np.pi, np.pi) Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0) @@ -115,7 +114,7 @@ def _get_affine_transform(self): .translate(0.5, 0.5) def get_xaxis_transform(self, which='grid'): - cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) + _api.check_in_list(['tick1', 'tick2', 'grid'], which=which) return self._xaxis_transform def get_xaxis_text1_transform(self, pad): @@ -125,7 +124,7 @@ def get_xaxis_text2_transform(self, pad): return self._xaxis_text2_transform, 'top', 'center' def get_yaxis_transform(self, which='grid'): - cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) + _api.check_in_list(['tick1', 'tick2', 'grid'], which=which) return self._yaxis_transform def get_yaxis_text1_transform(self, pad): @@ -147,22 +146,26 @@ def set_yscale(self, *args, **kwargs): set_xscale = set_yscale def set_xlim(self, *args, **kwargs): + """Not supported. Please consider using Cartopy.""" raise TypeError("Changing axes limits of a geographic projection is " "not supported. Please consider using Cartopy.") set_ylim = set_xlim + set_xbound = set_xlim + set_ybound = set_ylim + + def invert_xaxis(self): + """Not supported. Please consider using Cartopy.""" + raise TypeError("Changing axes limits of a geographic projection is " + "not supported. Please consider using Cartopy.") + + invert_yaxis = invert_xaxis def format_coord(self, lon, lat): """Return a format string formatting the coordinate.""" lon, lat = np.rad2deg([lon, lat]) - if lat >= 0.0: - ns = 'N' - else: - ns = 'S' - if lon >= 0.0: - ew = 'E' - else: - ew = 'W' + ns = 'N' if lat >= 0.0 else 'S' + ew = 'E' if lon >= 0.0 else 'W' return ('%f\N{DEGREE SIGN}%s, %f\N{DEGREE SIGN}%s' % (abs(lat), ns, abs(lon), ew)) @@ -202,17 +205,17 @@ def get_data_ratio(self): def can_zoom(self): """ - Return *True* if this axes supports the zoom box button functionality. + Return whether this Axes supports the zoom box button functionality. - This axes object does not support interactive zoom box. + This Axes object does not support interactive zoom box. """ return False def can_pan(self): """ - Return *True* if this axes supports the pan/zoom button functionality. + Return whether this Axes supports the pan/zoom button functionality. - This axes object does not support interactive pan/zoom. + This Axes object does not support interactive pan/zoom. """ return False @@ -241,7 +244,7 @@ def __init__(self, resolution): self._resolution = resolution def __str__(self): - return "{}({})".format(type(self).__name__, self._resolution) + return f"{type(self).__name__}({self._resolution})" def transform_path_non_affine(self, path): # docstring inherited @@ -255,19 +258,16 @@ class AitoffAxes(GeoAxes): class AitoffTransform(_GeoTransform): """The base Aitoff transform.""" - def transform_non_affine(self, ll): + def transform_non_affine(self, values): # docstring inherited - longitude, latitude = ll.T + longitude, latitude = values.T # Pre-compute some values half_long = longitude / 2.0 cos_latitude = np.cos(latitude) alpha = np.arccos(cos_latitude * np.cos(half_long)) - # Avoid divide-by-zero errors using same method as NumPy. - alpha[alpha == 0.0] = 1e-20 - # We want unnormalized sinc. numpy.sinc gives us normalized - sinc_alpha = np.sin(alpha) / alpha + sinc_alpha = np.sinc(alpha / np.pi) # np.sinc is sin(pi*x)/(pi*x). x = (cos_latitude * np.sin(half_long)) / sinc_alpha y = np.sin(latitude) / sinc_alpha @@ -279,10 +279,10 @@ def inverted(self): class InvertedAitoffTransform(_GeoTransform): - def transform_non_affine(self, xy): + def transform_non_affine(self, values): # docstring inherited # MGDTODO: Math is hard ;( - return xy + return np.full_like(values, np.nan) def inverted(self): # docstring inherited @@ -292,7 +292,7 @@ def __init__(self, *args, **kwargs): self._longitude_cap = np.pi / 2.0 super().__init__(*args, **kwargs) self.set_aspect(0.5, adjustable='box', anchor='C') - self.cla() + self.clear() def _get_core_transform(self, resolution): return self.AitoffTransform(resolution) @@ -304,9 +304,9 @@ class HammerAxes(GeoAxes): class HammerTransform(_GeoTransform): """The base Hammer transform.""" - def transform_non_affine(self, ll): + def transform_non_affine(self, values): # docstring inherited - longitude, latitude = ll.T + longitude, latitude = values.T half_long = longitude / 2.0 cos_latitude = np.cos(latitude) sqrt2 = np.sqrt(2.0) @@ -321,9 +321,9 @@ def inverted(self): class InvertedHammerTransform(_GeoTransform): - def transform_non_affine(self, xy): + def transform_non_affine(self, values): # docstring inherited - x, y = xy.T + x, y = values.T z = np.sqrt(1 - (x / 4) ** 2 - (y / 2) ** 2) longitude = 2 * np.arctan((z * x) / (2 * (2 * z ** 2 - 1))) latitude = np.arcsin(y*z) @@ -337,7 +337,7 @@ def __init__(self, *args, **kwargs): self._longitude_cap = np.pi / 2.0 super().__init__(*args, **kwargs) self.set_aspect(0.5, adjustable='box', anchor='C') - self.cla() + self.clear() def _get_core_transform(self, resolution): return self.HammerTransform(resolution) @@ -349,14 +349,14 @@ class MollweideAxes(GeoAxes): class MollweideTransform(_GeoTransform): """The base Mollweide transform.""" - def transform_non_affine(self, ll): + def transform_non_affine(self, values): # docstring inherited def d(theta): delta = (-(theta + np.sin(theta) - pi_sin_l) / (1 + np.cos(theta))) return delta, np.abs(delta) > 0.001 - longitude, latitude = ll.T + longitude, latitude = values.T clat = np.pi/2 - np.abs(latitude) ihigh = clat < 0.087 # within 5 degrees of the poles @@ -377,7 +377,7 @@ def d(theta): d = 0.5 * (3 * np.pi * e**2) ** (1.0/3) aux[ihigh] = (np.pi/2 - d) * np.sign(latitude[ihigh]) - xy = np.empty(ll.shape, dtype=float) + xy = np.empty(values.shape, dtype=float) xy[:, 0] = (2.0 * np.sqrt(2.0) / np.pi) * longitude * np.cos(aux) xy[:, 1] = np.sqrt(2.0) * np.sin(aux) @@ -389,9 +389,9 @@ def inverted(self): class InvertedMollweideTransform(_GeoTransform): - def transform_non_affine(self, xy): + def transform_non_affine(self, values): # docstring inherited - x, y = xy.T + x, y = values.T # from Equations (7, 8) of # https://mathworld.wolfram.com/MollweideProjection.html theta = np.arcsin(y / np.sqrt(2)) @@ -407,7 +407,7 @@ def __init__(self, *args, **kwargs): self._longitude_cap = np.pi / 2.0 super().__init__(*args, **kwargs) self.set_aspect(0.5, adjustable='box', anchor='C') - self.cla() + self.clear() def _get_core_transform(self, resolution): return self.MollweideTransform(resolution) @@ -429,9 +429,9 @@ def __init__(self, center_longitude, center_latitude, resolution): self._center_longitude = center_longitude self._center_latitude = center_latitude - def transform_non_affine(self, ll): + def transform_non_affine(self, values): # docstring inherited - longitude, latitude = ll.T + longitude, latitude = values.T clong = self._center_longitude clat = self._center_latitude cos_lat = np.cos(latitude) @@ -462,9 +462,9 @@ def __init__(self, center_longitude, center_latitude, resolution): self._center_longitude = center_longitude self._center_latitude = center_latitude - def transform_non_affine(self, xy): + def transform_non_affine(self, values): # docstring inherited - x, y = xy.T + x, y = values.T clong = self._center_longitude clat = self._center_latitude p = np.maximum(np.hypot(x, y), 1e-9) @@ -492,10 +492,11 @@ def __init__(self, *args, center_longitude=0, center_latitude=0, **kwargs): self._center_latitude = center_latitude super().__init__(*args, **kwargs) self.set_aspect('equal', adjustable='box', anchor='C') - self.cla() + self.clear() - def cla(self): - super().cla() + def clear(self): + # docstring inherited + super().clear() self.yaxis.set_major_formatter(NullFormatter()) def _get_core_transform(self, resolution): diff --git a/lib/matplotlib/projections/geo.pyi b/lib/matplotlib/projections/geo.pyi new file mode 100644 index 000000000000..93220f8cbcd5 --- /dev/null +++ b/lib/matplotlib/projections/geo.pyi @@ -0,0 +1,112 @@ +from matplotlib.axes import Axes +from matplotlib.ticker import Formatter +from matplotlib.transforms import Transform + +from typing import Any, Literal + +class GeoAxes(Axes): + class ThetaFormatter(Formatter): + def __init__(self, round_to: float = ...) -> None: ... + def __call__(self, x: float, pos: Any | None = ...): ... + RESOLUTION: float + def get_xaxis_transform( + self, which: Literal["tick1", "tick2", "grid"] = ... + ) -> Transform: ... + def get_xaxis_text1_transform( + self, pad: float + ) -> tuple[ + Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_xaxis_text2_transform( + self, pad: float + ) -> tuple[ + Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_yaxis_transform( + self, which: Literal["tick1", "tick2", "grid"] = ... + ) -> Transform: ... + def get_yaxis_text1_transform( + self, pad: float + ) -> tuple[ + Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_yaxis_text2_transform( + self, pad: float + ) -> tuple[ + Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def set_xlim(self, *args, **kwargs) -> tuple[float, float]: ... + def set_ylim(self, *args, **kwargs) -> tuple[float, float]: ... + def format_coord(self, lon: float, lat: float) -> str: ... + def set_longitude_grid(self, degrees: float) -> None: ... + def set_latitude_grid(self, degrees: float) -> None: ... + def set_longitude_grid_ends(self, degrees: float) -> None: ... + def get_data_ratio(self) -> float: ... + def can_zoom(self) -> bool: ... + def can_pan(self) -> bool: ... + def start_pan(self, x, y, button) -> None: ... + def end_pan(self) -> None: ... + def drag_pan(self, button, key, x, y) -> None: ... + +class _GeoTransform(Transform): + input_dims: int + output_dims: int + def __init__(self, resolution: int) -> None: ... + +class AitoffAxes(GeoAxes): + name: str + + class AitoffTransform(_GeoTransform): + def inverted(self) -> AitoffAxes.InvertedAitoffTransform: ... + + class InvertedAitoffTransform(_GeoTransform): + def inverted(self) -> AitoffAxes.AitoffTransform: ... + +class HammerAxes(GeoAxes): + name: str + + class HammerTransform(_GeoTransform): + def inverted(self) -> HammerAxes.InvertedHammerTransform: ... + + class InvertedHammerTransform(_GeoTransform): + def inverted(self) -> HammerAxes.HammerTransform: ... + +class MollweideAxes(GeoAxes): + name: str + + class MollweideTransform(_GeoTransform): + def inverted(self) -> MollweideAxes.InvertedMollweideTransform: ... + + class InvertedMollweideTransform(_GeoTransform): + def inverted(self) -> MollweideAxes.MollweideTransform: ... + +class LambertAxes(GeoAxes): + name: str + + class LambertTransform(_GeoTransform): + def __init__( + self, center_longitude: float, center_latitude: float, resolution: int + ) -> None: ... + def inverted(self) -> LambertAxes.InvertedLambertTransform: ... + + class InvertedLambertTransform(_GeoTransform): + def __init__( + self, center_longitude: float, center_latitude: float, resolution: int + ) -> None: ... + def inverted(self) -> LambertAxes.LambertTransform: ... + + def __init__( + self, + *args, + center_longitude: float = ..., + center_latitude: float = ..., + **kwargs + ) -> None: ... diff --git a/lib/matplotlib/projections/meson.build b/lib/matplotlib/projections/meson.build new file mode 100644 index 000000000000..221b93efadee --- /dev/null +++ b/lib/matplotlib/projections/meson.build @@ -0,0 +1,14 @@ +python_sources = [ + '__init__.py', + 'geo.py', + 'polar.py', +] + +typing_sources = [ + '__init__.pyi', + 'geo.pyi', + 'polar.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/projections') diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 16de1885daf7..948b3a6e704f 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1,9 +1,10 @@ -from collections import OrderedDict +import math import types import numpy as np -from matplotlib import cbook, rcParams +import matplotlib as mpl +from matplotlib import _api, cbook from matplotlib.axes import Axes import matplotlib.axis as maxis import matplotlib.markers as mmarkers @@ -11,42 +12,62 @@ from matplotlib.path import Path import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms -import matplotlib.spines as mspines +from matplotlib.spines import Spine class PolarTransform(mtransforms.Transform): + r""" + The base polar transform. + + This transform maps polar coordinates :math:`\theta, r` into Cartesian + coordinates :math:`x, y = r \cos(\theta), r \sin(\theta)` + (but does not fully transform into Axes coordinates or + handle positioning in screen space). + + This transformation is designed to be applied to data after any scaling + along the radial axis (e.g. log-scaling) has been applied to the input + data. + + Path segments at a fixed radius are automatically transformed to circular + arcs as long as ``path._interpolation_steps > 1``. """ - The base polar transform. This handles projection *theta* and - *r* into Cartesian coordinate space *x* and *y*, but does not - perform the ultimate affine transformation into the correct - position. - """ + input_dims = output_dims = 2 - def __init__(self, axis=None, use_rmin=True, - _apply_theta_transforms=True): + def __init__(self, axis=None, use_rmin=True, *, scale_transform=None): + """ + Parameters + ---------- + axis : `~matplotlib.axis.Axis`, optional + Axis associated with this transform. This is used to get the + minimum radial limit. + use_rmin : `bool`, optional + If ``True``, subtract the minimum radial axis limit before + transforming to Cartesian coordinates. *axis* must also be + specified for this to take effect. + """ super().__init__() self._axis = axis self._use_rmin = use_rmin - self._apply_theta_transforms = _apply_theta_transforms + self._scale_transform = scale_transform __str__ = mtransforms._make_str_method( "_axis", - use_rmin="_use_rmin", - _apply_theta_transforms="_apply_theta_transforms") + use_rmin="_use_rmin" + ) + + def _get_rorigin(self): + # Get lower r limit after being scaled by the radial scale transform + return self._scale_transform.transform( + (0, self._axis.get_rorigin()))[1] - def transform_non_affine(self, tr): + def transform_non_affine(self, values): # docstring inherited - t, r = np.transpose(tr) - # PolarAxes does not use the theta transforms here, but apply them for - # backwards-compatibility if not being used by it. - if self._apply_theta_transforms and self._axis is not None: - t *= self._axis.get_theta_direction() - t += self._axis.get_theta_offset() + theta, r = np.transpose(values) if self._use_rmin and self._axis is not None: - r = (r - self._axis.get_rorigin()) * self._axis.get_rsign() + r = (r - self._get_rorigin()) * self._axis.get_rsign() r = np.where(r >= 0, r, np.nan) - return np.column_stack([r * np.cos(t), r * np.sin(t)]) + return np.column_stack([r * np.cos(theta), r * np.sin(theta)]) def transform_path_non_affine(self, path): # docstring inherited @@ -68,7 +89,7 @@ def transform_path_non_affine(self, path): # that behavior here. last_td, td = np.rad2deg([last_t, t]) if self._use_rmin and self._axis is not None: - r = ((r - self._axis.get_rorigin()) + r = ((r - self._get_rorigin()) * self._axis.get_rsign()) if last_td <= td: while td - last_td > 360: @@ -92,7 +113,7 @@ def transform_path_non_affine(self, path): codes.extend(arc.codes[1:]) else: # Interpolate. trs = cbook.simple_linear_interpolation( - np.row_stack([(last_t, last_r), trs]), + np.vstack([(last_t, last_r), trs]), path._interpolation_steps)[1:] xys.extend(self.transform_non_affine(trs)) codes.extend([Path.LINETO] * len(trs)) @@ -104,20 +125,34 @@ def transform_path_non_affine(self, path): def inverted(self): # docstring inherited - return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin, - self._apply_theta_transforms) + return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin) class PolarAffine(mtransforms.Affine2DBase): - """ - The affine part of the polar projection. Scales the output so - that maximum radius rests on the edge of the axes circle. + r""" + The affine part of the polar projection. + + Scales the output so that maximum radius rests on the edge of the Axes + circle and the origin is mapped to (0.5, 0.5). The transform applied is + the same to x and y components and given by: + + .. math:: + + x_{1} = 0.5 \left [ \frac{x_{0}}{(r_{\max} - r_{\min})} + 1 \right ] + + :math:`r_{\min}, r_{\max}` are the minimum and maximum radial limits after + any scaling (e.g. log scaling) has been removed. """ def __init__(self, scale_transform, limits): """ - *limits* is the view limit of the data. The only part of - its bounds that is used is the y limits (for the radius limits). - The theta range is handled by the non-affine transform. + Parameters + ---------- + scale_transform : `~matplotlib.transforms.Transform` + Scaling transform for the data. This is used to remove any scaling + from the radial view limits. + limits : `~matplotlib.transforms.BboxBase` + View limits of the data. The only part of its bounds that is used + is the y limits (for the radius limits). """ super().__init__() self._scale_transform = scale_transform @@ -148,29 +183,31 @@ class InvertedPolarTransform(mtransforms.Transform): """ input_dims = output_dims = 2 - def __init__(self, axis=None, use_rmin=True, - _apply_theta_transforms=True): + def __init__(self, axis=None, use_rmin=True): + """ + Parameters + ---------- + axis : `~matplotlib.axis.Axis`, optional + Axis associated with this transform. This is used to get the + minimum radial limit. + use_rmin : `bool`, optional + If ``True``, add the minimum radial axis limit after + transforming from Cartesian coordinates. *axis* must also be + specified for this to take effect. + """ super().__init__() self._axis = axis self._use_rmin = use_rmin - self._apply_theta_transforms = _apply_theta_transforms __str__ = mtransforms._make_str_method( "_axis", - use_rmin="_use_rmin", - _apply_theta_transforms="_apply_theta_transforms") + use_rmin="_use_rmin") - def transform_non_affine(self, xy): + def transform_non_affine(self, values): # docstring inherited - x, y = xy.T + x, y = values.T r = np.hypot(x, y) theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi) - # PolarAxes does not use the theta transforms here, but apply them for - # backwards-compatibility if not being used by it. - if self._apply_theta_transforms and self._axis is not None: - theta -= self._axis.get_theta_offset() - theta *= self._axis.get_theta_direction() - theta %= 2 * np.pi if self._use_rmin and self._axis is not None: r += self._axis.get_rorigin() r *= self._axis.get_rsign() @@ -178,8 +215,7 @@ def transform_non_affine(self, xy): def inverted(self): # docstring inherited - return PolarAxes.PolarTransform(self._axis, self._use_rmin, - self._apply_theta_transforms) + return PolarAxes.PolarTransform(self._axis, self._use_rmin) class ThetaFormatter(mticker.Formatter): @@ -187,16 +223,12 @@ class ThetaFormatter(mticker.Formatter): Used to format the *theta* tick labels. Converts the native unit of radians into degrees and adds a degree symbol. """ + def __call__(self, x, pos=None): vmin, vmax = self.axis.get_view_interval() d = np.rad2deg(abs(vmax - vmin)) digits = max(-int(np.log10(d) - 1.5), 0) - # Use unicode rather than mathtext with \circ, so that it will work - # correctly with any arbitrary font (assuming it has a degree sign), - # whereas $5\circ$ will only work correctly with one of the supported - # math fonts (Computer Modern and STIX). - return ("{value:0.{digits:d}f}\N{DEGREE SIGN}" - .format(value=np.rad2deg(x), digits=digits)) + return f"{np.rad2deg(x):0.{digits}f}\N{DEGREE SIGN}" class _AxisWrapper: @@ -230,6 +262,7 @@ class ThetaLocator(mticker.Locator): view spans the entire circle. In such cases, the previously used default locations of every 45 degrees are returned. """ + def __init__(self, base): self.base = base self.axis = self.base.axis = _AxisWrapper(self.base.axis) @@ -241,30 +274,14 @@ def set_axis(self, axis): def __call__(self): lim = self.axis.get_view_interval() if _is_full_circle_deg(lim[0], lim[1]): - return np.arange(8) * 2 * np.pi / 8 + return np.deg2rad(min(lim)) + np.arange(8) * 2 * np.pi / 8 else: return np.deg2rad(self.base()) - @cbook.deprecated("3.2") - def autoscale(self): - return self.base.autoscale() - - @cbook.deprecated("3.3") - def pan(self, numsteps): - return self.base.pan(numsteps) - - def refresh(self): - # docstring inherited - return self.base.refresh() - def view_limits(self, vmin, vmax): vmin, vmax = np.rad2deg((vmin, vmax)) return np.deg2rad(self.base.view_limits(vmin, vmax)) - @cbook.deprecated("3.3") - def zoom(self, direction): - return self.base.zoom(direction) - class ThetaTick(maxis.XTick): """ @@ -282,9 +299,9 @@ class ThetaTick(maxis.XTick): def __init__(self, axes, *args, **kwargs): self._text1_translate = mtransforms.ScaledTranslation( - 0, 0, axes.figure.dpi_scale_trans) + 0, 0, axes.get_figure(root=False).dpi_scale_trans) self._text2_translate = mtransforms.ScaledTranslation( - 0, 0, axes.figure.dpi_scale_trans) + 0, 0, axes.get_figure(root=False).dpi_scale_trans) super().__init__(axes, *args, **kwargs) self.label1.set( rotation_mode='anchor', @@ -293,9 +310,8 @@ def __init__(self, axes, *args, **kwargs): rotation_mode='anchor', transform=self.label2.get_transform() + self._text2_translate) - def _apply_params(self, **kw): - super()._apply_params(**kw) - + def _apply_params(self, **kwargs): + super()._apply_params(**kwargs) # Ensure transform is correct; sometimes this gets reset. trans = self.label1.get_transform() if not trans.contains_branch(self._text1_translate): @@ -368,13 +384,7 @@ class ThetaAxis(maxis.XAxis): """ __name__ = 'thetaaxis' axis_name = 'theta' #: Read-only name identifying the axis. - - def _get_tick(self, major): - if major: - tick_kw = self._major_tick_kw - else: - tick_kw = self._minor_tick_kw - return ThetaTick(self.axes, 0, major=major, **tick_kw) + _tick_class = ThetaTick def _wrap_locator_formatter(self): self.set_major_locator(ThetaLocator(self.get_major_locator())) @@ -382,13 +392,21 @@ def _wrap_locator_formatter(self): self.isDefault_majloc = True self.isDefault_majfmt = True - def cla(self): - super().cla() + def clear(self): + # docstring inherited + super().clear() self.set_ticks_position('none') self._wrap_locator_formatter() def _set_scale(self, value, **kwargs): + if value != 'linear': + raise NotImplementedError( + "The xscale cannot be set on a polar plot") super()._set_scale(value, **kwargs) + # LinearScale.set_default_locators_and_formatters just set the major + # locator to be an AutoLocator, so we customize it here to have ticks + # at sensible degree multiples. + self.get_major_locator().set_params(steps=[1, 1.5, 3, 4.5, 9, 10]) self._wrap_locator_formatter() def _copy_tick_props(self, src, dest): @@ -408,54 +426,46 @@ class RadialLocator(mticker.Locator): """ Used to locate radius ticks. - Ensures that all ticks are strictly positive. For all other - tasks, it delegates to the base - :class:`~matplotlib.ticker.Locator` (which may be different - depending on the scale of the *r*-axis. + Ensures that all ticks are strictly positive. For all other tasks, it + delegates to the base `.Locator` (which may be different depending on the + scale of the *r*-axis). """ def __init__(self, base, axes=None): self.base = base self._axes = axes + def set_axis(self, axis): + self.base.set_axis(axis) + def __call__(self): - show_all = True # Ensure previous behaviour with full circle non-annular views. if self._axes: if _is_full_circle_rad(*self._axes.viewLim.intervalx): rorigin = self._axes.get_rorigin() * self._axes.get_rsign() if self._axes.get_rmin() <= rorigin: - show_all = False - if show_all: - return self.base() - else: - return [tick for tick in self.base() if tick > rorigin] - - @cbook.deprecated("3.2") - def autoscale(self): - return self.base.autoscale() - - @cbook.deprecated("3.3") - def pan(self, numsteps): - return self.base.pan(numsteps) + return [tick for tick in self.base() if tick > rorigin] + return self.base() - @cbook.deprecated("3.3") - def zoom(self, direction): - return self.base.zoom(direction) - - @cbook.deprecated("3.3") - def refresh(self): - # docstring inherited - return self.base.refresh() + def _zero_in_bounds(self): + """ + Return True if zero is within the valid values for the + scale of the radial axis. + """ + vmin, vmax = self._axes.yaxis._scale.limit_range_for_scale(0, 1, 1e-5) + return vmin == 0 def nonsingular(self, vmin, vmax): # docstring inherited - return ((0, 1) if (vmin, vmax) == (-np.inf, np.inf) # Init. limits. - else self.base.nonsingular(vmin, vmax)) + if self._zero_in_bounds() and (vmin, vmax) == (-np.inf, np.inf): + # Initial view limits + return (0, 1) + else: + return self.base.nonsingular(vmin, vmax) def view_limits(self, vmin, vmax): vmin, vmax = self.base.view_limits(vmin, vmax) - if vmax > vmin: + if self._zero_in_bounds() and vmax > vmin: # this allows inverted r/y-lims vmin = min(0, vmin) return mtransforms.nonsingular(vmin, vmax) @@ -470,7 +480,7 @@ class _ThetaShift(mtransforms.ScaledTranslation): Parameters ---------- axes : `~matplotlib.axes.Axes` - The owning axes; used to determine limits. + The owning Axes; used to determine limits. pad : float The padding to apply, in points. mode : {'min', 'max', 'rlabel'} @@ -478,7 +488,7 @@ class _ThetaShift(mtransforms.ScaledTranslation): of the axes, or using the rlabel position (``'rlabel'``). """ def __init__(self, axes, pad, mode): - super().__init__(pad, pad, axes.figure.dpi_scale_trans) + super().__init__(pad, pad, axes.get_figure(root=False).dpi_scale_trans) self.set_children(axes._realViewLim) self.axes = axes self.mode = mode @@ -490,24 +500,16 @@ def get_matrix(self): if self._invalid: if self.mode == 'rlabel': angle = ( - np.deg2rad(self.axes.get_rlabel_position()) * - self.axes.get_theta_direction() + - self.axes.get_theta_offset() + np.deg2rad(self.axes.get_rlabel_position() + * self.axes.get_theta_direction()) + + self.axes.get_theta_offset() + - np.pi / 2 ) - else: - if self.mode == 'min': - angle = self.axes._realViewLim.xmin - elif self.mode == 'max': - angle = self.axes._realViewLim.xmax - - if self.mode in ('rlabel', 'min'): - padx = np.cos(angle - np.pi / 2) - pady = np.sin(angle - np.pi / 2) - else: - padx = np.cos(angle + np.pi / 2) - pady = np.sin(angle + np.pi / 2) - - self._t = (self.pad * padx / 72, self.pad * pady / 72) + elif self.mode == 'min': + angle = self.axes._realViewLim.xmin - np.pi / 2 + elif self.mode == 'max': + angle = self.axes._realViewLim.xmax + np.pi / 2 + self._t = (self.pad * np.cos(angle) / 72, self.pad * np.sin(angle) / 72) return super().get_matrix() @@ -671,25 +673,20 @@ class RadialAxis(maxis.YAxis): """ __name__ = 'radialaxis' axis_name = 'radius' #: Read-only name identifying the axis. + _tick_class = RadialTick def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sticky_edges.y.append(0) - def _get_tick(self, major): - if major: - tick_kw = self._major_tick_kw - else: - tick_kw = self._minor_tick_kw - return RadialTick(self.axes, 0, major=major, **tick_kw) - def _wrap_locator_formatter(self): self.set_major_locator(RadialLocator(self.get_major_locator(), self.axes)) self.isDefault_majloc = True - def cla(self): - super().cla() + def clear(self): + # docstring inherited + super().clear() self.set_ticks_position('none') self._wrap_locator_formatter() @@ -718,7 +715,7 @@ def _is_full_circle_rad(thetamin, thetamax): class _WedgeBbox(mtransforms.Bbox): """ - Transform (theta, r) wedge Bbox into axes bounding box. + Transform (theta, r) wedge Bbox into Axes bounding box. Parameters ---------- @@ -790,10 +787,11 @@ def __init__(self, *args, super().__init__(*args, **kwargs) self.use_sticky_edges = True self.set_aspect('equal', adjustable='box', anchor='C') - self.cla() + self.clear() - def cla(self): - super().cla() + def clear(self): + # docstring inherited + super().clear() self.title.set_y(1.05) @@ -805,7 +803,7 @@ def cla(self): end.set_visible(False) self.set_xlim(0.0, 2 * np.pi) - self.grid(rcParams['polaraxes.grid']) + self.grid(mpl.rcParams['polaraxes.grid']) inner = self.spines.get('inner', None) if inner: inner.set_visible(False) @@ -816,13 +814,9 @@ def cla(self): def _init_axis(self): # This is moved out of __init__ because non-separable axes don't use it - self.xaxis = ThetaAxis(self) - self.yaxis = RadialAxis(self) - # Calling polar_axes.xaxis.cla() or polar_axes.xaxis.cla() - # results in weird artifacts. Therefore we disable this for - # now. - # self.spines['polar'].register_axis(self.yaxis) - self._update_transScale() + self.xaxis = ThetaAxis(self, clear=False) + self.yaxis = RadialAxis(self, clear=False) + self.spines['polar'].register_axis(self.yaxis) def _set_lim_and_transforms(self): # A view limit where the minimum radius can be locked if the user @@ -861,7 +855,8 @@ def _set_lim_and_transforms(self): # data. This one is aware of rmin self.transProjection = self.PolarTransform( self, - _apply_theta_transforms=False) + scale_transform=self.transScale + ) # Add dependency on rorigin. self.transProjection.set_children(self._originViewLim) @@ -872,9 +867,25 @@ def _set_lim_and_transforms(self): # The complete data transformation stack -- from data all the # way to display coordinates + # + # 1. Remove any radial axis scaling (e.g. log scaling) + # 2. Shift data in the theta direction + # 3. Project the data from polar to cartesian values + # (with the origin in the same place) + # 4. Scale and translate the cartesian values to Axes coordinates + # (here the origin is moved to the lower left of the Axes) + # 5. Move and scale to fill the Axes + # 6. Convert from Axes coordinates to Figure coordinates self.transData = ( - self.transScale + self.transShift + self.transProjection + - (self.transProjectionAffine + self.transWedge + self.transAxes)) + self.transScale + + self.transShift + + self.transProjection + + ( + self.transProjectionAffine + + self.transWedge + + self.transAxes + ) + ) # This is the transform for theta-axis ticks. It is # equivalent to transData, except it always puts r == 0.0 and r == 1.0 @@ -907,7 +918,7 @@ def _set_lim_and_transforms(self): self._r_label_position + self.transData) def get_xaxis_transform(self, which='grid'): - cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) + _api.check_in_list(['tick1', 'tick2', 'grid'], which=which) return self._xaxis_transform def get_xaxis_text1_transform(self, pad): @@ -922,7 +933,7 @@ def get_yaxis_transform(self, which='grid'): elif which == 'grid': return self._yaxis_transform else: - cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) + _api.check_in_list(['tick1', 'tick2', 'grid'], which=which) def get_yaxis_text1_transform(self, pad): thetamin, thetamax = self._realViewLim.intervalx @@ -945,9 +956,7 @@ def get_yaxis_text2_transform(self, pad): pad_shift = _ThetaShift(self, pad, 'min') return self._yaxis_text_transform + pad_shift, 'center', halign - @cbook._delete_parameter("3.3", "args") - @cbook._delete_parameter("3.3", "kwargs") - def draw(self, renderer, *args, **kwargs): + def draw(self, renderer): self._unstale_viewLim() thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) if thetamin > thetamax: @@ -991,20 +1000,18 @@ def draw(self, renderer, *args, **kwargs): self.yaxis.reset_ticks() self.yaxis.set_clip_path(self.patch) - super().draw(renderer, *args, **kwargs) + super().draw(renderer) def _gen_axes_patch(self): return mpatches.Wedge((0.5, 0.5), 0.5, 0.0, 360.0) def _gen_axes_spines(self): - spines = OrderedDict([ - ('polar', mspines.Spine.arc_spine(self, 'top', - (0.5, 0.5), 0.5, 0.0, 360.0)), - ('start', mspines.Spine.linear_spine(self, 'left')), - ('end', mspines.Spine.linear_spine(self, 'right')), - ('inner', mspines.Spine.arc_spine(self, 'bottom', - (0.5, 0.5), 0.0, 0.0, 360.0)) - ]) + spines = { + 'polar': Spine.arc_spine(self, 'top', (0.5, 0.5), 0.5, 0, 360), + 'start': Spine.linear_spine(self, 'left'), + 'end': Spine.linear_spine(self, 'right'), + 'inner': Spine.arc_spine(self, 'bottom', (0.5, 0.5), 0.0, 0, 360), + } spines['polar'].set_transform(self.transWedge + self.transAxes) spines['inner'].set_transform(self.transWedge + self.transAxes) spines['start'].set_transform(self._yaxis_transform) @@ -1040,30 +1047,21 @@ def set_thetalim(self, *args, **kwargs): where minval and maxval are the minimum and maximum limits. Values are wrapped in to the range :math:`[0, 2\pi]` (in radians), so for example it is possible to do ``set_thetalim(-np.pi / 2, np.pi / 2)`` to have - an axes symmetric around 0. A ValueError is raised if the absolute - angle difference is larger than :math:`2\pi`. + an axis symmetric around 0. A ValueError is raised if the absolute + angle difference is larger than a full circle. """ - thetamin = None - thetamax = None - left = None - right = None - - if len(args) == 2: - if args[0] is not None and args[1] is not None: - left, right = args - if abs(right - left) > 2 * np.pi: - raise ValueError('The angle range must be <= 2 pi') - + orig_lim = self.get_xlim() # in radians if 'thetamin' in kwargs: - thetamin = np.deg2rad(kwargs.pop('thetamin')) + kwargs['xmin'] = np.deg2rad(kwargs.pop('thetamin')) if 'thetamax' in kwargs: - thetamax = np.deg2rad(kwargs.pop('thetamax')) - - if thetamin is not None and thetamax is not None: - if abs(thetamax - thetamin) > 2 * np.pi: - raise ValueError('The angle range must be <= 360 degrees') - return tuple(np.rad2deg(self.set_xlim(left=left, right=right, - xmin=thetamin, xmax=thetamax))) + kwargs['xmax'] = np.deg2rad(kwargs.pop('thetamax')) + new_min, new_max = self.set_xlim(*args, **kwargs) + # Parsing all permutations of *args, **kwargs is tricky; it is simpler + # to let set_xlim() do it and then validate the limits. + if abs(new_max - new_min) > 2 * np.pi: + self.set_xlim(orig_lim) # un-accept the change + raise ValueError("The angle range must be less than a full circle") + return tuple(np.rad2deg((new_min, new_max))) def set_theta_offset(self, offset): """ @@ -1121,7 +1119,7 @@ def set_theta_direction(self, direction): elif direction in ('counterclockwise', 'anticlockwise', 1): mtx[0, 0] = 1 else: - cbook._check_in_list( + _api.check_in_list( [-1, 1, 'clockwise', 'counterclockwise', 'anticlockwise'], direction=direction) self._direction.invalidate() @@ -1197,9 +1195,17 @@ def get_rorigin(self): def get_rsign(self): return np.sign(self._originViewLim.y1 - self._originViewLim.y0) - def set_rlim(self, bottom=None, top=None, emit=True, auto=False, **kwargs): + def set_rlim(self, bottom=None, top=None, *, + emit=True, auto=False, **kwargs): """ - See `~.polar.PolarAxes.set_ylim`. + Set the radial axis view limits. + + This function behaves like `.Axes.set_ylim`, but additionally supports + *rmin* and *rmax* as aliases for *bottom* and *top*. + + See Also + -------- + .Axes.set_ylim """ if 'rmin' in kwargs: if bottom is None: @@ -1216,58 +1222,6 @@ def set_rlim(self, bottom=None, top=None, emit=True, auto=False, **kwargs): return self.set_ylim(bottom=bottom, top=top, emit=emit, auto=auto, **kwargs) - def set_ylim(self, bottom=None, top=None, emit=True, auto=False, - *, ymin=None, ymax=None): - """ - Set the data limits for the radial axis. - - Parameters - ---------- - bottom : float, optional - The bottom limit (default: None, which leaves the bottom - limit unchanged). - The bottom and top ylims may be passed as the tuple - (*bottom*, *top*) as the first positional argument (or as - the *bottom* keyword argument). - - top : float, optional - The top limit (default: None, which leaves the top limit - unchanged). - - emit : bool, default: True - Whether to notify observers of limit change. - - auto : bool or None, default: False - Whether to turn on autoscaling of the y-axis. True turns on, - False turns off, None leaves unchanged. - - ymin, ymax : float, optional - These arguments are deprecated and will be removed in a future - version. They are equivalent to *bottom* and *top* respectively, - and it is an error to pass both *ymin* and *bottom* or - *ymax* and *top*. - - Returns - ------- - bottom, top : (float, float) - The new y-axis limits in data coordinates. - """ - if ymin is not None: - if bottom is not None: - raise ValueError('Cannot supply both positional "bottom" ' - 'argument and kwarg "ymin"') - else: - bottom = ymin - if ymax is not None: - if top is not None: - raise ValueError('Cannot supply both positional "top" ' - 'argument and kwarg "ymax"') - else: - top = ymax - if top is None and np.iterable(bottom): - bottom, top = bottom[0], bottom[1] - return super().set_ylim(bottom=bottom, top=top, emit=emit, auto=auto) - def get_rlabel_position(self): """ Returns @@ -1297,7 +1251,10 @@ def set_rscale(self, *args, **kwargs): return Axes.set_yscale(self, *args, **kwargs) def set_rticks(self, *args, **kwargs): - return Axes.set_yticks(self, *args, **kwargs) + result = Axes.set_yticks(self, *args, **kwargs) + self.yaxis.set_major_locator( + self.RadialLocator(self.yaxis.get_major_locator(), self)) + return result def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): """ @@ -1328,7 +1285,18 @@ def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): Other Parameters ---------------- **kwargs - *kwargs* are optional `~.Text` properties for the labels. + *kwargs* are optional `.Text` properties for the labels. + + .. warning:: + + This only sets the properties of the current ticks. + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `.set_tick_params` instead if possible. See Also -------- @@ -1346,7 +1314,7 @@ def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): elif fmt is not None: self.xaxis.set_major_formatter(mticker.FormatStrFormatter(fmt)) for t in self.xaxis.get_ticklabels(): - t.update(kwargs) + t._internal_update(kwargs) return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels() def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs): @@ -1380,7 +1348,18 @@ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs): Other Parameters ---------------- **kwargs - *kwargs* are optional `~.Text` properties for the labels. + *kwargs* are optional `.Text` properties for the labels. + + .. warning:: + + This only sets the properties of the current ticks. + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `.set_tick_params` instead if possible. See Also -------- @@ -1401,21 +1380,52 @@ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs): angle = self.get_rlabel_position() self.set_rlabel_position(angle) for t in self.yaxis.get_ticklabels(): - t.update(kwargs) + t._internal_update(kwargs) return self.yaxis.get_gridlines(), self.yaxis.get_ticklabels() - def set_xscale(self, scale, *args, **kwargs): - if scale != 'linear': - raise NotImplementedError( - "You can not set the xscale on a polar plot.") - def format_coord(self, theta, r): # docstring inherited + screen_xy = self.transData.transform((theta, r)) + screen_xys = screen_xy + np.stack( + np.meshgrid([-1, 0, 1], [-1, 0, 1])).reshape((2, -1)).T + ts, rs = self.transData.inverted().transform(screen_xys).T + delta_t = abs((ts - theta + np.pi) % (2 * np.pi) - np.pi).max() + delta_t_halfturns = delta_t / np.pi + delta_t_degrees = delta_t_halfturns * 180 + delta_r = abs(rs - r).max() if theta < 0: theta += 2 * np.pi - theta /= np.pi - return ('\N{GREEK SMALL LETTER THETA}=%0.3f\N{GREEK SMALL LETTER PI} ' - '(%0.3f\N{DEGREE SIGN}), r=%0.3f') % (theta, theta * 180.0, r) + theta_halfturns = theta / np.pi + theta_degrees = theta_halfturns * 180 + + # See ScalarFormatter.format_data_short. For r, use #g-formatting + # (as for linear axes), but for theta, use f-formatting as scientific + # notation doesn't make sense and the trailing dot is ugly. + def format_sig(value, delta, opt, fmt): + # For "f", only count digits after decimal point. + prec = (max(0, -math.floor(math.log10(delta))) if fmt == "f" else + cbook._g_sig_digits(value, delta)) + return f"{value:-{opt}.{prec}{fmt}}" + + # In case fmt_xdata was not specified, resort to default + + if self.fmt_ydata is None: + r_label = format_sig(r, delta_r, "#", "g") + else: + r_label = self.format_ydata(r) + + if self.fmt_xdata is None: + return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} ' + '({}\N{DEGREE SIGN}), r={}').format( + format_sig(theta_halfturns, delta_t_halfturns, "", "f"), + format_sig(theta_degrees, delta_t_degrees, "", "f"), + r_label + ) + else: + return '\N{GREEK SMALL LETTER THETA}={}, r={}'.format( + self.format_xdata(theta), + r_label + ) def get_data_ratio(self): """ @@ -1428,17 +1438,17 @@ def get_data_ratio(self): def can_zoom(self): """ - Return *True* if this axes supports the zoom box button functionality. + Return whether this Axes supports the zoom box button functionality. - Polar axes do not support zoom boxes. + A polar Axes does not support zoom boxes. """ return False def can_pan(self): """ - Return *True* if this axes supports the pan/zoom button functionality. + Return whether this Axes supports the pan/zoom button functionality. - For polar axes, this is slightly misleading. Both panning and + For a polar Axes, this is slightly misleading. Both panning and zooming are performed by the same button. Panning is performed in azimuth while zooming is done along the radial. """ @@ -1495,9 +1505,12 @@ def drag_pan(self, button, key, x, y): self.set_rmax(p.rmax / scale) -# to keep things all self contained, we can put aliases to the Polar classes +# To keep things all self-contained, we can put aliases to the Polar classes # defined above. This isn't strictly necessary, but it makes some of the -# code more readable (and provides a backwards compatible Polar API) +# code more readable, and provides a backwards compatible Polar API. In +# particular, this is used by the :doc:`/gallery/specialty_plots/radar_chart` +# example to override PolarTransform on a PolarAxes subclass, so make sure that +# that example is unaffected before changing this. PolarAxes.PolarTransform = PolarTransform PolarAxes.PolarAffine = PolarAffine PolarAxes.InvertedPolarTransform = InvertedPolarTransform diff --git a/lib/matplotlib/projections/polar.pyi b/lib/matplotlib/projections/polar.pyi new file mode 100644 index 000000000000..fc1d508579b5 --- /dev/null +++ b/lib/matplotlib/projections/polar.pyi @@ -0,0 +1,194 @@ +import matplotlib.axis as maxis +import matplotlib.ticker as mticker +import matplotlib.transforms as mtransforms +from matplotlib.axes import Axes +from matplotlib.lines import Line2D +from matplotlib.text import Text + +import numpy as np +from numpy.typing import ArrayLike +from collections.abc import Sequence +from typing import Any, ClassVar, Literal, overload + +class PolarTransform(mtransforms.Transform): + input_dims: int + output_dims: int + def __init__( + self, + axis: PolarAxes | None = ..., + use_rmin: bool = ..., + *, + scale_transform: mtransforms.Transform | None = ..., + ) -> None: ... + def inverted(self) -> InvertedPolarTransform: ... + +class PolarAffine(mtransforms.Affine2DBase): + def __init__( + self, scale_transform: mtransforms.Transform, limits: mtransforms.BboxBase + ) -> None: ... + +class InvertedPolarTransform(mtransforms.Transform): + input_dims: int + output_dims: int + def __init__( + self, + axis: PolarAxes | None = ..., + use_rmin: bool = ..., + ) -> None: ... + def inverted(self) -> PolarTransform: ... + +class ThetaFormatter(mticker.Formatter): ... + +class _AxisWrapper: + def __init__(self, axis: maxis.Axis) -> None: ... + def get_view_interval(self) -> np.ndarray: ... + def set_view_interval(self, vmin: float, vmax: float) -> None: ... + def get_minpos(self) -> float: ... + def get_data_interval(self) -> np.ndarray: ... + def set_data_interval(self, vmin: float, vmax: float) -> None: ... + def get_tick_space(self) -> int: ... + +class ThetaLocator(mticker.Locator): + base: mticker.Locator + axis: _AxisWrapper | None + def __init__(self, base: mticker.Locator) -> None: ... + +class ThetaTick(maxis.XTick): + def __init__(self, axes: PolarAxes, *args, **kwargs) -> None: ... + +class ThetaAxis(maxis.XAxis): + axis_name: str + +class RadialLocator(mticker.Locator): + base: mticker.Locator + def __init__(self, base, axes: PolarAxes | None = ...) -> None: ... + +class RadialTick(maxis.YTick): ... + +class RadialAxis(maxis.YAxis): + axis_name: str + +class _WedgeBbox(mtransforms.Bbox): + def __init__( + self, + center: tuple[float, float], + viewLim: mtransforms.Bbox, + originLim: mtransforms.Bbox, + **kwargs, + ) -> None: ... + +class PolarAxes(Axes): + + PolarTransform: ClassVar[type] = PolarTransform + PolarAffine: ClassVar[type] = PolarAffine + InvertedPolarTransform: ClassVar[type] = InvertedPolarTransform + ThetaFormatter: ClassVar[type] = ThetaFormatter + RadialLocator: ClassVar[type] = RadialLocator + ThetaLocator: ClassVar[type] = ThetaLocator + + name: str + use_sticky_edges: bool + def __init__( + self, + *args, + theta_offset: float = ..., + theta_direction: float = ..., + rlabel_position: float = ..., + **kwargs, + ) -> None: ... + def get_xaxis_transform( + self, which: Literal["tick1", "tick2", "grid"] = ... + ) -> mtransforms.Transform: ... + def get_xaxis_text1_transform( + self, pad: float + ) -> tuple[ + mtransforms.Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_xaxis_text2_transform( + self, pad: float + ) -> tuple[ + mtransforms.Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_yaxis_transform( + self, which: Literal["tick1", "tick2", "grid"] = ... + ) -> mtransforms.Transform: ... + def get_yaxis_text1_transform( + self, pad: float + ) -> tuple[ + mtransforms.Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def get_yaxis_text2_transform( + self, pad: float + ) -> tuple[ + mtransforms.Transform, + Literal["center", "top", "bottom", "baseline", "center_baseline"], + Literal["center", "left", "right"], + ]: ... + def set_thetamax(self, thetamax: float) -> None: ... + def get_thetamax(self) -> float: ... + def set_thetamin(self, thetamin: float) -> None: ... + def get_thetamin(self) -> float: ... + @overload + def set_thetalim(self, minval: float, maxval: float, /) -> tuple[float, float]: ... + @overload + def set_thetalim(self, *, thetamin: float, thetamax: float) -> tuple[float, float]: ... + def set_theta_offset(self, offset: float) -> None: ... + def get_theta_offset(self) -> float: ... + def set_theta_zero_location( + self, + loc: Literal["N", "NW", "W", "SW", "S", "SE", "E", "NE"], + offset: float = ..., + ) -> None: ... + def set_theta_direction( + self, + direction: Literal[-1, 1, "clockwise", "counterclockwise", "anticlockwise"], + ) -> None: ... + def get_theta_direction(self) -> Literal[-1, 1]: ... + def set_rmax(self, rmax: float) -> None: ... + def get_rmax(self) -> float: ... + def set_rmin(self, rmin: float) -> None: ... + def get_rmin(self) -> float: ... + def set_rorigin(self, rorigin: float | None) -> None: ... + def get_rorigin(self) -> float: ... + def get_rsign(self) -> float: ... + def set_rlim( + self, + bottom: float | tuple[float, float] | None = ..., + top: float | None = ..., + *, + emit: bool = ..., + auto: bool = ..., + **kwargs, + ) -> tuple[float, float]: ... + def get_rlabel_position(self) -> float: ... + def set_rlabel_position(self, value: float) -> None: ... + def set_rscale(self, *args, **kwargs) -> None: ... + def set_rticks(self, *args, **kwargs) -> None: ... + def set_thetagrids( + self, + angles: ArrayLike, + labels: Sequence[str | Text] | None = ..., + fmt: str | None = ..., + **kwargs, + ) -> tuple[list[Line2D], list[Text]]: ... + def set_rgrids( + self, + radii: ArrayLike, + labels: Sequence[str | Text] | None = ..., + angle: float | None = ..., + fmt: str | None = ..., + **kwargs, + ) -> tuple[list[Line2D], list[Text]]: ... + def format_coord(self, theta: float, r: float) -> str: ... + def get_data_ratio(self) -> float: ... + def can_zoom(self) -> bool: ... + def can_pan(self) -> bool: ... + def start_pan(self, x: float, y: float, button: int) -> None: ... + def end_pan(self) -> None: ... + def drag_pan(self, button: Any, key: Any, x: float, y: float) -> None: ... diff --git a/lib/matplotlib/py.typed b/lib/matplotlib/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/matplotlib/pylab.py b/lib/matplotlib/pylab.py index 79a0c2c7668c..a50779cf6d26 100644 --- a/lib/matplotlib/pylab.py +++ b/lib/matplotlib/pylab.py @@ -1,14 +1,24 @@ """ +`pylab` is a historic interface and its use is strongly discouraged. The equivalent +replacement is `matplotlib.pyplot`. See :ref:`api_interfaces` for a full overview +of Matplotlib interfaces. + +`pylab` was designed to support a MATLAB-like way of working with all plotting related +functions directly available in the global namespace. This was achieved through a +wildcard import (``from pylab import *``). + .. warning:: - Since heavily importing into the global namespace may result in unexpected - behavior, the use of pylab is strongly discouraged. Use `matplotlib.pyplot` - instead. - -`pylab` is a module that includes `matplotlib.pyplot`, `numpy`, `numpy.fft`, -`numpy.linalg`, `numpy.random`, and some additional functions, all within -a single namespace. Its original purpose was to mimic a MATLAB-like way -of working by importing all functions into the global namespace. This is -considered bad style nowadays. + The use of `pylab` is discouraged for the following reasons: + + ``from pylab import *`` imports all the functions from `matplotlib.pyplot`, `numpy`, + `numpy.fft`, `numpy.linalg`, and `numpy.random`, and some additional functions into + the global namespace. + + Such a pattern is considered bad practice in modern python, as it clutters the global + namespace. Even more severely, in the case of `pylab`, this will overwrite some + builtin functions (e.g. the builtin `sum` will be replaced by `numpy.sum`), which + can lead to unexpected behavior. + """ from matplotlib.cbook import flatten, silent_list @@ -16,8 +26,7 @@ import matplotlib as mpl from matplotlib.dates import ( - date2num, num2date, datestr2num, drange, epoch2num, - num2epoch, mx2num, DateFormatter, IndexDateFormatter, DateLocator, + date2num, num2date, datestr2num, drange, DateFormatter, DateLocator, RRuleLocator, YearLocator, MonthLocator, WeekdayLocator, DayLocator, HourLocator, MinuteLocator, SecondLocator, rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY, @@ -49,3 +58,10 @@ # This is needed, or bytes will be numpy.random.bytes from # "from numpy.random import *" above bytes = __import__("builtins").bytes +# We also don't want the numpy version of these functions +abs = __import__("builtins").abs +bool = __import__("builtins").bool +max = __import__("builtins").max +min = __import__("builtins").min +pow = __import__("builtins").pow +round = __import__("builtins").round diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 73e77f6f0174..cf5c9b4b739f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3,7 +3,8 @@ """ `matplotlib.pyplot` is a state-based interface to matplotlib. It provides -a MATLAB-like way of plotting. +an implicit, MATLAB-like, way of plotting. It also opens figures on your +screen, and acts as the figure GUI manager. pyplot is mainly intended for interactive plots and simple cases of programmatic plot generation:: @@ -14,55 +15,139 @@ x = np.arange(0, 5, 0.1) y = np.sin(x) plt.plot(x, y) + plt.show() -The object-oriented API is recommended for more complex plots. +The explicit object-oriented API is recommended for complex plots, though +pyplot is still usually used to create the figure and often the Axes in the +figure. See `.pyplot.figure`, `.pyplot.subplots`, and +`.pyplot.subplot_mosaic` to create figures, and +:doc:`Axes API ` for the plotting methods on an Axes:: + + import numpy as np + import matplotlib.pyplot as plt + + x = np.arange(0, 5, 0.1) + y = np.sin(x) + fig, ax = plt.subplots() + ax.plot(x, y) + plt.show() + + +See :ref:`api_interfaces` for an explanation of the tradeoffs between the +implicit and explicit interfaces. """ +# fmt: off + +from __future__ import annotations + +from contextlib import AbstractContextManager, ExitStack +from enum import Enum import functools import importlib import inspect import logging -from numbers import Number -import re import sys +import threading import time -try: - import threading -except ImportError: - import dummy_threading as threading +from typing import TYPE_CHECKING, cast, overload -from cycler import cycler +from cycler import cycler # noqa: F401 import matplotlib import matplotlib.colorbar import matplotlib.image -from matplotlib import rcsetup, style -from matplotlib import _pylab_helpers, interactive +from matplotlib import _api +# Re-exported (import x as x) for typing. +from matplotlib import get_backend as get_backend, rcParams as rcParams +from matplotlib import cm as cm # noqa: F401 +from matplotlib import style as style # noqa: F401 +from matplotlib import _pylab_helpers +from matplotlib import interactive # noqa: F401 from matplotlib import cbook -from matplotlib import docstring -from matplotlib.backend_bases import FigureCanvasBase, MouseButton -from matplotlib.figure import Figure, figaspect -from matplotlib.gridspec import GridSpec -from matplotlib import rcParams, rcParamsDefault, get_backend, rcParamsOrig -from matplotlib.rcsetup import interactive_bk as _interactive_bk +from matplotlib import _docstring +from matplotlib.backend_bases import ( + FigureCanvasBase, FigureManagerBase, MouseButton) +from matplotlib.figure import Figure, FigureBase, figaspect +from matplotlib.gridspec import GridSpec, SubplotSpec +from matplotlib import rcsetup, rcParamsDefault, rcParamsOrig from matplotlib.artist import Artist -from matplotlib.axes import Axes, Subplot +from matplotlib.axes import Axes +from matplotlib.axes import Subplot # noqa: F401 +from matplotlib.backends import BackendFilter, backend_registry from matplotlib.projections import PolarAxes +from matplotlib.colorizer import _ColorizerInterface, ColorizingArtist, Colorizer from matplotlib import mlab # for detrend_none, window_hanning -from matplotlib.scale import get_scale_names +from matplotlib.scale import get_scale_names # noqa: F401 -from matplotlib import cm -from matplotlib.cm import get_cmap, register_cmap +from matplotlib.cm import _colormaps +from matplotlib.colors import _color_sequences, Colormap import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable, Hashable, Iterable, Sequence + import pathlib + import os + from typing import Any, BinaryIO, Literal, TypeVar + from typing_extensions import ParamSpec + + import PIL.Image + from numpy.typing import ArrayLike + import pandas as pd + + import matplotlib.axes + import matplotlib.artist + import matplotlib.backend_bases + from matplotlib.axis import Tick + from matplotlib.axes._base import _AxesBase + from matplotlib.backend_bases import Event + from matplotlib.cm import ScalarMappable + from matplotlib.contour import ContourSet, QuadContourSet + from matplotlib.collections import ( + Collection, + FillBetweenPolyCollection, + LineCollection, + PolyCollection, + PathCollection, + EventCollection, + QuadMesh, + ) + from matplotlib.colorbar import Colorbar + from matplotlib.container import ( + BarContainer, + ErrorbarContainer, + StemContainer, + ) + from matplotlib.figure import SubFigure + from matplotlib.legend import Legend + from matplotlib.mlab import GaussianKDE + from matplotlib.image import AxesImage, FigureImage + from matplotlib.patches import FancyArrow, StepPatch, Wedge + from matplotlib.quiver import Barbs, Quiver, QuiverKey + from matplotlib.scale import ScaleBase + from matplotlib.typing import ( + ColorType, + CoordsType, + HashableList, + LineStyleType, + MarkerType, + ) + from matplotlib.widgets import SubplotTool + + _P = ParamSpec('_P') + _R = TypeVar('_R') + _T = TypeVar('_T') + + # We may not need the following imports here: from matplotlib.colors import Normalize -from matplotlib.lines import Line2D +from matplotlib.lines import Line2D, AxLine from matplotlib.text import Text, Annotation -from matplotlib.patches import Polygon, Rectangle, Circle, Arrow -from matplotlib.widgets import SubplotTool, Button, Slider, Widget +from matplotlib.patches import Arrow, Circle, Rectangle # noqa: F401 +from matplotlib.patches import Polygon +from matplotlib.widgets import Button, Slider, Widget # noqa: F401 -from .ticker import ( +from .ticker import ( # noqa: F401 TickHelper, Formatter, FixedFormatter, NullFormatter, FuncFormatter, FormatStrFormatter, ScalarFormatter, LogFormatter, LogFormatterExponent, LogFormatterMathtext, Locator, IndexLocator, FixedLocator, NullLocator, @@ -71,182 +156,252 @@ _log = logging.getLogger(__name__) -_code_objs = { - cbook._rename_parameter: - cbook._rename_parameter("", "old", "new", lambda new: None).__code__, - cbook._make_keyword_only: - cbook._make_keyword_only("", "p", lambda p: None).__code__, -} +# Explicit rename instead of import-as for typing's sake. +colormaps = _colormaps +color_sequences = _color_sequences -def _copy_docstring_and_deprecators(method, func=None): +@overload +def _copy_docstring_and_deprecators( + method: Any, + func: Literal[None] = None +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... + + +@overload +def _copy_docstring_and_deprecators( + method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + + +def _copy_docstring_and_deprecators( + method: Any, + func: Callable[_P, _R] | None = None +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]] | Callable[_P, _R]: if func is None: - return functools.partial(_copy_docstring_and_deprecators, method) - decorators = [docstring.copy(method)] - # Check whether the definition of *method* includes _rename_parameter or - # _make_keyword_only decorators; if so, propagate them to the pyplot - # wrapper as well. - while getattr(method, "__wrapped__", None) is not None: - for decorator_maker, code in _code_objs.items(): - if method.__code__ is code: - kwargs = { - k: v.cell_contents - for k, v in zip(code.co_freevars, method.__closure__)} - assert kwargs["func"] is method.__wrapped__ - kwargs.pop("func") - decorators.append(decorator_maker(**kwargs)) + return cast('Callable[[Callable[_P, _R]], Callable[_P, _R]]', + functools.partial(_copy_docstring_and_deprecators, method)) + decorators: list[Callable[[Callable[_P, _R]], Callable[_P, _R]]] = [ + _docstring.copy(method) + ] + # Check whether the definition of *method* includes @_api.rename_parameter + # or @_api.make_keyword_only decorators; if so, propagate them to the + # pyplot wrapper as well. + while hasattr(method, "__wrapped__"): + potential_decorator = _api.deprecation.DECORATORS.get(method) + if potential_decorator: + decorators.append(potential_decorator) method = method.__wrapped__ for decorator in decorators[::-1]: func = decorator(func) + _add_pyplot_note(func, method) return func -## Global ## - - -_IP_REGISTERED = None -_INSTALL_FIG_OBSERVER = False +_NO_PYPLOT_NOTE = [ + 'FigureBase._gci', # wrapped_func is private + '_AxesBase._sci', # wrapped_func is private + 'Artist.findobj', # not a standard pyplot wrapper because it does not operate + # on the current Figure / Axes. Explanation of relation would + # be more complex and is not too important. +] -def install_repl_displayhook(): +def _add_pyplot_note(func, wrapped_func): """ - Install a repl display hook so that any stale figure are automatically - redrawn when control is returned to the repl. + Add a note to the docstring of *func* that it is a pyplot wrapper. - This works both with IPython and with vanilla python shells. + The note is added to the "Notes" section of the docstring. If that does + not exist, a "Notes" section is created. In numpydoc, the "Notes" + section is the third last possible section, only potentially followed by + "References" and "Examples". """ - global _IP_REGISTERED - global _INSTALL_FIG_OBSERVER - - class _NotIPython(Exception): - pass - - # see if we have IPython hooks around, if use them + if not func.__doc__: + return # nothing to do + + qualname = wrapped_func.__qualname__ + if qualname in _NO_PYPLOT_NOTE: + return + + wrapped_func_is_method = True + if "." not in qualname: + # method qualnames are prefixed by the class and ".", e.g. "Axes.plot" + wrapped_func_is_method = False + link = f"{wrapped_func.__module__}.{qualname}" + elif qualname.startswith("Axes."): # e.g. "Axes.plot" + link = ".axes." + qualname + elif qualname.startswith("_AxesBase."): # e.g. "_AxesBase.set_xlabel" + link = ".axes.Axes" + qualname[9:] + elif qualname.startswith("Figure."): # e.g. "Figure.figimage" + link = "." + qualname + elif qualname.startswith("FigureBase."): # e.g. "FigureBase.gca" + link = ".Figure" + qualname[10:] + elif qualname.startswith("FigureCanvasBase."): # "FigureBaseCanvas.mpl_connect" + link = "." + qualname + else: + raise RuntimeError(f"Wrapped method from unexpected class: {qualname}") - try: - if 'IPython' in sys.modules: - from IPython import get_ipython - ip = get_ipython() - if ip is None: - raise _NotIPython() + if wrapped_func_is_method: + message = f"This is the :ref:`pyplot wrapper ` for `{link}`." + else: + message = f"This is equivalent to `{link}`." + + # Find the correct insert position: + # - either we already have a "Notes" section into which we can insert + # - or we create one before the next present section. Note that in numpydoc, the + # "Notes" section is the third last possible section, only potentially followed + # by "References" and "Examples". + # - or we append a new "Notes" section at the end. + doc = inspect.cleandoc(func.__doc__) + if "\nNotes\n-----" in doc: + before, after = doc.split("\nNotes\n-----", 1) + elif (index := doc.find("\nReferences\n----------")) != -1: + before, after = doc[:index], doc[index:] + elif (index := doc.find("\nExamples\n--------")) != -1: + before, after = doc[:index], doc[index:] + else: + # No "Notes", "References", or "Examples" --> append to the end. + before = doc + "\n" + after = "" - if _IP_REGISTERED: - return + func.__doc__ = f"{before}\nNotes\n-----\n\n.. note::\n\n {message}\n{after}" - def post_execute(): - if matplotlib.is_interactive(): - draw_all() - # IPython >= 2 - try: - ip.events.register('post_execute', post_execute) - except AttributeError: - # IPython 1.x - ip.register_post_execute(post_execute) +## Global ## - _IP_REGISTERED = post_execute - _INSTALL_FIG_OBSERVER = False - # trigger IPython's eventloop integration, if available - from IPython.core.pylabtools import backend2gui +# The state controlled by {,un}install_repl_displayhook(). +_ReplDisplayHook = Enum("_ReplDisplayHook", ["NONE", "PLAIN", "IPYTHON"]) +_REPL_DISPLAYHOOK = _ReplDisplayHook.NONE - ipython_gui_name = backend2gui.get(get_backend()) - if ipython_gui_name: - ip.enable_gui(ipython_gui_name) - else: - _INSTALL_FIG_OBSERVER = True - # import failed or ipython is not running - except (ImportError, _NotIPython): - _INSTALL_FIG_OBSERVER = True +def _draw_all_if_interactive() -> None: + if matplotlib.is_interactive(): + draw_all() -def uninstall_repl_displayhook(): +def install_repl_displayhook() -> None: """ - Uninstall the matplotlib display hook. + Connect to the display hook of the current shell. - .. warning:: + The display hook gets called when the read-evaluate-print-loop (REPL) of + the shell has finished the execution of a command. We use this callback + to be able to automatically update a figure in interactive mode. - Need IPython >= 2 for this to work. For IPython < 2 will raise a - ``NotImplementedError`` + This works both with IPython and with vanilla python shells. + """ + global _REPL_DISPLAYHOOK + + if _REPL_DISPLAYHOOK is _ReplDisplayHook.IPYTHON: + return + + # See if we have IPython hooks around, if so use them. + # Use ``sys.modules.get(name)`` rather than ``name in sys.modules`` as + # entries can also have been explicitly set to None. + mod_ipython = sys.modules.get("IPython") + if not mod_ipython: + _REPL_DISPLAYHOOK = _ReplDisplayHook.PLAIN + return + ip = mod_ipython.get_ipython() + if not ip: + _REPL_DISPLAYHOOK = _ReplDisplayHook.PLAIN + return + + ip.events.register("post_execute", _draw_all_if_interactive) + _REPL_DISPLAYHOOK = _ReplDisplayHook.IPYTHON + + if mod_ipython.version_info[:2] < (8, 24): + # Use of backend2gui is not needed for IPython >= 8.24 as that functionality + # has been moved to Matplotlib. + # This code can be removed when Python 3.12, the latest version supported by + # IPython < 8.24, reaches end-of-life in late 2028. + from IPython.core.pylabtools import backend2gui + ipython_gui_name = backend2gui.get(get_backend()) + else: + _, ipython_gui_name = backend_registry.resolve_backend(get_backend()) + # trigger IPython's eventloop integration, if available + if ipython_gui_name: + ip.enable_gui(ipython_gui_name) - .. warning:: - If you are using vanilla python and have installed another - display hook this will reset ``sys.displayhook`` to what ever - function was there when matplotlib installed it's displayhook, - possibly discarding your changes. - """ - global _IP_REGISTERED - global _INSTALL_FIG_OBSERVER - if _IP_REGISTERED: +def uninstall_repl_displayhook() -> None: + """Disconnect from the display hook of the current shell.""" + global _REPL_DISPLAYHOOK + if _REPL_DISPLAYHOOK is _ReplDisplayHook.IPYTHON: from IPython import get_ipython ip = get_ipython() - try: - ip.events.unregister('post_execute', _IP_REGISTERED) - except AttributeError as err: - raise NotImplementedError("Can not unregister events " - "in IPython < 2.0") from err - _IP_REGISTERED = None - - if _INSTALL_FIG_OBSERVER: - _INSTALL_FIG_OBSERVER = False + ip.events.unregister("post_execute", _draw_all_if_interactive) + _REPL_DISPLAYHOOK = _ReplDisplayHook.NONE draw_all = _pylab_helpers.Gcf.draw_all -@functools.wraps(matplotlib.set_loglevel) -def set_loglevel(*args, **kwargs): # Ensure this appears in the pyplot docs. +# Ensure this appears in the pyplot docs. +@_copy_docstring_and_deprecators(matplotlib.set_loglevel) +def set_loglevel(*args, **kwargs) -> None: return matplotlib.set_loglevel(*args, **kwargs) @_copy_docstring_and_deprecators(Artist.findobj) -def findobj(o=None, match=None, include_self=True): +def findobj( + o: Artist | None = None, + match: Callable[[Artist], bool] | type[Artist] | None = None, + include_self: bool = True +) -> list[Artist]: if o is None: o = gcf() return o.findobj(match, include_self=include_self) -def _get_required_interactive_framework(backend_mod): - return getattr( - backend_mod.FigureCanvas, "required_interactive_framework", None) +_backend_mod: type[matplotlib.backend_bases._Backend] | None = None -def switch_backend(newbackend): +def _get_backend_mod() -> type[matplotlib.backend_bases._Backend]: + """ + Ensure that a backend is selected and return it. + + This is currently private, but may be made public in the future. """ - Close all open figures and set the Matplotlib backend. + if _backend_mod is None: + # Use rcParams._get("backend") to avoid going through the fallback + # logic (which will (re)import pyplot and then call switch_backend if + # we need to resolve the auto sentinel) + switch_backend(rcParams._get("backend")) + return cast(type[matplotlib.backend_bases._Backend], _backend_mod) - The argument is case-insensitive. Switching to an interactive backend is - possible only if no event loop for another interactive backend has started. - Switching to and from non-interactive backends is always possible. + +def switch_backend(newbackend: str) -> None: + """ + Set the pyplot backend. + + Switching to an interactive backend is possible only if no event loop for + another interactive backend has started. Switching to and from + non-interactive backends is always possible. + + If the new backend is different than the current backend then all open + Figures will be closed via ``plt.close('all')``. Parameters ---------- newbackend : str - The name of the backend to use. + The case-insensitive name of the backend to use. + """ global _backend_mod # make sure the init is pulled up so we can assign to it later import matplotlib.backends - close("all") if newbackend is rcsetup._auto_backend_sentinel: current_framework = cbook._get_running_interactive_framework() - mapping = {'qt5': 'qt5agg', - 'qt4': 'qt4agg', - 'gtk3': 'gtk3agg', - 'wx': 'wxagg', - 'tk': 'tkagg', - 'macosx': 'macosx', - 'headless': 'agg'} - - best_guess = mapping.get(current_framework, None) - if best_guess is not None: - candidates = [best_guess] + + if (current_framework and + (backend := backend_registry.backend_for_gui_framework( + current_framework))): + candidates = [backend] else: candidates = [] - candidates += ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"] + candidates += [ + "macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"] # Don't try to fallback on the cairo-based backends as they each have # an additional dependency (pycairo) over the agg-based backend, and @@ -265,18 +420,12 @@ def switch_backend(newbackend): switch_backend("agg") rcParamsOrig["backend"] = "agg" return + old_backend = rcParams._get('backend') # get without triggering backend resolution - # Backends are implemented as modules, but "inherit" default method - # implementations from backend_bases._Backend. This is achieved by - # creating a "class" that inherits from backend_bases._Backend and whose - # body is filled with the module's globals. + module = backend_registry.load_backend_module(newbackend) + canvas_class = module.FigureCanvas - backend_name = cbook._backend_module_name(newbackend) - - class backend_mod(matplotlib.backend_bases._Backend): - locals().update(vars(importlib.import_module(backend_name))) - - required_framework = _get_required_interactive_framework(backend_mod) + required_framework = canvas_class.required_interactive_framework if required_framework is not None: current_framework = cbook._get_running_interactive_framework() if (current_framework and required_framework @@ -286,9 +435,80 @@ class backend_mod(matplotlib.backend_bases._Backend): "framework, as {!r} is currently running".format( newbackend, required_framework, current_framework)) + # Load the new_figure_manager() and show() functions from the backend. + + # Classically, backends can directly export these functions. This should + # keep working for backcompat. + new_figure_manager = getattr(module, "new_figure_manager", None) + show = getattr(module, "show", None) + + # In that classical approach, backends are implemented as modules, but + # "inherit" default method implementations from backend_bases._Backend. + # This is achieved by creating a "class" that inherits from + # backend_bases._Backend and whose body is filled with the module globals. + class backend_mod(matplotlib.backend_bases._Backend): + locals().update(vars(module)) + + # However, the newer approach for defining new_figure_manager and + # show is to derive them from canvas methods. In that case, also + # update backend_mod accordingly; also, per-backend customization of + # draw_if_interactive is disabled. + if new_figure_manager is None: + + def new_figure_manager_given_figure(num, figure): + return canvas_class.new_manager(figure, num) + + def new_figure_manager(num, *args, FigureClass=Figure, **kwargs): + fig = FigureClass(*args, **kwargs) + return new_figure_manager_given_figure(num, fig) + + def draw_if_interactive() -> None: + if matplotlib.is_interactive(): + manager = _pylab_helpers.Gcf.get_active() + if manager: + manager.canvas.draw_idle() + + backend_mod.new_figure_manager_given_figure = ( # type: ignore[method-assign] + new_figure_manager_given_figure) + backend_mod.new_figure_manager = ( # type: ignore[method-assign] + new_figure_manager) + backend_mod.draw_if_interactive = ( # type: ignore[method-assign] + draw_if_interactive) + + # If the manager explicitly overrides pyplot_show, use it even if a global + # show is already present, as the latter may be here for backcompat. + manager_class = getattr(canvas_class, "manager_class", None) + # We can't compare directly manager_class.pyplot_show and FMB.pyplot_show because + # pyplot_show is a classmethod so the above constructs are bound classmethods, and + # thus always different (being bound to different classes). We also have to use + # getattr_static instead of vars as manager_class could have no __dict__. + manager_pyplot_show = inspect.getattr_static(manager_class, "pyplot_show", None) + base_pyplot_show = inspect.getattr_static(FigureManagerBase, "pyplot_show", None) + if (show is None + or (manager_pyplot_show is not None + and manager_pyplot_show != base_pyplot_show)): + if not manager_pyplot_show: + raise ValueError( + f"Backend {newbackend} defines neither FigureCanvas.manager_class nor " + f"a toplevel show function") + _pyplot_show = cast('Any', manager_class).pyplot_show + backend_mod.show = _pyplot_show # type: ignore[method-assign] + _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) + if newbackend in ("ipympl", "widget"): + # ipympl < 0.9.4 expects rcParams["backend"] to be the fully-qualified backend + # name "module://ipympl.backend_nbagg" not short names "ipympl" or "widget". + import importlib.metadata as im + from matplotlib import _parse_to_version_info # type: ignore[attr-defined] + try: + module_version = im.version("ipympl") + if _parse_to_version_info(module_version) < (0, 9, 4): + newbackend = "module://ipympl.backend_nbagg" + except im.PackageNotFoundError: + pass + rcParams['backend'] = rcParamsDefault['backend'] = newbackend _backend_mod = backend_mod for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: @@ -297,13 +517,34 @@ class backend_mod(matplotlib.backend_bases._Backend): # Need to keep a global reference to the backend for compatibility reasons. # See https://github.com/matplotlib/matplotlib/issues/6092 - matplotlib.backends.backend = newbackend + matplotlib.backends.backend = newbackend # type: ignore[attr-defined] - -def _warn_if_gui_out_of_main_thread(): - if (_get_required_interactive_framework(_backend_mod) - and threading.current_thread() is not threading.main_thread()): - cbook._warn_external( + # Make sure the repl display hook is installed in case we become interactive. + try: + install_repl_displayhook() + except NotImplementedError as err: + _log.warning("Fallback to a different backend") + raise ImportError from err + + +def _warn_if_gui_out_of_main_thread() -> None: + warn = False + canvas_class = cast(type[FigureCanvasBase], _get_backend_mod().FigureCanvas) + if canvas_class.required_interactive_framework: + if hasattr(threading, 'get_native_id'): + # This compares native thread ids because even if Python-level + # Thread objects match, the underlying OS thread (which is what + # really matters) may be different on Python implementations with + # green threads. + if threading.get_native_id() != threading.main_thread().native_id: + warn = True + else: + # Fall back to Python-level Thread if native IDs are unavailable, + # mainly for PyPy. + if threading.current_thread() is not threading.main_thread(): + warn = True + if warn: + _api.warn_external( "Starting a Matplotlib GUI outside of the main thread will likely " "fail.") @@ -312,58 +553,86 @@ def _warn_if_gui_out_of_main_thread(): def new_figure_manager(*args, **kwargs): """Create a new figure manager instance.""" _warn_if_gui_out_of_main_thread() - return _backend_mod.new_figure_manager(*args, **kwargs) + return _get_backend_mod().new_figure_manager(*args, **kwargs) # This function's signature is rewritten upon backend-load by switch_backend. def draw_if_interactive(*args, **kwargs): - return _backend_mod.draw_if_interactive(*args, **kwargs) + """ + Redraw the current figure if in interactive mode. + .. warning:: -# This function's signature is rewritten upon backend-load by switch_backend. -def show(*args, **kwargs): + End users will typically not have to call this function because the + the interactive mode takes care of this. """ - Display all open figures. + return _get_backend_mod().draw_if_interactive(*args, **kwargs) - In non-interactive mode, *block* defaults to True. All figures - will display and show will not return until all windows are closed. - If there are no figures, return immediately. - In interactive mode *block* defaults to False. This will ensure - that all of the figures are shown and this function immediately returns. +# This function's signature is rewritten upon backend-load by switch_backend. +def show(*args, **kwargs) -> None: + """ + Display all open figures. Parameters ---------- block : bool, optional + Whether to wait for all figures to be closed before returning. - If `True` block and run the GUI main loop until all windows + If `True` block and run the GUI main loop until all figure windows are closed. - If `False` ensure that all windows are displayed and return + If `False` ensure that all figure windows are displayed and return immediately. In this case, you are responsible for ensuring that the event loop is running to have responsive figures. + Defaults to True in non-interactive mode and to False in interactive + mode (see `.pyplot.isinteractive`). + See Also -------- - ion : enable interactive mode - ioff : disable interactive mode + ion : Enable interactive mode, which shows / updates the figure after + every plotting command, so that calling ``show()`` is not necessary. + ioff : Disable interactive mode. + savefig : Save the figure to an image file instead of showing it on screen. + + Notes + ----- + **Saving figures to file and showing a window at the same time** + + If you want an image file as well as a user interface window, use + `.pyplot.savefig` before `.pyplot.show`. At the end of (a blocking) + ``show()`` the figure is closed and thus unregistered from pyplot. Calling + `.pyplot.savefig` afterwards would save a new and thus empty figure. This + limitation of command order does not apply if the show is non-blocking or + if you keep a reference to the figure and use `.Figure.savefig`. + **Auto-show in jupyter notebooks** + + The jupyter backends (activated via ``%matplotlib inline``, + ``%matplotlib notebook``, or ``%matplotlib widget``), call ``show()`` at + the end of every cell by default. Thus, you usually don't have to call it + explicitly there. """ _warn_if_gui_out_of_main_thread() - return _backend_mod.show(*args, **kwargs) + return _get_backend_mod().show(*args, **kwargs) -def isinteractive(): +def isinteractive() -> bool: """ - Return if pyplot is in "interactive mode" or not. + Return whether plots are updated after every plotting command. + + The interactive mode is mainly useful if you build plots from the command + line and want to see the effect of each command while you are building the + figure. - If in interactive mode then: + In interactive mode: - newly created figures will be shown immediately; - figures will automatically redraw on change; - `.pyplot.show` will not block by default. - If not in interactive mode then: + In non-interactive mode: - newly created figures and changes to figures will not be reflected until explicitly asked to be; @@ -371,78 +640,30 @@ def isinteractive(): See Also -------- - ion : enable interactive mode - ioff : disable interactive mode - - show : show windows (and maybe block) - pause : show windows, run GUI event loop, and block for a time + ion : Enable interactive mode. + ioff : Disable interactive mode. + show : Show all figures (and maybe block). + pause : Show all figures, and block for a time. """ return matplotlib.is_interactive() -class _IoffContext: - """ - Context manager for `.ioff`. - - The state is changed in ``__init__()`` instead of ``__enter__()``. The - latter is a no-op. This allows using `.ioff` both as a function and - as a context. +# Note: The return type of ioff being AbstractContextManager +# instead of ExitStack is deliberate. +# See https://github.com/matplotlib/matplotlib/issues/27659 +# and https://github.com/matplotlib/matplotlib/pull/27667 for more info. +def ioff() -> AbstractContextManager: """ + Disable interactive mode. - def __init__(self): - self.wasinteractive = isinteractive() - matplotlib.interactive(False) - uninstall_repl_displayhook() - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - if self.wasinteractive: - matplotlib.interactive(True) - install_repl_displayhook() - else: - matplotlib.interactive(False) - uninstall_repl_displayhook() - - -class _IonContext: - """ - Context manager for `.ion`. - - The state is changed in ``__init__()`` instead of ``__enter__()``. The - latter is a no-op. This allows using `.ion` both as a function and - as a context. - """ - - def __init__(self): - self.wasinteractive = isinteractive() - matplotlib.interactive(True) - install_repl_displayhook() - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - if not self.wasinteractive: - matplotlib.interactive(False) - uninstall_repl_displayhook() - else: - matplotlib.interactive(True) - install_repl_displayhook() - - -def ioff(): - """ - Turn interactive mode off. + See `.pyplot.isinteractive` for more details. See Also -------- - ion : enable interactive mode - isinteractive : query current state - - show : show windows (and maybe block) - pause : show windows, run GUI event loop, and block for a time + ion : Enable interactive mode. + isinteractive : Whether interactive mode is enabled. + show : Show all figures (and maybe block). + pause : Show all figures, and block for a time. Notes ----- @@ -460,24 +681,33 @@ def ioff(): fig2 = plt.figure() # ... - To enable usage as a context manager, this function returns an - ``_IoffContext`` object. The return value is not intended to be stored - or accessed by the user. + To enable optional usage as a context manager, this function returns a + context manager object, which is not intended to be stored or + accessed by the user. """ - return _IoffContext() - - -def ion(): + stack = ExitStack() + stack.callback(ion if isinteractive() else ioff) + matplotlib.interactive(False) + uninstall_repl_displayhook() + return stack + + +# Note: The return type of ion being AbstractContextManager +# instead of ExitStack is deliberate. +# See https://github.com/matplotlib/matplotlib/issues/27659 +# and https://github.com/matplotlib/matplotlib/pull/27667 for more info. +def ion() -> AbstractContextManager: """ - Turn interactive mode on. + Enable interactive mode. + + See `.pyplot.isinteractive` for more details. See Also -------- - ioff : disable interactive mode - isinteractive : query current state - - show : show windows (and maybe block) - pause : show windows, run GUI event loop, and block for a time + ioff : Disable interactive mode. + isinteractive : Whether interactive mode is enabled. + show : Show all figures (and maybe block). + pause : Show all figures, and block for a time. Notes ----- @@ -495,14 +725,18 @@ def ion(): fig2 = plt.figure() # ... - To enable usage as a context manager, this function returns an - ``_IonContext`` object. The return value is not intended to be stored - or accessed by the user. + To enable optional usage as a context manager, this function returns a + context manager object, which is not intended to be stored or + accessed by the user. """ - return _IonContext() + stack = ExitStack() + stack.callback(ion if isinteractive() else ioff) + matplotlib.interactive(True) + install_repl_displayhook() + return stack -def pause(interval): +def pause(interval: float) -> None: """ Run the GUI event loop for *interval* seconds. @@ -516,8 +750,8 @@ def pause(interval): See Also -------- - matplotlib.animation : Complex animation - show : show figures and optional block forever + matplotlib.animation : Proper animations + show : Show all figures and optional block until all figures are closed. """ manager = _pylab_helpers.Gcf.get_active() if manager is not None: @@ -531,17 +765,20 @@ def pause(interval): @_copy_docstring_and_deprecators(matplotlib.rc) -def rc(group, **kwargs): +def rc(group: str, **kwargs) -> None: matplotlib.rc(group, **kwargs) @_copy_docstring_and_deprecators(matplotlib.rc_context) -def rc_context(rc=None, fname=None): +def rc_context( + rc: dict[str, Any] | None = None, + fname: str | pathlib.Path | os.PathLike | None = None, +) -> AbstractContextManager[None]: return matplotlib.rc_context(rc, fname) @_copy_docstring_and_deprecators(matplotlib.rcdefaults) -def rcdefaults(): +def rcdefaults() -> None: matplotlib.rcdefaults() if matplotlib.is_interactive(): draw_all() @@ -565,13 +802,16 @@ def setp(obj, *args, **kwargs): return matplotlib.artist.setp(obj, *args, **kwargs) -def xkcd(scale=1, length=100, randomness=2): +def xkcd( + scale: float = 1, length: float = 100, randomness: float = 2 +) -> ExitStack: """ - Turn on `xkcd `_ sketch-style drawing mode. This will - only have effect on things drawn after this function is called. + Turn on `xkcd `_ sketch-style drawing mode. + + This will only have an effect on things drawn after this function is called. - For best results, the "Humor Sans" font should be installed: it is - not included with Matplotlib. + For best results, install the `xkcd script `_ + font; xkcd fonts are not packaged with Matplotlib. Parameters ---------- @@ -584,8 +824,7 @@ def xkcd(scale=1, length=100, randomness=2): Notes ----- - This function works by a number of rcParams, so it will probably - override others you have set before. + This function works by a number of rcParams, overriding those set before. If you want the effects of this function to be temporary, it can be used as a context manager, for example:: @@ -598,66 +837,66 @@ def xkcd(scale=1, length=100, randomness=2): # This figure will be in regular style fig2 = plt.figure() """ - return _xkcd(scale, length, randomness) - - -class _xkcd: - # This cannot be implemented in terms of rc_context() because this needs to - # work as a non-contextmanager too. - - def __init__(self, scale, length, randomness): - self._orig = rcParams.copy() - - if rcParams['text.usetex']: - raise RuntimeError( - "xkcd mode is not compatible with text.usetex = True") - - from matplotlib import patheffects - rcParams.update({ - 'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue', - 'Comic Sans MS'], - 'font.size': 14.0, - 'path.sketch': (scale, length, randomness), - 'path.effects': [ - patheffects.withStroke(linewidth=4, foreground="w")], - 'axes.linewidth': 1.5, - 'lines.linewidth': 2.0, - 'figure.facecolor': 'white', - 'grid.linewidth': 0.0, - 'axes.grid': False, - 'axes.unicode_minus': False, - 'axes.edgecolor': 'black', - 'xtick.major.size': 8, - 'xtick.major.width': 3, - 'ytick.major.size': 8, - 'ytick.major.width': 3, - }) - - def __enter__(self): - return self - - def __exit__(self, *args): - dict.update(rcParams, self._orig) + # This cannot be implemented in terms of contextmanager() or rc_context() + # because this needs to work as a non-contextmanager too. + + if rcParams['text.usetex']: + raise RuntimeError( + "xkcd mode is not compatible with text.usetex = True") + + stack = ExitStack() + stack.callback(rcParams._update_raw, rcParams.copy()) # type: ignore[arg-type] + + from matplotlib import patheffects + rcParams.update({ + 'font.family': ['xkcd', 'xkcd Script', 'Comic Neue', 'Comic Sans MS'], + 'font.size': 14.0, + 'path.sketch': (scale, length, randomness), + 'path.effects': [ + patheffects.withStroke(linewidth=4, foreground="w")], + 'axes.linewidth': 1.5, + 'lines.linewidth': 2.0, + 'figure.facecolor': 'white', + 'grid.linewidth': 0.0, + 'axes.grid': False, + 'axes.unicode_minus': False, + 'axes.edgecolor': 'black', + 'xtick.major.size': 8, + 'xtick.major.width': 3, + 'ytick.major.size': 8, + 'ytick.major.width': 3, + }) + + return stack ## Figures ## -def figure(num=None, # autoincrement if None, else integer from 1-N - figsize=None, # defaults to rc figure.figsize - dpi=None, # defaults to rc figure.dpi - facecolor=None, # defaults to rc figure.facecolor - edgecolor=None, # defaults to rc figure.edgecolor - frameon=True, - FigureClass=Figure, - clear=False, - **kwargs - ): +def figure( + # autoincrement if None, else integer from 1-N + num: int | str | Figure | SubFigure | None = None, + # defaults to rc figure.figsize + figsize: ArrayLike # a 2-element ndarray is accepted as well + | tuple[float, float, Literal["in", "cm", "px"]] + | None = None, + # defaults to rc figure.dpi + dpi: float | None = None, + *, + # defaults to rc figure.facecolor + facecolor: ColorType | None = None, + # defaults to rc figure.edgecolor + edgecolor: ColorType | None = None, + frameon: bool = True, + FigureClass: type[Figure] = Figure, + clear: bool = False, + **kwargs +) -> Figure: """ Create a new figure, or activate an existing figure. Parameters ---------- - num : int or str, optional + num : int or str or `.Figure` or `.SubFigure`, optional A unique identifier for the figure. If a figure with that identifier already exists, this figure is made @@ -669,58 +908,87 @@ def figure(num=None, # autoincrement if None, else integer from 1-N will be used for the ``Figure.number`` attribute, otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). If *num* is a string, the figure label and the - window title is set to this value. + window title is set to this value. If num is a ``SubFigure``, its + parent ``Figure`` is activated. + + figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize` + The figure dimensions. This can be - figsize : (float, float), default: :rc:`figure.figsize` - Width, height in inches. + - a tuple ``(width, height, unit)``, where *unit* is one of "inch", "cm", + "px". + - a tuple ``(x, y)``, which is interpreted as ``(x, y, "inch")``. dpi : float, default: :rc:`figure.dpi` The resolution of the figure in dots-per-inch. - facecolor : color, default: :rc:`figure.facecolor` + facecolor : :mpltype:`color`, default: :rc:`figure.facecolor` The background color. - edgecolor : color, default: :rc:`figure.edgecolor` + edgecolor : :mpltype:`color`, default: :rc:`figure.edgecolor` The border color. frameon : bool, default: True If False, suppress drawing the figure frame. FigureClass : subclass of `~matplotlib.figure.Figure` - Optionally use a custom `.Figure` instance. + If set, an instance of this subclass will be created, rather than a + plain `.Figure`. clear : bool, default: False If True and the figure already exists, then it is cleared. - tight_layout : bool or dict, default: :rc:`figure.autolayout` - If ``False`` use *subplotpars*. If ``True`` adjust subplot - parameters using `.tight_layout` with default padding. - When providing a dict containing the keys ``pad``, ``w_pad``, - ``h_pad``, and ``rect``, the default `.tight_layout` paddings - will be overridden. + layout : {'constrained', 'compressed', 'tight', 'none', `.LayoutEngine`, None}, \ +default: None + The layout mechanism for positioning of plot elements to avoid + overlapping Axes decorations (labels, ticks, etc). Note that layout + managers can measurably slow down figure display. + + - 'constrained': The constrained layout solver adjusts Axes sizes + to avoid overlapping Axes decorations. Can handle complex plot + layouts and colorbars, and is thus recommended. + + See :ref:`constrainedlayout_guide` + for examples. - constrained_layout : bool, default: :rc:`figure.constrained_layout.use` - If ``True`` use constrained layout to adjust positioning of plot - elements. Like ``tight_layout``, but designed to be more - flexible. See - :doc:`/tutorials/intermediate/constrainedlayout_guide` - for examples. (Note: does not work with `add_subplot` or - `~.pyplot.subplot2grid`.) + - 'compressed': uses the same algorithm as 'constrained', but + removes extra space between fixed-aspect-ratio Axes. Best for + simple grids of Axes. + - 'tight': Use the tight layout mechanism. This is a relatively + simple algorithm that adjusts the subplot parameters so that + decorations do not overlap. See `.Figure.set_tight_layout` for + further details. - **kwargs : optional - See `~.matplotlib.figure.Figure` for other possible arguments. + - 'none': Do not use a layout engine. + + - A `.LayoutEngine` instance. Builtin layout classes are + `.ConstrainedLayoutEngine` and `.TightLayoutEngine`, more easily + accessible by 'constrained' and 'tight'. Passing an instance + allows third parties to provide their own layout engine. + + If not given, fall back to using the parameters *tight_layout* and + *constrained_layout*, including their config defaults + :rc:`figure.autolayout` and :rc:`figure.constrained_layout.use`. + + **kwargs + Additional keyword arguments are passed to the `.Figure` constructor. Returns ------- `~matplotlib.figure.Figure` - The `.Figure` instance returned will also be passed to - new_figure_manager in the backends, which allows to hook custom - `.Figure` classes into the pyplot interface. Additional kwargs will be - passed to the `.Figure` init function. Notes ----- + A newly created figure is passed to the `~.FigureCanvasBase.new_manager` + method or the `new_figure_manager` function provided by the current + backend, which install a canvas and a manager on the figure. + + Once this is done, :rc:`figure.hooks` are called, one at a time, on the + figure; these hooks allow arbitrary customization of the figure (e.g., + attaching callbacks) or of associated elements (e.g., modifying the + toolbar). See :doc:`/gallery/user_interfaces/mplcvd` for an example of + toolbar customization. + If you are creating many figures, make sure you explicitly call `.pyplot.close` on the figures you are not using, because this will enable pyplot to properly clean up the memory. @@ -728,41 +996,58 @@ def figure(num=None, # autoincrement if None, else integer from 1-N `~matplotlib.rcParams` defines the default values, which can be modified in the matplotlibrc file. """ - allnums = get_fignums() + + if isinstance(num, FigureBase): + # type narrowed to `Figure | SubFigure` by combination of input and isinstance + root_fig = num.get_figure(root=True) + if root_fig.canvas.manager is None: + raise ValueError("The passed figure is not managed by pyplot") + elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) + or not frameon or kwargs) and root_fig.canvas.manager.num in allnums: + _api.warn_external( + "Ignoring specified arguments in this call because figure " + f"with num: {root_fig.canvas.manager.num} already exists") + _pylab_helpers.Gcf.set_active(root_fig.canvas.manager) + return root_fig + next_num = max(allnums) + 1 if allnums else 1 fig_label = '' if num is None: num = next_num - elif isinstance(num, str): - fig_label = num - all_labels = get_figlabels() - if fig_label not in all_labels: - if fig_label == 'all': - cbook._warn_external( - "close('all') closes all existing figures.") - num = next_num - else: - inum = all_labels.index(fig_label) - num = allnums[inum] else: - num = int(num) # crude validation of num argument + if (any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) + or not frameon or kwargs) and num in allnums: + _api.warn_external( + "Ignoring specified arguments in this call " + f"because figure with num: {num} already exists") + if isinstance(num, str): + fig_label = num + all_labels = get_figlabels() + if fig_label not in all_labels: + if fig_label == 'all': + _api.warn_external("close('all') closes all existing figures.") + num = next_num + else: + inum = all_labels.index(fig_label) + num = allnums[inum] + else: + num = int(num) # crude validation of num argument - manager = _pylab_helpers.Gcf.get_fig_manager(num) + # Type of "num" has narrowed to int, but mypy can't quite see it + manager = _pylab_helpers.Gcf.get_fig_manager(num) # type: ignore[arg-type] if manager is None: max_open_warning = rcParams['figure.max_open_warning'] if len(allnums) == max_open_warning >= 1: - cbook._warn_external( + _api.warn_external( f"More than {max_open_warning} figures have been opened. " f"Figures created through the pyplot interface " f"(`matplotlib.pyplot.figure`) are retained until explicitly " f"closed and may consume too much memory. (To control this " - f"warning, see the rcParam `figure.max_open_warning`).", + f"warning, see the rcParam `figure.max_open_warning`). " + f"Consider using `matplotlib.pyplot.close()`.", RuntimeWarning) - if get_backend().lower() == 'ps': - dpi = 72 - manager = new_figure_manager( num, figsize=figsize, dpi=dpi, facecolor=facecolor, edgecolor=edgecolor, frameon=frameon, @@ -771,6 +1056,13 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if fig_label: fig.set_label(fig_label) + for hookspecs in rcParams["figure.hooks"]: + module_name, dotted_name = hookspecs.split(":") + obj: Any = importlib.import_module(module_name) + for part in dotted_name.split("."): + obj = getattr(obj, part) + obj(fig) + _pylab_helpers.Gcf._set_new_active_manager(manager) # make sure backends (inline) that we don't ship that expect this @@ -779,7 +1071,7 @@ def figure(num=None, # autoincrement if None, else integer from 1-N # FigureManager base class. draw_if_interactive() - if _INSTALL_FIG_OBSERVER: + if _REPL_DISPLAYHOOK is _ReplDisplayHook.PLAIN: fig.stale_callback = _auto_draw_if_interactive if clear: @@ -809,12 +1101,14 @@ def _auto_draw_if_interactive(fig, val): fig.canvas.draw_idle() -def gcf(): +def gcf() -> Figure: """ Get the current figure. - If no current figure exists, a new one is created using - `~.pyplot.figure()`. + If there is currently no figure on the pyplot figure stack, a new one is + created using `~.pyplot.figure()`. (To test whether there is currently a + figure on the pyplot figure stack, check whether `~.pyplot.get_fignums()` + is empty.) """ manager = _pylab_helpers.Gcf.get_active() if manager is not None: @@ -823,31 +1117,47 @@ def gcf(): return figure() -def fignum_exists(num): - """Return whether the figure with the given id exists.""" - return _pylab_helpers.Gcf.has_fignum(num) or num in get_figlabels() +def fignum_exists(num: int | str) -> bool: + """ + Return whether the figure with the given id exists. + + Parameters + ---------- + num : int or str + A figure identifier. + Returns + ------- + bool + Whether or not a figure with id *num* exists. + """ + return ( + _pylab_helpers.Gcf.has_fignum(num) + if isinstance(num, int) + else num in get_figlabels() + ) -def get_fignums(): + +def get_fignums() -> list[int]: """Return a list of existing figure numbers.""" return sorted(_pylab_helpers.Gcf.figs) -def get_figlabels(): +def get_figlabels() -> list[Any]: """Return a list of existing figure labels.""" managers = _pylab_helpers.Gcf.get_all_fig_managers() managers.sort(key=lambda m: m.num) return [m.canvas.figure.get_label() for m in managers] -def get_current_fig_manager(): +def get_current_fig_manager() -> FigureManagerBase | None: """ Return the figure manager of the current figure. The figure manager is a container for the actual backend-depended window that displays the figure on screen. - If if no current figure exists, a new one is created an its figure + If no current figure exists, a new one is created, and its figure manager is returned. Returns @@ -858,18 +1168,18 @@ def get_current_fig_manager(): @_copy_docstring_and_deprecators(FigureCanvasBase.mpl_connect) -def connect(s, func): +def connect(s: str, func: Callable[[Event], Any]) -> int: return gcf().canvas.mpl_connect(s, func) @_copy_docstring_and_deprecators(FigureCanvasBase.mpl_disconnect) -def disconnect(cid): - return gcf().canvas.mpl_disconnect(cid) +def disconnect(cid: int) -> None: + gcf().canvas.mpl_disconnect(cid) -def close(fig=None): +def close(fig: None | int | str | Figure | Literal["all"] = None) -> None: """ - Close a figure window. + Close a figure window, and unregister it from pyplot. Parameters ---------- @@ -882,6 +1192,14 @@ def close(fig=None): - ``str``: a figure name - 'all': all figures + Notes + ----- + pyplot maintains a reference to figures created with `figure()`. When + work on the figure is completed, it should be closed, i.e. deregistered + from pyplot, to free its memory (see also :rc:figure.max_open_warning). + Closing a figure window created by `show()` automatically deregisters the + figure. For all other use cases, most prominently `savefig()` without + `show()`, the figure must be deregistered explicitly using `close()`. """ if fig is None: manager = _pylab_helpers.Gcf.get_active() @@ -893,9 +1211,7 @@ def close(fig=None): _pylab_helpers.Gcf.destroy_all() elif isinstance(fig, int): _pylab_helpers.Gcf.destroy(fig) - elif hasattr(fig, 'int'): - # if we are dealing with a type UUID, we - # can use its integer representation + elif hasattr(fig, 'int'): # UUIDs get converted to ints by figure(). _pylab_helpers.Gcf.destroy(fig.int) elif isinstance(fig, str): all_labels = get_figlabels() @@ -905,16 +1221,16 @@ def close(fig=None): elif isinstance(fig, Figure): _pylab_helpers.Gcf.destroy_fig(fig) else: - raise TypeError("close() argument must be a Figure, an int, a string, " - "or None, not '%s'") + _api.check_isinstance( # type: ignore[unreachable] + (Figure, int, str, None), fig=fig) -def clf(): +def clf() -> None: """Clear the current figure.""" - gcf().clf() + gcf().clear() -def draw(): +def draw() -> None: """ Redraw the current figure. @@ -925,33 +1241,46 @@ def draw(): This is equivalent to calling ``fig.canvas.draw_idle()``, where ``fig`` is the current figure. + + See Also + -------- + .FigureCanvasBase.draw_idle + .FigureCanvasBase.draw """ gcf().canvas.draw_idle() @_copy_docstring_and_deprecators(Figure.savefig) -def savefig(*args, **kwargs): +def savefig(*args, **kwargs) -> None: fig = gcf() - res = fig.savefig(*args, **kwargs) - fig.canvas.draw_idle() # need this if 'transparent=True' to reset colors + # savefig default implementation has no return, so mypy is unhappy + # presumably this is here because subclasses can return? + res = fig.savefig(*args, **kwargs) # type: ignore[func-returns-value] + fig.canvas.draw_idle() # Need this if 'transparent=True', to reset colors. return res ## Putting things in figures ## -def figlegend(*args, **kwargs): +def figlegend(*args, **kwargs) -> Legend: return gcf().legend(*args, **kwargs) if Figure.legend.__doc__: - figlegend.__doc__ = Figure.legend.__doc__.replace("legend(", "figlegend(") + figlegend.__doc__ = Figure.legend.__doc__ \ + .replace(" legend(", " figlegend(") \ + .replace("fig.legend(", "plt.figlegend(") \ + .replace("ax.plot(", "plt.plot(") ## Axes ## -@docstring.dedent_interpd -def axes(arg=None, **kwargs): +@_docstring.interpd +def axes( + arg: None | tuple[float, float, float, float] = None, + **kwargs +) -> matplotlib.axes.Axes: """ - Add an axes to the current figure and make it the current axes. + Add an Axes to the current figure and make it the current Axes. Call signatures:: @@ -964,10 +1293,10 @@ def axes(arg=None, **kwargs): arg : None or 4-tuple The exact behavior of this function depends on the type: - - *None*: A new full window axes is added using - ``subplot(111, **kwargs)``. - - 4-tuple of floats *rect* = ``[left, bottom, width, height]``. - A new axes is added with dimensions *rect* in normalized + - *None*: A new full window Axes is added using + ``subplot(**kwargs)``. + - 4-tuple of floats *rect* = ``(left, bottom, width, height)``. + A new Axes is added with dimensions *rect* in normalized (0, 1) units using `~.Figure.add_axes` on the current figure. projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ @@ -979,18 +1308,18 @@ def axes(arg=None, **kwargs): polar : bool, default: False If True, equivalent to projection='polar'. - sharex, sharey : `~.axes.Axes`, optional + sharex, sharey : `~matplotlib.axes.Axes`, optional Share the x or y `~matplotlib.axis` with sharex and/or sharey. The axis will have the same limits, ticks, and scale as the axis - of the shared axes. + of the shared Axes. label : str - A label for the returned axes. + A label for the returned Axes. Returns ------- `~.axes.Axes`, or a subclass of `~.axes.Axes` - The returned axes class depends on the projection used. It is + The returned Axes class depends on the projection used. It is `~.axes.Axes` if rectilinear projection is used and `.projections.polar.PolarAxes` if polar projection is used. @@ -998,24 +1327,13 @@ def axes(arg=None, **kwargs): ---------------- **kwargs This method also takes the keyword arguments for - the returned axes class. The keyword arguments for the - rectilinear axes class `~.axes.Axes` can be found in + the returned Axes class. The keyword arguments for the + rectilinear Axes class `~.axes.Axes` can be found in the following table but there might also be other keyword - arguments if another projection is used, see the actual axes + arguments if another projection is used, see the actual Axes class. - %(Axes)s - - Notes - ----- - If the figure already has a axes with key (*args*, - *kwargs*) then it will simply make that axes current and - return it. This behavior is deprecated. Meanwhile, if you do - not want this behavior (i.e., you want to force the creation of a - new axes), you must use a unique set of args and kwargs. The axes - *label* attribute has been exposed for this purpose: if you want - two axes that are otherwise identical to be added to the figure, - make sure you give them unique labels. + %(Axes:kwdoc)s See Also -------- @@ -1029,47 +1347,59 @@ def axes(arg=None, **kwargs): -------- :: - # Creating a new full window axes + # Creating a new full window Axes plt.axes() - # Creating a new axes with specified dimensions and some kwargs - plt.axes((left, bottom, width, height), facecolor='w') + # Creating a new Axes with specified dimensions and a grey background + plt.axes((left, bottom, width, height), facecolor='grey') """ - + fig = gcf() + pos = kwargs.pop('position', None) if arg is None: - return subplot(111, **kwargs) + if pos is None: + return fig.add_subplot(**kwargs) + else: + return fig.add_axes(pos, **kwargs) else: - return gcf().add_axes(arg, **kwargs) + return fig.add_axes(arg, **kwargs) -def delaxes(ax=None): +def delaxes(ax: matplotlib.axes.Axes | None = None) -> None: """ - Remove an `~.axes.Axes` (defaulting to the current axes) from its figure. + Remove an `~.axes.Axes` (defaulting to the current Axes) from its figure. """ if ax is None: ax = gca() ax.remove() -def sca(ax): +def sca(ax: Axes) -> None: """ Set the current Axes to *ax* and the current Figure to the parent of *ax*. """ - if not hasattr(ax.figure.canvas, "manager"): - raise ValueError("Axes parent figure is not managed by pyplot") - _pylab_helpers.Gcf.set_active(ax.figure.canvas.manager) - ax.figure.sca(ax) + # Mypy sees ax.figure as potentially None, + # but if you are calling this, it won't be None + # Additionally the slight difference between `Figure` and `FigureBase` mypy catches + fig = ax.get_figure(root=False) + figure(fig) # type: ignore[arg-type] + fig.sca(ax) # type: ignore[union-attr] + + +def cla() -> None: + """Clear the current Axes.""" + # Not generated via boilerplate.py to allow a different docstring. + return gca().cla() -## More ways of creating axes ## +## More ways of creating Axes ## -@docstring.dedent_interpd -def subplot(*args, **kwargs): +@_docstring.interpd +def subplot(*args, **kwargs) -> Axes: """ - Add a subplot to the current figure. + Add an Axes to the current figure or retrieve an existing Axes. - Wrapper of `.Figure.add_subplot` with a difference in behavior - explained in the notes section. + This is a wrapper of `.Figure.add_subplot` which provides additional + behavior when working with the implicit API (see the notes section). Call signatures:: @@ -1105,63 +1435,56 @@ def subplot(*args, **kwargs): polar : bool, default: False If True, equivalent to projection='polar'. - sharex, sharey : `~.axes.Axes`, optional + sharex, sharey : `~matplotlib.axes.Axes`, optional Share the x or y `~matplotlib.axis` with sharex and/or sharey. The axis will have the same limits, ticks, and scale as the axis of the - shared axes. + shared Axes. label : str - A label for the returned axes. + A label for the returned Axes. Returns ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + `~.axes.Axes` - The axes of the subplot. The returned axes base class depends on - the projection used. It is `~.axes.Axes` if rectilinear projection - is used and `.projections.polar.PolarAxes` if polar projection - is used. The returned axes is then a subplot subclass of the - base class. + The Axes of the subplot. The returned Axes can actually be an instance + of a subclass, such as `.projections.polar.PolarAxes` for polar + projections. Other Parameters ---------------- **kwargs - This method also takes the keyword arguments for the returned axes + This method also takes the keyword arguments for the returned Axes base class; except for the *figure* argument. The keyword arguments for the rectilinear base class `~.axes.Axes` can be found in the following table but there might also be other keyword arguments if another projection is used. - %(Axes)s + %(Axes:kwdoc)s Notes ----- - Creating a subplot will delete any pre-existing subplot that overlaps - with it beyond sharing a boundary:: - - import matplotlib.pyplot as plt - # plot a line, implicitly creating a subplot(111) - plt.plot([1, 2, 3]) - # now create a subplot which represents the top plot of a grid - # with 2 rows and 1 column. Since this subplot will overlap the - # first, the plot (and its axes) previously created, will be removed - plt.subplot(211) + .. versionchanged:: 3.8 + In versions prior to 3.8, any preexisting Axes that overlap with the new Axes + beyond sharing a boundary was deleted. Deletion does not happen in more + recent versions anymore. Use `.Axes.remove` explicitly if needed. If you do not want this behavior, use the `.Figure.add_subplot` method or the `.pyplot.axes` function instead. - If the figure already has a subplot with key (*args*, - *kwargs*) then it will simply make that subplot current and - return it. This behavior is deprecated. Meanwhile, if you do - not want this behavior (i.e., you want to force the creation of a - new subplot), you must use a unique set of args and kwargs. The axes - *label* attribute has been exposed for this purpose: if you want - two subplots that are otherwise identical to be added to the figure, - make sure you give them unique labels. + If no *kwargs* are passed and there exists an Axes in the location + specified by *args* then that Axes will be returned rather than a new + Axes being created. - In rare circumstances, `.add_subplot` may be called with a single - argument, a subplot axes instance already created in the - present figure but not in the figure's list of axes. + If *kwargs* are passed and there exists an Axes in the location + specified by *args*, the projection type is the same, and the + *kwargs* match with the existing Axes, then the existing Axes is + returned. Otherwise a new Axes is created with the specified + parameters. We save a reference to the *kwargs* which we use + for this comparison. If any of the values in *kwargs* are + mutable we will not detect the case where they are mutated. + In these cases we suggest using `.Figure.add_subplot` and the + explicit Axes API rather than the implicit pyplot API. See Also -------- @@ -1177,10 +1500,10 @@ def subplot(*args, **kwargs): plt.subplot(221) # equivalent but more general - ax1=plt.subplot(2, 2, 1) + ax1 = plt.subplot(2, 2, 1) # add a subplot with no frame - ax2=plt.subplot(222, frameon=False) + ax2 = plt.subplot(222, frameon=False) # add a polar subplot plt.subplot(223, projection='polar') @@ -1193,45 +1516,127 @@ def subplot(*args, **kwargs): # add ax2 to the figure again plt.subplot(ax2) + + # make the first Axes "current" again + plt.subplot(221) + """ + # Here we will only normalize `polar=True` vs `projection='polar'` and let + # downstream code deal with the rest. + unset = object() + projection = kwargs.get('projection', unset) + polar = kwargs.pop('polar', unset) + if polar is not unset and polar: + # if we got mixed messages from the user, raise + if projection is not unset and projection != 'polar': + raise ValueError( + f"polar={polar}, yet projection={projection!r}. " + "Only one of these arguments should be supplied." + ) + kwargs['projection'] = projection = 'polar' # if subplot called without arguments, create subplot(1, 1, 1) if len(args) == 0: args = (1, 1, 1) - # This check was added because it is very easy to type - # subplot(1, 2, False) when subplots(1, 2, False) was intended - # (sharex=False, that is). In most cases, no error will - # ever occur, but mysterious behavior can result because what was - # intended to be the sharex argument is instead treated as a - # subplot index for subplot() + # This check was added because it is very easy to type subplot(1, 2, False) + # when subplots(1, 2, False) was intended (sharex=False, that is). In most + # cases, no error will ever occur, but mysterious behavior can result + # because what was intended to be the sharex argument is instead treated as + # a subplot index for subplot() if len(args) >= 3 and isinstance(args[2], bool): - cbook._warn_external("The subplot index argument to subplot() appears " - "to be a boolean. Did you intend to use " - "subplots()?") + _api.warn_external("The subplot index argument to subplot() appears " + "to be a boolean. Did you intend to use " + "subplots()?") # Check for nrows and ncols, which are not valid subplot args: if 'nrows' in kwargs or 'ncols' in kwargs: raise TypeError("subplot() got an unexpected keyword argument 'ncols' " "and/or 'nrows'. Did you intend to call subplots()?") fig = gcf() - ax = fig.add_subplot(*args, **kwargs) - bbox = ax.bbox - axes_to_delete = [] - for other_ax in fig.axes: - if other_ax == ax: - continue - if bbox.fully_overlaps(other_ax.bbox): - axes_to_delete.append(other_ax) - for ax_to_del in axes_to_delete: - delaxes(ax_to_del) + + # First, search for an existing subplot with a matching spec. + key = SubplotSpec._from_subplot_args(fig, args) + + for ax in fig.axes: + # If we found an Axes at the position, we can reuse it if the user passed no + # kwargs or if the Axes class and kwargs are identical. + if (ax.get_subplotspec() == key + and (kwargs == {} + or (ax._projection_init + == fig._process_projection_requirements(**kwargs)))): + break + else: + # we have exhausted the known Axes and none match, make a new one! + ax = fig.add_subplot(*args, **kwargs) + + fig.sca(ax) return ax -@cbook._make_keyword_only("3.3", "sharex") -def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, - subplot_kw=None, gridspec_kw=None, **fig_kw): +@overload +def subplots( + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Axes]: + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738 + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Any]: + ... + + +def subplots( + nrows: int = 1, ncols: int = 1, *, + sharex: bool | Literal["none", "all", "row", "col"] = False, + sharey: bool | Literal["none", "all", "row", "col"] = False, + squeeze: bool = True, + width_ratios: Sequence[float] | None = None, + height_ratios: Sequence[float] | None = None, + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + **fig_kw +) -> tuple[Figure, Any]: """ Create a figure and a set of subplots. @@ -1258,6 +1663,11 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, column subplot are created. To later turn other subplots' ticklabels on, use `~matplotlib.axes.Axes.tick_params`. + When subplots have a shared axis that has units, calling + `.Axis.set_units` will update each axis with the new units. + + Note that it is not possible to unshare axes. + squeeze : bool, default: True - If True, extra dimensions are squeezed out from the returned array of `~matplotlib.axes.Axes`: @@ -1272,6 +1682,18 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, always a 2D array containing Axes instances, even if it ends up being 1x1. + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. Equivalent + to ``gridspec_kw={'width_ratios': [...]}``. + + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. Convenience + for ``gridspec_kw={'height_ratios': [...]}``. + subplot_kw : dict, optional Dict with keywords passed to the `~matplotlib.figure.Figure.add_subplot` call used to create each @@ -1287,25 +1709,24 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, Returns ------- - fig : `~.figure.Figure` + fig : `.Figure` - ax : `.axes.Axes` or array of Axes - *ax* can be either a single `~matplotlib.axes.Axes` object or an - array of Axes objects if more than one subplot was created. The - dimensions of the resulting array can be controlled with the squeeze - keyword, see above. + ax : `~matplotlib.axes.Axes` or array of Axes + *ax* can be either a single `~.axes.Axes` object, or an array of Axes + objects if more than one subplot was created. The dimensions of the + resulting array can be controlled with the squeeze keyword, see above. Typical idioms for handling the return value are:: - # using the variable ax for single a Axes + # using the variable ax for a single Axes fig, ax = plt.subplots() # using the variable axs for multiple Axes fig, axs = plt.subplots(2, 2) # using tuple unpacking for multiple Axes - fig, (ax1, ax2) = plt.subplot(1, 2) - fig, ((ax1, ax2), (ax3, ax4)) = plt.subplot(2, 2) + fig, (ax1, ax2) = plt.subplots(1, 2) + fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2) The names ``ax`` and pluralized ``axs`` are preferred over ``axes`` because for the latter it's not clear if it refers to a single @@ -1338,8 +1759,8 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, ax1.set_title('Sharing Y axis') ax2.scatter(x, y) - # Create four polar axes and access them through the returned array - fig, axs = plt.subplots(2, 2, subplot_kw=dict(polar=True)) + # Create four polar Axes and access them through the returned array + fig, axs = plt.subplots(2, 2, subplot_kw=dict(projection="polar")) axs[0, 0].plot(x, y) axs[1, 1].scatter(x, y) @@ -1363,26 +1784,87 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, fig = figure(**fig_kw) axs = fig.subplots(nrows=nrows, ncols=ncols, sharex=sharex, sharey=sharey, squeeze=squeeze, subplot_kw=subplot_kw, - gridspec_kw=gridspec_kw) + gridspec_kw=gridspec_kw, height_ratios=height_ratios, + width_ratios=width_ratios) return fig, axs -def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, - empty_sentinel='.', **fig_kw): +@overload +def subplot_mosaic( + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[str, matplotlib.axes.Axes]]: ... + + +@overload +def subplot_mosaic( + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[_T, matplotlib.axes.Axes]]: ... + + +@overload +def subplot_mosaic( + mosaic: list[HashableList[Hashable]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: Any = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: ... + + +def subplot_mosaic( + mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], + *, + sharex: bool = False, + sharey: bool = False, + width_ratios: ArrayLike | None = None, + height_ratios: ArrayLike | None = None, + empty_sentinel: Any = '.', + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | + dict[_T | tuple[_T, ...], dict[str, Any]] | + dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, + **fig_kw: Any +) -> tuple[Figure, dict[str, matplotlib.axes.Axes]] | \ + tuple[Figure, dict[_T, matplotlib.axes.Axes]] | \ + tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: """ Build a layout of Axes based on ASCII art or nested lists. This is a helper function to build complex GridSpec layouts visually. - .. note :: - - This API is provisional and may be revised in the future based on - early user feedback. - + See :ref:`mosaic` + for an example and full API documentation Parameters ---------- - layout : list of list of {hashable or nested} or str + mosaic : list of list of {hashable or nested} or str A visual layout of how you want your Axes to be arranged labeled as strings. For example :: @@ -1390,7 +1872,7 @@ def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, x = [['A panel', 'A panel', 'edge'], ['C panel', '.', 'edge']] - Produces 4 axes: + produces 4 Axes: - 'A panel' which is 1 row high and spans the first two columns - 'edge' which is 2 rows high and is on the right edge @@ -1411,13 +1893,23 @@ def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, This only allows only single character Axes labels and does not allow nesting but is very terse. - subplot_kw : dict, optional - Dictionary with keywords passed to the `.Figure.add_subplot` call - used to create each subplot. + sharex, sharey : bool, default: False + If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared + among all subplots. In that case, tick label visibility and axis units + behave as for `subplots`. If False, each subplot's x- or y-axis will + be independent. - gridspec_kw : dict, optional - Dictionary with keywords passed to the `.GridSpec` constructor used - to create the grid the subplots are placed on. + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. Convenience + for ``gridspec_kw={'width_ratios': [...]}``. + + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. Convenience + for ``gridspec_kw={'height_ratios': [...]}``. empty_sentinel : object, optional Entry in the layout to mean "leave this space empty". Defaults @@ -1425,30 +1917,61 @@ def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, `inspect.cleandoc` to remove leading white space, which may interfere with using white-space as the empty sentinel. + subplot_kw : dict, optional + Dictionary with keywords passed to the `.Figure.add_subplot` call + used to create each subplot. These values may be overridden by + values in *per_subplot_kw*. + + per_subplot_kw : dict, optional + A dictionary mapping the Axes identifiers or tuples of identifiers + to a dictionary of keyword arguments to be passed to the + `.Figure.add_subplot` call used to create each subplot. The values + in these dictionaries have precedence over the values in + *subplot_kw*. + + If *mosaic* is a string, and thus all keys are single characters, + it is possible to use a single string instead of a tuple as keys; + i.e. ``"AB"`` is equivalent to ``("A", "B")``. + + .. versionadded:: 3.7 + + gridspec_kw : dict, optional + Dictionary with keywords passed to the `.GridSpec` constructor used + to create the grid the subplots are placed on. + **fig_kw All additional keyword arguments are passed to the `.pyplot.figure` call. Returns ------- - fig : `~.figure.Figure` + fig : `.Figure` The new figure dict[label, Axes] - A dictionary mapping the labels to the Axes objects. + A dictionary mapping the labels to the Axes objects. The order of + the Axes is left-to-right and top-to-bottom of their position in the + total layout. """ fig = figure(**fig_kw) - ax_dict = fig.subplot_mosaic( - layout, - subplot_kw=subplot_kw, - gridspec_kw=gridspec_kw, - empty_sentinel=empty_sentinel + ax_dict = fig.subplot_mosaic( # type: ignore[misc] + mosaic, # type: ignore[arg-type] + sharex=sharex, sharey=sharey, + height_ratios=height_ratios, width_ratios=width_ratios, + subplot_kw=subplot_kw, gridspec_kw=gridspec_kw, + empty_sentinel=empty_sentinel, + per_subplot_kw=per_subplot_kw, # type: ignore[arg-type] ) return fig, ax_dict -def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): +def subplot2grid( + shape: tuple[int, int], loc: tuple[int, int], + rowspan: int = 1, colspan: int = 1, + fig: Figure | None = None, + **kwargs +) -> matplotlib.axes.Axes: """ Create a subplot at a specific location inside a regular grid. @@ -1469,12 +1992,11 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): Returns ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + `~.axes.Axes` - The axes of the subplot. The returned axes base class depends on the - projection used. It is `~.axes.Axes` if rectilinear projection is used - and `.projections.polar.PolarAxes` if polar projection is used. The - returned axes is then a subplot subclass of the base class. + The Axes of the subplot. The returned Axes can actually be an instance + of a subclass, such as `.projections.polar.PolarAxes` for polar + projections. Notes ----- @@ -1488,32 +2010,18 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): gs = fig.add_gridspec(nrows, ncols) ax = fig.add_subplot(gs[row:row+rowspan, col:col+colspan]) """ - if fig is None: fig = gcf() - rows, cols = shape gs = GridSpec._check_gridspec_exists(fig, rows, cols) - subplotspec = gs.new_subplotspec(loc, rowspan=rowspan, colspan=colspan) - ax = fig.add_subplot(subplotspec, **kwargs) - bbox = ax.bbox - axes_to_delete = [] - for other_ax in fig.axes: - if other_ax == ax: - continue - if bbox.fully_overlaps(other_ax.bbox): - axes_to_delete.append(other_ax) - for ax_to_del in axes_to_delete: - delaxes(ax_to_del) - - return ax + return fig.add_subplot(subplotspec, **kwargs) -def twinx(ax=None): +def twinx(ax: matplotlib.axes.Axes | None = None) -> _AxesBase: """ - Make and return a second axes that shares the *x*-axis. The new axes will - overlay *ax* (or the current axes if *ax* is *None*), and its ticks will be + Make and return a second Axes that shares the *x*-axis. The new Axes will + overlay *ax* (or the current Axes if *ax* is *None*), and its ticks will be on the right. Examples @@ -1526,10 +2034,10 @@ def twinx(ax=None): return ax1 -def twiny(ax=None): +def twiny(ax: matplotlib.axes.Axes | None = None) -> _AxesBase: """ - Make and return a second axes that shares the *y*-axis. The new axes will - overlay *ax* (or the current axes if *ax* is *None*), and its ticks will be + Make and return a second Axes that shares the *y*-axis. The new Axes will + overlay *ax* (or the current Axes if *ax* is *None*), and its ticks will be on the top. Examples @@ -1542,50 +2050,32 @@ def twiny(ax=None): return ax1 -def subplot_tool(targetfig=None): +def subplot_tool(targetfig: Figure | None = None) -> SubplotTool | None: """ Launch a subplot tool window for a figure. - A `matplotlib.widgets.SubplotTool` instance is returned. You must maintain - a reference to the instance to keep the associated callbacks alive. + Returns + ------- + `matplotlib.widgets.SubplotTool` """ if targetfig is None: targetfig = gcf() - with rc_context({"toolbar": "none"}): # No navbar for the toolfig. - # Use new_figure_manager() instead of figure() so that the figure - # doesn't get registered with pyplot. - manager = new_figure_manager(-1, (6, 3)) - manager.set_window_title("Subplot configuration tool") - tool_fig = manager.canvas.figure - tool_fig.subplots_adjust(top=0.9) - manager.show() - return SubplotTool(targetfig, tool_fig) - - -# After deprecation elapses, this can be autogenerated by boilerplate.py. -@cbook._make_keyword_only("3.3", "pad") -def tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None): - """ - Adjust the padding between and around subplots. - - Parameters - ---------- - pad : float, default: 1.08 - Padding between the figure edge and the edges of subplots, - as a fraction of the font size. - h_pad, w_pad : float, default: *pad* - Padding (height/width) between edges of adjacent subplots, - as a fraction of the font size. - rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1) - A rectangle in normalized figure coordinates into which the whole - subplots area (including labels) will fit. - """ - gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + tb = targetfig.canvas.manager.toolbar # type: ignore[union-attr] + if hasattr(tb, "configure_subplots"): # toolbar2 + from matplotlib.backend_bases import NavigationToolbar2 + return cast(NavigationToolbar2, tb).configure_subplots() + elif hasattr(tb, "trigger_tool"): # toolmanager + from matplotlib.backend_bases import ToolContainerBase + cast(ToolContainerBase, tb).trigger_tool("subplots") + return None + else: + raise ValueError("subplot_tool can only be launched for figures with " + "an associated toolbar") -def box(on=None): +def box(on: bool | None = None) -> None: """ - Turn the axes box on or off on the current axes. + Turn the Axes box on or off on the current Axes. Parameters ---------- @@ -1606,9 +2096,9 @@ def box(on=None): ## Axis ## -def xlim(*args, **kwargs): +def xlim(*args, **kwargs) -> tuple[float, float]: """ - Get or set the x limits of the current axes. + Get or set the x limits of the current Axes. Call signatures:: @@ -1632,9 +2122,9 @@ def xlim(*args, **kwargs): Notes ----- Calling this function with no arguments (e.g. ``xlim()``) is the pyplot - equivalent of calling `~.Axes.get_xlim` on the current axes. + equivalent of calling `~.Axes.get_xlim` on the current Axes. Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_xlim` on the current axes. All arguments are passed though. + `~.Axes.set_xlim` on the current Axes. All arguments are passed though. """ ax = gca() if not args and not kwargs: @@ -1643,9 +2133,9 @@ def xlim(*args, **kwargs): return ret -def ylim(*args, **kwargs): +def ylim(*args, **kwargs) -> tuple[float, float]: """ - Get or set the y-limits of the current axes. + Get or set the y-limits of the current Axes. Call signatures:: @@ -1669,9 +2159,9 @@ def ylim(*args, **kwargs): Notes ----- Calling this function with no arguments (e.g. ``ylim()``) is the pyplot - equivalent of calling `~.Axes.get_ylim` on the current axes. + equivalent of calling `~.Axes.get_ylim` on the current Axes. Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_ylim` on the current axes. All arguments are passed though. + `~.Axes.set_ylim` on the current Axes. All arguments are passed though. """ ax = gca() if not args and not kwargs: @@ -1680,7 +2170,13 @@ def ylim(*args, **kwargs): return ret -def xticks(ticks=None, labels=None, **kwargs): +def xticks( + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs +) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the x-axis. @@ -1693,9 +2189,27 @@ def xticks(ticks=None, labels=None, **kwargs): labels : array-like, optional The labels to place at the given *ticks* locations. This argument can only be passed if *ticks* is passed as well. + minor : bool, default: False + If ``False``, get/set the major ticks/labels; if ``True``, the minor + ticks/labels. **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + + Returns ------- locs @@ -1707,9 +2221,9 @@ def xticks(ticks=None, labels=None, **kwargs): ----- Calling this function with no arguments (e.g. ``xticks()``) is the pyplot equivalent of calling `~.Axes.get_xticks` and `~.Axes.get_xticklabels` on - the current axes. + the current Axes. Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_xticks` and `~.Axes.set_xticklabels` on the current axes. + `~.Axes.set_xticks` and `~.Axes.set_xticklabels` on the current Axes. Examples -------- @@ -1722,25 +2236,33 @@ def xticks(ticks=None, labels=None, **kwargs): """ ax = gca() + locs: list[Tick] | np.ndarray if ticks is None: - locs = ax.get_xticks() + locs = ax.get_xticks(minor=minor) if labels is not None: raise TypeError("xticks(): Parameter 'labels' can't be set " "without setting 'ticks'") else: - locs = ax.set_xticks(ticks) + locs = ax.set_xticks(ticks, minor=minor) + labels_out: list[Text] = [] if labels is None: - labels = ax.get_xticklabels() + labels_out = ax.get_xticklabels(minor=minor) + for l in labels_out: + l._internal_update(kwargs) else: - labels = ax.set_xticklabels(labels, **kwargs) - for l in labels: - l.update(kwargs) + labels_out = ax.set_xticklabels(labels, minor=minor, **kwargs) - return locs, labels + return locs, labels_out -def yticks(ticks=None, labels=None, **kwargs): +def yticks( + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs +) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the y-axis. @@ -1753,9 +2275,26 @@ def yticks(ticks=None, labels=None, **kwargs): labels : array-like, optional The labels to place at the given *ticks* locations. This argument can only be passed if *ticks* is passed as well. + minor : bool, default: False + If ``False``, get/set the major ticks/labels; if ``True``, the minor + ticks/labels. **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + Returns ------- locs @@ -1767,9 +2306,9 @@ def yticks(ticks=None, labels=None, **kwargs): ----- Calling this function with no arguments (e.g. ``yticks()``) is the pyplot equivalent of calling `~.Axes.get_yticks` and `~.Axes.get_yticklabels` on - the current axes. + the current Axes. Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_yticks` and `~.Axes.set_yticklabels` on the current axes. + `~.Axes.set_yticks` and `~.Axes.set_yticklabels` on the current Axes. Examples -------- @@ -1782,25 +2321,33 @@ def yticks(ticks=None, labels=None, **kwargs): """ ax = gca() + locs: list[Tick] | np.ndarray if ticks is None: - locs = ax.get_yticks() + locs = ax.get_yticks(minor=minor) if labels is not None: raise TypeError("yticks(): Parameter 'labels' can't be set " "without setting 'ticks'") else: - locs = ax.set_yticks(ticks) + locs = ax.set_yticks(ticks, minor=minor) + labels_out: list[Text] = [] if labels is None: - labels = ax.get_yticklabels() + labels_out = ax.get_yticklabels(minor=minor) + for l in labels_out: + l._internal_update(kwargs) else: - labels = ax.set_yticklabels(labels, **kwargs) - for l in labels: - l.update(kwargs) + labels_out = ax.set_yticklabels(labels, minor=minor, **kwargs) - return locs, labels + return locs, labels_out -def rgrids(radii=None, labels=None, angle=None, fmt=None, **kwargs): +def rgrids( + radii: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + angle: float | None = None, + fmt: str | None = None, + **kwargs +) -> tuple[list[Line2D], list[Text]]: """ Get or set the radial gridlines on the current polar plot. @@ -1840,7 +2387,7 @@ def rgrids(radii=None, labels=None, angle=None, fmt=None, **kwargs): Other Parameters ---------------- **kwargs - *kwargs* are optional `~.Text` properties for the labels. + *kwargs* are optional `.Text` properties for the labels. See Also -------- @@ -1861,17 +2408,24 @@ def rgrids(radii=None, labels=None, angle=None, fmt=None, **kwargs): """ ax = gca() if not isinstance(ax, PolarAxes): - raise RuntimeError('rgrids only defined for polar axes') + raise RuntimeError('rgrids only defined for polar Axes') if all(p is None for p in [radii, labels, angle, fmt]) and not kwargs: - lines = ax.yaxis.get_gridlines() - labels = ax.yaxis.get_ticklabels() + lines_out: list[Line2D] = ax.yaxis.get_gridlines() + labels_out: list[Text] = ax.yaxis.get_ticklabels() + elif radii is None: + raise TypeError("'radii' cannot be None when other parameters are passed") else: - lines, labels = ax.set_rgrids( + lines_out, labels_out = ax.set_rgrids( radii, labels=labels, angle=angle, fmt=fmt, **kwargs) - return lines, labels + return lines_out, labels_out -def thetagrids(angles=None, labels=None, fmt=None, **kwargs): +def thetagrids( + angles: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + fmt: str | None = None, + **kwargs +) -> tuple[list[Line2D], list[Text]]: """ Get or set the theta gridlines on the current polar plot. @@ -1908,7 +2462,7 @@ def thetagrids(angles=None, labels=None, fmt=None, **kwargs): Other Parameters ---------------- **kwargs - *kwargs* are optional `~.Text` properties for the labels. + *kwargs* are optional `.Text` properties for the labels. See Also -------- @@ -1929,34 +2483,36 @@ def thetagrids(angles=None, labels=None, fmt=None, **kwargs): """ ax = gca() if not isinstance(ax, PolarAxes): - raise RuntimeError('thetagrids only defined for polar axes') + raise RuntimeError('thetagrids only defined for polar Axes') if all(param is None for param in [angles, labels, fmt]) and not kwargs: - lines = ax.xaxis.get_ticklines() - labels = ax.xaxis.get_ticklabels() + lines_out: list[Line2D] = ax.xaxis.get_ticklines() + labels_out: list[Text] = ax.xaxis.get_ticklabels() + elif angles is None: + raise TypeError("'angles' cannot be None when other parameters are passed") else: - lines, labels = ax.set_thetagrids(angles, - labels=labels, fmt=fmt, **kwargs) - return lines, labels - - -## Plotting Info ## + lines_out, labels_out = ax.set_thetagrids(angles, + labels=labels, fmt=fmt, + **kwargs) + return lines_out, labels_out -def plotting(): - pass - - -def get_plot_commands(): +@_api.deprecated("3.7", pending=True) +def get_plot_commands() -> list[str]: """ Get a sorted list of all of the plotting commands. """ + NON_PLOT_COMMANDS = { + 'connect', 'disconnect', 'get_current_fig_manager', 'ginput', + 'new_figure_manager', 'waitforbuttonpress'} + return [name for name in _get_pyplot_commands() + if name not in NON_PLOT_COMMANDS] + + +def _get_pyplot_commands() -> list[str]: # This works by searching for all functions in this module and removing # a few hard-coded exclusions, as well as all of the colormap-setting # functions, and anything marked as private with a preceding underscore. - exclude = {'colormaps', 'colors', 'connect', 'disconnect', - 'get_plot_commands', 'get_current_fig_manager', 'ginput', - 'plotting', 'waitforbuttonpress'} - exclude |= set(colormaps()) + exclude = {'colormaps', 'colors', 'get_plot_commands', *colormaps} this_module = inspect.getmodule(get_plot_commands) return sorted( name for name, obj in globals().items() @@ -1965,309 +2521,16 @@ def get_plot_commands(): and inspect.getmodule(obj) is this_module) -def colormaps(): - """ - Matplotlib provides a number of colormaps, and others can be added using - :func:`~matplotlib.cm.register_cmap`. This function documents the built-in - colormaps, and will also return a list of all registered colormaps if - called. - - You can set the colormap for an image, pcolor, scatter, etc, - using a keyword argument:: - - imshow(X, cmap=cm.hot) - - or using the :func:`set_cmap` function:: - - imshow(X) - pyplot.set_cmap('hot') - pyplot.set_cmap('jet') - - In interactive mode, :func:`set_cmap` will update the colormap post-hoc, - allowing you to see which one works best for your data. - - All built-in colormaps can be reversed by appending ``_r``: For instance, - ``gray_r`` is the reverse of ``gray``. - - There are several common color schemes used in visualization: - - Sequential schemes - for unipolar data that progresses from low to high - Diverging schemes - for bipolar data that emphasizes positive or negative deviations from a - central value - Cyclic schemes - for plotting values that wrap around at the endpoints, such as phase - angle, wind direction, or time of day - Qualitative schemes - for nominal data that has no inherent ordering, where color is used - only to distinguish categories - - Matplotlib ships with 4 perceptually uniform color maps which are - the recommended color maps for sequential data: - - ========= =================================================== - Colormap Description - ========= =================================================== - inferno perceptually uniform shades of black-red-yellow - magma perceptually uniform shades of black-red-white - plasma perceptually uniform shades of blue-red-yellow - viridis perceptually uniform shades of blue-green-yellow - ========= =================================================== - - The following colormaps are based on the `ColorBrewer - `_ color specifications and designs developed by - Cynthia Brewer: - - ColorBrewer Diverging (luminance is highest at the midpoint, and - decreases towards differently-colored endpoints): - - ======== =================================== - Colormap Description - ======== =================================== - BrBG brown, white, blue-green - PiYG pink, white, yellow-green - PRGn purple, white, green - PuOr orange, white, purple - RdBu red, white, blue - RdGy red, white, gray - RdYlBu red, yellow, blue - RdYlGn red, yellow, green - Spectral red, orange, yellow, green, blue - ======== =================================== - - ColorBrewer Sequential (luminance decreases monotonically): - - ======== ==================================== - Colormap Description - ======== ==================================== - Blues white to dark blue - BuGn white, light blue, dark green - BuPu white, light blue, dark purple - GnBu white, light green, dark blue - Greens white to dark green - Greys white to black (not linear) - Oranges white, orange, dark brown - OrRd white, orange, dark red - PuBu white, light purple, dark blue - PuBuGn white, light purple, dark green - PuRd white, light purple, dark red - Purples white to dark purple - RdPu white, pink, dark purple - Reds white to dark red - YlGn light yellow, dark green - YlGnBu light yellow, light green, dark blue - YlOrBr light yellow, orange, dark brown - YlOrRd light yellow, orange, dark red - ======== ==================================== - - ColorBrewer Qualitative: - - (For plotting nominal data, `.ListedColormap` is used, - not `.LinearSegmentedColormap`. Different sets of colors are - recommended for different numbers of categories.) - - * Accent - * Dark2 - * Paired - * Pastel1 - * Pastel2 - * Set1 - * Set2 - * Set3 - - A set of colormaps derived from those of the same name provided - with Matlab are also included: - - ========= ======================================================= - Colormap Description - ========= ======================================================= - autumn sequential linearly-increasing shades of red-orange-yellow - bone sequential increasing black-white color map with - a tinge of blue, to emulate X-ray film - cool linearly-decreasing shades of cyan-magenta - copper sequential increasing shades of black-copper - flag repetitive red-white-blue-black pattern (not cyclic at - endpoints) - gray sequential linearly-increasing black-to-white - grayscale - hot sequential black-red-yellow-white, to emulate blackbody - radiation from an object at increasing temperatures - jet a spectral map with dark endpoints, blue-cyan-yellow-red; - based on a fluid-jet simulation by NCSA [#]_ - pink sequential increasing pastel black-pink-white, meant - for sepia tone colorization of photographs - prism repetitive red-yellow-green-blue-purple-...-green pattern - (not cyclic at endpoints) - spring linearly-increasing shades of magenta-yellow - summer sequential linearly-increasing shades of green-yellow - winter linearly-increasing shades of blue-green - ========= ======================================================= - - A set of palettes from the `Yorick scientific visualisation - package `_, an evolution of - the GIST package, both by David H. Munro are included: - - ============ ======================================================= - Colormap Description - ============ ======================================================= - gist_earth mapmaker's colors from dark blue deep ocean to green - lowlands to brown highlands to white mountains - gist_heat sequential increasing black-red-orange-white, to emulate - blackbody radiation from an iron bar as it grows hotter - gist_ncar pseudo-spectral black-blue-green-yellow-red-purple-white - colormap from National Center for Atmospheric - Research [#]_ - gist_rainbow runs through the colors in spectral order from red to - violet at full saturation (like *hsv* but not cyclic) - gist_stern "Stern special" color table from Interactive Data - Language software - ============ ======================================================= - - A set of cyclic color maps: - - ================ ================================================= - Colormap Description - ================ ================================================= - hsv red-yellow-green-cyan-blue-magenta-red, formed by - changing the hue component in the HSV color space - twilight perceptually uniform shades of - white-blue-black-red-white - twilight_shifted perceptually uniform shades of - black-blue-white-red-black - ================ ================================================= - - Other miscellaneous schemes: - - ============= ======================================================= - Colormap Description - ============= ======================================================= - afmhot sequential black-orange-yellow-white blackbody - spectrum, commonly used in atomic force microscopy - brg blue-red-green - bwr diverging blue-white-red - coolwarm diverging blue-gray-red, meant to avoid issues with 3D - shading, color blindness, and ordering of colors [#]_ - CMRmap "Default colormaps on color images often reproduce to - confusing grayscale images. The proposed colormap - maintains an aesthetically pleasing color image that - automatically reproduces to a monotonic grayscale with - discrete, quantifiable saturation levels." [#]_ - cubehelix Unlike most other color schemes cubehelix was designed - by D.A. Green to be monotonically increasing in terms - of perceived brightness. Also, when printed on a black - and white postscript printer, the scheme results in a - greyscale with monotonically increasing brightness. - This color scheme is named cubehelix because the (r, g, b) - values produced can be visualised as a squashed helix - around the diagonal in the (r, g, b) color cube. - gnuplot gnuplot's traditional pm3d scheme - (black-blue-red-yellow) - gnuplot2 sequential color printable as gray - (black-blue-violet-yellow-white) - ocean green-blue-white - rainbow spectral purple-blue-green-yellow-orange-red colormap - with diverging luminance - seismic diverging blue-white-red - nipy_spectral black-purple-blue-green-yellow-red-white spectrum, - originally from the Neuroimaging in Python project - terrain mapmaker's colors, blue-green-yellow-brown-white, - originally from IGOR Pro - turbo Spectral map (purple-blue-green-yellow-orange-red) with - a bright center and darker endpoints. A smoother - alternative to jet. - ============= ======================================================= - - The following colormaps are redundant and may be removed in future - versions. It's recommended to use the names in the descriptions - instead, which produce identical output: - - ========= ======================================================= - Colormap Description - ========= ======================================================= - gist_gray identical to *gray* - gist_yarg identical to *gray_r* - binary identical to *gray_r* - ========= ======================================================= - - .. rubric:: Footnotes - - .. [#] Rainbow colormaps, ``jet`` in particular, are considered a poor - choice for scientific visualization by many researchers: `Rainbow Color - Map (Still) Considered Harmful - `_ - - .. [#] Resembles "BkBlAqGrYeOrReViWh200" from NCAR Command - Language. See `Color Table Gallery - `_ - - .. [#] See `Diverging Color Maps for Scientific Visualization - `_ by Kenneth Moreland. - - .. [#] See `A Color Map for Effective Black-and-White Rendering of - Color-Scale Images - `_ - by Carey Rappaport - """ - return sorted(cm._cmap_registry) - - -def _setup_pyplot_info_docstrings(): - """ - Setup the docstring of `plotting` and of the colormap-setting functions. - - These must be done after the entire module is imported, so it is called - from the end of this module, which is generated by boilerplate.py. - """ - commands = get_plot_commands() - - first_sentence = re.compile(r"(?:\s*).+?\.(?:\s+|$)", flags=re.DOTALL) - - # Collect the first sentence of the docstring for all of the - # plotting commands. - rows = [] - max_name = len("Function") - max_summary = len("Description") - for name in commands: - doc = globals()[name].__doc__ - summary = '' - if doc is not None: - match = first_sentence.match(doc) - if match is not None: - summary = inspect.cleandoc(match.group(0)).replace('\n', ' ') - name = '`%s`' % name - rows.append([name, summary]) - max_name = max(max_name, len(name)) - max_summary = max(max_summary, len(summary)) - - separator = '=' * max_name + ' ' + '=' * max_summary - lines = [ - separator, - '{:{}} {:{}}'.format('Function', max_name, 'Description', max_summary), - separator, - ] + [ - '{:{}} {:{}}'.format(name, max_name, summary, max_summary) - for name, summary in rows - ] + [ - separator, - ] - plotting.__doc__ = '\n'.join(lines) - - for cm_name in colormaps(): - if cm_name in globals(): - globals()[cm_name].__doc__ = f""" - Set the colormap to {cm_name!r}. - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - - ## Plotting part 1: manually generated functions and wrappers ## @_copy_docstring_and_deprecators(Figure.colorbar) -def colorbar(mappable=None, cax=None, ax=None, **kw): +def colorbar( + mappable: ScalarMappable | ColorizingArtist | None = None, + cax: matplotlib.axes.Axes | None = None, + ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, + **kwargs +) -> Colorbar: if mappable is None: mappable = gci() if mappable is None: @@ -2275,11 +2538,11 @@ def colorbar(mappable=None, cax=None, ax=None, **kw): 'creation. First define a mappable such as ' 'an image (with imshow) or a contour set (' 'with contourf).') - ret = gcf().colorbar(mappable, cax=cax, ax=ax, **kw) + ret = gcf().colorbar(mappable, cax=cax, ax=ax, **kwargs) return ret -def clim(vmin=None, vmax=None): +def clim(vmin: float | None = None, vmax: float | None = None) -> None: """ Set the color limits of the current image. @@ -2300,7 +2563,36 @@ def clim(vmin=None, vmax=None): im.set_clim(vmin, vmax) -def set_cmap(cmap): +def get_cmap(name: Colormap | str | None = None, lut: int | None = None) -> Colormap: + """ + Get a colormap instance, defaulting to rc values if *name* is None. + + Parameters + ---------- + name : `~matplotlib.colors.Colormap` or str or None, default: None + If a `.Colormap` instance, it will be returned. Otherwise, the name of + a colormap known to Matplotlib, which will be resampled by *lut*. The + default, None, means :rc:`image.cmap`. + lut : int or None, default: None + If *name* is not already a Colormap instance and *lut* is not None, the + colormap will be resampled to have *lut* entries in the lookup table. + + Returns + ------- + Colormap + """ + if name is None: + name = rcParams['image.cmap'] + if isinstance(name, Colormap): + return name + _api.check_in_list(sorted(_colormaps), name=name) + if lut is None: + return _colormaps[name] + else: + return _colormaps[name].resampled(lut) + + +def set_cmap(cmap: Colormap | str) -> None: """ Set the default colormap, and applies it to the current image if any. @@ -2312,10 +2604,9 @@ def set_cmap(cmap): See Also -------- colormaps - matplotlib.cm.register_cmap - matplotlib.cm.get_cmap + get_cmap """ - cmap = cm.get_cmap(cmap) + cmap = get_cmap(cmap) rc('image', cmap=cmap.name) im = gci() @@ -2325,44 +2616,53 @@ def set_cmap(cmap): @_copy_docstring_and_deprecators(matplotlib.image.imread) -def imread(fname, format=None): +def imread( + fname: str | pathlib.Path | BinaryIO, format: str | None = None +) -> np.ndarray: return matplotlib.image.imread(fname, format) @_copy_docstring_and_deprecators(matplotlib.image.imsave) -def imsave(fname, arr, **kwargs): - return matplotlib.image.imsave(fname, arr, **kwargs) +def imsave( + fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs +) -> None: + matplotlib.image.imsave(fname, arr, **kwargs) -def matshow(A, fignum=None, **kwargs): +def matshow(A: ArrayLike, fignum: None | int = None, **kwargs) -> AxesImage: """ - Display an array as a matrix in a new figure window. + Display a 2D array as a matrix in a new figure window. + + The origin is set at the upper left hand corner. + The indexing is ``(row, column)`` so that the first index runs vertically + and the second index runs horizontally in the figure: + + .. code-block:: none - The origin is set at the upper left hand corner and rows (first - dimension of the array) are displayed horizontally. The aspect - ratio of the figure window is that of the array, unless this would - make an excessively short or narrow figure. + A[0, 0] ⋯ A[0, M-1] + ⋮ ⋮ + A[N-1, 0] ⋯ A[N-1, M-1] + + The aspect ratio of the figure window is that of the array, + unless this would make an excessively short or narrow figure. Tick labels for the xaxis are placed on top. Parameters ---------- - A : array-like(M, N) + A : 2D array-like The matrix to be displayed. - fignum : None or int or False - If *None*, create a new figure window with automatic numbering. - - If a nonzero integer, draw into the figure with the given number - (create it if it does not exist). + fignum : None or int + If *None*, create a new, appropriately sized figure window. - If 0, use the current axes (or create one if it does not exist). + If 0, use the current Axes (creating one if there is none, without ever + adjusting the figure size). - .. note:: - - Because of how `.Axes.matshow` tries to set the figure aspect - ratio to be the one of the array, strange things may happen if you - reuse an existing figure. + Otherwise, create a new Axes on the figure with the given number + (creating it at the appropriate size if it does not exist, but not + adjusting the figure size otherwise). Note that this will be drawn on + top of any preexisting Axes on the figure. Returns ------- @@ -2377,54 +2677,70 @@ def matshow(A, fignum=None, **kwargs): if fignum == 0: ax = gca() else: - # Extract actual aspect ratio of array and make appropriately sized - # figure. - fig = figure(fignum, figsize=figaspect(A)) - ax = fig.add_axes([0.15, 0.09, 0.775, 0.775]) + if fignum is not None and fignum_exists(fignum): + # Do not try to set a figure size. + figsize = None + else: + # Extract actual aspect ratio of array and make appropriately sized figure. + figsize = figaspect(A) + fig = figure(fignum, figsize=figsize) + ax = fig.add_axes((0.15, 0.09, 0.775, 0.775)) im = ax.matshow(A, **kwargs) sci(im) return im -def polar(*args, **kwargs): +def polar(*args, **kwargs) -> list[Line2D]: """ Make a polar plot. call signature:: - polar(theta, r, **kwargs) + polar(theta, r, [fmt], **kwargs) - Multiple *theta*, *r* arguments are supported, with format strings, as in - `plot`. + This is a convenience wrapper around `.pyplot.plot`. It ensures that the + current Axes is polar (or creates one if needed) and then passes all parameters + to ``.pyplot.plot``. + + .. note:: + When making polar plots using the :ref:`pyplot API `, + ``polar()`` should typically be the first command because that makes sure + a polar Axes is created. Using other commands such as ``plt.title()`` + before this can lead to the implicit creation of a rectangular Axes, in which + case a subsequent ``polar()`` call will fail. """ # If an axis already exists, check if it has a polar projection if gcf().get_axes(): - if not isinstance(gca(), PolarAxes): - cbook._warn_external('Trying to create polar plot on an axis ' - 'that does not have a polar projection.') - ax = gca(polar=True) - ret = ax.plot(*args, **kwargs) - return ret + ax = gca() + if not isinstance(ax, PolarAxes): + _api.warn_deprecated( + "3.10", + message="There exists a non-polar current Axes. Therefore, the " + "resulting plot from 'polar()' is non-polar. You likely " + "should call 'polar()' before any other pyplot plotting " + "commands. " + "Support for this scenario is deprecated in %(since)s and " + "will raise an error in %(removal)s" + ) + else: + ax = axes(projection="polar") + return ax.plot(*args, **kwargs) # If rcParams['backend_fallback'] is true, and an interactive backend is # requested, ignore rcParams['backend'] and force selection of a backend that # is compatible with the current running interactive framework. -if (rcParams["backend_fallback"] - and dict.__getitem__(rcParams, "backend") in ( - set(_interactive_bk) - {'WebAgg', 'nbAgg'}) - and cbook._get_running_interactive_framework()): - dict.__setitem__(rcParams, "backend", rcsetup._auto_backend_sentinel) -# Set up the backend. -switch_backend(rcParams["backend"]) - -# Just to be safe. Interactive mode can be turned on without -# calling `plt.ion()` so register it again here. -# This is safe because multiple calls to `install_repl_displayhook` -# are no-ops and the registered function respect `mpl.is_interactive()` -# to determine if they should trigger a draw. -install_repl_displayhook() - +if rcParams["backend_fallback"]: + requested_backend = rcParams._get_backend_or_none() # type: ignore[attr-defined] + requested_backend = None if requested_backend is None else requested_backend.lower() + available_backends = backend_registry.list_builtin(BackendFilter.INTERACTIVE) + if ( + requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) + and cbook._get_running_interactive_framework() + ): + rcParams._set("backend", rcsetup._auto_backend_sentinel) + +# fmt: on ################# REMAINING CONTENT GENERATED BY boilerplate.py ############## @@ -2432,328 +2748,739 @@ def polar(*args, **kwargs): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.figimage) def figimage( - X, xo=0, yo=0, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, origin=None, resize=False, **kwargs): + X: ArrayLike, + xo: int = 0, + yo: int = 0, + alpha: float | None = None, + norm: str | Normalize | None = None, + cmap: str | Colormap | None = None, + vmin: float | None = None, + vmax: float | None = None, + origin: Literal["upper", "lower"] | None = None, + resize: bool = False, + *, + colorizer: Colorizer | None = None, + **kwargs, +) -> FigureImage: return gcf().figimage( - X, xo=xo, yo=yo, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, - vmax=vmax, origin=origin, resize=resize, **kwargs) + X, + xo=xo, + yo=yo, + alpha=alpha, + norm=norm, + cmap=cmap, + vmin=vmin, + vmax=vmax, + origin=origin, + resize=resize, + colorizer=colorizer, + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.text) -def figtext(x, y, s, fontdict=None, **kwargs): +def figtext( + x: float, y: float, s: str, fontdict: dict[str, Any] | None = None, **kwargs +) -> Text: return gcf().text(x, y, s, fontdict=fontdict, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.gca) -def gca(**kwargs): - return gcf().gca(**kwargs) +def gca() -> Axes: + return gcf().gca() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure._gci) -def gci(): +def gci() -> ColorizingArtist | None: return gcf()._gci() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.ginput) def ginput( - n=1, timeout=30, show_clicks=True, - mouse_add=MouseButton.LEFT, mouse_pop=MouseButton.RIGHT, - mouse_stop=MouseButton.MIDDLE): + n: int = 1, + timeout: float = 30, + show_clicks: bool = True, + mouse_add: MouseButton = MouseButton.LEFT, + mouse_pop: MouseButton = MouseButton.RIGHT, + mouse_stop: MouseButton = MouseButton.MIDDLE, +) -> list[tuple[int, int]]: return gcf().ginput( - n=n, timeout=timeout, show_clicks=show_clicks, - mouse_add=mouse_add, mouse_pop=mouse_pop, - mouse_stop=mouse_stop) + n=n, + timeout=timeout, + show_clicks=show_clicks, + mouse_add=mouse_add, + mouse_pop=mouse_pop, + mouse_stop=mouse_stop, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.subplots_adjust) def subplots_adjust( - left=None, bottom=None, right=None, top=None, wspace=None, - hspace=None): - return gcf().subplots_adjust( - left=left, bottom=bottom, right=right, top=top, wspace=wspace, - hspace=hspace) + left: float | None = None, + bottom: float | None = None, + right: float | None = None, + top: float | None = None, + wspace: float | None = None, + hspace: float | None = None, +) -> None: + gcf().subplots_adjust( + left=left, bottom=bottom, right=right, top=top, wspace=wspace, hspace=hspace + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.suptitle) -def suptitle(t, **kwargs): +def suptitle(t: str, **kwargs) -> Text: return gcf().suptitle(t, **kwargs) +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Figure.tight_layout) +def tight_layout( + *, + pad: float = 1.08, + h_pad: float | None = None, + w_pad: float | None = None, + rect: tuple[float, float, float, float] | None = None, +) -> None: + gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + + # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure.waitforbuttonpress) -def waitforbuttonpress(timeout=-1): +def waitforbuttonpress(timeout: float = -1) -> None | bool: return gcf().waitforbuttonpress(timeout=timeout) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.acorr) -def acorr(x, *, data=None, **kwargs): - return gca().acorr( - x, **({"data": data} if data is not None else {}), **kwargs) +def acorr( + x: ArrayLike, *, data=None, **kwargs +) -> tuple[np.ndarray, np.ndarray, LineCollection | Line2D, Line2D | None]: + return gca().acorr(x, **({"data": data} if data is not None else {}), **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.angle_spectrum) def angle_spectrum( - x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, *, - data=None, **kwargs): + x: ArrayLike, + Fs: float | None = None, + Fc: int | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, Line2D]: return gca().angle_spectrum( - x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, - **({"data": data} if data is not None else {}), **kwargs) + x, + Fs=Fs, + Fc=Fc, + window=window, + pad_to=pad_to, + sides=sides, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.annotate) -def annotate(text, xy, *args, **kwargs): - return gca().annotate(text, xy, *args, **kwargs) +def annotate( + text: str, + xy: tuple[float, float], + xytext: tuple[float, float] | None = None, + xycoords: CoordsType = "data", + textcoords: CoordsType | None = None, + arrowprops: dict[str, Any] | None = None, + annotation_clip: bool | None = None, + **kwargs, +) -> Annotation: + return gca().annotate( + text, + xy, + xytext=xytext, + xycoords=xycoords, + textcoords=textcoords, + arrowprops=arrowprops, + annotation_clip=annotation_clip, + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.arrow) -def arrow(x, y, dx, dy, **kwargs): +def arrow(x: float, y: float, dx: float, dy: float, **kwargs) -> FancyArrow: return gca().arrow(x, y, dx, dy, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.autoscale) -def autoscale(enable=True, axis='both', tight=None): - return gca().autoscale(enable=enable, axis=axis, tight=tight) +def autoscale( + enable: bool = True, + axis: Literal["both", "x", "y"] = "both", + tight: bool | None = None, +) -> None: + gca().autoscale(enable=enable, axis=axis, tight=tight) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axhline) -def axhline(y=0, xmin=0, xmax=1, **kwargs): +def axhline(y: float = 0, xmin: float = 0, xmax: float = 1, **kwargs) -> Line2D: return gca().axhline(y=y, xmin=xmin, xmax=xmax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axhspan) -def axhspan(ymin, ymax, xmin=0, xmax=1, **kwargs): +def axhspan( + ymin: float, ymax: float, xmin: float = 0, xmax: float = 1, **kwargs +) -> Rectangle: return gca().axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axis) -def axis(*args, emit=True, **kwargs): - return gca().axis(*args, emit=emit, **kwargs) +def axis( + arg: tuple[float, float, float, float] | bool | str | None = None, + /, + *, + emit: bool = True, + **kwargs, +) -> tuple[float, float, float, float]: + return gca().axis(arg, emit=emit, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axline) -def axline(xy1, xy2=None, *, slope=None, **kwargs): +def axline( + xy1: tuple[float, float], + xy2: tuple[float, float] | None = None, + *, + slope: float | None = None, + **kwargs, +) -> AxLine: return gca().axline(xy1, xy2=xy2, slope=slope, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axvline) -def axvline(x=0, ymin=0, ymax=1, **kwargs): +def axvline(x: float = 0, ymin: float = 0, ymax: float = 1, **kwargs) -> Line2D: return gca().axvline(x=x, ymin=ymin, ymax=ymax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.axvspan) -def axvspan(xmin, xmax, ymin=0, ymax=1, **kwargs): +def axvspan( + xmin: float, xmax: float, ymin: float = 0, ymax: float = 1, **kwargs +) -> Rectangle: return gca().axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.bar) def bar( - x, height, width=0.8, bottom=None, *, align='center', - data=None, **kwargs): + x: float | ArrayLike, + height: float | ArrayLike, + width: float | ArrayLike = 0.8, + bottom: float | ArrayLike | None = None, + *, + align: Literal["center", "edge"] = "center", + data=None, + **kwargs, +) -> BarContainer: return gca().bar( - x, height, width=width, bottom=bottom, align=align, - **({"data": data} if data is not None else {}), **kwargs) + x, + height, + width=width, + bottom=bottom, + align=align, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.barbs) -def barbs(*args, data=None, **kw): - return gca().barbs( - *args, **({"data": data} if data is not None else {}), **kw) +def barbs(*args, data=None, **kwargs) -> Barbs: + return gca().barbs(*args, **({"data": data} if data is not None else {}), **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.barh) -def barh(y, width, height=0.8, left=None, *, align='center', **kwargs): +def barh( + y: float | ArrayLike, + width: float | ArrayLike, + height: float | ArrayLike = 0.8, + left: float | ArrayLike | None = None, + *, + align: Literal["center", "edge"] = "center", + data=None, + **kwargs, +) -> BarContainer: return gca().barh( - y, width, height=height, left=left, align=align, **kwargs) + y, + width, + height=height, + left=left, + align=align, + **({"data": data} if data is not None else {}), + **kwargs, + ) + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.bar_label) +def bar_label( + container: BarContainer, + labels: ArrayLike | None = None, + *, + fmt: str | Callable[[float], str] = "%g", + label_type: Literal["center", "edge"] = "edge", + padding: float | ArrayLike = 0, + **kwargs, +) -> list[Annotation]: + return gca().bar_label( + container, + labels=labels, + fmt=fmt, + label_type=label_type, + padding=padding, + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.boxplot) def boxplot( - x, notch=None, sym=None, vert=None, whis=None, - positions=None, widths=None, patch_artist=None, - bootstrap=None, usermedians=None, conf_intervals=None, - meanline=None, showmeans=None, showcaps=None, showbox=None, - showfliers=None, boxprops=None, labels=None, flierprops=None, - medianprops=None, meanprops=None, capprops=None, - whiskerprops=None, manage_ticks=True, autorange=False, - zorder=None, *, data=None): + x: ArrayLike | Sequence[ArrayLike], + notch: bool | None = None, + sym: str | None = None, + vert: bool | None = None, + orientation: Literal["vertical", "horizontal"] = "vertical", + whis: float | tuple[float, float] | None = None, + positions: ArrayLike | None = None, + widths: float | ArrayLike | None = None, + patch_artist: bool | None = None, + bootstrap: int | None = None, + usermedians: ArrayLike | None = None, + conf_intervals: ArrayLike | None = None, + meanline: bool | None = None, + showmeans: bool | None = None, + showcaps: bool | None = None, + showbox: bool | None = None, + showfliers: bool | None = None, + boxprops: dict[str, Any] | None = None, + tick_labels: Sequence[str] | None = None, + flierprops: dict[str, Any] | None = None, + medianprops: dict[str, Any] | None = None, + meanprops: dict[str, Any] | None = None, + capprops: dict[str, Any] | None = None, + whiskerprops: dict[str, Any] | None = None, + manage_ticks: bool = True, + autorange: bool = False, + zorder: float | None = None, + capwidths: float | ArrayLike | None = None, + label: Sequence[str] | None = None, + *, + data=None, +) -> dict[str, Any]: return gca().boxplot( - x, notch=notch, sym=sym, vert=vert, whis=whis, - positions=positions, widths=widths, patch_artist=patch_artist, - bootstrap=bootstrap, usermedians=usermedians, - conf_intervals=conf_intervals, meanline=meanline, - showmeans=showmeans, showcaps=showcaps, showbox=showbox, - showfliers=showfliers, boxprops=boxprops, labels=labels, - flierprops=flierprops, medianprops=medianprops, - meanprops=meanprops, capprops=capprops, - whiskerprops=whiskerprops, manage_ticks=manage_ticks, - autorange=autorange, zorder=zorder, - **({"data": data} if data is not None else {})) + x, + notch=notch, + sym=sym, + vert=vert, + orientation=orientation, + whis=whis, + positions=positions, + widths=widths, + patch_artist=patch_artist, + bootstrap=bootstrap, + usermedians=usermedians, + conf_intervals=conf_intervals, + meanline=meanline, + showmeans=showmeans, + showcaps=showcaps, + showbox=showbox, + showfliers=showfliers, + boxprops=boxprops, + tick_labels=tick_labels, + flierprops=flierprops, + medianprops=medianprops, + meanprops=meanprops, + capprops=capprops, + whiskerprops=whiskerprops, + manage_ticks=manage_ticks, + autorange=autorange, + zorder=zorder, + capwidths=capwidths, + label=label, + **({"data": data} if data is not None else {}), + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.broken_barh) -def broken_barh(xranges, yrange, *, data=None, **kwargs): +def broken_barh( + xranges: Sequence[tuple[float, float]], + yrange: tuple[float, float], + *, + data=None, + **kwargs, +) -> PolyCollection: return gca().broken_barh( - xranges, yrange, - **({"data": data} if data is not None else {}), **kwargs) - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_copy_docstring_and_deprecators(Axes.cla) -def cla(): - return gca().cla() + xranges, yrange, **({"data": data} if data is not None else {}), **kwargs + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.clabel) -def clabel(CS, levels=None, **kwargs): +def clabel(CS: ContourSet, levels: ArrayLike | None = None, **kwargs) -> list[Text]: return gca().clabel(CS, levels=levels, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.cohere) def cohere( - x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, - window=mlab.window_hanning, noverlap=0, pad_to=None, - sides='default', scale_by_freq=None, *, data=None, **kwargs): + x: ArrayLike, + y: ArrayLike, + NFFT: int = 256, + Fs: float = 2, + Fc: int = 0, + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike], ArrayLike] = mlab.detrend_none, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike = mlab.window_hanning, + noverlap: int = 0, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] = "default", + scale_by_freq: bool | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray]: return gca().cohere( - x, y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, - noverlap=noverlap, pad_to=pad_to, sides=sides, + x, + y, + NFFT=NFFT, + Fs=Fs, + Fc=Fc, + detrend=detrend, + window=window, + noverlap=noverlap, + pad_to=pad_to, + sides=sides, scale_by_freq=scale_by_freq, - **({"data": data} if data is not None else {}), **kwargs) + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.contour) -def contour(*args, data=None, **kwargs): +def contour(*args, data=None, **kwargs) -> QuadContourSet: __ret = gca().contour( - *args, **({"data": data} if data is not None else {}), - **kwargs) - if __ret._A is not None: sci(__ret) # noqa + *args, **({"data": data} if data is not None else {}), **kwargs + ) + if __ret._A is not None: # type: ignore[attr-defined] + sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.contourf) -def contourf(*args, data=None, **kwargs): +def contourf(*args, data=None, **kwargs) -> QuadContourSet: __ret = gca().contourf( - *args, **({"data": data} if data is not None else {}), - **kwargs) - if __ret._A is not None: sci(__ret) # noqa + *args, **({"data": data} if data is not None else {}), **kwargs + ) + if __ret._A is not None: # type: ignore[attr-defined] + sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.csd) def csd( - x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, - noverlap=None, pad_to=None, sides=None, scale_by_freq=None, - return_line=None, *, data=None, **kwargs): + x: ArrayLike, + y: ArrayLike, + NFFT: int | None = None, + Fs: float | None = None, + Fc: int | None = None, + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike], ArrayLike] + | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + noverlap: int | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + scale_by_freq: bool | None = None, + return_line: bool | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, Line2D]: return gca().csd( - x, y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, - noverlap=noverlap, pad_to=pad_to, sides=sides, - scale_by_freq=scale_by_freq, return_line=return_line, - **({"data": data} if data is not None else {}), **kwargs) + x, + y, + NFFT=NFFT, + Fs=Fs, + Fc=Fc, + detrend=detrend, + window=window, + noverlap=noverlap, + pad_to=pad_to, + sides=sides, + scale_by_freq=scale_by_freq, + return_line=return_line, + **({"data": data} if data is not None else {}), + **kwargs, + ) + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.ecdf) +def ecdf( + x: ArrayLike, + weights: ArrayLike | None = None, + *, + complementary: bool = False, + orientation: Literal["vertical", "horizontal"] = "vertical", + compress: bool = False, + data=None, + **kwargs, +) -> Line2D: + return gca().ecdf( + x, + weights=weights, + complementary=complementary, + orientation=orientation, + compress=compress, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.errorbar) def errorbar( - x, y, yerr=None, xerr=None, fmt='', ecolor=None, - elinewidth=None, capsize=None, barsabove=False, lolims=False, - uplims=False, xlolims=False, xuplims=False, errorevery=1, - capthick=None, *, data=None, **kwargs): + x: float | ArrayLike, + y: float | ArrayLike, + yerr: float | ArrayLike | None = None, + xerr: float | ArrayLike | None = None, + fmt: str = "", + ecolor: ColorType | None = None, + elinewidth: float | None = None, + capsize: float | None = None, + barsabove: bool = False, + lolims: bool | ArrayLike = False, + uplims: bool | ArrayLike = False, + xlolims: bool | ArrayLike = False, + xuplims: bool | ArrayLike = False, + errorevery: int | tuple[int, int] = 1, + capthick: float | None = None, + elinestyle: LineStyleType | None = None, + *, + data=None, + **kwargs, +) -> ErrorbarContainer: return gca().errorbar( - x, y, yerr=yerr, xerr=xerr, fmt=fmt, ecolor=ecolor, - elinewidth=elinewidth, capsize=capsize, barsabove=barsabove, - lolims=lolims, uplims=uplims, xlolims=xlolims, - xuplims=xuplims, errorevery=errorevery, capthick=capthick, - **({"data": data} if data is not None else {}), **kwargs) + x, + y, + yerr=yerr, + xerr=xerr, + fmt=fmt, + ecolor=ecolor, + elinewidth=elinewidth, + capsize=capsize, + barsabove=barsabove, + lolims=lolims, + uplims=uplims, + xlolims=xlolims, + xuplims=xuplims, + errorevery=errorevery, + capthick=capthick, + elinestyle=elinestyle, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.eventplot) def eventplot( - positions, orientation='horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, - linestyles='solid', *, data=None, **kwargs): + positions: ArrayLike | Sequence[ArrayLike], + orientation: Literal["horizontal", "vertical"] = "horizontal", + lineoffsets: float | Sequence[float] = 1, + linelengths: float | Sequence[float] = 1, + linewidths: float | Sequence[float] | None = None, + colors: ColorType | Sequence[ColorType] | None = None, + alpha: float | Sequence[float] | None = None, + linestyles: LineStyleType | Sequence[LineStyleType] = "solid", + *, + data=None, + **kwargs, +) -> EventCollection: return gca().eventplot( - positions, orientation=orientation, lineoffsets=lineoffsets, - linelengths=linelengths, linewidths=linewidths, colors=colors, + positions, + orientation=orientation, + lineoffsets=lineoffsets, + linelengths=linelengths, + linewidths=linewidths, + colors=colors, + alpha=alpha, linestyles=linestyles, - **({"data": data} if data is not None else {}), **kwargs) + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.fill) -def fill(*args, data=None, **kwargs): - return gca().fill( - *args, **({"data": data} if data is not None else {}), - **kwargs) +def fill(*args, data=None, **kwargs) -> list[Polygon]: + return gca().fill(*args, **({"data": data} if data is not None else {}), **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.fill_between) def fill_between( - x, y1, y2=0, where=None, interpolate=False, step=None, *, - data=None, **kwargs): + x: ArrayLike, + y1: ArrayLike | float, + y2: ArrayLike | float = 0, + where: Sequence[bool] | None = None, + interpolate: bool = False, + step: Literal["pre", "post", "mid"] | None = None, + *, + data=None, + **kwargs, +) -> FillBetweenPolyCollection: return gca().fill_between( - x, y1, y2=y2, where=where, interpolate=interpolate, step=step, - **({"data": data} if data is not None else {}), **kwargs) + x, + y1, + y2=y2, + where=where, + interpolate=interpolate, + step=step, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.fill_betweenx) def fill_betweenx( - y, x1, x2=0, where=None, step=None, interpolate=False, *, - data=None, **kwargs): + y: ArrayLike, + x1: ArrayLike | float, + x2: ArrayLike | float = 0, + where: Sequence[bool] | None = None, + step: Literal["pre", "post", "mid"] | None = None, + interpolate: bool = False, + *, + data=None, + **kwargs, +) -> FillBetweenPolyCollection: return gca().fill_betweenx( - y, x1, x2=x2, where=where, step=step, interpolate=interpolate, - **({"data": data} if data is not None else {}), **kwargs) + y, + x1, + x2=x2, + where=where, + step=step, + interpolate=interpolate, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.grid) -def grid(b=None, which='major', axis='both', **kwargs): - return gca().grid(b=b, which=which, axis=axis, **kwargs) +def grid( + visible: bool | None = None, + which: Literal["major", "minor", "both"] = "major", + axis: Literal["both", "x", "y"] = "both", + **kwargs, +) -> None: + gca().grid(visible=visible, which=which, axis=axis, **kwargs) + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.grouped_bar) +def grouped_bar( + heights: Sequence[ArrayLike] | dict[str, ArrayLike] | np.ndarray | pd.DataFrame, + *, + positions: ArrayLike | None = None, + group_spacing: float | None = 1.5, + bar_spacing: float | None = 0, + tick_labels: Sequence[str] | None = None, + labels: Sequence[str] | None = None, + orientation: Literal["vertical", "horizontal"] = "vertical", + colors: Iterable[ColorType] | None = None, + **kwargs, +) -> list[BarContainer]: + return gca().grouped_bar( + heights, + positions=positions, + group_spacing=group_spacing, + bar_spacing=bar_spacing, + tick_labels=tick_labels, + labels=labels, + orientation=orientation, + colors=colors, + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hexbin) def hexbin( - x, y, C=None, gridsize=100, bins=None, xscale='linear', - yscale='linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors='face', - reduce_C_function=np.mean, mincnt=None, marginals=False, *, - data=None, **kwargs): + x: ArrayLike, + y: ArrayLike, + C: ArrayLike | None = None, + gridsize: int | tuple[int, int] = 100, + bins: Literal["log"] | int | Sequence[float] | None = None, + xscale: Literal["linear", "log"] = "linear", + yscale: Literal["linear", "log"] = "linear", + extent: tuple[float, float, float, float] | None = None, + cmap: str | Colormap | None = None, + norm: str | Normalize | None = None, + vmin: float | None = None, + vmax: float | None = None, + alpha: float | None = None, + linewidths: float | None = None, + edgecolors: Literal["face", "none"] | ColorType = "face", + reduce_C_function: Callable[[np.ndarray | list[float]], float] = np.mean, + mincnt: int | None = None, + marginals: bool = False, + colorizer: Colorizer | None = None, + *, + data=None, + **kwargs, +) -> PolyCollection: __ret = gca().hexbin( - x, y, C=C, gridsize=gridsize, bins=bins, xscale=xscale, - yscale=yscale, extent=extent, cmap=cmap, norm=norm, vmin=vmin, - vmax=vmax, alpha=alpha, linewidths=linewidths, - edgecolors=edgecolors, reduce_C_function=reduce_C_function, - mincnt=mincnt, marginals=marginals, - **({"data": data} if data is not None else {}), **kwargs) + x, + y, + C=C, + gridsize=gridsize, + bins=bins, + xscale=xscale, + yscale=yscale, + extent=extent, + cmap=cmap, + norm=norm, + vmin=vmin, + vmax=vmax, + alpha=alpha, + linewidths=linewidths, + edgecolors=edgecolors, + reduce_C_function=reduce_C_function, + mincnt=mincnt, + marginals=marginals, + colorizer=colorizer, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret) return __ret @@ -2761,27 +3488,100 @@ def hexbin( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hist) def hist( - x, bins=None, range=None, density=False, weights=None, - cumulative=False, bottom=None, histtype='bar', align='mid', - orientation='vertical', rwidth=None, log=False, color=None, - label=None, stacked=False, *, data=None, **kwargs): + x: ArrayLike | Sequence[ArrayLike], + bins: int | Sequence[float] | str | None = None, + range: tuple[float, float] | None = None, + density: bool = False, + weights: ArrayLike | None = None, + cumulative: bool | float = False, + bottom: ArrayLike | float | None = None, + histtype: Literal["bar", "barstacked", "step", "stepfilled"] = "bar", + align: Literal["left", "mid", "right"] = "mid", + orientation: Literal["vertical", "horizontal"] = "vertical", + rwidth: float | None = None, + log: bool = False, + color: ColorType | Sequence[ColorType] | None = None, + label: str | Sequence[str] | None = None, + stacked: bool = False, + *, + data=None, + **kwargs, +) -> tuple[ + np.ndarray | list[np.ndarray], + np.ndarray, + BarContainer | Polygon | list[BarContainer | Polygon], +]: return gca().hist( - x, bins=bins, range=range, density=density, weights=weights, - cumulative=cumulative, bottom=bottom, histtype=histtype, - align=align, orientation=orientation, rwidth=rwidth, log=log, - color=color, label=label, stacked=stacked, - **({"data": data} if data is not None else {}), **kwargs) + x, + bins=bins, + range=range, + density=density, + weights=weights, + cumulative=cumulative, + bottom=bottom, + histtype=histtype, + align=align, + orientation=orientation, + rwidth=rwidth, + log=log, + color=color, + label=label, + stacked=stacked, + **({"data": data} if data is not None else {}), + **kwargs, + ) + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.stairs) +def stairs( + values: ArrayLike, + edges: ArrayLike | None = None, + *, + orientation: Literal["vertical", "horizontal"] = "vertical", + baseline: float | ArrayLike | None = 0, + fill: bool = False, + data=None, + **kwargs, +) -> StepPatch: + return gca().stairs( + values, + edges=edges, + orientation=orientation, + baseline=baseline, + fill=fill, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hist2d) def hist2d( - x, y, bins=10, range=None, density=False, weights=None, - cmin=None, cmax=None, *, data=None, **kwargs): + x: ArrayLike, + y: ArrayLike, + bins: None | int | tuple[int, int] | ArrayLike | tuple[ArrayLike, ArrayLike] = 10, + range: ArrayLike | None = None, + density: bool = False, + weights: ArrayLike | None = None, + cmin: float | None = None, + cmax: float | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, QuadMesh]: __ret = gca().hist2d( - x, y, bins=bins, range=range, density=density, - weights=weights, cmin=cmin, cmax=cmax, - **({"data": data} if data is not None else {}), **kwargs) + x, + y, + bins=bins, + range=range, + density=density, + weights=weights, + cmin=cmin, + cmax=cmax, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret[-1]) return __ret @@ -2789,88 +3589,171 @@ def hist2d( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hlines) def hlines( - y, xmin, xmax, colors=None, linestyles='solid', label='', *, - data=None, **kwargs): + y: float | ArrayLike, + xmin: float | ArrayLike, + xmax: float | ArrayLike, + colors: ColorType | Sequence[ColorType] | None = None, + linestyles: LineStyleType = "solid", + label: str = "", + *, + data=None, + **kwargs, +) -> LineCollection: return gca().hlines( - y, xmin, xmax, colors=colors, linestyles=linestyles, - label=label, **({"data": data} if data is not None else {}), - **kwargs) + y, + xmin, + xmax, + colors=colors, + linestyles=linestyles, + label=label, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.imshow) def imshow( - X, cmap=None, norm=None, aspect=None, interpolation=None, - alpha=None, vmin=None, vmax=None, origin=None, extent=None, *, - filternorm=True, filterrad=4.0, resample=None, url=None, - data=None, **kwargs): + X: ArrayLike | PIL.Image.Image, + cmap: str | Colormap | None = None, + norm: str | Normalize | None = None, + *, + aspect: Literal["equal", "auto"] | float | None = None, + interpolation: str | None = None, + alpha: float | ArrayLike | None = None, + vmin: float | None = None, + vmax: float | None = None, + colorizer: Colorizer | None = None, + origin: Literal["upper", "lower"] | None = None, + extent: tuple[float, float, float, float] | None = None, + interpolation_stage: Literal["data", "rgba", "auto"] | None = None, + filternorm: bool = True, + filterrad: float = 4.0, + resample: bool | None = None, + url: str | None = None, + data=None, + **kwargs, +) -> AxesImage: __ret = gca().imshow( - X, cmap=cmap, norm=norm, aspect=aspect, - interpolation=interpolation, alpha=alpha, vmin=vmin, - vmax=vmax, origin=origin, extent=extent, - filternorm=filternorm, filterrad=filterrad, resample=resample, - url=url, **({"data": data} if data is not None else {}), - **kwargs) + X, + cmap=cmap, + norm=norm, + aspect=aspect, + interpolation=interpolation, + alpha=alpha, + vmin=vmin, + vmax=vmax, + colorizer=colorizer, + origin=origin, + extent=extent, + interpolation_stage=interpolation_stage, + filternorm=filternorm, + filterrad=filterrad, + resample=resample, + url=url, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.legend) -def legend(*args, **kwargs): +def legend(*args, **kwargs) -> Legend: return gca().legend(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.locator_params) -def locator_params(axis='both', tight=None, **kwargs): - return gca().locator_params(axis=axis, tight=tight, **kwargs) +def locator_params( + axis: Literal["both", "x", "y"] = "both", tight: bool | None = None, **kwargs +) -> None: + gca().locator_params(axis=axis, tight=tight, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.loglog) -def loglog(*args, **kwargs): +def loglog(*args, **kwargs) -> list[Line2D]: return gca().loglog(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.magnitude_spectrum) def magnitude_spectrum( - x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, - scale=None, *, data=None, **kwargs): + x: ArrayLike, + Fs: float | None = None, + Fc: int | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + scale: Literal["default", "linear", "dB"] | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, Line2D]: return gca().magnitude_spectrum( - x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, - scale=scale, **({"data": data} if data is not None else {}), - **kwargs) + x, + Fs=Fs, + Fc=Fc, + window=window, + pad_to=pad_to, + sides=sides, + scale=scale, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.margins) -def margins(*margins, x=None, y=None, tight=True): +def margins( + *margins: float, + x: float | None = None, + y: float | None = None, + tight: bool | None = True, +) -> tuple[float, float] | None: return gca().margins(*margins, x=x, y=y, tight=tight) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.minorticks_off) -def minorticks_off(): - return gca().minorticks_off() +def minorticks_off() -> None: + gca().minorticks_off() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.minorticks_on) -def minorticks_on(): - return gca().minorticks_on() +def minorticks_on() -> None: + gca().minorticks_on() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.pcolor) def pcolor( - *args, shading=None, alpha=None, norm=None, cmap=None, - vmin=None, vmax=None, data=None, **kwargs): + *args: ArrayLike, + shading: Literal["flat", "nearest", "auto"] | None = None, + alpha: float | None = None, + norm: str | Normalize | None = None, + cmap: str | Colormap | None = None, + vmin: float | None = None, + vmax: float | None = None, + colorizer: Colorizer | None = None, + data=None, + **kwargs, +) -> Collection: __ret = gca().pcolor( - *args, shading=shading, alpha=alpha, norm=norm, cmap=cmap, - vmin=vmin, vmax=vmax, - **({"data": data} if data is not None else {}), **kwargs) + *args, + shading=shading, + alpha=alpha, + norm=norm, + cmap=cmap, + vmin=vmin, + vmax=vmax, + colorizer=colorizer, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret) return __ret @@ -2878,13 +3761,31 @@ def pcolor( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.pcolormesh) def pcolormesh( - *args, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, shading=None, antialiased=False, data=None, - **kwargs): + *args: ArrayLike, + alpha: float | None = None, + norm: str | Normalize | None = None, + cmap: str | Colormap | None = None, + vmin: float | None = None, + vmax: float | None = None, + colorizer: Colorizer | None = None, + shading: Literal["flat", "nearest", "gouraud", "auto"] | None = None, + antialiased: bool = False, + data=None, + **kwargs, +) -> QuadMesh: __ret = gca().pcolormesh( - *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, - vmax=vmax, shading=shading, antialiased=antialiased, - **({"data": data} if data is not None else {}), **kwargs) + *args, + alpha=alpha, + norm=norm, + cmap=cmap, + vmin=vmin, + vmax=vmax, + colorizer=colorizer, + shading=shading, + antialiased=antialiased, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret) return __ret @@ -2892,119 +3793,248 @@ def pcolormesh( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.phase_spectrum) def phase_spectrum( - x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, *, - data=None, **kwargs): + x: ArrayLike, + Fs: float | None = None, + Fc: int | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, Line2D]: return gca().phase_spectrum( - x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, - **({"data": data} if data is not None else {}), **kwargs) + x, + Fs=Fs, + Fc=Fc, + window=window, + pad_to=pad_to, + sides=sides, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.pie) def pie( - x, explode=None, labels=None, colors=None, autopct=None, - pctdistance=0.6, shadow=False, labeldistance=1.1, - startangle=0, radius=1, counterclock=True, wedgeprops=None, - textprops=None, center=(0, 0), frame=False, - rotatelabels=False, *, normalize=None, data=None): + x: ArrayLike, + explode: ArrayLike | None = None, + labels: Sequence[str] | None = None, + colors: ColorType | Sequence[ColorType] | None = None, + autopct: str | Callable[[float], str] | None = None, + pctdistance: float = 0.6, + shadow: bool = False, + labeldistance: float | None = 1.1, + startangle: float = 0, + radius: float = 1, + counterclock: bool = True, + wedgeprops: dict[str, Any] | None = None, + textprops: dict[str, Any] | None = None, + center: tuple[float, float] = (0, 0), + frame: bool = False, + rotatelabels: bool = False, + *, + normalize: bool = True, + hatch: str | Sequence[str] | None = None, + data=None, +) -> tuple[list[Wedge], list[Text]] | tuple[list[Wedge], list[Text], list[Text]]: return gca().pie( - x, explode=explode, labels=labels, colors=colors, - autopct=autopct, pctdistance=pctdistance, shadow=shadow, - labeldistance=labeldistance, startangle=startangle, - radius=radius, counterclock=counterclock, - wedgeprops=wedgeprops, textprops=textprops, center=center, - frame=frame, rotatelabels=rotatelabels, normalize=normalize, - **({"data": data} if data is not None else {})) + x, + explode=explode, + labels=labels, + colors=colors, + autopct=autopct, + pctdistance=pctdistance, + shadow=shadow, + labeldistance=labeldistance, + startangle=startangle, + radius=radius, + counterclock=counterclock, + wedgeprops=wedgeprops, + textprops=textprops, + center=center, + frame=frame, + rotatelabels=rotatelabels, + normalize=normalize, + hatch=hatch, + **({"data": data} if data is not None else {}), + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.plot) -def plot(*args, scalex=True, scaley=True, data=None, **kwargs): +def plot( + *args: float | ArrayLike | str, + scalex: bool = True, + scaley: bool = True, + data=None, + **kwargs, +) -> list[Line2D]: return gca().plot( - *args, scalex=scalex, scaley=scaley, - **({"data": data} if data is not None else {}), **kwargs) - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_copy_docstring_and_deprecators(Axes.plot_date) -def plot_date( - x, y, fmt='o', tz=None, xdate=True, ydate=False, *, - data=None, **kwargs): - return gca().plot_date( - x, y, fmt=fmt, tz=tz, xdate=xdate, ydate=ydate, - **({"data": data} if data is not None else {}), **kwargs) + *args, + scalex=scalex, + scaley=scaley, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.psd) def psd( - x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, - noverlap=None, pad_to=None, sides=None, scale_by_freq=None, - return_line=None, *, data=None, **kwargs): + x: ArrayLike, + NFFT: int | None = None, + Fs: float | None = None, + Fc: int | None = None, + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike], ArrayLike] + | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + noverlap: int | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + scale_by_freq: bool | None = None, + return_line: bool | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, Line2D]: return gca().psd( - x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, - noverlap=noverlap, pad_to=pad_to, sides=sides, - scale_by_freq=scale_by_freq, return_line=return_line, - **({"data": data} if data is not None else {}), **kwargs) + x, + NFFT=NFFT, + Fs=Fs, + Fc=Fc, + detrend=detrend, + window=window, + noverlap=noverlap, + pad_to=pad_to, + sides=sides, + scale_by_freq=scale_by_freq, + return_line=return_line, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.quiver) -def quiver(*args, data=None, **kw): +def quiver(*args, data=None, **kwargs) -> Quiver: __ret = gca().quiver( - *args, **({"data": data} if data is not None else {}), **kw) + *args, **({"data": data} if data is not None else {}), **kwargs + ) sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.quiverkey) -def quiverkey(Q, X, Y, U, label, **kw): - return gca().quiverkey(Q, X, Y, U, label, **kw) +def quiverkey( + Q: Quiver, X: float, Y: float, U: float, label: str, **kwargs +) -> QuiverKey: + return gca().quiverkey(Q, X, Y, U, label, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.scatter) def scatter( - x, y, s=None, c=None, marker=None, cmap=None, norm=None, - vmin=None, vmax=None, alpha=None, linewidths=None, - verts=cbook.deprecation._deprecated_parameter, - edgecolors=None, *, plotnonfinite=False, data=None, **kwargs): + x: float | ArrayLike, + y: float | ArrayLike, + s: float | ArrayLike | None = None, + c: ArrayLike | Sequence[ColorType] | ColorType | None = None, + marker: MarkerType | None = None, + cmap: str | Colormap | None = None, + norm: str | Normalize | None = None, + vmin: float | None = None, + vmax: float | None = None, + alpha: float | None = None, + linewidths: float | Sequence[float] | None = None, + *, + edgecolors: Literal["face", "none"] | ColorType | Sequence[ColorType] | None = None, + colorizer: Colorizer | None = None, + plotnonfinite: bool = False, + data=None, + **kwargs, +) -> PathCollection: __ret = gca().scatter( - x, y, s=s, c=c, marker=marker, cmap=cmap, norm=norm, - vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, - verts=verts, edgecolors=edgecolors, + x, + y, + s=s, + c=c, + marker=marker, + cmap=cmap, + norm=norm, + vmin=vmin, + vmax=vmax, + alpha=alpha, + linewidths=linewidths, + edgecolors=edgecolors, + colorizer=colorizer, plotnonfinite=plotnonfinite, - **({"data": data} if data is not None else {}), **kwargs) + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.semilogx) -def semilogx(*args, **kwargs): +def semilogx(*args, **kwargs) -> list[Line2D]: return gca().semilogx(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.semilogy) -def semilogy(*args, **kwargs): +def semilogy(*args, **kwargs) -> list[Line2D]: return gca().semilogy(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.specgram) def specgram( - x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, - noverlap=None, cmap=None, xextent=None, pad_to=None, - sides=None, scale_by_freq=None, mode=None, scale=None, - vmin=None, vmax=None, *, data=None, **kwargs): + x: ArrayLike, + NFFT: int | None = None, + Fs: float | None = None, + Fc: int | None = None, + detrend: Literal["none", "mean", "linear"] + | Callable[[ArrayLike], ArrayLike] + | None = None, + window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None, + noverlap: int | None = None, + cmap: str | Colormap | None = None, + xextent: tuple[float, float] | None = None, + pad_to: int | None = None, + sides: Literal["default", "onesided", "twosided"] | None = None, + scale_by_freq: bool | None = None, + mode: Literal["default", "psd", "magnitude", "angle", "phase"] | None = None, + scale: Literal["default", "linear", "dB"] | None = None, + vmin: float | None = None, + vmax: float | None = None, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, AxesImage]: __ret = gca().specgram( - x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, - noverlap=noverlap, cmap=cmap, xextent=xextent, pad_to=pad_to, - sides=sides, scale_by_freq=scale_by_freq, mode=mode, - scale=scale, vmin=vmin, vmax=vmax, - **({"data": data} if data is not None else {}), **kwargs) + x, + NFFT=NFFT, + Fs=Fs, + Fc=Fc, + detrend=detrend, + window=window, + noverlap=noverlap, + cmap=cmap, + xextent=xextent, + pad_to=pad_to, + sides=sides, + scale_by_freq=scale_by_freq, + mode=mode, + scale=scale, + vmin=vmin, + vmax=vmax, + **({"data": data} if data is not None else {}), + **kwargs, + ) sci(__ret[-1]) return __ret @@ -3012,62 +4042,140 @@ def specgram( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.spy) def spy( - Z, precision=0, marker=None, markersize=None, aspect='equal', - origin='upper', **kwargs): + Z: ArrayLike, + precision: float | Literal["present"] = 0, + marker: str | None = None, + markersize: float | None = None, + aspect: Literal["equal", "auto"] | float | None = "equal", + origin: Literal["upper", "lower"] = "upper", + **kwargs, +) -> AxesImage: __ret = gca().spy( - Z, precision=precision, marker=marker, markersize=markersize, - aspect=aspect, origin=origin, **kwargs) - if isinstance(__ret, cm.ScalarMappable): sci(__ret) # noqa + Z, + precision=precision, + marker=marker, + markersize=markersize, + aspect=aspect, + origin=origin, + **kwargs, + ) + if isinstance(__ret, _ColorizerInterface): + sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.stackplot) def stackplot( - x, *args, labels=(), colors=None, baseline='zero', data=None, - **kwargs): + x, *args, labels=(), colors=None, hatch=None, baseline="zero", data=None, **kwargs +): return gca().stackplot( - x, *args, labels=labels, colors=colors, baseline=baseline, - **({"data": data} if data is not None else {}), **kwargs) + x, + *args, + labels=labels, + colors=colors, + hatch=hatch, + baseline=baseline, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.stem) def stem( - *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, - label=None, use_line_collection=True, orientation='vertical', - data=None): + *args: ArrayLike | str, + linefmt: str | None = None, + markerfmt: str | None = None, + basefmt: str | None = None, + bottom: float = 0, + label: str | None = None, + orientation: Literal["vertical", "horizontal"] = "vertical", + data=None, +) -> StemContainer: return gca().stem( - *args, linefmt=linefmt, markerfmt=markerfmt, basefmt=basefmt, - bottom=bottom, label=label, - use_line_collection=use_line_collection, + *args, + linefmt=linefmt, + markerfmt=markerfmt, + basefmt=basefmt, + bottom=bottom, + label=label, orientation=orientation, - **({"data": data} if data is not None else {})) + **({"data": data} if data is not None else {}), + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.step) -def step(x, y, *args, where='pre', data=None, **kwargs): +def step( + x: ArrayLike, + y: ArrayLike, + *args, + where: Literal["pre", "post", "mid"] = "pre", + data=None, + **kwargs, +) -> list[Line2D]: return gca().step( - x, y, *args, where=where, - **({"data": data} if data is not None else {}), **kwargs) + x, + y, + *args, + where=where, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.streamplot) def streamplot( - x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, - transform=None, zorder=None, start_points=None, maxlength=4.0, - integration_direction='both', *, data=None): + x, + y, + u, + v, + density=1, + linewidth=None, + color=None, + cmap=None, + norm=None, + arrowsize=1, + arrowstyle="-|>", + minlength=0.1, + transform=None, + zorder=None, + start_points=None, + maxlength=4.0, + integration_direction="both", + broken_streamlines=True, + *, + integration_max_step_scale=1.0, + integration_max_error_scale=1.0, + num_arrows=1, + data=None, +): __ret = gca().streamplot( - x, y, u, v, density=density, linewidth=linewidth, color=color, - cmap=cmap, norm=norm, arrowsize=arrowsize, - arrowstyle=arrowstyle, minlength=minlength, - transform=transform, zorder=zorder, start_points=start_points, + x, + y, + u, + v, + density=density, + linewidth=linewidth, + color=color, + cmap=cmap, + norm=norm, + arrowsize=arrowsize, + arrowstyle=arrowstyle, + minlength=minlength, + transform=transform, + zorder=zorder, + start_points=start_points, maxlength=maxlength, integration_direction=integration_direction, - **({"data": data} if data is not None else {})) + broken_streamlines=broken_streamlines, + integration_max_step_scale=integration_max_step_scale, + integration_max_error_scale=integration_max_error_scale, + num_arrows=num_arrows, + **({"data": data} if data is not None else {}), + ) sci(__ret.lines) return __ret @@ -3075,47 +4183,80 @@ def streamplot( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.table) def table( - cellText=None, cellColours=None, cellLoc='right', - colWidths=None, rowLabels=None, rowColours=None, - rowLoc='left', colLabels=None, colColours=None, - colLoc='center', loc='bottom', bbox=None, edges='closed', - **kwargs): + cellText=None, + cellColours=None, + cellLoc="right", + colWidths=None, + rowLabels=None, + rowColours=None, + rowLoc="left", + colLabels=None, + colColours=None, + colLoc="center", + loc="bottom", + bbox=None, + edges="closed", + **kwargs, +): return gca().table( - cellText=cellText, cellColours=cellColours, cellLoc=cellLoc, - colWidths=colWidths, rowLabels=rowLabels, - rowColours=rowColours, rowLoc=rowLoc, colLabels=colLabels, - colColours=colColours, colLoc=colLoc, loc=loc, bbox=bbox, - edges=edges, **kwargs) + cellText=cellText, + cellColours=cellColours, + cellLoc=cellLoc, + colWidths=colWidths, + rowLabels=rowLabels, + rowColours=rowColours, + rowLoc=rowLoc, + colLabels=colLabels, + colColours=colColours, + colLoc=colLoc, + loc=loc, + bbox=bbox, + edges=edges, + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.text) -def text(x, y, s, fontdict=None, **kwargs): +def text( + x: float, y: float, s: str, fontdict: dict[str, Any] | None = None, **kwargs +) -> Text: return gca().text(x, y, s, fontdict=fontdict, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.tick_params) -def tick_params(axis='both', **kwargs): - return gca().tick_params(axis=axis, **kwargs) +def tick_params(axis: Literal["both", "x", "y"] = "both", **kwargs) -> None: + gca().tick_params(axis=axis, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.ticklabel_format) def ticklabel_format( - *, axis='both', style='', scilimits=None, useOffset=None, - useLocale=None, useMathText=None): - return gca().ticklabel_format( - axis=axis, style=style, scilimits=scilimits, - useOffset=useOffset, useLocale=useLocale, - useMathText=useMathText) + *, + axis: Literal["both", "x", "y"] = "both", + style: Literal["", "sci", "scientific", "plain"] | None = None, + scilimits: tuple[int, int] | None = None, + useOffset: bool | float | None = None, + useLocale: bool | None = None, + useMathText: bool | None = None, +) -> None: + gca().ticklabel_format( + axis=axis, + style=style, + scilimits=scilimits, + useOffset=useOffset, + useLocale=useLocale, + useMathText=useMathText, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.tricontour) def tricontour(*args, **kwargs): __ret = gca().tricontour(*args, **kwargs) - if __ret._A is not None: sci(__ret) # noqa + if __ret._A is not None: # type: ignore[attr-defined] + sci(__ret) return __ret @@ -3123,18 +4264,35 @@ def tricontour(*args, **kwargs): @_copy_docstring_and_deprecators(Axes.tricontourf) def tricontourf(*args, **kwargs): __ret = gca().tricontourf(*args, **kwargs) - if __ret._A is not None: sci(__ret) # noqa + if __ret._A is not None: # type: ignore[attr-defined] + sci(__ret) return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.tripcolor) def tripcolor( - *args, alpha=1.0, norm=None, cmap=None, vmin=None, vmax=None, - shading='flat', facecolors=None, **kwargs): + *args, + alpha=1.0, + norm=None, + cmap=None, + vmin=None, + vmax=None, + shading="flat", + facecolors=None, + **kwargs, +): __ret = gca().tripcolor( - *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, - vmax=vmax, shading=shading, facecolors=facecolors, **kwargs) + *args, + alpha=alpha, + norm=norm, + cmap=cmap, + vmin=vmin, + vmax=vmax, + shading=shading, + facecolors=facecolors, + **kwargs, + ) sci(__ret) return __ret @@ -3148,100 +4306,361 @@ def triplot(*args, **kwargs): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.violinplot) def violinplot( - dataset, positions=None, vert=True, widths=0.5, - showmeans=False, showextrema=True, showmedians=False, - quantiles=None, points=100, bw_method=None, *, data=None): + dataset: ArrayLike | Sequence[ArrayLike], + positions: ArrayLike | None = None, + vert: bool | None = None, + orientation: Literal["vertical", "horizontal"] = "vertical", + widths: float | ArrayLike = 0.5, + showmeans: bool = False, + showextrema: bool = True, + showmedians: bool = False, + quantiles: Sequence[float | Sequence[float]] | None = None, + points: int = 100, + bw_method: Literal["scott", "silverman"] + | float + | Callable[[GaussianKDE], float] + | None = None, + side: Literal["both", "low", "high"] = "both", + facecolor: Sequence[ColorType] | ColorType | None = None, + linecolor: Sequence[ColorType] | ColorType | None = None, + *, + data=None, +) -> dict[str, Collection]: return gca().violinplot( - dataset, positions=positions, vert=vert, widths=widths, - showmeans=showmeans, showextrema=showextrema, - showmedians=showmedians, quantiles=quantiles, points=points, + dataset, + positions=positions, + vert=vert, + orientation=orientation, + widths=widths, + showmeans=showmeans, + showextrema=showextrema, + showmedians=showmedians, + quantiles=quantiles, + points=points, bw_method=bw_method, - **({"data": data} if data is not None else {})) + side=side, + facecolor=facecolor, + linecolor=linecolor, + **({"data": data} if data is not None else {}), + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.vlines) def vlines( - x, ymin, ymax, colors=None, linestyles='solid', label='', *, - data=None, **kwargs): + x: float | ArrayLike, + ymin: float | ArrayLike, + ymax: float | ArrayLike, + colors: ColorType | Sequence[ColorType] | None = None, + linestyles: LineStyleType = "solid", + label: str = "", + *, + data=None, + **kwargs, +) -> LineCollection: return gca().vlines( - x, ymin, ymax, colors=colors, linestyles=linestyles, - label=label, **({"data": data} if data is not None else {}), - **kwargs) + x, + ymin, + ymax, + colors=colors, + linestyles=linestyles, + label=label, + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.xcorr) def xcorr( - x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, - maxlags=10, *, data=None, **kwargs): + x: ArrayLike, + y: ArrayLike, + normed: bool = True, + detrend: Callable[[ArrayLike], ArrayLike] = mlab.detrend_none, + usevlines: bool = True, + maxlags: int = 10, + *, + data=None, + **kwargs, +) -> tuple[np.ndarray, np.ndarray, LineCollection | Line2D, Line2D | None]: return gca().xcorr( - x, y, normed=normed, detrend=detrend, usevlines=usevlines, + x, + y, + normed=normed, + detrend=detrend, + usevlines=usevlines, maxlags=maxlags, - **({"data": data} if data is not None else {}), **kwargs) + **({"data": data} if data is not None else {}), + **kwargs, + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes._sci) -def sci(im): - return gca()._sci(im) +def sci(im: ColorizingArtist) -> None: + gca()._sci(im) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.set_title) -def title(label, fontdict=None, loc=None, pad=None, *, y=None, **kwargs): - return gca().set_title( - label, fontdict=fontdict, loc=loc, pad=pad, y=y, **kwargs) +def title( + label: str, + fontdict: dict[str, Any] | None = None, + loc: Literal["left", "center", "right"] | None = None, + pad: float | None = None, + *, + y: float | None = None, + **kwargs, +) -> Text: + return gca().set_title(label, fontdict=fontdict, loc=loc, pad=pad, y=y, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.set_xlabel) -def xlabel(xlabel, fontdict=None, labelpad=None, *, loc=None, **kwargs): +def xlabel( + xlabel: str, + fontdict: dict[str, Any] | None = None, + labelpad: float | None = None, + *, + loc: Literal["left", "center", "right"] | None = None, + **kwargs, +) -> Text: return gca().set_xlabel( - xlabel, fontdict=fontdict, labelpad=labelpad, loc=loc, - **kwargs) + xlabel, fontdict=fontdict, labelpad=labelpad, loc=loc, **kwargs + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.set_ylabel) -def ylabel(ylabel, fontdict=None, labelpad=None, *, loc=None, **kwargs): +def ylabel( + ylabel: str, + fontdict: dict[str, Any] | None = None, + labelpad: float | None = None, + *, + loc: Literal["bottom", "center", "top"] | None = None, + **kwargs, +) -> Text: return gca().set_ylabel( - ylabel, fontdict=fontdict, labelpad=labelpad, loc=loc, - **kwargs) + ylabel, fontdict=fontdict, labelpad=labelpad, loc=loc, **kwargs + ) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.set_xscale) -def xscale(value, **kwargs): - return gca().set_xscale(value, **kwargs) +def xscale(value: str | ScaleBase, **kwargs) -> None: + gca().set_xscale(value, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.set_yscale) -def yscale(value, **kwargs): - return gca().set_yscale(value, **kwargs) - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def autumn(): set_cmap('autumn') -def bone(): set_cmap('bone') -def cool(): set_cmap('cool') -def copper(): set_cmap('copper') -def flag(): set_cmap('flag') -def gray(): set_cmap('gray') -def hot(): set_cmap('hot') -def hsv(): set_cmap('hsv') -def jet(): set_cmap('jet') -def pink(): set_cmap('pink') -def prism(): set_cmap('prism') -def spring(): set_cmap('spring') -def summer(): set_cmap('summer') -def winter(): set_cmap('winter') -def magma(): set_cmap('magma') -def inferno(): set_cmap('inferno') -def plasma(): set_cmap('plasma') -def viridis(): set_cmap('viridis') -def nipy_spectral(): set_cmap('nipy_spectral') - - -_setup_pyplot_info_docstrings() +def yscale(value: str | ScaleBase, **kwargs) -> None: + gca().set_yscale(value, **kwargs) + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def autumn() -> None: + """ + Set the colormap to 'autumn'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("autumn") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def bone() -> None: + """ + Set the colormap to 'bone'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("bone") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def cool() -> None: + """ + Set the colormap to 'cool'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("cool") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def copper() -> None: + """ + Set the colormap to 'copper'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("copper") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def flag() -> None: + """ + Set the colormap to 'flag'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("flag") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def gray() -> None: + """ + Set the colormap to 'gray'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("gray") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def hot() -> None: + """ + Set the colormap to 'hot'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("hot") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def hsv() -> None: + """ + Set the colormap to 'hsv'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("hsv") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def jet() -> None: + """ + Set the colormap to 'jet'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("jet") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def pink() -> None: + """ + Set the colormap to 'pink'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("pink") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def prism() -> None: + """ + Set the colormap to 'prism'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("prism") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def spring() -> None: + """ + Set the colormap to 'spring'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("spring") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def summer() -> None: + """ + Set the colormap to 'summer'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("summer") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def winter() -> None: + """ + Set the colormap to 'winter'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("winter") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def magma() -> None: + """ + Set the colormap to 'magma'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("magma") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def inferno() -> None: + """ + Set the colormap to 'inferno'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("inferno") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def plasma() -> None: + """ + Set the colormap to 'plasma'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("plasma") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def viridis() -> None: + """ + Set the colormap to 'viridis'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("viridis") + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def nipy_spectral() -> None: + """ + Set the colormap to 'nipy_spectral'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap("nipy_spectral") diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index f7119d3a95e7..91c510ca7060 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -15,12 +15,11 @@ """ import math -import weakref import numpy as np from numpy import ma -from matplotlib import cbook, docstring, font_manager +from matplotlib import _api, cbook, _docstring import matplotlib.artist as martist import matplotlib.collections as mcollections from matplotlib.patches import CirclePolygon @@ -33,29 +32,29 @@ Call signature:: - quiver([X, Y], U, V, [C], **kw) + quiver([X, Y], U, V, [C], /, **kwargs) *X*, *Y* define the arrow locations, *U*, *V* define the arrow directions, and -*C* optionally sets the color. +*C* optionally sets the color. The arguments *X*, *Y*, *U*, *V*, *C* are +positional-only. -**Arrow size** +**Arrow length** The default settings auto-scales the length of the arrows to a reasonable size. To change this behavior see the *scale* and *scale_units* parameters. **Arrow shape** -The defaults give a slightly swept-back arrow; to make the head a -triangle, make *headaxislength* the same as *headlength*. To make the -arrow more pointed, reduce *headwidth* or increase *headlength* and -*headaxislength*. To make the head smaller relative to the shaft, -scale down all the head parameters. You will probably do best to leave -minshaft alone. +The arrow shape is determined by *width*, *headwidth*, *headlength* and +*headaxislength*. See the notes below. -**Arrow outline** +**Arrow styling** + +Each arrow is internally represented by a filled polygon with a default edge +linewidth of 0. As a result, an arrow is rather a filled area, not a line with +a head, and `.PolyCollection` properties like *linewidth*, *edgecolor*, +*facecolor*, etc. act accordingly. -*linewidths* and *edgecolors* can be used to customize the arrow -outlines. Parameters ---------- @@ -70,11 +69,12 @@ must match the column and row dimensions of *U* and *V*. U, V : 1D or 2D array-like - The x and y direction components of the arrow vectors. + The x and y direction components of the arrow vectors. The interpretation + of these components (in data or in screen space) depends on *angles*. - They must have the same number of elements, matching the number of arrow - locations. *U* and *V* may be masked. Only locations unmasked in - *U*, *V*, and *C* will be drawn. + *U* and *V* must have the same number of elements, matching the number of + arrow locations in *X*, *Y*. *U* and *V* may be masked. Locations masked + in any of *U*, *V*, and *C* will not be drawn. C : 1D or 2D array-like, optional Numeric data that defines the arrow colors by colormapping via *norm* and @@ -84,80 +84,134 @@ use *color* instead. The size of *C* must match the number of arrow locations. -units : {'width', 'height', 'dots', 'inches', 'x', 'y' 'xy'}, default: 'width' - The arrow dimensions (except for *length*) are measured in multiples of - this unit. - - The following values are supported: - - - 'width', 'height': The width or height of the axis. - - 'dots', 'inches': Pixels or inches based on the figure dpi. - - 'x', 'y', 'xy': *X*, *Y* or :math:`\\sqrt{X^2 + Y^2}` in data units. - - The arrows scale differently depending on the units. For - 'x' or 'y', the arrows get larger as one zooms in; for other - units, the arrow size is independent of the zoom state. For - 'width or 'height', the arrow size increases with the width and - height of the axes, respectively, when the window is resized; - for 'dots' or 'inches', resizing does not change the arrows. - angles : {'uv', 'xy'} or array-like, default: 'uv' Method for determining the angle of the arrows. - - 'uv': The arrow axis aspect ratio is 1 so that - if *U* == *V* the orientation of the arrow on the plot is 45 degrees - counter-clockwise from the horizontal axis (positive to the right). + - 'uv': Arrow directions are based on + :ref:`display coordinates `; i.e. a 45° angle will + always show up as diagonal on the screen, irrespective of figure or Axes + aspect ratio or Axes data ranges. This is useful when the arrows represent + a quantity whose direction is not tied to the x and y data coordinates. - Use this if the arrows symbolize a quantity that is not based on - *X*, *Y* data coordinates. + If *U* == *V* the orientation of the arrow on the plot is 45 degrees + counter-clockwise from the horizontal axis (positive to the right). - - 'xy': Arrows point from (x, y) to (x+u, y+v). - Use this for plotting a gradient field, for example. + - 'xy': Arrow direction in data coordinates, i.e. the arrows point from + (x, y) to (x+u, y+v). This is ideal for vector fields or gradient plots + where the arrows should directly represent movements or gradients in the + x and y directions. - - Alternatively, arbitrary angles may be specified explicitly as an array - of values in degrees, counter-clockwise from the horizontal axis. + - Arbitrary angles may be specified explicitly as an array of values + in degrees, counter-clockwise from the horizontal axis. In this case *U*, *V* is only used to determine the length of the arrows. + For example, ``angles=[30, 60, 90]`` will orient the arrows at 30, 60, and 90 + degrees respectively, regardless of the *U* and *V* components. + Note: inverting a data axis will correspondingly invert the arrows only with ``angles='xy'``. +pivot : {'tail', 'mid', 'middle', 'tip'}, default: 'tail' + The part of the arrow that is anchored to the *X*, *Y* grid. The arrow + rotates about this point. + + 'mid' is a synonym for 'middle'. + scale : float, optional - Number of data units per arrow length unit, e.g., m/s per plot width; a - smaller scale parameter makes the arrow longer. Default is *None*. + Scales the length of the arrow inversely. + + Number of data values represented by one unit of arrow length on the plot. + For example, if the data represents velocity in meters per second (m/s), the + scale parameter determines how many meters per second correspond to one unit of + arrow length relative to the width of the plot. + Smaller scale parameter makes the arrow longer. + + By default, an autoscaling algorithm is used to scale the arrow length to a + reasonable size, which is based on the average vector length and the number of + vectors. + + The arrow length unit is given by the *scale_units* parameter. + +scale_units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, default: 'width' + + The physical image unit, which is used for rendering the scaled arrow data *U*, *V*. + + The rendered arrow length is given by + + length in x direction = $\\frac{u}{\\mathrm{scale}} \\mathrm{scale_unit}$ - If *None*, a simple autoscaling algorithm is used, based on the average - vector length and the number of vectors. The arrow length unit is given by - the *scale_units* parameter. + length in y direction = $\\frac{v}{\\mathrm{scale}} \\mathrm{scale_unit}$ -scale_units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, optional - If the *scale* kwarg is *None*, the arrow length unit. Default is *None*. + For example, ``(u, v) = (0.5, 0)`` with ``scale=10, scale_unit="width"`` results + in a horizontal arrow with a length of *0.5 / 10 * "width"*, i.e. 0.05 times the + Axes width. - e.g. *scale_units* is 'inches', *scale* is 2.0, and ``(u, v) = (1, 0)``, - then the vector will be 0.5 inches long. + Supported values are: - If *scale_units* is 'width' or 'height', then the vector will be half the - width/height of the axes. + - 'width' or 'height': The arrow length is scaled relative to the width or height + of the Axes. + For example, ``scale_units='width', scale=1.0``, will result in an arrow length + of width of the Axes. - If *scale_units* is 'x' then the vector will be 0.5 x-axis - units. To plot vectors in the x-y plane, with u and v having - the same units as x and y, use - ``angles='xy', scale_units='xy', scale=1``. + - 'dots': The arrow length of the arrows is in measured in display dots (pixels). + + - 'inches': Arrow lengths are scaled based on the DPI (dots per inch) of the figure. + This ensures that the arrows have a consistent physical size on the figure, + in inches, regardless of data values or plot scaling. + For example, ``(u, v) = (1, 0)`` with ``scale_units='inches', scale=2`` results + in a 0.5 inch-long arrow. + + - 'x' or 'y': The arrow length is scaled relative to the x or y axis units. + For example, ``(u, v) = (0, 1)`` with ``scale_units='x', scale=1`` results + in a vertical arrow with the length of 1 x-axis unit. + + - 'xy': Arrow length will be same as 'x' or 'y' units. + This is useful for creating vectors in the x-y plane where u and v have + the same units as x and y. To plot vectors in the x-y plane with u and v having + the same units as x and y, use ``angles='xy', scale_units='xy', scale=1``. + + Note: Setting *scale_units* without setting scale does not have any effect because + the scale units only differ by a constant factor and that is rescaled through + autoscaling. + +units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, default: 'width' + Affects the arrow size (except for the length). In particular, the shaft + *width* is measured in multiples of this unit. + + Supported values are: + + - 'width', 'height': The width or height of the Axes. + - 'dots', 'inches': Pixels or inches based on the figure dpi. + - 'x', 'y', 'xy': *X*, *Y* or :math:`\\sqrt{X^2 + Y^2}` in data units. + + The following table summarizes how these values affect the visible arrow + size under zooming and figure size changes: + + ================= ================= ================== + units zoom figure size change + ================= ================= ================== + 'x', 'y', 'xy' arrow size scales — + 'width', 'height' — arrow size scales + 'dots', 'inches' — — + ================= ================= ================== width : float, optional - Shaft width in arrow units; default depends on choice of units, - above, and number of vectors; a typical starting value is about - 0.005 times the width of the plot. + Shaft width in arrow units. All head parameters are relative to *width*. + + The default depends on choice of *units* above, and number of vectors; + a typical starting value is about 0.005 times the width of the plot. headwidth : float, default: 3 - Head width as multiple of shaft width. + Head width as multiple of shaft *width*. See the notes below. headlength : float, default: 5 - Head length as multiple of shaft width. + Head length as multiple of shaft *width*. See the notes below. headaxislength : float, default: 4.5 - Head length at shaft intersection. + Head length at shaft intersection as multiple of shaft *width*. + See the notes below. minshaft : float, default: 1 Length below which arrow scales, in units of head length. Do not @@ -167,29 +221,57 @@ Minimum length as a multiple of shaft width; if an arrow length is less than this, plot a dot (hexagon) of this diameter instead. -pivot : {'tail', 'mid', 'middle', 'tip'}, default: 'tail' - The part of the arrow that is anchored to the *X*, *Y* grid. The arrow - rotates about this point. - - 'mid' is a synonym for 'middle'. - -color : color or color sequence, optional +color : :mpltype:`color` or list :mpltype:`color`, optional Explicit color(s) for the arrows. If *C* has been set, *color* has no effect. - This is a synonym for the `~.PolyCollection` *facecolor* parameter. + This is a synonym for the `.PolyCollection` *facecolor* parameter. Other Parameters ---------------- +data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs : `~matplotlib.collections.PolyCollection` properties, optional All other keyword arguments are passed on to `.PolyCollection`: - %(PolyCollection)s + %(PolyCollection:kwdoc)s + +Returns +------- +`~matplotlib.quiver.Quiver` See Also -------- .Axes.quiverkey : Add a key to a quiver plot. -""" % docstring.interpd.params + +Notes +----- + +**Arrow shape** + +The arrow is drawn as a polygon using the nodes as shown below. The values +*headwidth*, *headlength*, and *headaxislength* are in units of *width*. + +.. image:: /_static/quiver_sizes.svg + :width: 500px + +The defaults give a slightly swept-back arrow. Here are some guidelines how to +get other head shapes: + +- To make the head a triangle, make *headaxislength* the same as *headlength*. +- To make the arrow more pointed, reduce *headwidth* or increase *headlength* + and *headaxislength*. +- To make the head smaller relative to the shaft, scale down all the head + parameters proportionally. +- To remove the head completely, set all *head* parameters to 0. +- To get a diamond-shaped head, make *headaxislength* larger than *headlength*. +- Warning: For *headaxislength* < (*headlength* / *headwidth*), the "headaxis" + nodes (i.e. the ones connecting the head with the shaft) will protrude out + of the head in forward direction so that the arrow head looks broken. +""" % _docstring.interpd.params + +_docstring.interpd.register(quiver_doc=_quiver_doc) class QuiverKey(martist.Artist): @@ -201,7 +283,7 @@ class QuiverKey(martist.Artist): def __init__(self, Q, X, Y, U, label, *, angle=0, coordinates='axes', color=None, labelsep=0.1, labelpos='N', labelcolor=None, fontproperties=None, - **kw): + zorder=None, **kwargs): """ Add a key to a quiver plot. @@ -214,7 +296,7 @@ def __init__(self, Q, X, Y, U, label, Parameters ---------- - Q : `matplotlib.quiver.Quiver` + Q : `~matplotlib.quiver.Quiver` A `.Quiver` object as returned by a call to `~.Axes.quiver()`. X, Y : float The location of the key. @@ -224,7 +306,7 @@ def __init__(self, Q, X, Y, U, label, The key label (e.g., length and units of the key). angle : float, default: 0 The angle of the key arrow, in degrees anti-clockwise from the - x-axis. + horizontal axis. coordinates : {'axes', 'figure', 'data', 'inches'}, default: 'axes' Coordinate system and units for *X*, *Y*: 'axes' and 'figure' are normalized coordinate systems with (0, 0) in the lower left and @@ -232,19 +314,21 @@ def __init__(self, Q, X, Y, U, label, (used for the locations of the vectors in the quiver plot itself); 'inches' is position in the figure in inches, with (0, 0) at the lower left corner. - color : color + color : :mpltype:`color` Overrides face and edge colors from *Q*. labelpos : {'N', 'S', 'E', 'W'} Position the label above, below, to the right, to the left of the arrow, respectively. labelsep : float, default: 0.1 Distance in inches between the arrow and the label. - labelcolor : color, default: :rc:`text.color` + labelcolor : :mpltype:`color`, default: :rc:`text.color` Label color. fontproperties : dict, optional A dictionary with keyword arguments accepted by the `~matplotlib.font_manager.FontProperties` initializer: *family*, *style*, *variant*, *size*, *weight*. + zorder : float + The zorder of the key. The default is 0.1 above *Q*. **kwargs Any additional keyword arguments are used to override vector properties taken from *Q*. @@ -259,48 +343,28 @@ def __init__(self, Q, X, Y, U, label, self.color = color self.label = label self._labelsep_inches = labelsep - self.labelsep = (self._labelsep_inches * Q.axes.figure.dpi) - - # try to prevent closure over the real self - weak_self = weakref.ref(self) - - def on_dpi_change(fig): - self_weakref = weak_self() - if self_weakref is not None: - self_weakref.labelsep = self_weakref._labelsep_inches * fig.dpi - # simple brute force update works because _init is called at - # the start of draw. - self_weakref._initialized = False - - self._cid = Q.axes.figure.callbacks.connect( - 'dpi_changed', on_dpi_change) self.labelpos = labelpos self.labelcolor = labelcolor self.fontproperties = fontproperties or dict() - self.kw = kw - _fp = self.fontproperties - # boxprops = dict(facecolor='red') + self.kw = kwargs self.text = mtext.Text( - text=label, # bbox=boxprops, + text=label, horizontalalignment=self.halign[self.labelpos], verticalalignment=self.valign[self.labelpos], - fontproperties=font_manager.FontProperties._from_any(_fp)) - + fontproperties=self.fontproperties) if self.labelcolor is not None: self.text.set_color(self.labelcolor) - self._initialized = False - self.zorder = Q.zorder + 0.1 + self._dpi_at_last_init = None + self.zorder = zorder if zorder is not None else Q.zorder + 0.1 - def remove(self): - # docstring inherited - self.Q.axes.figure.callbacks.disconnect(self._cid) - self._cid = None - super().remove() # pass the remove call up the stack + @property + def labelsep(self): + return self._labelsep_inches * self.Q.axes.get_figure(root=True).dpi def _init(self): - if True: # not self._initialized: - if not self.Q._initialized: + if True: # self._dpi_at_last_init != self.axes.get_figure().dpi + if self.Q._dpi_at_last_init != self.Q.axes.get_figure(root=True).dpi: self.Q._init() self._set_transform() with cbook._setattr_cm(self.Q, pivot=self.pivot[self.labelpos], @@ -308,55 +372,45 @@ def _init(self): Umask=ma.nomask): u = self.U * np.cos(np.radians(self.angle)) v = self.U * np.sin(np.radians(self.angle)) - angle = (self.Q.angles if isinstance(self.Q.angles, str) - else 'uv') - self.verts = self.Q._make_verts( - np.array([u]), np.array([v]), angle) - kw = self.Q.polykw - kw.update(self.kw) + self.verts = self.Q._make_verts([[0., 0.]], + np.array([u]), np.array([v]), 'uv') + kwargs = self.Q.polykw + kwargs.update(self.kw) self.vector = mcollections.PolyCollection( - self.verts, - offsets=[(self.X, self.Y)], - transOffset=self.get_transform(), - **kw) + self.verts, + offsets=[(self.X, self.Y)], + offset_transform=self.get_transform(), + **kwargs) if self.color is not None: self.vector.set_color(self.color) self.vector.set_transform(self.Q.get_transform()) self.vector.set_figure(self.get_figure()) - self._initialized = True - - def _text_x(self, x): - if self.labelpos == 'E': - return x + self.labelsep - elif self.labelpos == 'W': - return x - self.labelsep - else: - return x + self._dpi_at_last_init = self.Q.axes.get_figure(root=True).dpi - def _text_y(self, y): - if self.labelpos == 'N': - return y + self.labelsep - elif self.labelpos == 'S': - return y - self.labelsep - else: - return y + def _text_shift(self): + return { + "N": (0, +self.labelsep), + "S": (0, -self.labelsep), + "E": (+self.labelsep, 0), + "W": (-self.labelsep, 0), + }[self.labelpos] @martist.allow_rasterization def draw(self, renderer): self._init() self.vector.draw(renderer) - x, y = self.get_transform().transform((self.X, self.Y)) - self.text.set_x(self._text_x(x)) - self.text.set_y(self._text_y(y)) + pos = self.get_transform().transform((self.X, self.Y)) + self.text.set_position(pos + self._text_shift()) self.text.draw(renderer) self.stale = False def _set_transform(self): - self.set_transform(cbook._check_getitem({ + fig = self.Q.axes.get_figure(root=False) + self.set_transform(_api.check_getitem({ "data": self.Q.axes.transData, "axes": self.Q.axes.transAxes, - "figure": self.Q.axes.figure.transFigure, - "inches": self.Q.axes.figure.dpi_scale_trans, + "figure": fig.transFigure, + "inches": fig.dpi_scale_trans, }, coordinates=self.coord)) def set_figure(self, fig): @@ -364,9 +418,8 @@ def set_figure(self, fig): self.text.set_figure(fig) def contains(self, mouseevent): - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} # Maybe the dictionary should allow one to # distinguish between a text hit and a vector hit. if (self.text.contains(mouseevent)[0] or @@ -374,11 +427,6 @@ def contains(self, mouseevent): return True, {} return False, {} - @cbook.deprecated("3.2") - @property - def quiverkey_doc(self): - return self.__init__.__doc__ - def _parse_args(*args, caller_name='function'): """ @@ -401,20 +449,19 @@ def _parse_args(*args, caller_name='function'): """ X = Y = C = None - len_args = len(args) - if len_args == 2: + nargs = len(args) + if nargs == 2: # The use of atleast_1d allows for handling scalar arguments while also # keeping masked arrays U, V = np.atleast_1d(*args) - elif len_args == 3: + elif nargs == 3: U, V, C = np.atleast_1d(*args) - elif len_args == 4: + elif nargs == 4: X, Y, U, V = np.atleast_1d(*args) - elif len_args == 5: + elif nargs == 5: X, Y, U, V, C = np.atleast_1d(*args) else: - raise TypeError(f'{caller_name} takes 2-5 positional arguments but ' - f'{len_args} were given') + raise _api.nargs_error(caller_name, takes="from 2 to 5", given=nargs) nr, nc = (1, U.shape[0]) if U.ndim == 1 else U.shape @@ -422,13 +469,13 @@ def _parse_args(*args, caller_name='function'): X = X.ravel() Y = Y.ravel() if len(X) == nc and len(Y) == nr: - X, Y = [a.ravel() for a in np.meshgrid(X, Y)] + X, Y = (a.ravel() for a in np.meshgrid(X, Y)) elif len(X) != len(Y): raise ValueError('X and Y must be the same size, but ' f'X.size is {X.size} and Y.size is {Y.size}.') else: indexgrid = np.meshgrid(np.arange(nc), np.arange(nr)) - X, Y = [np.ravel(a) for a in indexgrid] + X, Y = (np.ravel(a) for a in indexgrid) # Size validation for U, V, C is left to the set_UVC method. return X, Y, U, V, C @@ -459,11 +506,11 @@ class Quiver(mcollections.PolyCollection): _PIVOT_VALS = ('tail', 'middle', 'tip') - @docstring.Substitution(_quiver_doc) + @_docstring.Substitution(_quiver_doc) def __init__(self, ax, *args, scale=None, headwidth=3, headlength=5, headaxislength=4.5, minshaft=1, minlength=1, units='width', scale_units=None, - angles='uv', width=None, color='k', pivot='tail', **kw): + angles='uv', width=None, color='k', pivot='tail', **kwargs): """ The constructor takes one required argument, an Axes instance, followed by the args and kwargs described @@ -471,7 +518,7 @@ def __init__(self, ax, *args, %s """ self._axes = ax # The attr actually set by the Artist.axes property. - X, Y, U, V, C = _parse_args(*args, caller_name='quiver()') + X, Y, U, V, C = _parse_args(*args, caller_name='quiver') self.X = X self.Y = Y self.XY = np.column_stack((X, Y)) @@ -490,39 +537,16 @@ def __init__(self, ax, *args, if pivot.lower() == 'mid': pivot = 'middle' self.pivot = pivot.lower() - cbook._check_in_list(self._PIVOT_VALS, pivot=self.pivot) - - self.transform = kw.pop('transform', ax.transData) - kw.setdefault('facecolors', color) - kw.setdefault('linewidths', (0,)) - super().__init__([], offsets=self.XY, transOffset=self.transform, - closed=False, **kw) - self.polykw = kw + _api.check_in_list(self._PIVOT_VALS, pivot=self.pivot) + + self.transform = kwargs.pop('transform', ax.transData) + kwargs.setdefault('facecolors', color) + kwargs.setdefault('linewidths', (0,)) + super().__init__([], offsets=self.XY, offset_transform=self.transform, + closed=False, **kwargs) + self.polykw = kwargs self.set_UVC(U, V, C) - self._initialized = False - - weak_self = weakref.ref(self) # Prevent closure over the real self. - - def on_dpi_change(fig): - self_weakref = weak_self() - if self_weakref is not None: - # vertices depend on width, span which in turn depend on dpi - self_weakref._new_UV = True - # simple brute force update works because _init is called at - # the start of draw. - self_weakref._initialized = False - - self._cid = ax.figure.callbacks.connect('dpi_changed', on_dpi_change) - - @cbook.deprecated("3.3", alternative="axes") - def ax(self): - return self.axes - - def remove(self): - # docstring inherited - self.axes.figure.callbacks.disconnect(self._cid) - self._cid = None - super().remove() # pass the remove call up the stack + self._dpi_at_last_init = None def _init(self): """ @@ -531,7 +555,7 @@ def _init(self): """ # It seems that there are not enough event notifications # available to have this work on an as-needed basis at present. - if True: # not self._initialized: + if True: # self._dpi_at_last_init != self.axes.figure.dpi trans = self._set_transform() self.span = trans.inverted().transform_bbox(self.axes.bbox).width if self.width is None: @@ -539,15 +563,16 @@ def _init(self): self.width = 0.06 * self.span / sn # _make_verts sets self.scale if not already specified - if not self._initialized and self.scale is None: - self._make_verts(self.U, self.V, self.angles) + if (self._dpi_at_last_init != self.axes.get_figure(root=True).dpi + and self.scale is None): + self._make_verts(self.XY, self.U, self.V, self.angles) - self._initialized = True + self._dpi_at_last_init = self.axes.get_figure(root=True).dpi def get_datalim(self, transData): trans = self.get_transform() - transOffset = self.get_offset_transform() - full_transform = (trans - transData) + (transOffset - transData) + offset_trf = self.get_offset_transform() + full_transform = (trans - transData) + (offset_trf - transData) XY = full_transform.transform(self.XY) bbox = transforms.Bbox.null() bbox.update_from_data_xy(XY, ignore=True) @@ -556,9 +581,8 @@ def get_datalim(self, transData): @martist.allow_rasterization def draw(self, renderer): self._init() - verts = self._make_verts(self.U, self.V, self.angles) + verts = self._make_verts(self.XY, self.U, self.V, self.angles) self.set_verts(verts, closed=False) - self._new_UV = False super().draw(renderer) self.stale = False @@ -587,40 +611,21 @@ def set_UVC(self, U, V, C=None): self.Umask = mask if C is not None: self.set_array(C) - self._new_UV = True self.stale = True def _dots_per_unit(self, units): - """ - Return a scale factor for converting from units to pixels - """ - if units in ('x', 'y', 'xy'): - if units == 'x': - dx0 = self.axes.viewLim.width - dx1 = self.axes.bbox.width - elif units == 'y': - dx0 = self.axes.viewLim.height - dx1 = self.axes.bbox.height - else: # 'xy' is assumed - dxx0 = self.axes.viewLim.width - dxx1 = self.axes.bbox.width - dyy0 = self.axes.viewLim.height - dyy1 = self.axes.bbox.height - dx1 = np.hypot(dxx1, dyy1) - dx0 = np.hypot(dxx0, dyy0) - dx = dx1 / dx0 - else: - if units == 'width': - dx = self.axes.bbox.width - elif units == 'height': - dx = self.axes.bbox.height - elif units == 'dots': - dx = 1.0 - elif units == 'inches': - dx = self.axes.figure.dpi - else: - raise ValueError('unrecognized units') - return dx + """Return a scale factor for converting from units to pixels.""" + bb = self.axes.bbox + vl = self.axes.viewLim + return _api.check_getitem({ + 'x': bb.width / vl.width, + 'y': bb.height / vl.height, + 'xy': np.hypot(*bb.size) / np.hypot(*vl.size), + 'width': bb.width, + 'height': bb.height, + 'dots': 1., + 'inches': self.axes.get_figure(root=True).dpi, + }, units=units) def _set_transform(self): """ @@ -633,33 +638,38 @@ def _set_transform(self): self.set_transform(trans) return trans - def _angles_lengths(self, U, V, eps=1): - xy = self.axes.transData.transform(self.XY) + # Calculate angles and lengths for segment between (x, y), (x+u, y+v) + def _angles_lengths(self, XY, U, V, eps=1): + xy = self.axes.transData.transform(XY) uv = np.column_stack((U, V)) - xyp = self.axes.transData.transform(self.XY + eps * uv) + xyp = self.axes.transData.transform(XY + eps * uv) dxy = xyp - xy angles = np.arctan2(dxy[:, 1], dxy[:, 0]) lengths = np.hypot(*dxy.T) / eps return angles, lengths - def _make_verts(self, U, V, angles): + # XY is stacked [X, Y]. + # See quiver() doc for meaning of X, Y, U, V, angles. + def _make_verts(self, XY, U, V, angles): uv = (U + V * 1j) str_angles = angles if isinstance(angles, str) else '' if str_angles == 'xy' and self.scale_units == 'xy': # Here eps is 1 so that if we get U, V by diffing # the X, Y arrays, the vectors will connect the # points, regardless of the axis scaling (including log). - angles, lengths = self._angles_lengths(U, V, eps=1) + angles, lengths = self._angles_lengths(XY, U, V, eps=1) elif str_angles == 'xy' or self.scale_units == 'xy': # Calculate eps based on the extents of the plot # so that we don't end up with roundoff error from # adding a small number to a large. eps = np.abs(self.axes.dataLim.extents).max() * 0.001 - angles, lengths = self._angles_lengths(U, V, eps=eps) + angles, lengths = self._angles_lengths(XY, U, V, eps=eps) + if str_angles and self.scale_units == 'xy': a = lengths else: a = np.abs(uv) + if self.scale is None: sn = max(10, math.sqrt(self.N)) if self.Umask is not ma.nomask: @@ -669,6 +679,7 @@ def _make_verts(self, U, V, angles): # crude auto-scaling # scale is typical arrow length as a multiple of the arrow width scale = 1.8 * amean * sn / self.span + if self.scale_units is None: if self.scale is None: self.scale = scale @@ -744,7 +755,7 @@ def _h_arrows(self, length): # float first, as with 'mid'. X = X - X[:, 3, np.newaxis] elif self.pivot != 'tail': - cbook._check_in_list(["middle", "tip", "tail"], pivot=self.pivot) + _api.check_in_list(["middle", "tip", "tail"], pivot=self.pivot) tooshort = length < self.minlength if tooshort.any(): @@ -760,21 +771,20 @@ def _h_arrows(self, length): # Mask handling is deferred to the caller, _make_verts. return X, Y - quiver_doc = _quiver_doc - _barbs_doc = r""" -Plot a 2D field of barbs. +Plot a 2D field of wind barbs. Call signature:: - barbs([X, Y], U, V, [C], **kw) + barbs([X, Y], U, V, [C], /, **kwargs) Where *X*, *Y* define the barb locations, *U*, *V* define the barb directions, and *C* optionally sets the color. -All arguments may be 1D or 2D. *U*, *V*, *C* may be masked arrays, but masked -*X*, *Y* are not supported at present. +The arguments *X*, *Y*, *U*, *V*, *C* are positional-only and may be +1D or 2D. *U*, *V*, *C* may be masked arrays, but masked *X*, *Y* +are not supported at present. Barbs are traditionally used in meteorology as a way to plot the speed and direction of wind observations, but can technically be used to @@ -833,12 +843,12 @@ def _h_arrows(self, length): rotates about this point. This can also be a number, which shifts the start of the barb that many points away from grid point. -barbcolor : color or color sequence +barbcolor : :mpltype:`color` or color sequence The color of all parts of the barb except for the flags. This parameter is analogous to the *edgecolor* parameter for polygons, which can be used instead. However this parameter will override facecolor. -flagcolor : color or color sequence +flagcolor : :mpltype:`color` or color sequence The color of any flags on the barb. This parameter is analogous to the *facecolor* parameter for polygons, which can be used instead. However, this parameter will override facecolor. If this is not set (and *C* has @@ -889,14 +899,17 @@ def _h_arrows(self, length): Other Parameters ---------------- +data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs The barbs can further be customized using `.PolyCollection` keyword arguments: - %(PolyCollection)s -""" % docstring.interpd.params + %(PolyCollection:kwdoc)s +""" % _docstring.interpd.params -docstring.interpd.update(barbs_doc=_barbs_doc) +_docstring.interpd.register(barbs_doc=_barbs_doc) class Barbs(mcollections.PolyCollection): @@ -908,19 +921,21 @@ class Barbs(mcollections.PolyCollection): are changed using the :meth:`set_offsets` collection method. Possibly this method will be useful in animations. - There is one internal function :meth:`_find_tails` which finds + There is one internal function :meth:`!_find_tails` which finds exactly what should be put on the barb given the vector magnitude. - From there :meth:`_make_barbs` is used to find the vertices of the + From there :meth:`!_make_barbs` is used to find the vertices of the polygon to represent the barb based on this information. """ + # This may be an abuse of polygons here to render what is essentially maybe # 1 triangle and a series of lines. It works fine as far as I can tell # however. - @docstring.interpd + + @_docstring.interpd def __init__(self, ax, *args, pivot='tip', length=7, barbcolor=None, flagcolor=None, sizes=None, fill_empty=False, barb_increments=None, - rounding=True, flip_barb=False, **kw): + rounding=True, flip_barb=False, **kwargs): """ The constructor takes one required argument, an Axes instance, followed by the args and kwargs described @@ -932,11 +947,9 @@ def __init__(self, ax, *args, self.barb_increments = barb_increments or dict() self.rounding = rounding self.flip = np.atleast_1d(flip_barb) - transform = kw.pop('transform', ax.transData) + transform = kwargs.pop('transform', ax.transData) self._pivot = pivot self._length = length - barbcolor = barbcolor - flagcolor = flagcolor # Flagcolor and barbcolor provide convenience parameters for # setting the facecolor and edgecolor, respectively, of the barb @@ -944,68 +957,68 @@ def __init__(self, ax, *args, # rest of the barb by default if None in (barbcolor, flagcolor): - kw['edgecolors'] = 'face' + kwargs['edgecolors'] = 'face' if flagcolor: - kw['facecolors'] = flagcolor + kwargs['facecolors'] = flagcolor elif barbcolor: - kw['facecolors'] = barbcolor + kwargs['facecolors'] = barbcolor else: # Set to facecolor passed in or default to black - kw.setdefault('facecolors', 'k') + kwargs.setdefault('facecolors', 'k') else: - kw['edgecolors'] = barbcolor - kw['facecolors'] = flagcolor + kwargs['edgecolors'] = barbcolor + kwargs['facecolors'] = flagcolor # Explicitly set a line width if we're not given one, otherwise # polygons are not outlined and we get no barbs - if 'linewidth' not in kw and 'lw' not in kw: - kw['linewidth'] = 1 + if 'linewidth' not in kwargs and 'lw' not in kwargs: + kwargs['linewidth'] = 1 # Parse out the data arrays from the various configurations supported - x, y, u, v, c = _parse_args(*args, caller_name='barbs()') + x, y, u, v, c = _parse_args(*args, caller_name='barbs') self.x = x self.y = y xy = np.column_stack((x, y)) # Make a collection barb_size = self._length ** 2 / 4 # Empirically determined - super().__init__([], (barb_size,), offsets=xy, transOffset=transform, - **kw) + super().__init__( + [], (barb_size,), offsets=xy, offset_transform=transform, **kwargs) self.set_transform(transforms.IdentityTransform()) self.set_UVC(u, v, c) def _find_tails(self, mag, rounding=True, half=5, full=10, flag=50): """ - Find how many of each of the tail pieces is necessary. Flag - specifies the increment for a flag, barb for a full barb, and half for - half a barb. Mag should be the magnitude of a vector (i.e., >= 0). + Find how many of each of the tail pieces is necessary. - This returns a tuple of: - - (*number of flags*, *number of barbs*, *half_flag*, *empty_flag*) + Parameters + ---------- + mag : `~numpy.ndarray` + Vector magnitudes; must be non-negative (and an actual ndarray). + rounding : bool, default: True + Whether to round or to truncate to the nearest half-barb. + half, full, flag : float, defaults: 5, 10, 50 + Increments for a half-barb, a barb, and a flag. - The bool *half_flag* indicates whether half of a barb is needed, - since there should only ever be one half on a given - barb. *empty_flag* flag is an array of flags to easily tell if - a barb is empty (too low to plot any barbs/flags. + Returns + ------- + n_flags, n_barbs : int array + For each entry in *mag*, the number of flags and barbs. + half_flag : bool array + For each entry in *mag*, whether a half-barb is needed. + empty_flag : bool array + For each entry in *mag*, whether nothing is drawn. """ - # If rounding, round to the nearest multiple of half, the smallest # increment if rounding: - mag = half * (mag / half + 0.5).astype(int) - - num_flags = np.floor(mag / flag).astype(int) - mag = mag % flag - - num_barb = np.floor(mag / full).astype(int) - mag = mag % full - + mag = half * np.around(mag / half) + n_flags, mag = divmod(mag, flag) + n_barb, mag = divmod(mag, full) half_flag = mag >= half - empty_flag = ~(half_flag | (num_flags > 0) | (num_barb > 0)) - - return num_flags, num_barb, half_flag, empty_flag + empty_flag = ~(half_flag | (n_flags > 0) | (n_barb > 0)) + return n_flags.astype(int), n_barb.astype(int), half_flag, empty_flag def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length, pivot, sizes, fill_empty, flip): @@ -1152,8 +1165,10 @@ def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length, return barb_list def set_UVC(self, U, V, C=None): - self.u = ma.masked_invalid(U, copy=False).ravel() - self.v = ma.masked_invalid(V, copy=False).ravel() + # We need to ensure we have a copy, not a reference to an array that + # might change before draw(). + self.u = ma.masked_invalid(U, copy=True).ravel() + self.v = ma.masked_invalid(V, copy=True).ravel() # Flip needs to have the same number of entries as everything else. # Use broadcast_to to avoid a bloated array of identical values. @@ -1164,7 +1179,7 @@ def set_UVC(self, U, V, C=None): flip = self.flip if C is not None: - c = ma.masked_invalid(C, copy=False).ravel() + c = ma.masked_invalid(C, copy=True).ravel() x, y, u, v, c, flip = cbook.delete_masked_points( self.x.ravel(), self.y.ravel(), self.u, self.v, c, flip.ravel()) @@ -1175,9 +1190,8 @@ def set_UVC(self, U, V, C=None): _check_consistent_shapes(x, y, u, v, flip) magnitude = np.hypot(u, v) - flags, barbs, halves, empty = self._find_tails(magnitude, - self.rounding, - **self.barb_increments) + flags, barbs, halves, empty = self._find_tails( + magnitude, self.rounding, **self.barb_increments) # Get the vertices for each of the barbs @@ -1212,5 +1226,3 @@ def set_offsets(self, xy): xy = np.column_stack((x, y)) super().set_offsets(xy) self.stale = True - - barbs_doc = _barbs_doc diff --git a/lib/matplotlib/quiver.pyi b/lib/matplotlib/quiver.pyi new file mode 100644 index 000000000000..8a14083c4348 --- /dev/null +++ b/lib/matplotlib/quiver.pyi @@ -0,0 +1,184 @@ +import matplotlib.artist as martist +import matplotlib.collections as mcollections +from matplotlib.axes import Axes +from matplotlib.figure import Figure, SubFigure +from matplotlib.text import Text +from matplotlib.transforms import Transform, Bbox + + +import numpy as np +from numpy.typing import ArrayLike +from collections.abc import Sequence +from typing import Any, Literal, overload +from matplotlib.typing import ColorType + +class QuiverKey(martist.Artist): + halign: dict[Literal["N", "S", "E", "W"], Literal["left", "center", "right"]] + valign: dict[Literal["N", "S", "E", "W"], Literal["top", "center", "bottom"]] + pivot: dict[Literal["N", "S", "E", "W"], Literal["middle", "tip", "tail"]] + Q: Quiver + X: float + Y: float + U: float + angle: float + coord: Literal["axes", "figure", "data", "inches"] + color: ColorType | None + label: str + labelpos: Literal["N", "S", "E", "W"] + labelcolor: ColorType | None + fontproperties: dict[str, Any] + kw: dict[str, Any] + text: Text + zorder: float + def __init__( + self, + Q: Quiver, + X: float, + Y: float, + U: float, + label: str, + *, + angle: float = ..., + coordinates: Literal["axes", "figure", "data", "inches"] = ..., + color: ColorType | None = ..., + labelsep: float = ..., + labelpos: Literal["N", "S", "E", "W"] = ..., + labelcolor: ColorType | None = ..., + fontproperties: dict[str, Any] | None = ..., + zorder: float | None = ..., + **kwargs + ) -> None: ... + @property + def labelsep(self) -> float: ... + def set_figure(self, fig: Figure | SubFigure) -> None: ... + +class Quiver(mcollections.PolyCollection): + X: ArrayLike + Y: ArrayLike + XY: ArrayLike + U: ArrayLike + V: ArrayLike + Umask: ArrayLike + N: int + scale: float | None + headwidth: float + headlength: float + headaxislength: float + minshaft: float + minlength: float + units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] + scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] | None + angles: Literal["uv", "xy"] | ArrayLike + width: float | None + pivot: Literal["tail", "middle", "tip"] + transform: Transform + polykw: dict[str, Any] + + @overload + def __init__( + self, + ax: Axes, + U: ArrayLike, + V: ArrayLike, + C: ArrayLike = ..., + *, + scale: float | None = ..., + headwidth: float = ..., + headlength: float = ..., + headaxislength: float = ..., + minshaft: float = ..., + minlength: float = ..., + units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] = ..., + scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] + | None = ..., + angles: Literal["uv", "xy"] | ArrayLike = ..., + width: float | None = ..., + color: ColorType | Sequence[ColorType] = ..., + pivot: Literal["tail", "mid", "middle", "tip"] = ..., + **kwargs + ) -> None: ... + @overload + def __init__( + self, + ax: Axes, + X: ArrayLike, + Y: ArrayLike, + U: ArrayLike, + V: ArrayLike, + C: ArrayLike = ..., + *, + scale: float | None = ..., + headwidth: float = ..., + headlength: float = ..., + headaxislength: float = ..., + minshaft: float = ..., + minlength: float = ..., + units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] = ..., + scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] + | None = ..., + angles: Literal["uv", "xy"] | ArrayLike = ..., + width: float | None = ..., + color: ColorType | Sequence[ColorType] = ..., + pivot: Literal["tail", "mid", "middle", "tip"] = ..., + **kwargs + ) -> None: ... + def get_datalim(self, transData: Transform) -> Bbox: ... + def set_UVC( + self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... + ) -> None: ... + +class Barbs(mcollections.PolyCollection): + sizes: dict[str, float] + fill_empty: bool + barb_increments: dict[str, float] + rounding: bool + flip: np.ndarray + x: ArrayLike + y: ArrayLike + u: ArrayLike + v: ArrayLike + + @overload + def __init__( + self, + ax: Axes, + U: ArrayLike, + V: ArrayLike, + C: ArrayLike = ..., + *, + pivot: str = ..., + length: int = ..., + barbcolor: ColorType | Sequence[ColorType] | None = ..., + flagcolor: ColorType | Sequence[ColorType] | None = ..., + sizes: dict[str, float] | None = ..., + fill_empty: bool = ..., + barb_increments: dict[str, float] | None = ..., + rounding: bool = ..., + flip_barb: bool | ArrayLike = ..., + **kwargs + ) -> None: ... + @overload + def __init__( + self, + ax: Axes, + X: ArrayLike, + Y: ArrayLike, + U: ArrayLike, + V: ArrayLike, + C: ArrayLike = ..., + *, + pivot: str = ..., + length: int = ..., + barbcolor: ColorType | Sequence[ColorType] | None = ..., + flagcolor: ColorType | Sequence[ColorType] | None = ..., + sizes: dict[str, float] | None = ..., + fill_empty: bool = ..., + barb_increments: dict[str, float] | None = ..., + rounding: bool = ..., + flip_barb: bool | ArrayLike = ..., + **kwargs + ) -> None: ... + def set_UVC( + self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... + ) -> None: ... + def set_offsets(self, xy: ArrayLike) -> None: ... diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index ad807d72a89f..02e3601ff4c2 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -9,43 +9,31 @@ The default values of the rc settings are set in the default matplotlibrc file. Any additions or deletions to the parameter set listed here should also be -propagated to the :file:`matplotlibrc.template` in Matplotlib's root source -directory. +propagated to the :file:`lib/matplotlib/mpl-data/matplotlibrc` in Matplotlib's +root source directory. """ import ast from functools import lru_cache, reduce -import logging -from numbers import Number +from numbers import Real import operator +import os import re import numpy as np -from matplotlib import animation, cbook +import matplotlib as mpl +from matplotlib import _api, cbook +from matplotlib.backends import backend_registry from matplotlib.cbook import ls_mapper -from matplotlib.fontconfig_pattern import parse_fontconfig_pattern -from matplotlib.colors import is_color_like +from matplotlib.colors import Colormap, is_color_like +from matplotlib._fontconfig_pattern import parse_fontconfig_pattern +from matplotlib._enums import JoinStyle, CapStyle # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler -_log = logging.getLogger(__name__) -# The capitalized forms are needed for ipython at present; this may -# change for later versions. -interactive_bk = ['GTK3Agg', 'GTK3Cairo', - 'MacOSX', - 'nbAgg', - 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo', - 'TkAgg', 'TkCairo', - 'WebAgg', - 'WX', 'WXAgg', 'WXCairo'] -non_interactive_bk = ['agg', 'cairo', - 'pdf', 'pgf', 'ps', 'svg', 'template'] -all_backends = interactive_bk + non_interactive_bk - - class ValidateInStrings: def __init__(self, key, valid, ignorecase=False, *, _deprecated_since=None): @@ -64,9 +52,9 @@ def func(s): def __call__(self, s): if self._deprecated_since: name, = (k for k, v in globals().items() if v is self) - cbook.warn_deprecated( + _api.warn_deprecated( self._deprecated_since, name=name, obj_type="function") - if self.ignorecase: + if self.ignorecase and isinstance(s, str): s = s.lower() if s in self.valid: return self.valid[s] @@ -80,7 +68,26 @@ def __call__(self, s): raise ValueError(msg) -@lru_cache() +def _single_string_color_list(s, scalar_validator): + """ + Convert the string *s* to a list of colors interpreting it either as a + color sequence name, or a string containing single-letter colors. + """ + try: + colors = mpl.color_sequences[s] + except KeyError: + try: + # Sometimes, a list of colors might be a single string + # of single-letter colornames. So give that a shot. + colors = [scalar_validator(v.strip()) for v in s if v.strip()] + except ValueError: + raise ValueError(f'{s!r} is neither a color sequence name nor can ' + 'it be interpreted as a list of colors') + + return colors + + +@lru_cache def _listify_validator(scalar_validator, allow_stringlist=False, *, n=None, doc=None): def f(s): @@ -90,9 +97,8 @@ def f(s): if v.strip()] except Exception: if allow_stringlist: - # Sometimes, a list of colors might be a single string - # of single-letter colornames. So give that a shot. - val = [scalar_validator(v.strip()) for v in s if v.strip()] + # Special handling for colors + val = _single_string_color_list(s, scalar_validator) else: raise # Allow any ordered sequence type -- generators, np.ndarray, pd.Series @@ -113,9 +119,9 @@ def f(s): return val try: - f.__name__ = "{}list".format(scalar_validator.__name__) + f.__name__ = f"{scalar_validator.__name__}list" except AttributeError: # class instance. - f.__name__ = "{}List".format(type(scalar_validator).__name__) + f.__name__ = f"{type(scalar_validator).__name__}List" f.__qualname__ = f.__qualname__.rsplit(".", 1)[0] + "." + f.__name__ f.__doc__ = doc if doc is not None else scalar_validator.__doc__ return f @@ -144,66 +150,7 @@ def validate_bool(b): elif b in ('f', 'n', 'no', 'off', 'false', '0', 0, False): return False else: - raise ValueError('Could not convert "%s" to bool' % b) - - -@cbook.deprecated("3.3") -def validate_bool_maybe_none(b): - """Convert b to ``bool`` or raise, passing through *None*.""" - if isinstance(b, str): - b = b.lower() - if b is None or b == 'none': - return None - if b in ('t', 'y', 'yes', 'on', 'true', '1', 1, True): - return True - elif b in ('f', 'n', 'no', 'off', 'false', '0', 0, False): - return False - else: - raise ValueError('Could not convert "%s" to bool' % b) - - -def _validate_date_converter(s): - if s is None: - return - s = validate_string(s) - if s not in ['auto', 'concise']: - cbook._warn_external(f'date.converter string must be "auto" ' - f'or "concise", not "{s}". Check your ' - 'matplotlibrc') - return - import matplotlib.dates as mdates - mdates._rcParam_helper.set_converter(s) - - -def _validate_date_int_mult(s): - if s is None: - return - s = validate_bool(s) - import matplotlib.dates as mdates - mdates._rcParam_helper.set_int_mult(s) - - -def _validate_tex_preamble(s): - if s is None or s == 'None': - cbook.warn_deprecated( - "3.3", message="Support for setting the 'text.latex.preamble' or " - "'pgf.preamble' rcParam to None is deprecated since %(since)s and " - "will be removed %(removal)s; set it to an empty string instead.") - return "" - try: - if isinstance(s, str): - return s - elif np.iterable(s): - cbook.warn_deprecated( - "3.3", message="Support for setting the 'text.latex.preamble' " - "or 'pgf.preamble' rcParam to a list of strings is deprecated " - "since %(since)s and will be removed %(removal)s; set it to a " - "single string instead.") - return '\n'.join(s) - else: - raise TypeError - except TypeError as e: - raise ValueError('Could not convert "%s" to string' % s) from e + raise ValueError(f'Cannot convert {b!r} to bool') def validate_axisbelow(s): @@ -213,14 +160,8 @@ def validate_axisbelow(s): if isinstance(s, str): if s == 'line': return 'line' - if s.lower().startswith('line'): - cbook.warn_deprecated( - "3.3", message=f"Support for setting axes.axisbelow to " - f"{s!r} to mean 'line' is deprecated since %(since)s and " - f"will be removed %(removal)s; set it to 'line' instead.") - return 'line' - raise ValueError('%s cannot be interpreted as' - ' True, False, or "line"' % s) + raise ValueError(f'{s!r} cannot be interpreted as' + ' True, False, or "line"') def validate_dpi(s): @@ -242,11 +183,21 @@ def _make_type_validator(cls, *, allow_none=False): def validator(s): if (allow_none and - (s is None or isinstance(s, str) and s.lower() == "none")): + (s is None or cbook._str_lower_equal(s, "none"))): + if cbook._str_lower_equal(s, "none") and s != "None": + _api.warn_deprecated( + "3.11", + message=f"Using the capitalization {s!r} in matplotlibrc for " + "*None* is deprecated in %(removal)s and will lead to an " + "error from version 3.13 onward. Please use 'None' " + "instead." + ) return None + if cls is str and not isinstance(s, str): + raise ValueError(f'Could not convert {s!r} to str') try: return cls(s) - except ValueError as e: + except (TypeError, ValueError) as e: raise ValueError( f'Could not convert {s!r} to {cls.__name__}') from e @@ -270,6 +221,29 @@ def validator(s): validate_float, doc='return a list of floats') +def _validate_marker(s): + try: + return validate_int(s) + except ValueError as e: + try: + return validate_string(s) + except ValueError as e: + raise ValueError('Supported markers are [string, int]') from e + + +_validate_markerlist = _listify_validator( + _validate_marker, doc='return a list of markers') + + +def _validate_pathlike(s): + if isinstance(s, (str, os.PathLike)): + # Store value as str because savefig.directory needs to distinguish + # between "" (cwd) and "." (cwd, but gets updated by user selections). + return os.fsdecode(s) + else: + return validate_string(s) + + def validate_fonttype(s): """ Confirm that this is a Postscript or PDF font type that we know how to @@ -293,57 +267,26 @@ def validate_fonttype(s): return fonttype -_validate_standard_backends = ValidateInStrings( - 'backend', all_backends, ignorecase=True) _auto_backend_sentinel = object() def validate_backend(s): - backend = ( - s if s is _auto_backend_sentinel or s.startswith("module://") - else _validate_standard_backends(s)) - return backend - - -validate_toolbar = ValidateInStrings( - 'toolbar', ['None', 'toolbar2', 'toolmanager'], ignorecase=True, - _deprecated_since="3.3") - - -@cbook.deprecated("3.3") -def _make_nseq_validator(cls, n=None, allow_none=False): - - def validator(s): - """Convert *n* objects using ``cls``, or raise.""" - if isinstance(s, str): - s = [x.strip() for x in s.split(',')] - if n is not None and len(s) != n: - raise ValueError( - f'Expected exactly {n} comma-separated values, ' - f'but got {len(s)} comma-separated values: {s}') - else: - if n is not None and len(s) != n: - raise ValueError( - f'Expected exactly {n} values, ' - f'but got {len(s)} values: {s}') - try: - return [cls(val) if not allow_none or val is not None else val - for val in s] - except ValueError as e: - raise ValueError( - f'Could not convert all entries to {cls.__name__}s') from e - - return validator - - -@cbook.deprecated("3.3") -def validate_nseq_float(n): - return _make_nseq_validator(float, n) + if s is _auto_backend_sentinel or backend_registry.is_valid_backend(s): + return s + else: + msg = (f"'{s}' is not a valid value for backend; supported values are " + f"{backend_registry.list_all()}") + raise ValueError(msg) -@cbook.deprecated("3.3") -def validate_nseq_int(n): - return _make_nseq_validator(int, n) +def _validate_toolbar(s): + s = ValidateInStrings( + 'toolbar', ['None', 'toolbar2', 'toolmanager'], ignorecase=True)(s) + if s == 'toolmanager': + _api.warn_external( + "Treat the new Tool classes introduced in v1.5 as experimental " + "for now; the API and rcParam may change in future versions.") + return s def validate_color_or_inherit(s): @@ -359,6 +302,12 @@ def validate_color_or_auto(s): return validate_color(s) +def _validate_color_or_edge(s): + if cbook._str_equal(s, 'edge'): + return s + return validate_color(s) + + def validate_color_for_prop_cycle(s): # N-th color cycle syntax can't go into the color cycle. if isinstance(s, str) and re.match("^C[0-9]$", s): @@ -366,6 +315,27 @@ def validate_color_for_prop_cycle(s): return validate_color(s) +def _validate_color_or_linecolor(s): + if cbook._str_equal(s, 'linecolor'): + return s + elif cbook._str_equal(s, 'mfc') or cbook._str_equal(s, 'markerfacecolor'): + return 'markerfacecolor' + elif cbook._str_equal(s, 'mec') or cbook._str_equal(s, 'markeredgecolor'): + return 'markeredgecolor' + elif s is None: + return None + elif isinstance(s, str) and len(s) == 6 or len(s) == 8: + stmp = '#' + s + if is_color_like(stmp): + return stmp + if s.lower() == 'none': + return None + elif is_color_like(s): + return s + + raise ValueError(f'{s!r} does not look like a color arg') + + def validate_color(s): """Return a valid color arg.""" if isinstance(s, str): @@ -393,8 +363,11 @@ def validate_color(s): validate_colorlist = _listify_validator( validate_color, allow_stringlist=True, doc='return a list of colorspecs') -validate_orientation = ValidateInStrings( - 'orientation', ['landscape', 'portrait'], _deprecated_since="3.3") + + +def _validate_cmap(s): + _api.check_isinstance((str, Colormap), cmap=s) + return s def validate_aspect(s): @@ -443,28 +416,25 @@ def validate_fontweight(s): raise ValueError(f'{s} is not a valid font weight.') from e +def validate_fontstretch(s): + stretchvalues = [ + 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', + 'normal', 'semi-expanded', 'expanded', 'extra-expanded', + 'ultra-expanded'] + # Note: Historically, stretchvalues have been case-sensitive in Matplotlib + if s in stretchvalues: + return s + try: + return int(s) + except (ValueError, TypeError) as e: + raise ValueError(f'{s} is not a valid font stretch.') from e + + def validate_font_properties(s): parse_fontconfig_pattern(s) return s -def _validate_mathtext_fallback_to_cm(b): - """ - Temporary validate for fallback_to_cm, while deprecated - - """ - if isinstance(b, str): - b = b.lower() - if b is None or b == 'none': - return None - else: - cbook.warn_deprecated( - "3.3", message="Support for setting the 'mathtext.fallback_to_cm' " - "rcParam is deprecated since %(since)s and will be removed " - "%(removal)s; use 'mathtext.fallback : 'cm' instead.") - return validate_bool_maybe_none(b) - - def _validate_mathtext_fallback(s): _fallback_fonts = ['cm', 'stix', 'stixsans'] if isinstance(s, str): @@ -480,19 +450,6 @@ def _validate_mathtext_fallback(s): "fallback off.") -validate_fontset = ValidateInStrings( - 'fontset', - ['dejavusans', 'dejavuserif', 'cm', 'stix', 'stixsans', 'custom'], - _deprecated_since="3.3") -validate_mathtext_default = ValidateInStrings( - 'default', "rm cal it tt sf bf default bb frak scr regular".split(), - _deprecated_since="3.3") -_validate_alignment = ValidateInStrings( - 'alignment', - ['center', 'top', 'bottom', 'baseline', 'center_baseline'], - _deprecated_since="3.3") - - def validate_whiskers(s): try: return _listify_validator(validate_float, n=2)(s) @@ -500,18 +457,10 @@ def validate_whiskers(s): try: return float(s) except ValueError as e: - raise ValueError("Not a valid whisker value ['range', float, " + raise ValueError("Not a valid whisker value [float, " "(float, float)]") from e -validate_ps_papersize = ValidateInStrings( - 'ps_papersize', - ['auto', 'letter', 'legal', 'ledger', - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'a10', - 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', - ], ignorecase=True, _deprecated_since="3.3") - - def validate_ps_distiller(s): if isinstance(s, str): s = s.lower() @@ -549,63 +498,27 @@ def _is_iterable_not_string_like(x): # nonsensically interpreted as sequences of numbers (codepoints). return np.iterable(x) and not isinstance(x, (str, bytes, bytearray)) - # (offset, (on, off, on, off, ...)) - if (_is_iterable_not_string_like(ls) - and len(ls) == 2 - and isinstance(ls[0], (type(None), Number)) - and _is_iterable_not_string_like(ls[1]) - and len(ls[1]) % 2 == 0 - and all(isinstance(elem, Number) for elem in ls[1])): - if ls[0] is None: - cbook.warn_deprecated( - "3.3", message="Passing the dash offset as None is deprecated " - "since %(since)s and support for it will be removed " - "%(removal)s; pass it as zero instead.") - ls = (0, ls[1]) - return ls - # For backcompat: (on, off, on, off, ...); the offset is implicitly None. - if (_is_iterable_not_string_like(ls) - and len(ls) % 2 == 0 - and all(isinstance(elem, Number) for elem in ls)): - return (0, ls) - raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.") - - -def _deprecate_case_insensitive_join_cap(s): - s_low = s.lower() - if s != s_low: - if s_low in ['miter', 'round', 'bevel']: - cbook.warn_deprecated( - "3.3", message="Case-insensitive capstyles are deprecated " - "since %(since)s and support for them will be removed " - "%(removal)s; please pass them in lowercase.") - elif s_low in ['butt', 'round', 'projecting']: - cbook.warn_deprecated( - "3.3", message="Case-insensitive joinstyles are deprecated " - "since %(since)s and support for them will be removed " - "%(removal)s; please pass them in lowercase.") - # Else, error out at the check_in_list stage. - return s_low - - -def validate_joinstyle(s): - s = _deprecate_case_insensitive_join_cap(s) - cbook._check_in_list(['miter', 'round', 'bevel'], joinstyle=s) - return s + if _is_iterable_not_string_like(ls): + if len(ls) == 2 and _is_iterable_not_string_like(ls[1]): + # (offset, (on, off, on, off, ...)) + offset, onoff = ls + else: + # For backcompat: (on, off, on, off, ...); the offset is implicit. + offset = 0 + onoff = ls + if (isinstance(offset, Real) + and len(onoff) % 2 == 0 + and all(isinstance(elem, Real) for elem in onoff)): + return (offset, onoff) -def validate_capstyle(s): - s = _deprecate_case_insensitive_join_cap(s) - cbook._check_in_list(['butt', 'round', 'projecting'], capstyle=s) - return s + raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.") validate_fillstyle = ValidateInStrings( 'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none']) -validate_joinstylelist = _listify_validator(validate_joinstyle) -validate_capstylelist = _listify_validator(validate_capstyle) validate_fillstylelist = _listify_validator(validate_fillstyle) @@ -615,14 +528,11 @@ def validate_markevery(s): Parameters ---------- - s : None, int, float, slice, length-2 tuple of ints, - length-2 tuple of floats, list of ints + s : None, int, (int, int), slice, float, (float, float), or list[int] Returns ------- - None, int, float, slice, length-2 tuple of ints, - length-2 tuple of floats, list of ints - + None, int, (int, int), slice, float, (float, float), or list[int] """ # Validate s against type slice float int and None if isinstance(s, (slice, float, int, type(None))): @@ -648,62 +558,6 @@ def validate_markevery(s): validate_markeverylist = _listify_validator(validate_markevery) -validate_legend_loc = ValidateInStrings( - 'legend_loc', - ['best', - 'upper right', - 'upper left', - 'lower left', - 'lower right', - 'right', - 'center left', - 'center right', - 'lower center', - 'upper center', - 'center'], ignorecase=True, _deprecated_since="3.3") - -validate_svg_fonttype = ValidateInStrings( - 'svg.fonttype', ['none', 'path'], _deprecated_since="3.3") - - -@cbook.deprecated("3.3") -def validate_hinting(s): - return _validate_hinting(s) - - -# Replace by plain list in _prop_validators after deprecation period. -_validate_hinting = ValidateInStrings( - 'text.hinting', - ['default', 'no_autohint', 'force_autohint', 'no_hinting', - 'auto', 'native', 'either', 'none'], - ignorecase=True) - - -validate_pgf_texsystem = ValidateInStrings( - 'pgf.texsystem', ['xelatex', 'lualatex', 'pdflatex'], - _deprecated_since="3.3") - - -@cbook.deprecated("3.3") -def validate_movie_writer(s): - # writers.list() would only list actually available writers, but - # FFMpeg.isAvailable is slow and not worth paying for at every import. - if s in animation.writers._registered: - return s - else: - raise ValueError(f"Supported animation writers are " - f"{sorted(animation.writers._registered)}") - - -validate_movie_frame_fmt = ValidateInStrings( - 'animation.frame_format', ['png', 'jpeg', 'tiff', 'raw', 'rgba', 'ppm', - 'sgi', 'bmp', 'pbm', 'svg'], - _deprecated_since="3.3") -validate_axis_locator = ValidateInStrings( - 'major', ['minor', 'both', 'major'], _deprecated_since="3.3") -validate_movie_html_fmt = ValidateInStrings( - 'animation.html', ['html5', 'jshtml', 'none'], _deprecated_since="3.3") - def validate_bbox(s): if isinstance(s, str): @@ -720,22 +574,25 @@ def validate_bbox(s): def validate_sketch(s): + if isinstance(s, str): - s = s.lower() + s = s.lower().strip() + if s.startswith("(") and s.endswith(")"): + s = s[1:-1] if s == 'none' or s is None: return None try: return tuple(_listify_validator(validate_float, n=3)(s)) - except ValueError: - raise ValueError("Expected a (scale, length, randomness) triplet") + except ValueError as exc: + raise ValueError("Expected a (scale, length, randomness) tuple") from exc -def _validate_greaterequal0_lessthan1(s): +def _validate_greaterthan_minushalf(s): s = validate_float(s) - if 0 <= s < 1: + if s > -0.5: return s else: - raise RuntimeError(f'Value must be >=0 and <1; got {s}') + raise RuntimeError(f'Value must be >-0.5; got {s}') def _validate_greaterequal0_lessequal1(s): @@ -746,14 +603,12 @@ def _validate_greaterequal0_lessequal1(s): raise RuntimeError(f'Value must be >=0 and <=1; got {s}') -_range_validators = { # Slightly nicer (internal) API. - "0 <= x < 1": _validate_greaterequal0_lessthan1, - "0 <= x <= 1": _validate_greaterequal0_lessequal1, -} - - -validate_grid_axis = ValidateInStrings( - 'axes.grid.axis', ['x', 'y', 'both'], _deprecated_since="3.3") +def _validate_int_greaterequal0(s): + s = validate_int(s) + if s >= 0: + return s + else: + raise RuntimeError(f'Value must be >=0; got {s}') def validate_hatch(s): @@ -764,7 +619,7 @@ def validate_hatch(s): """ if not isinstance(s, str): raise ValueError("Hatch pattern must be a string") - cbook._check_isinstance(str, hatch_pattern=s) + _api.check_isinstance(str, hatch_pattern=s) unknown = set(s) - {'\\', '/', '|', '-', '+', '*', '.', 'x', 'o', 'O'} if unknown: raise ValueError("Unknown hatch symbol(s): %s" % list(unknown)) @@ -775,6 +630,24 @@ def validate_hatch(s): validate_dashlist = _listify_validator(validate_floatlist) +def _validate_minor_tick_ndivs(n): + """ + Validate ndiv parameter related to the minor ticks. + It controls the number of minor ticks to be placed between + two major ticks. + """ + + if cbook._str_lower_equal(n, 'auto'): + return n + try: + n = _validate_int_greaterequal0(n) + return n + except (RuntimeError, ValueError): + pass + + raise ValueError("'tick.minor.ndivs' must be 'auto' or non-negative int") + + _prop_validators = { 'color': _listify_validator(validate_color_for_prop_cycle, allow_stringlist=True), @@ -782,8 +655,8 @@ def validate_hatch(s): 'linestyle': _listify_validator(_validate_linestyle), 'facecolor': validate_colorlist, 'edgecolor': validate_colorlist, - 'joinstyle': validate_joinstylelist, - 'capstyle': validate_capstylelist, + 'joinstyle': _listify_validator(JoinStyle), + 'capstyle': _listify_validator(CapStyle), 'fillstyle': validate_fillstylelist, 'markerfacecolor': validate_colorlist, 'markersize': validate_floatlist, @@ -791,7 +664,7 @@ def validate_hatch(s): 'markeredgecolor': validate_colorlist, 'markevery': validate_markeverylist, 'alpha': validate_floatlist, - 'marker': validate_stringlist, + 'marker': _validate_markerlist, 'hatch': validate_hatchlist, 'dashes': validate_dashlist, } @@ -816,7 +689,7 @@ def cycler(*args, **kwargs): Call signatures:: cycler(cycler) - cycler(label=values[, label2=values2[, ...]]) + cycler(label=values, label2=values2, ...) cycler(label, values) Form 1 copies a given `~cycler.Cycler` object. @@ -870,12 +743,12 @@ def cycler(*args, **kwargs): if len(args) == 1: if not isinstance(args[0], Cycler): raise TypeError("If only one positional argument given, it must " - " be a Cycler instance.") + "be a Cycler instance.") return validate_cycler(args[0]) elif len(args) == 2: pairs = [(args[0], args[1])] elif len(args) > 2: - raise TypeError("No more than 2 positional arguments allowed") + raise _api.nargs_error('cycler', '0-2', len(args)) else: pairs = kwargs.items() @@ -893,6 +766,58 @@ def cycler(*args, **kwargs): return reduce(operator.add, (ccycler(k, v) for k, v in validated)) +class _DunderChecker(ast.NodeVisitor): + def visit_Attribute(self, node): + if node.attr.startswith("__") and node.attr.endswith("__"): + raise ValueError("cycler strings with dunders are forbidden") + self.generic_visit(node) + + +# A validator dedicated to the named legend loc +_validate_named_legend_loc = ValidateInStrings( + 'legend.loc', + [ + "best", + "upper right", "upper left", "lower left", "lower right", "right", + "center left", "center right", "lower center", "upper center", + "center"], + ignorecase=True) + + +def _validate_legend_loc(loc): + """ + Confirm that loc is a type which rc.Params["legend.loc"] supports. + + .. versionadded:: 3.8 + + Parameters + ---------- + loc : str | int | (float, float) | str((float, float)) + The location of the legend. + + Returns + ------- + loc : str | int | (float, float) or raise ValueError exception + The location of the legend. + """ + if isinstance(loc, str): + try: + return _validate_named_legend_loc(loc) + except ValueError: + pass + try: + loc = ast.literal_eval(loc) + except (SyntaxError, ValueError): + pass + if isinstance(loc, int): + if 0 <= loc <= 10: + return loc + if isinstance(loc, tuple): + if len(loc) == 2 and all(isinstance(e, Real) for e in loc): + return loc + raise ValueError(f"{loc} is not a valid legend location.") + + def validate_cycler(s): """Return a Cycler object from a string repr or the object itself.""" if isinstance(s, str): @@ -904,23 +829,21 @@ def validate_cycler(s): # I locked it down by only having the 'cycler()' function available. # UPDATE: Partly plugging a security hole. # I really should have read this: - # http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html + # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html # We should replace this eval with a combo of PyParsing and # ast.literal_eval() try: - if '.__' in s.replace(' ', ''): - raise ValueError("'%s' seems to have dunder methods. Raising" - " an exception for your safety") + _DunderChecker().visit(ast.parse(s)) s = eval(s, {'cycler': cycler, '__builtins__': {}}) except BaseException as e: - raise ValueError("'%s' is not a valid cycler construction: %s" % - (s, e)) from e + raise ValueError(f"{s!r} is not a valid cycler construction: {e}" + ) from e # Should make sure what comes from the above eval() # is a Cycler object. if isinstance(s, Cycler): cycler_inst = s else: - raise ValueError("object was not a string or Cycler instance: %s" % s) + raise ValueError(f"Object is not a string or Cycler instance: {s!r}") unknowns = cycler_inst.keys - (set(_prop_validators) | set(_prop_aliases)) if unknowns: @@ -932,12 +855,11 @@ def validate_cycler(s): for prop in cycler_inst.keys: norm_prop = _prop_aliases.get(prop, prop) if norm_prop != prop and norm_prop in cycler_inst.keys: - raise ValueError("Cannot specify both '{0}' and alias '{1}'" - " in the same prop_cycle".format(norm_prop, prop)) + raise ValueError(f"Cannot specify both {norm_prop!r} and alias " + f"{prop!r} in the same prop_cycle") if norm_prop in checker: - raise ValueError("Another property was already aliased to '{0}'." - " Collision normalizing '{1}'.".format(norm_prop, - prop)) + raise ValueError(f"Another property was already aliased to " + f"{norm_prop!r}. Collision normalizing {prop!r}.") checker.update([norm_prop]) # This is just an extra-careful check, just in case there is some @@ -967,25 +889,8 @@ def validate_hist_bins(s): return validate_floatlist(s) except ValueError: pass - raise ValueError("'hist.bins' must be one of {}, an int or" - " a sequence of floats".format(valid_strs)) - - -@cbook.deprecated("3.3") -def validate_webagg_address(s): - if s is not None: - import socket - try: - socket.inet_aton(s) - except socket.error as e: - raise ValueError( - "'webagg.address' is not a valid IP address") from e - return s - raise ValueError("'webagg.address' is not a valid IP address") - - -validate_axes_titlelocation = ValidateInStrings( - 'axes.titlelocation', ['left', 'center', 'right'], _deprecated_since="3.3") + raise ValueError(f"'hist.bins' must be one of {valid_strs}, an int or" + " a sequence of floats") class _ignorecase(list): @@ -1003,13 +908,13 @@ def _convert_validator_spec(key, conv): # Mapping of rcParams to validators. # Converters given as lists or _ignorecase are converted to ValidateInStrings # immediately below. -# The rcParams defaults are defined in matplotlibrc.template, which gets copied -# to matplotlib/mpl-data/matplotlibrc by the setup script. +# The rcParams defaults are defined in lib/matplotlib/mpl-data/matplotlibrc, which +# gets copied to matplotlib/mpl-data/matplotlibrc by the setup script. _validators = { "backend": validate_backend, "backend_fallback": validate_bool, - "toolbar": _ignorecase(["none", "toolbar2", "toolmanager"]), - "datapath": validate_any, # see _get_data_path_cached + "figure.hooks": validate_stringlist, + "toolbar": _validate_toolbar, "interactive": validate_bool, "timezone": validate_string, @@ -1022,16 +927,16 @@ def _convert_validator_spec(key, conv): "lines.linewidth": validate_float, # line width in points "lines.linestyle": _validate_linestyle, # solid line "lines.color": validate_color, # first color in color cycle - "lines.marker": validate_string, # marker name + "lines.marker": _validate_marker, # marker name "lines.markerfacecolor": validate_color_or_auto, # default color "lines.markeredgecolor": validate_color_or_auto, # default color "lines.markeredgewidth": validate_float, "lines.markersize": validate_float, # markersize, in points "lines.antialiased": validate_bool, # antialiased (no jaggies) - "lines.dash_joinstyle": validate_joinstyle, - "lines.solid_joinstyle": validate_joinstyle, - "lines.dash_capstyle": validate_capstyle, - "lines.solid_capstyle": validate_capstyle, + "lines.dash_joinstyle": JoinStyle, + "lines.solid_joinstyle": JoinStyle, + "lines.dash_capstyle": CapStyle, + "lines.solid_capstyle": CapStyle, "lines.dashed_pattern": validate_floatlist, "lines.dashdot_pattern": validate_floatlist, "lines.dotted_pattern": validate_floatlist, @@ -1052,7 +957,7 @@ def _convert_validator_spec(key, conv): "patch.antialiased": validate_bool, # antialiased (no jaggies) ## hatch props - "hatch.color": validate_color, + "hatch.color": _validate_color_or_edge, "hatch.linewidth": validate_float, ## Histogram properties @@ -1071,7 +976,7 @@ def _convert_validator_spec(key, conv): "boxplot.meanline": validate_bool, "boxplot.flierprops.color": validate_color, - "boxplot.flierprops.marker": validate_string, + "boxplot.flierprops.marker": _validate_marker, "boxplot.flierprops.markerfacecolor": validate_color_or_auto, "boxplot.flierprops.markeredgecolor": validate_color, "boxplot.flierprops.markeredgewidth": validate_float, @@ -1096,7 +1001,7 @@ def _convert_validator_spec(key, conv): "boxplot.medianprops.linestyle": _validate_linestyle, "boxplot.meanprops.color": validate_color, - "boxplot.meanprops.marker": validate_string, + "boxplot.meanprops.marker": _validate_marker, "boxplot.meanprops.markerfacecolor": validate_color, "boxplot.meanprops.markeredgecolor": validate_color, "boxplot.meanprops.markersize": validate_float, @@ -1104,10 +1009,11 @@ def _convert_validator_spec(key, conv): "boxplot.meanprops.linewidth": validate_float, ## font props + "font.enable_last_resort": validate_bool, "font.family": validate_stringlist, # used by text object "font.style": validate_string, "font.variant": validate_string, - "font.stretch": validate_string, + "font.stretch": validate_fontstretch, "font.weight": validate_fontweight, "font.size": validate_float, # Base font size in points "font.serif": validate_stringlist, @@ -1119,40 +1025,43 @@ def _convert_validator_spec(key, conv): # text props "text.color": validate_color, "text.usetex": validate_bool, - "text.latex.preamble": _validate_tex_preamble, - "text.latex.preview": validate_bool, - "text.hinting": _validate_hinting, + "text.latex.preamble": validate_string, + "text.hinting": ["default", "no_autohint", "force_autohint", + "no_hinting", "auto", "native", "either", "none"], "text.hinting_factor": validate_int, "text.kerning_factor": validate_int, "text.antialiased": validate_bool, + "text.parse_math": validate_bool, "mathtext.cal": validate_font_properties, "mathtext.rm": validate_font_properties, "mathtext.tt": validate_font_properties, "mathtext.it": validate_font_properties, "mathtext.bf": validate_font_properties, + "mathtext.bfit": validate_font_properties, "mathtext.sf": validate_font_properties, "mathtext.fontset": ["dejavusans", "dejavuserif", "cm", "stix", "stixsans", "custom"], - "mathtext.default": ["rm", "cal", "it", "tt", "sf", "bf", "default", + "mathtext.default": ["rm", "cal", "bfit", "it", "tt", "sf", "bf", "default", "bb", "frak", "scr", "regular"], - "mathtext.fallback_to_cm": _validate_mathtext_fallback_to_cm, "mathtext.fallback": _validate_mathtext_fallback, - "image.aspect": validate_aspect, # equal, auto, a number - "image.interpolation": validate_string, - "image.cmap": validate_string, # gray, jet, etc. - "image.lut": validate_int, # lookup table - "image.origin": ["upper", "lower"], - "image.resample": validate_bool, + "image.aspect": validate_aspect, # equal, auto, a number + "image.interpolation": validate_string, + "image.interpolation_stage": ["auto", "data", "rgba"], + "image.cmap": _validate_cmap, # gray, jet, etc. + "image.lut": validate_int, # lookup table + "image.origin": ["upper", "lower"], + "image.resample": validate_bool, # Specify whether vector graphics backends will combine all images on a - # set of axes into a single composite image + # set of Axes into a single composite image "image.composite_image": validate_bool, # contour props "contour.negative_linestyle": _validate_linestyle, "contour.corner_mask": validate_bool, "contour.linewidth": validate_float_or_None, + "contour.algorithm": ["mpl2005", "mpl2014", "serial", "threaded"], # errorbar props "errorbar.capsize": validate_float, @@ -1162,7 +1071,7 @@ def _convert_validator_spec(key, conv): "xaxis.labellocation": ["left", "center", "right"], "yaxis.labellocation": ["bottom", "center", "top"], - # axes props + # Axes props "axes.axisbelow": validate_axisbelow, "axes.facecolor": validate_color, # background color "axes.edgecolor": validate_color, # edge color @@ -1173,13 +1082,13 @@ def _convert_validator_spec(key, conv): "axes.spines.bottom": validate_bool, # denoting data boundary. "axes.spines.top": validate_bool, - "axes.titlesize": validate_fontsize, # axes title fontsize - "axes.titlelocation": ["left", "center", "right"], # axes title alignment - "axes.titleweight": validate_fontweight, # axes title font weight - "axes.titlecolor": validate_color_or_auto, # axes title font color + "axes.titlesize": validate_fontsize, # Axes title fontsize + "axes.titlelocation": ["left", "center", "right"], # Axes title alignment + "axes.titleweight": validate_fontweight, # Axes title font weight + "axes.titlecolor": validate_color_or_auto, # Axes title font color # title location, axes units, None means auto "axes.titley": validate_float_or_None, - # pad from axes top decoration to title in points + # pad from Axes top decoration to title in points "axes.titlepad": validate_float, "axes.grid": validate_bool, # display grid or not "axes.grid.which": ["minor", "both", "major"], # which grids are drawn @@ -1205,14 +1114,28 @@ def _convert_validator_spec(key, conv): # If "data", axes limits are set close to the data. # If "round_numbers" axes limits are set to the nearest round numbers. "axes.autolimit_mode": ["data", "round_numbers"], - "axes.xmargin": _range_validators["0 <= x <= 1"], # margin added to xaxis - "axes.ymargin": _range_validators["0 <= x <= 1"], # margin added to yaxis + "axes.xmargin": _validate_greaterthan_minushalf, # margin added to xaxis + "axes.ymargin": _validate_greaterthan_minushalf, # margin added to yaxis + "axes.zmargin": _validate_greaterthan_minushalf, # margin added to zaxis + + "polaraxes.grid": validate_bool, # display polar grid or not + "axes3d.grid": validate_bool, # display 3d grid + "axes3d.automargin": validate_bool, # automatically add margin when + # manually setting 3D axis limits + + "axes3d.xaxis.panecolor": validate_color, # 3d background pane + "axes3d.yaxis.panecolor": validate_color, # 3d background pane + "axes3d.zaxis.panecolor": validate_color, # 3d background pane + + "axes3d.depthshade": validate_bool, # depth shade for 3D scatter plots + "axes3d.depthshade_minalpha": validate_float, # min alpha value for depth shading - "polaraxes.grid": validate_bool, # display polar grid or not - "axes3d.grid": validate_bool, # display 3d grid + "axes3d.mouserotationstyle": ["azel", "trackball", "sphere", "arcball"], + "axes3d.trackballsize": validate_float, + "axes3d.trackballborder": validate_float, # scatter props - "scatter.marker": validate_string, + "scatter.marker": _validate_marker, "scatter.edgecolors": validate_string, "date.epoch": _validate_date, @@ -1224,18 +1147,13 @@ def _convert_validator_spec(key, conv): "date.autoformatter.second": validate_string, "date.autoformatter.microsecond": validate_string, - # 'auto', 'concise', 'auto-noninterval' - 'date.converter': _validate_date_converter, + 'date.converter': ['auto', 'concise'], # for auto date locator, choose interval_multiples - 'date.interval_multiples': _validate_date_int_mult, + 'date.interval_multiples': validate_bool, # legend properties "legend.fancybox": validate_bool, - "legend.loc": _ignorecase([ - "best", - "upper right", "upper left", "lower left", "lower right", "right", - "center left", "center right", "lower center", "upper center", - "center"]), + "legend.loc": _validate_legend_loc, # the number of points in the legend line "legend.numpoints": validate_int, @@ -1243,12 +1161,15 @@ def _convert_validator_spec(key, conv): "legend.scatterpoints": validate_int, "legend.fontsize": validate_fontsize, "legend.title_fontsize": validate_fontsize_None, - # the relative size of legend markers vs. original + # color of the legend + "legend.labelcolor": _validate_color_or_linecolor, + # the relative size of legend markers vs. original "legend.markerscale": validate_float, + # using dict in rcParams not yet supported, so make sure it is bool "legend.shadow": validate_bool, - # whether or not to draw a frame around legend + # whether or not to draw a frame around legend "legend.frameon": validate_bool, - # alpha value of the legend frame + # alpha value of the legend frame "legend.framealpha": validate_float_or_None, ## the following dimensions are in fraction of the font size @@ -1261,9 +1182,9 @@ def _convert_validator_spec(key, conv): "legend.handleheight": validate_float, # the space between the legend line and legend text "legend.handletextpad": validate_float, - # the border between the axes and legend edge + # the border between the Axes and legend edge "legend.borderaxespad": validate_float, - # the border between the axes and legend edge + # the border between the Axes and legend edge "legend.columnspacing": validate_float, "legend.facecolor": validate_color_or_inherit, "legend.edgecolor": validate_color_or_inherit, @@ -1280,15 +1201,16 @@ def _convert_validator_spec(key, conv): "xtick.major.pad": validate_float, # distance to label in points "xtick.minor.pad": validate_float, # distance to label in points "xtick.color": validate_color, # color of xticks - "xtick.labelcolor": validate_color_or_inherit, - # color of xtick labels + "xtick.labelcolor": validate_color_or_inherit, # color of xtick labels "xtick.minor.visible": validate_bool, # visibility of minor xticks "xtick.minor.top": validate_bool, # draw top minor xticks "xtick.minor.bottom": validate_bool, # draw bottom minor xticks "xtick.major.top": validate_bool, # draw top major xticks "xtick.major.bottom": validate_bool, # draw bottom major xticks + # number of minor xticks + "xtick.minor.ndivs": _validate_minor_tick_ndivs, "xtick.labelsize": validate_fontsize, # fontsize of xtick labels - "xtick.direction": validate_string, # direction of xticks + "xtick.direction": ["out", "in", "inout"], # direction of xticks "xtick.alignment": ["center", "right", "left"], "ytick.left": validate_bool, # draw ticks on left side @@ -1302,15 +1224,16 @@ def _convert_validator_spec(key, conv): "ytick.major.pad": validate_float, # distance to label in points "ytick.minor.pad": validate_float, # distance to label in points "ytick.color": validate_color, # color of yticks - "ytick.labelcolor": validate_color_or_inherit, - # color of ytick labels + "ytick.labelcolor": validate_color_or_inherit, # color of ytick labels "ytick.minor.visible": validate_bool, # visibility of minor yticks "ytick.minor.left": validate_bool, # draw left minor yticks "ytick.minor.right": validate_bool, # draw right minor yticks "ytick.major.left": validate_bool, # draw left major yticks "ytick.major.right": validate_bool, # draw right major yticks + # number of minor yticks + "ytick.minor.ndivs": _validate_minor_tick_ndivs, "ytick.labelsize": validate_fontsize, # fontsize of ytick labels - "ytick.direction": validate_string, # direction of yticks + "ytick.direction": ["out", "in", "inout"], # direction of yticks "ytick.alignment": [ "center", "top", "bottom", "baseline", "center_baseline"], @@ -1324,6 +1247,10 @@ def _convert_validator_spec(key, conv): "figure.titlesize": validate_fontsize, "figure.titleweight": validate_fontweight, + # figure labels + "figure.labelsize": validate_fontsize, + "figure.labelweight": validate_fontweight, + # figure size in inches: width by height "figure.figsize": _listify_validator(validate_float, n=2), "figure.dpi": validate_float, @@ -1333,42 +1260,42 @@ def _convert_validator_spec(key, conv): "figure.autolayout": validate_bool, "figure.max_open_warning": validate_int, "figure.raise_window": validate_bool, + "macosx.window_mode": ["system", "tab", "window"], - "figure.subplot.left": _range_validators["0 <= x <= 1"], - "figure.subplot.right": _range_validators["0 <= x <= 1"], - "figure.subplot.bottom": _range_validators["0 <= x <= 1"], - "figure.subplot.top": _range_validators["0 <= x <= 1"], - "figure.subplot.wspace": _range_validators["0 <= x < 1"], - "figure.subplot.hspace": _range_validators["0 <= x < 1"], + "figure.subplot.left": validate_float, + "figure.subplot.right": validate_float, + "figure.subplot.bottom": validate_float, + "figure.subplot.top": validate_float, + "figure.subplot.wspace": validate_float, + "figure.subplot.hspace": validate_float, "figure.constrained_layout.use": validate_bool, # run constrained_layout? # wspace and hspace are fraction of adjacent subplots to use for space. # Much smaller than above because we don't need room for the text. - "figure.constrained_layout.hspace": _range_validators["0 <= x < 1"], - "figure.constrained_layout.wspace": _range_validators["0 <= x < 1"], - # buffer around the axes, in inches. - 'figure.constrained_layout.h_pad': validate_float, - 'figure.constrained_layout.w_pad': validate_float, + "figure.constrained_layout.hspace": validate_float, + "figure.constrained_layout.wspace": validate_float, + # buffer around the Axes, in inches. + "figure.constrained_layout.h_pad": validate_float, + "figure.constrained_layout.w_pad": validate_float, ## Saving figure's properties 'savefig.dpi': validate_dpi, 'savefig.facecolor': validate_color_or_auto, 'savefig.edgecolor': validate_color_or_auto, 'savefig.orientation': ['landscape', 'portrait'], - 'savefig.jpeg_quality': validate_int, "savefig.format": validate_string, "savefig.bbox": validate_bbox, # "tight", or "standard" (= None) "savefig.pad_inches": validate_float, # default directory in savefig dialog box - "savefig.directory": validate_string, + "savefig.directory": _validate_pathlike, "savefig.transparent": validate_bool, "tk.window_focus": validate_bool, # Maintain shell focus for TkAgg # Set the papersize/type - "ps.papersize": _ignorecase(["auto", "letter", "legal", "ledger", - *[f"{ab}{i}" - for ab in "ab" for i in range(11)]]), + "ps.papersize": _ignorecase( + ["figure", "letter", "legal", "ledger", + *[f"{ab}{i}" for ab in "ab" for i in range(11)]]), "ps.useafm": validate_bool, # use ghostscript or xpdf to distill ps output "ps.usedistiller": validate_ps_distiller, @@ -1382,18 +1309,19 @@ def _convert_validator_spec(key, conv): "pgf.texsystem": ["xelatex", "lualatex", "pdflatex"], # latex variant used "pgf.rcfonts": validate_bool, # use mpl's rc settings for font config - "pgf.preamble": _validate_tex_preamble, # custom LaTeX preamble + "pgf.preamble": validate_string, # custom LaTeX preamble # write raster image data into the svg file "svg.image_inline": validate_bool, "svg.fonttype": ["none", "path"], # save text as text ("none") or "paths" "svg.hashsalt": validate_string_or_None, + "svg.id": validate_string_or_None, # set this when you want to generate hardcopy docstring "docstring.hardcopy": validate_bool, "path.simplify": validate_bool, - "path.simplify_threshold": _range_validators["0 <= x <= 1"], + "path.simplify_threshold": _validate_greaterequal0_lessequal1, "path.snap": validate_bool, "path.sketch": validate_sketch, "path.effects": validate_anylist, @@ -1413,7 +1341,6 @@ def _convert_validator_spec(key, conv): "keymap.grid_minor": validate_stringlist, "keymap.yscale": validate_stringlist, "keymap.xscale": validate_stringlist, - "keymap.all_axes": validate_stringlist, "keymap.help": validate_stringlist, "keymap.copy": validate_stringlist, @@ -1428,42 +1355,28 @@ def _convert_validator_spec(key, conv): # Controls image format when frames are written to disk "animation.frame_format": ["png", "jpeg", "tiff", "raw", "rgba", "ppm", "sgi", "bmp", "pbm", "svg"], - # Additional arguments for HTML writer - "animation.html_args": validate_stringlist, # Path to ffmpeg binary. If just binary name, subprocess uses $PATH. - "animation.ffmpeg_path": validate_string, + "animation.ffmpeg_path": _validate_pathlike, # Additional arguments for ffmpeg movie writer (using pipes) "animation.ffmpeg_args": validate_stringlist, - # Path to AVConv binary. If just binary name, subprocess uses $PATH. - "animation.avconv_path": validate_string, - # Additional arguments for avconv movie writer (using pipes) - "animation.avconv_args": validate_stringlist, # Path to convert binary. If just binary name, subprocess uses $PATH. - "animation.convert_path": validate_string, + "animation.convert_path": _validate_pathlike, # Additional arguments for convert movie writer (using pipes) "animation.convert_args": validate_stringlist, - "mpl_toolkits.legacy_colorbar": validate_bool, - # Classic (pre 2.0) compatibility mode # This is used for things that are hard to make backward compatible # with a sane rcParam alone. This does *not* turn on classic mode # altogether. For that use `matplotlib.style.use("classic")`. "_internal.classic_mode": validate_bool } -_hardcoded_defaults = { # Defaults not inferred from matplotlibrc.template... - # ... because it can"t be: - "backend": _auto_backend_sentinel, +_hardcoded_defaults = { # Defaults not inferred from + # lib/matplotlib/mpl-data/matplotlibrc... # ... because they are private: "_internal.classic_mode": False, # ... because they are deprecated: - "animation.avconv_path": "avconv", - "animation.avconv_args": [], - "animation.html_args": [], - "mathtext.fallback_to_cm": None, - "keymap.all_axes": ["a"], - "savefig.jpeg_quality": 95, - "text.latex.preview": False, + # No current deprecations. + # backend is handled separately when constructing rcParamsDefault. } _validators = {k: _convert_validator_spec(k, conv) for k, conv in _validators.items()} diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi new file mode 100644 index 000000000000..eb1d7c9f3a33 --- /dev/null +++ b/lib/matplotlib/rcsetup.pyi @@ -0,0 +1,157 @@ +from cycler import Cycler + +from collections.abc import Callable, Iterable +from typing import Any, Literal, TypeVar +from matplotlib.typing import ColorType, LineStyleType, MarkEveryType + + +_T = TypeVar("_T") + +def _listify_validator(s: Callable[[Any], _T]) -> Callable[[Any], list[_T]]: ... + +class ValidateInStrings: + key: str + ignorecase: bool + valid: dict[str, str] + def __init__( + self, + key: str, + valid: Iterable[str], + ignorecase: bool = ..., + *, + _deprecated_since: str | None = ... + ) -> None: ... + def __call__(self, s: Any) -> str: ... + +def validate_any(s: Any) -> Any: ... +def validate_anylist(s: Any) -> list[Any]: ... +def validate_bool(b: Any) -> bool: ... +def validate_axisbelow(s: Any) -> bool | Literal["line"]: ... +def validate_dpi(s: Any) -> Literal["figure"] | float: ... +def validate_string(s: Any) -> str: ... +def validate_string_or_None(s: Any) -> str | None: ... +def validate_stringlist(s: Any) -> list[str]: ... +def validate_int(s: Any) -> int: ... +def validate_int_or_None(s: Any) -> int | None: ... +def validate_float(s: Any) -> float: ... +def validate_float_or_None(s: Any) -> float | None: ... +def validate_floatlist(s: Any) -> list[float]: ... +def _validate_marker(s: Any) -> int | str: ... +def _validate_markerlist(s: Any) -> list[int | str]: ... +def validate_fonttype(s: Any) -> int: ... + +_auto_backend_sentinel: object + +def validate_backend(s: Any) -> str: ... +def validate_color_or_inherit(s: Any) -> Literal["inherit"] | ColorType: ... +def validate_color_or_auto(s: Any) -> ColorType | Literal["auto"]: ... +def _validate_color_or_edge(s: Any) -> ColorType | Literal["edge"]: ... +def validate_color_for_prop_cycle(s: Any) -> ColorType: ... +def validate_color(s: Any) -> ColorType: ... +def validate_colorlist(s: Any) -> list[ColorType]: ... +def _validate_color_or_linecolor( + s: Any, +) -> ColorType | Literal["linecolor", "markerfacecolor", "markeredgecolor"] | None: ... +def validate_aspect(s: Any) -> Literal["auto", "equal"] | float: ... +def validate_fontsize_None( + s: Any, +) -> Literal[ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger", +] | float | None: ... +def validate_fontsize( + s: Any, +) -> Literal[ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger", +] | float: ... +def validate_fontsizelist( + s: Any, +) -> list[ + Literal[ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger", + ] + | float +]: ... +def validate_fontweight( + s: Any, +) -> Literal[ + "ultralight", + "light", + "normal", + "regular", + "book", + "medium", + "roman", + "semibold", + "demibold", + "demi", + "bold", + "heavy", + "extra bold", + "black", +] | int: ... +def validate_fontstretch( + s: Any, +) -> Literal[ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded", +] | int: ... +def validate_font_properties(s: Any) -> dict[str, Any]: ... +def validate_whiskers(s: Any) -> list[float] | float: ... +def validate_ps_distiller(s: Any) -> None | Literal["ghostscript", "xpdf"]: ... + +validate_fillstyle: ValidateInStrings + +def validate_fillstylelist( + s: Any, +) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ... +def validate_markevery(s: Any) -> MarkEveryType: ... +def _validate_linestyle(s: Any) -> LineStyleType: ... +def validate_markeverylist(s: Any) -> list[MarkEveryType]: ... +def validate_bbox(s: Any) -> Literal["tight", "standard"] | None: ... +def validate_sketch(s: Any) -> None | tuple[float, float, float]: ... +def validate_hatch(s: Any) -> str: ... +def validate_hatchlist(s: Any) -> list[str]: ... +def validate_dashlist(s: Any) -> list[list[float]]: ... + +# TODO: copy cycler overloads? +def cycler(*args, **kwargs) -> Cycler: ... +def validate_cycler(s: Any) -> Cycler: ... +def validate_hist_bins( + s: Any, +) -> Literal["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"] | int | list[ + float +]: ... + +# At runtime is added in __init__.py +defaultParams: dict[str, Any] diff --git a/lib/matplotlib/sankey.py b/lib/matplotlib/sankey.py index 72bc6b205e12..637cfc849f9d 100644 --- a/lib/matplotlib/sankey.py +++ b/lib/matplotlib/sankey.py @@ -11,7 +11,7 @@ from matplotlib.path import Path from matplotlib.patches import PathPatch from matplotlib.transforms import Affine2D -from matplotlib import docstring +from matplotlib import _docstring _log = logging.getLogger(__name__) @@ -49,7 +49,7 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, that there is consistent alignment and formatting. In order to draw a complex Sankey diagram, create an instance of - :class:`Sankey` by calling it without any kwargs:: + `Sankey` by calling it without any kwargs:: sankey = Sankey() @@ -70,7 +70,7 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, Other Parameters ---------------- - ax : `~.axes.Axes` + ax : `~matplotlib.axes.Axes` Axes onto which the data should be plotted. If *ax* isn't provided, new Axes will be created. scale : float @@ -83,10 +83,12 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, unit : str The physical unit associated with the flow quantities. If *unit* is None, then none of the quantities are labeled. - format : str - A Python number formatting string to be used in labeling the flow - as a quantity (i.e., a number times a unit, where the unit is - given). + format : str or callable + A Python number formatting string or callable used to label the + flows with their quantities (i.e., a number times a unit, where the + unit is given). If a format string is given, the label will be + ``format % quantity``. If a callable is given, it will be called + with ``quantity`` as an argument. gap : float Space between paths that break in/break away to/from the top or bottom. @@ -107,8 +109,8 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, magnitude of the sum of connected flows cannot be greater than *tolerance*. **kwargs - Any additional keyword arguments will be passed to :meth:`add`, - which will create the first subdiagram. + Any additional keyword arguments will be passed to `add`, which + will create the first subdiagram. See Also -------- @@ -136,7 +138,7 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, raise ValueError( "'tolerance' is negative, but it must be a magnitude") - # Create axes if necessary. + # Create Axes if necessary. if ax is None: import matplotlib.pyplot as plt fig = plt.figure() @@ -202,12 +204,12 @@ def _arc(self, quadrant=0, cw=True, radius=1, center=(0, 0)): # Insignificant # [6.12303177e-17, 1.00000000e+00]]) [0.00000000e+00, 1.00000000e+00]]) - if quadrant == 0 or quadrant == 2: + if quadrant in (0, 2): if cw: vertices = ARC_VERTICES else: vertices = ARC_VERTICES[:, ::-1] # Swap x and y. - elif quadrant == 1 or quadrant == 3: + else: # 1, 3 # Negate x. if cw: # Swap x and y. @@ -297,15 +299,11 @@ def _add_output(self, path, angle, flow, length): else: # Vertical x += self.gap if angle == UP: - sign = 1 + sign, quadrant = 1, 3 else: - sign = -1 + sign, quadrant = -1, 0 tip = [x - flow / 2.0, y + sign * (length + tipheight)] - if angle == UP: - quadrant = 3 - else: - quadrant = 0 # Inner arc isn't needed if inner radius is zero if self.radius: path.extend(self._arc(quadrant=quadrant, @@ -349,7 +347,7 @@ def _revert(self, path, first_action=Path.LINETO): # path[2] = path[2][::-1] # return path - @docstring.dedent_interpd + @_docstring.interpd def add(self, patchlabel='', flows=None, orientations=None, labels='', trunklength=1.0, pathlengths=0.25, prior=None, connect=(0, 0), rotation=0, **kwargs): @@ -373,7 +371,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', the outside in. If the sum of the inputs and outputs is - nonzero, the discrepancy will appear as a cubic Bezier curve along + nonzero, the discrepancy will appear as a cubic Bézier curve along the top and bottom edges of the trunk. orientations : list of {-1, 0, 1} @@ -429,17 +427,14 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', properties, listed below. For example, one may want to use ``fill=False`` or ``label="A legend entry"``. - %(Patch)s + %(Patch:kwdoc)s See Also -------- Sankey.finish """ # Check and preprocess the arguments. - if flows is None: - flows = np.array([1.0, -1.0]) - else: - flows = np.array(flows) + flows = np.array([1.0, -1.0]) if flows is None else np.array(flows) n = flows.shape[0] # Number of flows if rotation is None: rotation = 0 @@ -523,7 +518,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', if orient == 1: if is_input: angles[i] = DOWN - elif not is_input: + elif is_input is False: # Be specific since is_input can be None. angles[i] = UP elif orient == 0: @@ -536,7 +531,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', f"but it must be -1, 0, or 1") if is_input: angles[i] = UP - elif not is_input: + elif is_input is False: angles[i] = DOWN # Justify the lengths of the paths. @@ -559,7 +554,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', if angle == DOWN and is_input: pathlengths[i] = ullength ullength += flow - elif angle == UP and not is_input: + elif angle == UP and is_input is False: pathlengths[i] = urlength urlength -= flow # Flow is negative for outputs. # Determine the lengths of the bottom-side arrows @@ -569,7 +564,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', if angle == UP and is_input: pathlengths[n - i - 1] = lllength lllength += flow - elif angle == DOWN and not is_input: + elif angle == DOWN and is_input is False: pathlengths[n - i - 1] = lrlength lrlength -= flow # Determine the lengths of the left-side arrows @@ -589,7 +584,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', for i, (angle, is_input, spec) in enumerate(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))): if angle == RIGHT: - if not is_input: + if is_input is False: if has_right_output: pathlengths[i] = 0 else: @@ -635,7 +630,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', if angle == DOWN and is_input: tips[i, :], label_locations[i, :] = self._add_input( ulpath, angle, *spec) - elif angle == UP and not is_input: + elif angle == UP and is_input is False: tips[i, :], label_locations[i, :] = self._add_output( urpath, angle, *spec) # Add the bottom-side inputs and outputs from the middle outwards. @@ -645,7 +640,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', tip, label_location = self._add_input(llpath, angle, *spec) tips[n - i - 1, :] = tip label_locations[n - i - 1, :] = label_location - elif angle == DOWN and not is_input: + elif angle == DOWN and is_input is False: tip, label_location = self._add_output(lrpath, angle, *spec) tips[n - i - 1, :] = tip label_locations[n - i - 1, :] = label_location @@ -668,7 +663,7 @@ def add(self, patchlabel='', flows=None, orientations=None, labels='', has_right_output = False for i, (angle, is_input, spec) in enumerate(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))): - if angle == RIGHT and not is_input: + if angle == RIGHT and is_input is False: if not has_right_output: # Make sure the upper path extends # at least as far as the lower one. @@ -728,7 +723,7 @@ def _get_angle(a, r): fc = kwargs.pop('fc', kwargs.pop('facecolor', None)) lw = kwargs.pop('lw', kwargs.pop('linewidth', None)) if fc is None: - fc = next(self.ax._get_patches_for_fill.prop_cycler)['color'] + fc = self.ax._get_patches_for_fill.get_next_color() patch = PathPatch(Path(vertices, codes), fc=fc, lw=lw, **kwargs) self.ax.add_patch(patch) @@ -739,7 +734,13 @@ def _get_angle(a, r): if label is None or angle is None: label = '' elif self.unit is not None: - quantity = self.format % abs(number) + self.unit + if isinstance(self.format, str): + quantity = self.format % abs(number) + self.unit + elif callable(self.format): + quantity = self.format(number) + else: + raise TypeError( + 'format must be callable or a format string') if label != '': label += "\n" label += quantity @@ -776,38 +777,30 @@ def _get_angle(a, r): def finish(self): """ - Adjust the axes and return a list of information about the Sankey + Adjust the Axes and return a list of information about the Sankey subdiagram(s). - Return value is a list of subdiagrams represented with the following - fields: - - =============== =================================================== - Field Description - =============== =================================================== - *patch* Sankey outline (an instance of - :class:`~matplotlib.patches.PathPatch`) - *flows* values of the flows (positive for input, negative - for output) - *angles* list of angles of the arrows [deg/90] - For example, if the diagram has not been rotated, - an input to the top side will have an angle of 3 - (DOWN), and an output from the top side will have - an angle of 1 (UP). If a flow has been skipped - (because its magnitude is less than *tolerance*), - then its angle will be *None*. - *tips* array in which each row is an [x, y] pair - indicating the positions of the tips (or "dips") of - the flow paths - If the magnitude of a flow is less the *tolerance* - for the instance of :class:`Sankey`, the flow is - skipped and its tip will be at the center of the - diagram. - *text* :class:`~matplotlib.text.Text` instance for the - label of the diagram - *texts* list of :class:`~matplotlib.text.Text` instances - for the labels of flows - =============== =================================================== + Returns a list of subdiagrams with the following fields: + + ======== ============================================================= + Field Description + ======== ============================================================= + *patch* Sankey outline (a `~matplotlib.patches.PathPatch`). + *flows* Flow values (positive for input, negative for output). + *angles* List of angles of the arrows [deg/90]. + For example, if the diagram has not been rotated, + an input to the top side has an angle of 3 (DOWN), + and an output from the top side has an angle of 1 (UP). + If a flow has been skipped (because its magnitude is less + than *tolerance*), then its angle will be *None*. + *tips* (N, 2)-array of the (x, y) positions of the tips (or "dips") + of the flow paths. + If the magnitude of a flow is less the *tolerance* of this + `Sankey` instance, the flow is skipped and its tip will be at + the center of the diagram. + *text* `.Text` instance for the diagram label. + *texts* List of `.Text` instances for the flow labels. + ======== ============================================================= See Also -------- diff --git a/lib/matplotlib/sankey.pyi b/lib/matplotlib/sankey.pyi new file mode 100644 index 000000000000..33565b998a9c --- /dev/null +++ b/lib/matplotlib/sankey.pyi @@ -0,0 +1,61 @@ +from matplotlib.axes import Axes + +from collections.abc import Callable, Iterable +from typing import Any +from typing_extensions import Self # < Py 3.11 + +import numpy as np + +__license__: str +__credits__: list[str] +__author__: str +__version__: str + +RIGHT: int +UP: int +DOWN: int + +# TODO typing units +class Sankey: + diagrams: list[Any] + ax: Axes + unit: Any + format: str | Callable[[float], str] + scale: float + gap: float + radius: float + shoulder: float + offset: float + margin: float + pitch: float + tolerance: float + extent: np.ndarray + def __init__( + self, + ax: Axes | None = ..., + scale: float = ..., + unit: Any = ..., + format: str | Callable[[float], str] = ..., + gap: float = ..., + radius: float = ..., + shoulder: float = ..., + offset: float = ..., + head_angle: float = ..., + margin: float = ..., + tolerance: float = ..., + **kwargs + ) -> None: ... + def add( + self, + patchlabel: str = ..., + flows: Iterable[float] | None = ..., + orientations: Iterable[int] | None = ..., + labels: str | Iterable[str | None] = ..., + trunklength: float = ..., + pathlengths: float | Iterable[float] = ..., + prior: int | None = ..., + connect: tuple[int, int] = ..., + rotation: float = ..., + **kwargs + ) -> Self: ... + def finish(self) -> list[Any]: ... diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 0e1b1a0cb4ac..44fbe5209c4d 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -1,24 +1,45 @@ """ Scales define the distribution of data values on an axis, e.g. a log scaling. -They are attached to an `~.axis.Axis` and hold a `.Transform`, which is -responsible for the actual data transformation. +The mapping is implemented through `.Transform` subclasses. -See also `.axes.Axes.set_xscale` and the scales examples in the documentation. -""" +The following scales are built-in: + +.. _builtin_scales: + +============= ===================== ================================ ================================= +Name Class Transform Inverted transform +============= ===================== ================================ ================================= +"asinh" `AsinhScale` `AsinhTransform` `InvertedAsinhTransform` +"function" `FuncScale` `FuncTransform` `FuncTransform` +"functionlog" `FuncScaleLog` `FuncTransform` + `LogTransform` `InvertedLogTransform` + `FuncTransform` +"linear" `LinearScale` `.IdentityTransform` `.IdentityTransform` +"log" `LogScale` `LogTransform` `InvertedLogTransform` +"logit" `LogitScale` `LogitTransform` `LogisticTransform` +"symlog" `SymmetricalLogScale` `SymmetricalLogTransform` `InvertedSymmetricalLogTransform` +============= ===================== ================================ ================================= + +A user will often only use the scale name, e.g. when setting the scale through +`~.Axes.set_xscale`: ``ax.set_xscale("log")``. + +See also the :ref:`scales examples ` in the documentation. + +Custom scaling can be achieved through `FuncScale`, or by creating your own +`ScaleBase` subclass and corresponding transforms (see :doc:`/gallery/scales/custom_scale`). +Third parties can register their scales by name through `register_scale`. +""" # noqa: E501 import inspect import textwrap import numpy as np -from numpy import ma import matplotlib as mpl -from matplotlib import cbook, docstring +from matplotlib import _api, _docstring from matplotlib.ticker import ( NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter, NullLocator, LogLocator, AutoLocator, AutoMinorLocator, - SymmetricalLogLocator, LogitLocator) + SymmetricalLogLocator, AsinhLocator, LogitLocator) from matplotlib.transforms import Transform, IdentityTransform @@ -28,16 +49,20 @@ class ScaleBase: Scales are separable transformations, working on a single dimension. - Any subclasses will want to override: - - - :attr:`name` - - :meth:`get_transform` - - :meth:`set_default_locators_and_formatters` - - And optionally: - - - :meth:`limit_range_for_scale` - + Subclasses should override + + :attr:`!name` + The scale's name. + :meth:`get_transform` + A method returning a `.Transform`, which converts data coordinates to + scaled coordinates. This transform should be invertible, so that e.g. + mouse positions can be converted back to data coordinates. + :meth:`set_default_locators_and_formatters` + A method that sets default locators and formatters for an `~.axis.Axis` + that uses this scale. + :meth:`limit_range_for_scale` + An optional method that "fixes" the axis range to acceptable values, + e.g. restricting log-scaled axes to positive values. """ def __init__(self, axis): @@ -46,7 +71,7 @@ def __init__(self, axis): Notes ----- - The following note is for scale implementors. + The following note is for scale implementers. For back-compatibility reasons, scales take an `~matplotlib.axis.Axis` object as first argument. However, this argument should not @@ -56,8 +81,7 @@ def __init__(self, axis): def get_transform(self): """ - Return the :class:`~matplotlib.transforms.Transform` object - associated with this scale. + Return the `.Transform` object associated with this scale. """ raise NotImplementedError() @@ -91,7 +115,7 @@ def __init__(self, axis): # constructor docstring, which would otherwise end up interpolated into # the docstring of Axis.set_scale. """ - """ + """ # noqa: D419 def set_default_locators_and_formatters(self, axis): # docstring inherited @@ -194,27 +218,26 @@ def set_default_locators_and_formatters(self, axis): class LogTransform(Transform): input_dims = output_dims = 1 - @cbook._rename_parameter("3.3", "nonpos", "nonpositive") def __init__(self, base, nonpositive='clip'): super().__init__() if base <= 0 or base == 1: raise ValueError('The log base cannot be <= 0 or == 1') self.base = base - self._clip = cbook._check_getitem( + self._clip = _api.check_getitem( {"clip": True, "mask": False}, nonpositive=nonpositive) def __str__(self): return "{}(base={}, nonpositive={!r})".format( type(self).__name__, self.base, "clip" if self._clip else "mask") - def transform_non_affine(self, a): + def transform_non_affine(self, values): # Ignore invalid values due to nans being passed to the transform. with np.errstate(divide="ignore", invalid="ignore"): log = {np.e: np.log, 2: np.log2, 10: np.log10}.get(self.base) if log: # If possible, do everything in a single call to NumPy. - out = log(a) + out = log(values) else: - out = np.log(a) + out = np.log(values) out /= np.log(self.base) if self._clip: # SVG spec says that conforming viewers must support values up @@ -226,7 +249,7 @@ def transform_non_affine(self, a): # pass. On the other hand, in practice, we want to clip beyond # np.log10(np.nextafter(0, 1)) ~ -323 # so 1000 seems safe. - out[a <= 0] = -1000 + out[values <= 0] = -1000 return out def inverted(self): @@ -241,10 +264,10 @@ def __init__(self, base): self.base = base def __str__(self): - return "{}(base={})".format(type(self).__name__, self.base) + return f"{type(self).__name__}(base={self.base})" - def transform_non_affine(self, a): - return ma.power(self.base, a) + def transform_non_affine(self, values): + return np.power(self.base, values) def inverted(self): return LogTransform(self.base) @@ -256,17 +279,7 @@ class LogScale(ScaleBase): """ name = 'log' - @cbook.deprecated("3.3", alternative="scale.LogTransform") - @property - def LogTransform(self): - return LogTransform - - @cbook.deprecated("3.3", alternative="scale.InvertedLogTransform") - @property - def InvertedLogTransform(self): - return InvertedLogTransform - - def __init__(self, axis, **kwargs): + def __init__(self, axis, *, base=10, subs=None, nonpositive="clip"): """ Parameters ---------- @@ -282,18 +295,6 @@ def __init__(self, axis, **kwargs): in a log10 scale, ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 8 logarithmically spaced minor ticks between each major tick. """ - # After the deprecation, the whole (outer) __init__ can be replaced by - # def __init__(self, axis, *, base=10, subs=None, nonpositive="clip") - # The following is to emit the right warnings depending on the axis - # used, as the *old* kwarg names depended on the axis. - axis_name = getattr(axis, "axis_name", "x") - @cbook._rename_parameter("3.3", f"base{axis_name}", "base") - @cbook._rename_parameter("3.3", f"subs{axis_name}", "subs") - @cbook._rename_parameter("3.3", f"nonpos{axis_name}", "nonpositive") - def __init__(*, base=10, subs=None, nonpositive="clip"): - return base, subs, nonpositive - - base, subs, nonpositive = __init__(**kwargs) self._transform = LogTransform(base, nonpositive) self.subs = subs @@ -333,7 +334,7 @@ def __init__(self, axis, functions, base=10): """ Parameters ---------- - axis : `matplotlib.axis.Axis` + axis : `~matplotlib.axis.Axis` The axis for the scale. functions : (callable, callable) two-tuple of the forward and inverse functions for the scale. @@ -376,14 +377,14 @@ def __init__(self, base, linthresh, linscale): self._linscale_adj = (linscale / (1.0 - self.base ** -1)) self._log_base = np.log(base) - def transform_non_affine(self, a): - abs_a = np.abs(a) + def transform_non_affine(self, values): + abs_a = np.abs(values) with np.errstate(divide="ignore", invalid="ignore"): - out = np.sign(a) * self.linthresh * ( + out = np.sign(values) * self.linthresh * ( self._linscale_adj + np.log(abs_a / self.linthresh) / self._log_base) inside = abs_a <= self.linthresh - out[inside] = a[inside] * self._linscale_adj + out[inside] = values[inside] * self._linscale_adj return out def inverted(self): @@ -403,14 +404,14 @@ def __init__(self, base, linthresh, linscale): self.linscale = linscale self._linscale_adj = (linscale / (1.0 - self.base ** -1)) - def transform_non_affine(self, a): - abs_a = np.abs(a) + def transform_non_affine(self, values): + abs_a = np.abs(values) with np.errstate(divide="ignore", invalid="ignore"): - out = np.sign(a) * self.linthresh * ( + out = np.sign(values) * self.linthresh * ( np.power(self.base, abs_a / self.linthresh - self._linscale_adj)) inside = abs_a <= self.invlinthresh - out[inside] = a[inside] / self._linscale_adj + out[inside] = values[inside] / self._linscale_adj return out def inverted(self): @@ -428,6 +429,8 @@ class SymmetricalLogScale(ScaleBase): *linthresh* allows the user to specify the size of this range (-*linthresh*, *linthresh*). + See :doc:`/gallery/scales/symlog_demo` for a detailed description. + Parameters ---------- base : float, default: 10 @@ -452,28 +455,7 @@ class SymmetricalLogScale(ScaleBase): """ name = 'symlog' - @cbook.deprecated("3.3", alternative="scale.SymmetricalLogTransform") - @property - def SymmetricalLogTransform(self): - return SymmetricalLogTransform - - @cbook.deprecated( - "3.3", alternative="scale.InvertedSymmetricalLogTransform") - @property - def InvertedSymmetricalLogTransform(self): - return InvertedSymmetricalLogTransform - - def __init__(self, axis, **kwargs): - axis_name = getattr(axis, "axis_name", "x") - # See explanation in LogScale.__init__. - @cbook._rename_parameter("3.3", f"base{axis_name}", "base") - @cbook._rename_parameter("3.3", f"linthresh{axis_name}", "linthresh") - @cbook._rename_parameter("3.3", f"subs{axis_name}", "subs") - @cbook._rename_parameter("3.3", f"linscale{axis_name}", "linscale") - def __init__(*, base=10, linthresh=2, subs=None, linscale=1): - return base, linthresh, subs, linscale - - base, linthresh, subs, linscale = __init__(**kwargs) + def __init__(self, axis, *, base=10, linthresh=2, subs=None, linscale=1): self._transform = SymmetricalLogTransform(base, linthresh, linscale) self.subs = subs @@ -494,49 +476,164 @@ def get_transform(self): return self._transform +class AsinhTransform(Transform): + """Inverse hyperbolic-sine transformation used by `.AsinhScale`""" + input_dims = output_dims = 1 + + def __init__(self, linear_width): + super().__init__() + if linear_width <= 0.0: + raise ValueError("Scale parameter 'linear_width' " + + "must be strictly positive") + self.linear_width = linear_width + + def transform_non_affine(self, values): + return self.linear_width * np.arcsinh(values / self.linear_width) + + def inverted(self): + return InvertedAsinhTransform(self.linear_width) + + +class InvertedAsinhTransform(Transform): + """Hyperbolic sine transformation used by `.AsinhScale`""" + input_dims = output_dims = 1 + + def __init__(self, linear_width): + super().__init__() + self.linear_width = linear_width + + def transform_non_affine(self, values): + return self.linear_width * np.sinh(values / self.linear_width) + + def inverted(self): + return AsinhTransform(self.linear_width) + + +class AsinhScale(ScaleBase): + """ + A quasi-logarithmic scale based on the inverse hyperbolic sine (asinh) + + For values close to zero, this is essentially a linear scale, + but for large magnitude values (either positive or negative) + it is asymptotically logarithmic. The transition between these + linear and logarithmic regimes is smooth, and has no discontinuities + in the function gradient in contrast to + the `.SymmetricalLogScale` ("symlog") scale. + + Specifically, the transformation of an axis coordinate :math:`a` is + :math:`a \\rightarrow a_0 \\sinh^{-1} (a / a_0)` where :math:`a_0` + is the effective width of the linear region of the transformation. + In that region, the transformation is + :math:`a \\rightarrow a + \\mathcal{O}(a^3)`. + For large values of :math:`a` the transformation behaves as + :math:`a \\rightarrow a_0 \\, \\mathrm{sgn}(a) \\ln |a| + \\mathcal{O}(1)`. + + .. note:: + + This API is provisional and may be revised in the future + based on early user feedback. + """ + + name = 'asinh' + + auto_tick_multipliers = { + 3: (2, ), + 4: (2, ), + 5: (2, ), + 8: (2, 4), + 10: (2, 5), + 16: (2, 4, 8), + 64: (4, 16), + 1024: (256, 512) + } + + def __init__(self, axis, *, linear_width=1.0, + base=10, subs='auto', **kwargs): + """ + Parameters + ---------- + linear_width : float, default: 1 + The scale parameter (elsewhere referred to as :math:`a_0`) + defining the extent of the quasi-linear region, + and the coordinate values beyond which the transformation + becomes asymptotically logarithmic. + base : int, default: 10 + The number base used for rounding tick locations + on a logarithmic scale. If this is less than one, + then rounding is to the nearest integer multiple + of powers of ten. + subs : sequence of int + Multiples of the number base used for minor ticks. + If set to 'auto', this will use built-in defaults, + e.g. (2, 5) for base=10. + """ + super().__init__(axis) + self._transform = AsinhTransform(linear_width) + self._base = int(base) + if subs == 'auto': + self._subs = self.auto_tick_multipliers.get(self._base) + else: + self._subs = subs + + linear_width = property(lambda self: self._transform.linear_width) + + def get_transform(self): + return self._transform + + def set_default_locators_and_formatters(self, axis): + axis.set(major_locator=AsinhLocator(self.linear_width, + base=self._base), + minor_locator=AsinhLocator(self.linear_width, + base=self._base, + subs=self._subs), + minor_formatter=NullFormatter()) + if self._base > 1: + axis.set_major_formatter(LogFormatterSciNotation(self._base)) + else: + axis.set_major_formatter('{x:.3g}') + + class LogitTransform(Transform): input_dims = output_dims = 1 - @cbook._rename_parameter("3.3", "nonpos", "nonpositive") def __init__(self, nonpositive='mask'): super().__init__() - cbook._check_in_list(['mask', 'clip'], nonpositive=nonpositive) + _api.check_in_list(['mask', 'clip'], nonpositive=nonpositive) self._nonpositive = nonpositive self._clip = {"clip": True, "mask": False}[nonpositive] - def transform_non_affine(self, a): + def transform_non_affine(self, values): """logit transform (base 10), masked or clipped""" with np.errstate(divide="ignore", invalid="ignore"): - out = np.log10(a / (1 - a)) + out = np.log10(values / (1 - values)) if self._clip: # See LogTransform for choice of clip value. - out[a <= 0] = -1000 - out[1 <= a] = 1000 + out[values <= 0] = -1000 + out[1 <= values] = 1000 return out def inverted(self): return LogisticTransform(self._nonpositive) def __str__(self): - return "{}({!r})".format(type(self).__name__, self._nonpositive) + return f"{type(self).__name__}({self._nonpositive!r})" class LogisticTransform(Transform): input_dims = output_dims = 1 - @cbook._rename_parameter("3.3", "nonpos", "nonpositive") def __init__(self, nonpositive='mask'): super().__init__() self._nonpositive = nonpositive - def transform_non_affine(self, a): + def transform_non_affine(self, values): """logistic transform (base 10)""" - return 1.0 / (1 + 10**(-a)) + return 1.0 / (1 + 10**(-values)) def inverted(self): return LogitTransform(self._nonpositive) def __str__(self): - return "{}({!r})".format(type(self).__name__, self._nonpositive) + return f"{type(self).__name__}({self._nonpositive!r})" class LogitScale(ScaleBase): @@ -548,13 +645,12 @@ class LogitScale(ScaleBase): """ name = 'logit' - @cbook._rename_parameter("3.3", "nonpos", "nonpositive") def __init__(self, axis, nonpositive='mask', *, one_half=r"\frac{1}{2}", use_overline=False): r""" Parameters ---------- - axis : `matplotlib.axis.Axis` + axis : `~matplotlib.axis.Axis` Currently unused. nonpositive : {'mask', 'clip'} Determines the behavior for values beyond the open interval ]0, 1[. @@ -607,6 +703,7 @@ def limit_range_for_scale(self, vmin, vmax, minpos): 'linear': LinearScale, 'log': LogScale, 'symlog': SymmetricalLogScale, + 'asinh': AsinhScale, 'logit': LogitScale, 'function': FuncScale, 'functionlog': FuncScaleLog, @@ -625,11 +722,10 @@ def scale_factory(scale, axis, **kwargs): Parameters ---------- scale : {%(names)s} - axis : `matplotlib.axis.Axis` + axis : `~matplotlib.axis.Axis` """ - scale = scale.lower() - cbook._check_in_list(_scale_mapping, scale=scale) - return _scale_mapping[scale](axis, **kwargs) + scale_cls = _api.check_getitem(_scale_mapping, scale=scale) + return scale_cls(axis, **kwargs) if scale_factory.__doc__: @@ -655,16 +751,17 @@ def _get_scale_docs(): """ docs = [] for name, scale_class in _scale_mapping.items(): + docstring = inspect.getdoc(scale_class.__init__) or "" docs.extend([ f" {name!r}", "", - textwrap.indent(inspect.getdoc(scale_class.__init__), " " * 8), + textwrap.indent(docstring, " " * 8), "" ]) return "\n".join(docs) -docstring.interpd.update( +_docstring.interpd.register( scale_type='{%s}' % ', '.join([repr(x) for x in get_scale_names()]), scale_docs=_get_scale_docs().rstrip(), ) diff --git a/lib/matplotlib/scale.pyi b/lib/matplotlib/scale.pyi new file mode 100644 index 000000000000..7fec8e68cc5a --- /dev/null +++ b/lib/matplotlib/scale.pyi @@ -0,0 +1,178 @@ +from matplotlib.axis import Axis +from matplotlib.transforms import Transform + +from collections.abc import Callable, Iterable +from typing import Literal +from numpy.typing import ArrayLike + +class ScaleBase: + def __init__(self, axis: Axis | None) -> None: ... + def get_transform(self) -> Transform: ... + def set_default_locators_and_formatters(self, axis: Axis) -> None: ... + def limit_range_for_scale( + self, vmin: float, vmax: float, minpos: float + ) -> tuple[float, float]: ... + +class LinearScale(ScaleBase): + name: str + +class FuncTransform(Transform): + input_dims: int + output_dims: int + def __init__( + self, + forward: Callable[[ArrayLike], ArrayLike], + inverse: Callable[[ArrayLike], ArrayLike], + ) -> None: ... + def inverted(self) -> FuncTransform: ... + +class FuncScale(ScaleBase): + name: str + def __init__( + self, + axis: Axis | None, + functions: tuple[ + Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike] + ], + ) -> None: ... + +class LogTransform(Transform): + input_dims: int + output_dims: int + base: float + def __init__( + self, base: float, nonpositive: Literal["clip", "mask"] = ... + ) -> None: ... + def inverted(self) -> InvertedLogTransform: ... + +class InvertedLogTransform(Transform): + input_dims: int + output_dims: int + base: float + def __init__(self, base: float) -> None: ... + def inverted(self) -> LogTransform: ... + +class LogScale(ScaleBase): + name: str + subs: Iterable[int] | None + def __init__( + self, + axis: Axis | None, + *, + base: float = ..., + subs: Iterable[int] | None = ..., + nonpositive: Literal["clip", "mask"] = ... + ) -> None: ... + @property + def base(self) -> float: ... + def get_transform(self) -> Transform: ... + +class FuncScaleLog(LogScale): + def __init__( + self, + axis: Axis | None, + functions: tuple[ + Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike] + ], + base: float = ..., + ) -> None: ... + @property + def base(self) -> float: ... + def get_transform(self) -> Transform: ... + +class SymmetricalLogTransform(Transform): + input_dims: int + output_dims: int + base: float + linthresh: float + linscale: float + def __init__(self, base: float, linthresh: float, linscale: float) -> None: ... + def inverted(self) -> InvertedSymmetricalLogTransform: ... + +class InvertedSymmetricalLogTransform(Transform): + input_dims: int + output_dims: int + base: float + linthresh: float + invlinthresh: float + linscale: float + def __init__(self, base: float, linthresh: float, linscale: float) -> None: ... + def inverted(self) -> SymmetricalLogTransform: ... + +class SymmetricalLogScale(ScaleBase): + name: str + subs: Iterable[int] | None + def __init__( + self, + axis: Axis | None, + *, + base: float = ..., + linthresh: float = ..., + subs: Iterable[int] | None = ..., + linscale: float = ... + ) -> None: ... + @property + def base(self) -> float: ... + @property + def linthresh(self) -> float: ... + @property + def linscale(self) -> float: ... + def get_transform(self) -> SymmetricalLogTransform: ... + +class AsinhTransform(Transform): + input_dims: int + output_dims: int + linear_width: float + def __init__(self, linear_width: float) -> None: ... + def inverted(self) -> InvertedAsinhTransform: ... + +class InvertedAsinhTransform(Transform): + input_dims: int + output_dims: int + linear_width: float + def __init__(self, linear_width: float) -> None: ... + def inverted(self) -> AsinhTransform: ... + +class AsinhScale(ScaleBase): + name: str + auto_tick_multipliers: dict[int, tuple[int, ...]] + def __init__( + self, + axis: Axis | None, + *, + linear_width: float = ..., + base: float = ..., + subs: Iterable[int] | Literal["auto"] | None = ..., + **kwargs + ) -> None: ... + @property + def linear_width(self) -> float: ... + def get_transform(self) -> AsinhTransform: ... + +class LogitTransform(Transform): + input_dims: int + output_dims: int + def __init__(self, nonpositive: Literal["mask", "clip"] = ...) -> None: ... + def inverted(self) -> LogisticTransform: ... + +class LogisticTransform(Transform): + input_dims: int + output_dims: int + def __init__(self, nonpositive: Literal["mask", "clip"] = ...) -> None: ... + def inverted(self) -> LogitTransform: ... + +class LogitScale(ScaleBase): + name: str + def __init__( + self, + axis: Axis | None, + nonpositive: Literal["mask", "clip"] = ..., + *, + one_half: str = ..., + use_overline: bool = ... + ) -> None: ... + def get_transform(self) -> LogitTransform: ... + +def get_scale_names() -> list[str]: ... +def scale_factory(scale: str, axis: Axis, **kwargs) -> ScaleBase: ... +def register_scale(scale_class: type[ScaleBase]) -> None: ... diff --git a/lib/matplotlib/sphinxext/figmpl_directive.py b/lib/matplotlib/sphinxext/figmpl_directive.py new file mode 100644 index 000000000000..7cb9c6c04e8a --- /dev/null +++ b/lib/matplotlib/sphinxext/figmpl_directive.py @@ -0,0 +1,286 @@ +""" +Add a ``figure-mpl`` directive that is a responsive version of ``figure``. + +This implementation is very similar to ``.. figure::``, except it also allows a +``srcset=`` argument to be passed to the image tag, hence allowing responsive +resolution images. + +There is no particular reason this could not be used standalone, but is meant +to be used with :doc:`/api/sphinxext_plot_directive_api`. + +Note that the directory organization is a bit different than ``.. figure::``. +See the *FigureMpl* documentation below. + +""" +import os +from os.path import relpath +from pathlib import PurePath, Path +import shutil + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst.directives.images import Figure, Image +from sphinx.errors import ExtensionError + +import matplotlib + + +class figmplnode(nodes.General, nodes.Element): + pass + + +class FigureMpl(Figure): + """ + Implements a directive to allow an optional hidpi image. + + Meant to be used with the *plot_srcset* configuration option in conf.py, + and gets set in the TEMPLATE of plot_directive.py + + e.g.:: + + .. figure-mpl:: plot_directive/some_plots-1.png + :alt: bar + :srcset: plot_directive/some_plots-1.png, + plot_directive/some_plots-1.2x.png 2.00x + :class: plot-directive + + The resulting html (at ``some_plots.html``) is:: + + bar + + Note that the handling of subdirectories is different than that used by the sphinx + figure directive:: + + .. figure-mpl:: plot_directive/nestedpage/index-1.png + :alt: bar + :srcset: plot_directive/nestedpage/index-1.png + plot_directive/nestedpage/index-1.2x.png 2.00x + :class: plot_directive + + The resulting html (at ``nestedpage/index.html``):: + + bar + + where the subdirectory is included in the image name for uniqueness. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 2 + final_argument_whitespace = False + option_spec = { + 'alt': directives.unchanged, + 'height': directives.length_or_unitless, + 'width': directives.length_or_percentage_or_unitless, + 'scale': directives.nonnegative_int, + 'align': Image.align, + 'class': directives.class_option, + 'caption': directives.unchanged, + 'srcset': directives.unchanged, + } + + def run(self): + + image_node = figmplnode() + + imagenm = self.arguments[0] + image_node['alt'] = self.options.get('alt', '') + image_node['align'] = self.options.get('align', None) + image_node['class'] = self.options.get('class', None) + image_node['width'] = self.options.get('width', None) + image_node['height'] = self.options.get('height', None) + image_node['scale'] = self.options.get('scale', None) + image_node['caption'] = self.options.get('caption', None) + + # we would like uri to be the highest dpi version so that + # latex etc will use that. But for now, lets just make + # imagenm... maybe pdf one day? + + image_node['uri'] = imagenm + image_node['srcset'] = self.options.get('srcset', None) + + return [image_node] + + +def _parse_srcsetNodes(st): + """ + parse srcset... + """ + entries = st.split(',') + srcset = {} + for entry in entries: + spl = entry.strip().split(' ') + if len(spl) == 1: + srcset[0] = spl[0] + elif len(spl) == 2: + mult = spl[1][:-1] + srcset[float(mult)] = spl[0] + else: + raise ExtensionError(f'srcset argument "{entry}" is invalid.') + return srcset + + +def _copy_images_figmpl(self, node): + + # these will be the temporary place the plot-directive put the images eg: + # ../../../build/html/plot_directive/users/explain/artists/index-1.png + if node['srcset']: + srcset = _parse_srcsetNodes(node['srcset']) + else: + srcset = None + + # the rst file's location: eg /Users/username/matplotlib/doc/users/explain/artists + docsource = PurePath(self.document['source']).parent + + # get the relpath relative to root: + srctop = self.builder.srcdir + rel = relpath(docsource, srctop).replace('.', '').replace(os.sep, '-') + if len(rel): + rel += '-' + # eg: users/explain/artists + + imagedir = PurePath(self.builder.outdir, self.builder.imagedir) + # eg: /Users/username/matplotlib/doc/build/html/_images/users/explain/artists + + Path(imagedir).mkdir(parents=True, exist_ok=True) + + # copy all the sources to the imagedir: + if srcset: + for src in srcset.values(): + # the entries in srcset are relative to docsource's directory + abspath = PurePath(docsource, src) + name = rel + abspath.name + shutil.copyfile(abspath, imagedir / name) + else: + abspath = PurePath(docsource, node['uri']) + name = rel + abspath.name + shutil.copyfile(abspath, imagedir / name) + + return imagedir, srcset, rel + + +def visit_figmpl_html(self, node): + + imagedir, srcset, rel = _copy_images_figmpl(self, node) + + # /doc/examples/subd/plot_1.rst + docsource = PurePath(self.document['source']) + # /doc/ + # make sure to add the trailing slash: + srctop = PurePath(self.builder.srcdir, '') + # examples/subd/plot_1.rst + relsource = relpath(docsource, srctop) + # /doc/build/html + desttop = PurePath(self.builder.outdir, '') + # /doc/build/html/examples/subd + dest = desttop / relsource + + # ../../_images/ for dirhtml and ../_images/ for html + imagerel = PurePath(relpath(imagedir, dest.parent)).as_posix() + if self.builder.name == "dirhtml": + imagerel = f'..{imagerel}' + + # make uri also be relative... + nm = PurePath(node['uri'][1:]).name + uri = f'{imagerel}/{rel}{nm}' + img_attrs = {'src': uri, 'alt': node['alt']} + + # make srcset str. Need to change all the prefixes! + maxsrc = uri + if srcset: + maxmult = -1 + srcsetst = '' + for mult, src in srcset.items(): + nm = PurePath(src[1:]).name + # ../../_images/plot_1_2_0x.png + path = f'{imagerel}/{rel}{nm}' + srcsetst += path + if mult == 0: + srcsetst += ', ' + else: + srcsetst += f' {mult:1.2f}x, ' + + if mult > maxmult: + maxmult = mult + maxsrc = path + + # trim trailing comma and space... + img_attrs['srcset'] = srcsetst[:-2] + + if node['class'] is not None: + img_attrs['class'] = ' '.join(node['class']) + for style in ['width', 'height', 'scale']: + if node[style]: + if 'style' not in img_attrs: + img_attrs['style'] = f'{style}: {node[style]};' + else: + img_attrs['style'] += f'{style}: {node[style]};' + + # + self.body.append( + self.starttag( + node, 'figure', + CLASS=f'align-{node["align"]}' if node['align'] else 'align-center')) + self.body.append( + self.starttag(node, 'a', CLASS='reference internal image-reference', + href=maxsrc) + + self.emptytag(node, 'img', **img_attrs) + + '\n') + if node['caption']: + self.body.append(self.starttag(node, 'figcaption')) + self.body.append(self.starttag(node, 'p')) + self.body.append(self.starttag(node, 'span', CLASS='caption-text')) + self.body.append(node['caption']) + self.body.append('

\n') + self.body.append('\n') + + +def visit_figmpl_latex(self, node): + + if node['srcset'] is not None: + imagedir, srcset = _copy_images_figmpl(self, node) + maxmult = -1 + # choose the highest res version for latex: + maxmult = max(srcset, default=-1) + node['uri'] = PurePath(srcset[maxmult]).name + + self.visit_figure(node) + + +def depart_figmpl_html(self, node): + pass + + +def depart_figmpl_latex(self, node): + self.depart_figure(node) + + +def figurempl_addnode(app): + app.add_node(figmplnode, + html=(visit_figmpl_html, depart_figmpl_html), + latex=(visit_figmpl_latex, depart_figmpl_latex)) + + +def setup(app): + app.add_directive("figure-mpl", FigureMpl) + figurempl_addnode(app) + metadata = {'parallel_read_safe': True, 'parallel_write_safe': True, + 'version': matplotlib.__version__} + return metadata diff --git a/lib/matplotlib/sphinxext/mathmpl.py b/lib/matplotlib/sphinxext/mathmpl.py index 6c3b1c1087ab..30f024524258 100644 --- a/lib/matplotlib/sphinxext/mathmpl.py +++ b/lib/matplotlib/sphinxext/mathmpl.py @@ -1,14 +1,86 @@ +r""" +A role and directive to display mathtext in Sphinx +================================================== + +The ``mathmpl`` Sphinx extension creates a mathtext image in Matplotlib and +shows it in html output. Thus, it is a true and faithful representation of what +you will see if you pass a given LaTeX string to Matplotlib (see +:ref:`mathtext`). + +.. warning:: + In most cases, you will likely want to use one of `Sphinx's builtin Math + extensions + `__ + instead of this one. The builtin Sphinx math directive uses MathJax to + render mathematical expressions, and addresses accessibility concerns that + ``mathmpl`` doesn't address. + +Mathtext may be included in two ways: + +1. Inline, using the role:: + + This text uses inline math: :mathmpl:`\alpha > \beta`. + + which produces: + + This text uses inline math: :mathmpl:`\alpha > \beta`. + +2. Standalone, using the directive:: + + Here is some standalone math: + + .. mathmpl:: + + \alpha > \beta + + which produces: + + Here is some standalone math: + + .. mathmpl:: + + \alpha > \beta + +Options +------- + +The ``mathmpl`` role and directive both support the following options: + +fontset : str, default: 'cm' + The font set to use when displaying math. See :rc:`mathtext.fontset`. + +fontsize : float + The font size, in points. Defaults to the value from the extension + configuration option defined below. + +Configuration options +--------------------- + +The mathtext extension has the following configuration options: + +mathmpl_fontsize : float, default: 10.0 + Default font size, in points. + +mathmpl_srcset : list of str, default: [] + Additional image sizes to generate when embedding in HTML, to support + `responsive resolution images + `__. + The list should contain additional x-descriptors (``'1.5x'``, ``'2x'``, + etc.) to generate (1x is the default and always included.) + +""" + import hashlib from pathlib import Path from docutils import nodes from docutils.parsers.rst import Directive, directives import sphinx +from sphinx.errors import ConfigError, ExtensionError import matplotlib as mpl -from matplotlib import cbook -from matplotlib.mathtext import MathTextParser -mathtext_parser = MathTextParser("Bitmap") +from matplotlib import _api, mathtext +from matplotlib.rcsetup import validate_float_or_None # Define LaTeX math node: @@ -17,7 +89,7 @@ class latex_math(nodes.General, nodes.Element): def fontset_choice(arg): - return directives.choice(arg, MathTextParser._font_type_mapping) + return directives.choice(arg, mathtext.MathTextParser._font_type_mapping) def math_role(role, rawtext, text, lineno, inliner, @@ -27,38 +99,43 @@ def math_role(role, rawtext, text, lineno, inliner, node = latex_math(rawtext) node['latex'] = latex node['fontset'] = options.get('fontset', 'cm') + node['fontsize'] = options.get('fontsize', + setup.app.config.mathmpl_fontsize) return [node], [] -math_role.options = {'fontset': fontset_choice} +math_role.options = {'fontset': fontset_choice, + 'fontsize': validate_float_or_None} class MathDirective(Directive): + """ + The ``.. mathmpl::`` directive, as documented in the module's docstring. + """ has_content = True required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {'fontset': fontset_choice} + option_spec = {'fontset': fontset_choice, + 'fontsize': validate_float_or_None} def run(self): latex = ''.join(self.content) node = latex_math(self.block_text) node['latex'] = latex node['fontset'] = self.options.get('fontset', 'cm') + node['fontsize'] = self.options.get('fontsize', + setup.app.config.mathmpl_fontsize) return [node] # This uses mathtext to render the expression -def latex2png(latex, filename, fontset='cm'): - latex = "$%s$" % latex - with mpl.rc_context({'mathtext.fontset': fontset}): - if Path(filename).exists(): - depth = mathtext_parser.get_depth(latex, dpi=100) - else: - try: - depth = mathtext_parser.to_png(filename, latex, dpi=100) - except Exception: - cbook._warn_external( - f"Could not render math expression {latex}") - depth = 0 +def latex2png(latex, filename, fontset='cm', fontsize=10, dpi=100): + with mpl.rc_context({'mathtext.fontset': fontset, 'font.size': fontsize}): + try: + depth = mathtext.math_to_image( + f"${latex}$", filename, dpi=dpi, format="png") + except Exception: + _api.warn_external(f"Could not render math expression {latex}") + depth = 0 return depth @@ -67,14 +144,29 @@ def latex2html(node, source): inline = isinstance(node.parent, nodes.TextElement) latex = node['latex'] fontset = node['fontset'] + fontsize = node['fontsize'] name = 'math-{}'.format( - hashlib.md5((latex + fontset).encode()).hexdigest()[-10:]) + hashlib.sha256( + f'{latex}{fontset}{fontsize}'.encode(), + usedforsecurity=False, + ).hexdigest()[-10:]) destdir = Path(setup.app.builder.outdir, '_images', 'mathmpl') destdir.mkdir(parents=True, exist_ok=True) - dest = destdir / f'{name}.png' - depth = latex2png(latex, dest, fontset) + dest = destdir / f'{name}.png' + depth = latex2png(latex, dest, fontset, fontsize=fontsize) + + srcset = [] + for size in setup.app.config.mathmpl_srcset: + filename = f'{name}-{size.replace(".", "_")}.png' + latex2png(latex, destdir / filename, fontset, fontsize=fontsize, + dpi=100 * float(size[:-1])) + srcset.append( + f'{setup.app.builder.imgpath}/mathmpl/{filename} {size}') + if srcset: + srcset = (f'srcset="{setup.app.builder.imgpath}/mathmpl/{name}.png, ' + + ', '.join(srcset) + '" ') if inline: cls = '' @@ -86,11 +178,35 @@ def latex2html(node, source): style = '' return (f'') + f' {srcset}{cls}{style}/>') + + +def _config_inited(app, config): + # Check for srcset hidpi images + for i, size in enumerate(app.config.mathmpl_srcset): + if size[-1] == 'x': # "2x" = "2.0" + try: + float(size[:-1]) + except ValueError: + raise ConfigError( + f'Invalid value for mathmpl_srcset parameter: {size!r}. ' + 'Must be a list of strings with the multiplicative ' + 'factor followed by an "x". e.g. ["2.0x", "1.5x"]') + else: + raise ConfigError( + f'Invalid value for mathmpl_srcset parameter: {size!r}. ' + 'Must be a list of strings with the multiplicative ' + 'factor followed by an "x". e.g. ["2.0x", "1.5x"]') def setup(app): setup.app = app + app.add_config_value('mathmpl_fontsize', 10.0, True) + app.add_config_value('mathmpl_srcset', [], True) + try: + app.connect('config-inited', _config_inited) # Sphinx 1.8+ + except ExtensionError: + app.connect('env-updated', lambda app, env: _config_inited(app, None)) # Add visit/depart methods to HTML-Translator: def visit_latex_math_html(self, node): diff --git a/lib/matplotlib/sphinxext/meson.build b/lib/matplotlib/sphinxext/meson.build new file mode 100644 index 000000000000..35bb96fecbe1 --- /dev/null +++ b/lib/matplotlib/sphinxext/meson.build @@ -0,0 +1,13 @@ +python_sources = [ + '__init__.py', + 'figmpl_directive.py', + 'mathmpl.py', + 'plot_directive.py', + 'roles.py', +] + +typing_sources = [ +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/sphinxext') diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index d8f3048ad556..af858e344afa 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -2,10 +2,13 @@ A directive for including a Matplotlib plot in a Sphinx document ================================================================ -By default, in HTML output, `plot` will include a .png file with a link to a -high-res .png and .pdf. In LaTeX output, it will include a .pdf. +This is a Sphinx extension providing a reStructuredText directive +``.. plot::`` for including a plot in a Sphinx document. -The source code for the plot may be included in one of three ways: +In HTML output, ``.. plot::`` will include a .png file with a link +to a high-res .png and .pdf. In LaTeX output, it will include a .pdf. + +The plot content may be defined in one of three ways: 1. **A path to a source file** as the argument to the directive:: @@ -16,7 +19,7 @@ .. plot:: path/to/plot.py - The plot's caption. + The plot caption. Additionally, one may specify the name of a function to call (with no arguments) immediately after importing the module:: @@ -28,10 +31,8 @@ .. plot:: import matplotlib.pyplot as plt - import matplotlib.image as mpimg - import numpy as np - img = mpimg.imread('_static/stinkbug.png') - imgplot = plt.imshow(img) + plt.plot([1, 2, 3], [4, 5, 6]) + plt.title("A plotting example") 3. Using **doctest** syntax:: @@ -44,95 +45,128 @@ Options ------- -The ``plot`` directive supports the following options: +The ``.. plot::`` directive supports the following options: + +``:format:`` : {'python', 'doctest'} + The format of the input. If unset, the format is auto-detected. - format : {'python', 'doctest'} - The format of the input. +``:include-source:`` : bool + Whether to display the source code. The default can be changed using + the ``plot_include_source`` variable in :file:`conf.py` (which itself + defaults to False). - include-source : bool - Whether to display the source code. The default can be changed - using the `plot_include_source` variable in :file:`conf.py`. +``:show-source-link:`` : bool + Whether to show a link to the source in HTML. The default can be + changed using the ``plot_html_show_source_link`` variable in + :file:`conf.py` (which itself defaults to True). - encoding : str - If this source file is in a non-UTF8 or non-ASCII encoding, the - encoding must be specified using the ``:encoding:`` option. The - encoding will not be inferred using the ``-*- coding -*-`` metacomment. +``:context:`` : bool or str + If provided, the code will be run in the context of all previous plot + directives for which the ``:context:`` option was specified. This only + applies to inline code plot directives, not those run from files. If + the ``:context: reset`` option is specified, the context is reset + for this and future plots, and previous figures are closed prior to + running the code. ``:context: close-figs`` keeps the context but closes + previous figures before running the code. - context : bool or str - If provided, the code will be run in the context of all previous plot - directives for which the ``:context:`` option was specified. This only - applies to inline code plot directives, not those run from files. If - the ``:context: reset`` option is specified, the context is reset - for this and future plots, and previous figures are closed prior to - running the code. ``:context: close-figs`` keeps the context but closes - previous figures before running the code. +``:nofigs:`` : bool + If specified, the code block will be run, but no figures will be + inserted. This is usually useful with the ``:context:`` option. - nofigs : bool - If specified, the code block will be run, but no figures will be - inserted. This is usually useful with the ``:context:`` option. +``:caption:`` : str + If specified, the option's argument will be used as a caption for the + figure. This overwrites the caption given in the content, when the plot + is generated from a file. -Additionally, this directive supports all of the options of the `image` -directive, except for *target* (since plot will add its own target). These -include *alt*, *height*, *width*, *scale*, *align* and *class*. +Additionally, this directive supports all the options of the `image directive +`_, +except for ``:target:`` (since plot will add its own target). These include +``:alt:``, ``:height:``, ``:width:``, ``:scale:``, ``:align:`` and ``:class:``. Configuration options --------------------- The plot directive has the following configuration options: - plot_include_source - Default value for the include-source option - - plot_html_show_source_link - Whether to show a link to the source in HTML. - - plot_pre_code - Code that should be executed before each plot. If not specified or None - it will default to a string containing:: - - import numpy as np - from matplotlib import pyplot as plt +plot_include_source + Default value for the include-source option (default: False). - plot_basedir - Base directory, to which ``plot::`` file names are relative - to. (If None or empty, file names are relative to the - directory where the file containing the directive is.) +plot_html_show_source_link + Whether to show a link to the source in HTML (default: True). - plot_formats - File formats to generate. List of tuples or strings:: +plot_pre_code + Code that should be executed before each plot. If None (the default), + it will default to a string containing:: - [(suffix, dpi), suffix, ...] - - that determine the file format and the DPI. For entries whose - DPI was omitted, sensible defaults are chosen. When passing from - the command line through sphinx_build the list should be passed as - suffix:dpi,suffix:dpi, ... - - plot_html_show_formats - Whether to show links to the files in HTML. - - plot_rcparams - A dictionary containing any non-standard rcParams that should - be applied before each plot. - - plot_apply_rcparams - By default, rcParams are applied when ``:context:`` option is not used - in a plot directive. This configuration option overrides this behavior - and applies rcParams before each plot. - - plot_working_directory - By default, the working directory will be changed to the directory of - the example, so the code can get at its data files, if any. Also its - path will be added to `sys.path` so it can import any helper modules - sitting beside it. This configuration option can be used to specify - a central directory (also added to `sys.path`) where data files and - helper modules for all code are located. + import numpy as np + from matplotlib import pyplot as plt + +plot_basedir + Base directory, to which ``plot::`` file names are relative to. + If None or empty (the default), file names are relative to the + directory where the file containing the directive is. + +plot_formats + File formats to generate (default: ['png', 'hires.png', 'pdf']). + List of tuples or strings:: + + [(suffix, dpi), suffix, ...] + + that determine the file format and the DPI. For entries whose + DPI was omitted, sensible defaults are chosen. When passing from + the command line through sphinx_build the list should be passed as + suffix:dpi,suffix:dpi, ... + +plot_html_show_formats + Whether to show links to the files in HTML (default: True). + +plot_rcparams + A dictionary containing any non-standard rcParams that should + be applied before each plot (default: {}). + +plot_apply_rcparams + By default, rcParams are applied when ``:context:`` option is not used + in a plot directive. If set, this configuration option overrides this + behavior and applies rcParams before each plot. + +plot_working_directory + By default, the working directory will be changed to the directory of + the example, so the code can get at its data files, if any. Also its + path will be added to `sys.path` so it can import any helper modules + sitting beside it. This configuration option can be used to specify + a central directory (also added to `sys.path`) where data files and + helper modules for all code are located. + +plot_template + Provide a customized template for preparing restructured text. + +plot_srcset + Allow the srcset image option for responsive image resolutions. List of + strings with the multiplicative factors followed by an "x". + e.g. ["2.0x", "1.5x"]. "2.0x" will create a png with the default "png" + resolution from plot_formats, multiplied by 2. If plot_srcset is + specified, the plot directive uses the + :doc:`/api/sphinxext_figmpl_directive_api` (instead of the usual figure + directive) in the intermediary rst file that is generated. + The plot_srcset option is incompatible with *singlehtml* builds, and an + error will be raised. + +Notes on how it works +--------------------- - plot_template - Provide a customized template for preparing restructured text. +The plot directive runs the code it is given, either in the source file or the +code under the directive. The figure created (if any) is saved in the sphinx +build directory under a subdirectory named ``plot_directive``. It then creates +an intermediate rst file that calls a ``.. figure:`` directive (or +``.. figmpl::`` directive if ``plot_srcset`` is being used) and has links to +the ``*.png`` files in the ``plot_directive`` directory. These translations can +be customized by changing the *plot_template*. See the source of +:doc:`/api/sphinxext_plot_directive_api` for the templates defined in *TEMPLATE* +and *TEMPLATE_SRCSET*. """ import contextlib +import doctest from io import StringIO import itertools import os @@ -148,13 +182,14 @@ from docutils.parsers.rst.directives.images import Image import jinja2 # Sphinx dependency. +from sphinx.errors import ExtensionError + import matplotlib from matplotlib.backend_bases import FigureManagerBase import matplotlib.pyplot as plt from matplotlib import _pylab_helpers, cbook matplotlib.use("agg") -align = Image.align __version__ = 2 @@ -173,7 +208,7 @@ def _option_boolean(arg): elif arg.strip().lower() in ('yes', '1', 'true'): return True else: - raise ValueError('"%s" unknown boolean' % arg) + raise ValueError(f'{arg!r} unknown boolean') def _option_context(arg): @@ -186,11 +221,6 @@ def _option_format(arg): return directives.choice(arg, ('python', 'doctest')) -def _option_align(arg): - return directives.choice(arg, ("top", "middle", "bottom", "left", "center", - "right")) - - def mark_plot_labels(app, document): """ To make plots referenceable, we need to move the reference from the @@ -233,23 +263,35 @@ class PlotDirective(Directive): 'height': directives.length_or_unitless, 'width': directives.length_or_percentage_or_unitless, 'scale': directives.nonnegative_int, - 'align': _option_align, + 'align': Image.align, 'class': directives.class_option, 'include-source': _option_boolean, + 'show-source-link': _option_boolean, 'format': _option_format, 'context': _option_context, 'nofigs': directives.flag, - 'encoding': directives.encoding, + 'caption': directives.unchanged, } def run(self): """Run the plot directive.""" - return run(self.arguments, self.content, self.options, - self.state_machine, self.state, self.lineno) + try: + return run(self.arguments, self.content, self.options, + self.state_machine, self.state, self.lineno) + except Exception as e: + raise self.error(str(e)) + + +def _copy_css_file(app, exc): + if exc is None and app.builder.format == 'html': + src = cbook._get_data_path('plot_directive/plot_directive.css') + dst = app.outdir / Path('_static') + dst.mkdir(exist_ok=True) + # Use copyfile because we do not want to copy src's permissions. + shutil.copyfile(src, dst / Path('plot_directive.css')) def setup(app): - import matplotlib setup.app = app setup.config = app.config setup.confdir = app.confdir @@ -264,9 +306,10 @@ def setup(app): app.add_config_value('plot_apply_rcparams', False, True) app.add_config_value('plot_working_directory', None, True) app.add_config_value('plot_template', None, True) - + app.add_config_value('plot_srcset', [], True) app.connect('doctree-read', mark_plot_labels) - + app.add_css_file('plot_directive.css') + app.connect('build-finished', _copy_css_file) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': matplotlib.__version__} return metadata @@ -289,70 +332,96 @@ def contains_doctest(text): return bool(m) -def unescape_doctest(text): - """ - Extract code from a piece of text, which contains either Python code - or doctests. - """ - if not contains_doctest(text): - return text - - code = "" - for line in text.split("\n"): - m = re.match(r'^\s*(>>>|\.\.\.) (.*)$', line) - if m: - code += m.group(2) + "\n" - elif line.strip(): - code += "# " + line.strip() + "\n" - else: - code += "\n" - return code - - -def split_code_at_show(text): +def _split_code_at_show(text, function_name): """Split code at plt.show().""" - parts = [] - is_doctest = contains_doctest(text) - part = [] - for line in text.split("\n"): - if (not is_doctest and line.strip() == 'plt.show()') or \ - (is_doctest and line.strip() == '>>> plt.show()'): - part.append(line) + is_doctest = contains_doctest(text) + if function_name is None: + parts = [] + part = [] + for line in text.split("\n"): + if ((not is_doctest and line.startswith('plt.show(')) or + (is_doctest and line.strip() == '>>> plt.show()')): + part.append(line) + parts.append("\n".join(part)) + part = [] + else: + part.append(line) + if "\n".join(part).strip(): parts.append("\n".join(part)) - part = [] - else: - part.append(line) - if "\n".join(part).strip(): - parts.append("\n".join(part)) - return parts + else: + parts = [text] + return is_doctest, parts # ----------------------------------------------------------------------------- # Template # ----------------------------------------------------------------------------- - -TEMPLATE = """ +_SOURCECODE = """ {{ source_code }} .. only:: html - {% if source_link or (html_show_formats and not multi_image) %} + {% if src_name or (html_show_formats and not multi_image) %} ( - {%- if source_link -%} - `Source code <{{ source_link }}>`__ + {%- if src_name -%} + :download:`Source code <{{ build_dir }}/{{ src_name }}>` {%- endif -%} {%- if html_show_formats and not multi_image -%} {%- for img in images -%} {%- for fmt in img.formats -%} - {%- if source_link or not loop.first -%}, {% endif -%} - `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ + {%- if src_name or not loop.first -%}, {% endif -%} + :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>` {%- endfor -%} {%- endfor -%} {%- endif -%} ) {% endif %} +""" + +TEMPLATE_SRCSET = _SOURCECODE + """ + {% for img in images %} + .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }} + {% for option in options -%} + {{ option }} + {% endfor %} + {%- if caption -%} + {{ caption }} {# appropriate leading whitespace added beforehand #} + {% endif -%} + {%- if srcset -%} + :srcset: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }} + {%- for sr in srcset -%} + , {{ build_dir }}/{{ img.basename }}.{{ sr }}.{{ default_fmt }} {{sr}} + {%- endfor -%} + {% endif %} + + {% if html_show_formats and multi_image %} + ( + {%- for fmt in img.formats -%} + {%- if not loop.first -%}, {% endif -%} + :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>` + {%- endfor -%} + ) + {% endif %} + + + {% endfor %} + +.. only:: not html + + {% for img in images %} + .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.* + {% for option in options -%} + {{ option }} + {% endfor -%} + + {{ caption }} {# appropriate leading whitespace added beforehand #} + {% endfor %} + +""" + +TEMPLATE = _SOURCECODE + """ {% for img in images %} .. figure:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }} @@ -364,12 +433,12 @@ def split_code_at_show(text): ( {%- for fmt in img.formats -%} {%- if not loop.first -%}, {% endif -%} - `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ + :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>` {%- endfor -%} ) {%- endif -%} - {{ caption }} + {{ caption }} {# appropriate leading whitespace added beforehand #} {% endfor %} .. only:: not html @@ -378,9 +447,9 @@ def split_code_at_show(text): .. figure:: {{ build_dir }}/{{ img.basename }}.* {% for option in options -%} {{ option }} - {% endfor %} + {% endfor -%} - {{ caption }} + {{ caption }} {# appropriate leading whitespace added beforehand #} {% endfor %} """ @@ -406,27 +475,39 @@ def __init__(self, basename, dirname): self.formats = [] def filename(self, format): - return os.path.join(self.dirname, "%s.%s" % (self.basename, format)) + return os.path.join(self.dirname, f"{self.basename}.{format}") def filenames(self): return [self.filename(fmt) for fmt in self.formats] -def out_of_date(original, derived): +def out_of_date(original, derived, includes=None): """ - Return whether *derived* is out-of-date relative to *original*, both of - which are full file paths. + Return whether *derived* is out-of-date relative to *original* or any of + the RST files included in it using the RST include directive (*includes*). + *derived* and *original* are full paths, and *includes* is optionally a + list of full paths which may have been included in the *original*. """ - return (not os.path.exists(derived) or - (os.path.exists(original) and - os.stat(derived).st_mtime < os.stat(original).st_mtime)) + if not os.path.exists(derived): + return True + + if includes is None: + includes = [] + files_to_check = [original, *includes] + + def out_of_date_one(original, derived_mtime): + return (os.path.exists(original) and + derived_mtime < os.stat(original).st_mtime) + + derived_mtime = os.stat(derived).st_mtime + return any(out_of_date_one(f, derived_mtime) for f in files_to_check) class PlotError(RuntimeError): pass -def run_code(code, code_path, ns=None, function_name=None): +def _run_code(code, code_path, ns=None, function_name=None): """ Import a Python module from a path, and run the function given by name, if function_name is not None. @@ -440,13 +521,13 @@ def run_code(code, code_path, ns=None, function_name=None): try: os.chdir(setup.config.plot_working_directory) except OSError as err: - raise OSError(str(err) + '\n`plot_working_directory` option in' - 'Sphinx configuration file must be a valid ' - 'directory path') from err + raise OSError(f'{err}\n`plot_working_directory` option in ' + f'Sphinx configuration file must be a valid ' + f'directory path') from err except TypeError as err: - raise TypeError(str(err) + '\n`plot_working_directory` option in ' - 'Sphinx configuration file must be a string or ' - 'None') from err + raise TypeError(f'{err}\n`plot_working_directory` option in ' + f'Sphinx configuration file must be a string or ' + f'None') from err elif code_path is not None: dirname = os.path.abspath(os.path.dirname(code_path)) os.chdir(dirname) @@ -455,7 +536,6 @@ def run_code(code, code_path, ns=None, function_name=None): sys, argv=[code_path], path=[os.getcwd(), *sys.path]), \ contextlib.redirect_stdout(StringIO()): try: - code = unescape_doctest(code) if ns is None: ns = {} if not ns: @@ -505,36 +585,55 @@ def get_plot_formats(config): return formats +def _parse_srcset(entries): + """ + Parse srcset for multiples... + """ + srcset = {} + for entry in entries: + entry = entry.strip() + if len(entry) >= 2: + mult = entry[:-1] + srcset[float(mult)] = entry + else: + raise ExtensionError(f'srcset argument {entry!r} is invalid.') + return srcset + + def render_figures(code, code_path, output_dir, output_base, context, function_name, config, context_reset=False, - close_figs=False): + close_figs=False, + code_includes=None): """ Run a pyplot script and save the images in *output_dir*. Save the images under *output_dir* with file names derived from *output_base* """ - formats = get_plot_formats(config) - # -- Try to determine if all images already exist + if function_name is not None: + output_base = f'{output_base}_{function_name}' + formats = get_plot_formats(config) - code_pieces = split_code_at_show(code) + # Try to determine if all images already exist + is_doctest, code_pieces = _split_code_at_show(code, function_name) # Look for single-figure output files first - all_exists = True img = ImageFile(output_base, output_dir) for format, dpi in formats: - if out_of_date(code_path, img.filename(format)): + if context or out_of_date(code_path, img.filename(format), + includes=code_includes): all_exists = False break img.formats.append(format) + else: + all_exists = True if all_exists: return [(code, [img])] # Then look for multi-figure output files results = [] - all_exists = True for i, code_piece in enumerate(code_pieces): images = [] for j in itertools.count(): @@ -544,7 +643,8 @@ def render_figures(code, code_path, output_dir, output_base, context, else: img = ImageFile('%s_%02d' % (output_base, j), output_dir) for fmt, dpi in formats: - if out_of_date(code_path, img.filename(fmt)): + if context or out_of_date(code_path, img.filename(fmt), + includes=code_includes): all_exists = False break img.formats.append(fmt) @@ -557,6 +657,8 @@ def render_figures(code, code_path, output_dir, output_base, context, if not all_exists: break results.append((code_piece, images)) + else: + all_exists = True if all_exists: return results @@ -564,10 +666,7 @@ def render_figures(code, code_path, output_dir, output_base, context, # We didn't find the files, so build them results = [] - if context: - ns = plot_context - else: - ns = {} + ns = plot_context if context else {} if context_reset: clear_state(config.plot_rcparams) @@ -582,7 +681,9 @@ def render_figures(code, code_path, output_dir, output_base, context, elif close_figs: plt.close('all') - run_code(code_piece, code_path, ns, function_name) + _run_code(doctest.script_from_examples(code_piece) if is_doctest + else code_piece, + code_path, ns, function_name) images = [] fig_managers = _pylab_helpers.Gcf.get_all_fig_managers() @@ -595,9 +696,18 @@ def render_figures(code, code_path, output_dir, output_base, context, img = ImageFile("%s_%02d_%02d" % (output_base, i, j), output_dir) images.append(img) + for fmt, dpi in formats: try: figman.canvas.figure.savefig(img.filename(fmt), dpi=dpi) + if fmt == formats[0][0] and config.plot_srcset: + # save a 2x, 3x etc version of the default... + srcset = _parse_srcset(config.plot_srcset) + for mult, suffix in srcset.items(): + fm = f'{suffix}.{fmt}' + img.formats.append(fm) + figman.canvas.figure.savefig(img.filename(fm), + dpi=int(dpi * mult)) except Exception as err: raise PlotError(traceback.format_exc()) from err img.formats.append(fmt) @@ -615,10 +725,23 @@ def run(arguments, content, options, state_machine, state, lineno): config = document.settings.env.config nofigs = 'nofigs' in options + if config.plot_srcset and setup.app.builder.name == 'singlehtml': + raise ExtensionError( + 'plot_srcset option not compatible with single HTML writer') + formats = get_plot_formats(config) default_fmt = formats[0][0] options.setdefault('include-source', config.plot_include_source) + options.setdefault('show-source-link', config.plot_html_show_source_link) + + if 'class' in options: + # classes are parsed into a list of string, and output by simply + # printing the list, abusing the fact that RST guarantees to strip + # non-conforming characters + options['class'] = ['plot-directive'] + options['class'] + else: + options.setdefault('class', ['plot-directive']) keep_context = 'context' in options context_opt = None if not keep_context else options['context'] @@ -632,10 +755,19 @@ def run(arguments, content, options, state_machine, state, lineno): else: source_file_name = os.path.join(setup.confdir, config.plot_basedir, directives.uri(arguments[0])) - # If there is content, it will be passed as a caption. caption = '\n'.join(content) + # Enforce unambiguous use of captions. + if "caption" in options: + if caption: + raise ValueError( + 'Caption specified in both content and options.' + ' Please remove ambiguity.' + ) + # Use caption option + caption = options["caption"] + # If the optional function name is provided, use it if len(arguments) == 2: function_name = arguments[1] @@ -652,7 +784,7 @@ def run(arguments, content, options, state_machine, state, lineno): base, ext = os.path.splitext(os.path.basename(source_file_name)) output_base = '%s-%d.py' % (base, counter) function_name = None - caption = '' + caption = options.get('caption', '') base, source_ext = os.path.splitext(output_base) if source_ext in ('.py', '.rst', '.txt'): @@ -673,9 +805,7 @@ def run(arguments, content, options, state_machine, state, lineno): # determine output directory name fragment source_rel_name = relpath(source_file_name, setup.confdir) - source_rel_dir = os.path.dirname(source_rel_name) - while source_rel_dir.startswith(os.path.sep): - source_rel_dir = source_rel_dir[1:] + source_rel_dir = os.path.dirname(source_rel_name).lstrip(os.path.sep) # build_dir: where to place output files (temporarily) build_dir = os.path.join(os.path.dirname(setup.app.doctreedir), @@ -685,38 +815,55 @@ def run(arguments, content, options, state_machine, state, lineno): # see note in Python docs for warning about symbolic links on Windows. # need to compare source and dest paths at end build_dir = os.path.normpath(build_dir) - - if not os.path.exists(build_dir): - os.makedirs(build_dir) - - # output_dir: final location in the builder's directory - dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir, - source_rel_dir)) - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) # no problem here for me, but just use built-ins + os.makedirs(build_dir, exist_ok=True) # how to link to files from the RST file - dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir), - source_rel_dir).replace(os.path.sep, '/') try: build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/') except ValueError: # on Windows, relpath raises ValueError when path and start are on # different mounts/drives build_dir_link = build_dir - source_link = dest_dir_link + '/' + output_base + source_ext + + # get list of included rst files so that the output is updated when any + # plots in the included files change. These attributes are modified by the + # include directive (see the docutils.parsers.rst.directives.misc module). + try: + source_file_includes = [os.path.join(os.getcwd(), t[0]) + for t in state.document.include_log] + except AttributeError: + # the document.include_log attribute only exists in docutils >=0.17, + # before that we need to inspect the state machine + possible_sources = {os.path.join(setup.confdir, t[0]) + for t in state_machine.input_lines.items} + source_file_includes = [f for f in possible_sources + if os.path.isfile(f)] + # remove the source file itself from the includes + try: + source_file_includes.remove(source_file_name) + except ValueError: + pass + + # save script (if necessary) + if options['show-source-link']: + Path(build_dir, output_base + source_ext).write_text( + doctest.script_from_examples(code) + if source_file_name == rst_file and is_doctest + else code, + encoding='utf-8') # make figures try: - results = render_figures(code, - source_file_name, - build_dir, - output_base, - keep_context, - function_name, - config, + results = render_figures(code=code, + code_path=source_file_name, + output_dir=build_dir, + output_base=output_base, + context=keep_context, + function_name=function_name, + config=config, context_reset=context_opt == 'reset', - close_figs=context_opt == 'close-figs') + close_figs=context_opt == 'close-figs', + code_includes=source_file_includes) errors = [] except PlotError as err: reporter = state.memo.reporter @@ -728,9 +875,11 @@ def run(arguments, content, options, state_machine, state, lineno): errors = [sm] # Properly indent the caption - caption = '\n'.join(' ' + line.strip() - for line in caption.split('\n')) - + if caption and config.plot_srcset: + caption = ':caption: ' + caption.replace('\n', ' ') + elif caption: + caption = '\n' + '\n'.join(' ' + line.strip() + for line in caption.split('\n')) # generate output restructuredtext total_lines = [] for j, (code_piece, images) in enumerate(results): @@ -747,48 +896,41 @@ def run(arguments, content, options, state_machine, state, lineno): if nofigs: images = [] + if 'alt' in options: + options['alt'] = options['alt'].replace('\n', ' ') + opts = [ - ':%s: %s' % (key, val) for key, val in options.items() + f':{key}: {val}' for key, val in options.items() if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] - # Not-None src_link signals the need for a source link in the generated - # html - if j == 0 and config.plot_html_show_source_link: - src_link = source_link + # Not-None src_name signals the need for a source download in the + # generated html + if j == 0 and options['show-source-link']: + src_name = output_base + source_ext else: - src_link = None + src_name = None + if config.plot_srcset: + srcset = [*_parse_srcset(config.plot_srcset).values()] + template = TEMPLATE_SRCSET + else: + srcset = None + template = TEMPLATE - result = jinja2.Template(config.plot_template or TEMPLATE).render( + result = jinja2.Template(config.plot_template or template).render( default_fmt=default_fmt, - dest_dir=dest_dir_link, build_dir=build_dir_link, - source_link=src_link, + src_name=src_name, multi_image=len(images) > 1, options=opts, + srcset=srcset, images=images, source_code=source_code, html_show_formats=config.plot_html_show_formats and len(images), caption=caption) - total_lines.extend(result.split("\n")) total_lines.extend("\n") if total_lines: state_machine.insert_input(total_lines, source=source_file_name) - # copy image files to builder's output directory, if necessary - Path(dest_dir).mkdir(parents=True, exist_ok=True) - - for code_piece, images in results: - for img in images: - for fn in img.filenames(): - destimg = os.path.join(dest_dir, os.path.basename(fn)) - if fn != destimg: - shutil.copyfile(fn, destimg) - - # copy script (if necessary) - Path(dest_dir, output_base + source_ext).write_text( - unescape_doctest(code) if source_file_name == rst_file else code, - encoding='utf-8') - return errors diff --git a/lib/matplotlib/sphinxext/roles.py b/lib/matplotlib/sphinxext/roles.py new file mode 100644 index 000000000000..c3e57ebc3aec --- /dev/null +++ b/lib/matplotlib/sphinxext/roles.py @@ -0,0 +1,148 @@ +""" +Custom roles for the Matplotlib documentation. + +.. warning:: + + These roles are considered semi-public. They are only intended to be used in + the Matplotlib documentation. + +However, it can happen that downstream packages end up pulling these roles into +their documentation, which will result in documentation build errors. The following +describes the exact mechanism and how to fix the errors. + +There are two ways, Matplotlib docstrings can end up in downstream documentation. +You have to subclass a Matplotlib class and either use the ``:inherited-members:`` +option in your autodoc configuration, or you have to override a method without +specifying a new docstring; the new method will inherit the original docstring and +still render in your autodoc. If the docstring contains one of the custom sphinx +roles, you'll see one of the following error messages: + +.. code-block:: none + + Unknown interpreted text role "mpltype". + Unknown interpreted text role "rc". + +To fix this, you can add this module as extension to your sphinx :file:`conf.py`:: + + extensions = [ + 'matplotlib.sphinxext.roles', + # Other extensions. + ] + +.. warning:: + + Direct use of these roles in other packages is not officially supported. We + reserve the right to modify or remove these roles without prior notification. +""" + +from urllib.parse import urlsplit, urlunsplit + +from docutils import nodes + +import matplotlib +from matplotlib import rcParamsDefault + + +class _QueryReference(nodes.Inline, nodes.TextElement): + """ + Wraps a reference or pending reference to add a query string. + + The query string is generated from the attributes added to this node. + + Also equivalent to a `~docutils.nodes.literal` node. + """ + + def to_query_string(self): + """Generate query string from node attributes.""" + return '&'.join(f'{name}={value}' for name, value in self.attlist()) + + +def _visit_query_reference_node(self, node): + """ + Resolve *node* into query strings on its ``reference`` children. + + Then act as if this is a `~docutils.nodes.literal`. + """ + query = node.to_query_string() + for refnode in node.findall(nodes.reference): + uri = urlsplit(refnode['refuri'])._replace(query=query) + refnode['refuri'] = urlunsplit(uri) + + self.visit_literal(node) + + +def _depart_query_reference_node(self, node): + """ + Act as if this is a `~docutils.nodes.literal`. + """ + self.depart_literal(node) + + +def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries. + + Usage: Give the desired ``rcParams`` key as parameter. + + :code:`:rc:`figure.dpi`` will render as: :rc:`figure.dpi` + """ + # Generate a pending cross-reference so that Sphinx will ensure this link + # isn't broken at some point in the future. + title = f'rcParams["{text}"]' + target = 'matplotlibrc-sample' + ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', + 'ref', lineno) + + qr = _QueryReference(rawtext, highlight=text) + qr += ref_nodes + node_list = [qr] + + # The default backend would be printed as "agg", but that's not correct (as + # the default is actually determined by fallback). + if text in rcParamsDefault and text != "backend": + node_list.extend([ + nodes.Text(' (default: '), + nodes.literal('', repr(rcParamsDefault[text])), + nodes.Text(')'), + ]) + + return node_list, messages + + +def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:mpltype:`` for custom matplotlib types. + + In Matplotlib, there are a number of type-like concepts that do not have a + direct type representation; example: color. This role allows to properly + highlight them in the docs and link to their definition. + + Currently supported values: + + - :code:`:mpltype:`color`` will render as: :mpltype:`color` + + """ + mpltype = text + type_to_link_target = { + 'color': 'colors_def', + 'hatch': 'hatch_def', + } + if mpltype not in type_to_link_target: + raise ValueError(f"Unknown mpltype: {mpltype!r}") + + node_list, messages = inliner.interpreted( + mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) + return node_list, messages + + +def setup(app): + app.add_role("rc", _rcparam_role) + app.add_role("mpltype", _mpltype_role) + app.add_node( + _QueryReference, + html=(_visit_query_reference_node, _depart_query_reference_node), + latex=(_visit_query_reference_node, _depart_query_reference_node), + text=(_visit_query_reference_node, _depart_query_reference_node), + ) + return {"version": matplotlib.__version__, + "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 757918e0d9bc..7e77a393f2a2 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -1,7 +1,10 @@ +from collections.abc import MutableMapping +import functools + import numpy as np -import matplotlib -from matplotlib import cbook, docstring, rcParams +import matplotlib as mpl +from matplotlib import _api, _docstring from matplotlib.artist import allow_rasterization import matplotlib.transforms as mtransforms import matplotlib.patches as mpatches @@ -20,15 +23,16 @@ class Spine(mpatches.Patch): Spines are subclasses of `.Patch`, and inherit much of their behavior. - Spines draw a line, a circle, or an arc depending if + Spines draw a line, a circle, or an arc depending on if `~.Spine.set_patch_line`, `~.Spine.set_patch_circle`, or `~.Spine.set_patch_arc` has been called. Line-like is the default. + For examples see :ref:`spines_examples`. """ def __str__(self): return "Spine" - @docstring.dedent_interpd + @_docstring.interpd def __init__(self, axes, spine_type, path, **kwargs): """ Parameters @@ -45,15 +49,15 @@ def __init__(self, axes, spine_type, path, **kwargs): **kwargs Valid keyword arguments are: - %(Patch)s + %(Patch:kwdoc)s """ super().__init__(**kwargs) self.axes = axes - self.set_figure(self.axes.figure) + self.set_figure(self.axes.get_figure(root=False)) self.spine_type = spine_type self.set_facecolor('none') - self.set_edgecolor(rcParams['axes.edgecolor']) - self.set_linewidth(rcParams['axes.linewidth']) + self.set_edgecolor(mpl.rcParams['axes.edgecolor']) + self.set_linewidth(mpl.rcParams['axes.linewidth']) self.set_capstyle('projecting') self.axis = None @@ -61,13 +65,12 @@ def __init__(self, axes, spine_type, path, **kwargs): self.set_transform(self.axes.transData) # default transform self._bounds = None # default bounds - self._smart_bounds = False # deprecated in 3.2 # Defer initial position determination. (Not much support for # non-rectangular axes is currently implemented, and this lets # them pass through the spines machinery without errors.) self._position = None - cbook._check_isinstance(matplotlib.path.Path, path=path) + _api.check_isinstance(mpath.Path, path=path) self._path = path # To support drawing both linear and circular spines, this @@ -82,23 +85,6 @@ def __init__(self, axes, spine_type, path, **kwargs): # Note: This cannot be calculated until this is added to an Axes self._patch_transform = mtransforms.IdentityTransform() - @cbook.deprecated("3.2") - def set_smart_bounds(self, value): - """Set the spine and associated axis to have smart bounds.""" - self._smart_bounds = value - - # also set the axis if possible - if self.spine_type in ('left', 'right'): - self.axes.yaxis.set_smart_bounds(value) - elif self.spine_type in ('top', 'bottom'): - self.axes.xaxis.set_smart_bounds(value) - self.stale = True - - @cbook.deprecated("3.2") - def get_smart_bounds(self): - """Return whether the spine has smart bounds.""" - return self._smart_bounds - def set_patch_arc(self, center, radius, theta1, theta2): """Set the spine to be arc-like.""" self._patch_type = 'arc' @@ -166,15 +152,16 @@ def get_window_extent(self, renderer=None): # make sure the location is updated so that transforms etc are correct: self._adjust_location() bb = super().get_window_extent(renderer=renderer) - if self.axis is None: + if self.axis is None or not self.axis.get_visible(): return bb bboxes = [bb] - tickstocheck = [self.axis.majorTicks[0]] - if len(self.axis.minorTicks) > 1: - # only pad for minor ticks if there are more than one - # of them. There is always one... - tickstocheck.append(self.axis.minorTicks[1]) - for tick in tickstocheck: + drawn_ticks = self.axis._update_ticks() + + major_tick = next(iter({*drawn_ticks} & {*self.axis.majorTicks}), None) + minor_tick = next(iter({*drawn_ticks} & {*self.axis.minorTicks}), None) + for tick in [major_tick, minor_tick]: + if tick is None: + continue bb0 = bb.frozen() tickl = tick._size tickdir = tick._tickdir @@ -187,8 +174,9 @@ def get_window_extent(self, renderer=None): else: padout = 0.5 padin = 0.5 - padout = padout * tickl / 72 * self.figure.dpi - padin = padin * tickl / 72 * self.figure.dpi + dpi = self.get_figure(root=True).dpi + padout = padout * tickl / 72 * dpi + padin = padin * tickl / 72 * dpi if tick.tick1line.get_visible(): if self.spine_type == 'left': @@ -227,15 +215,22 @@ def register_axis(self, axis): properties when needed. """ self.axis = axis - if self.axis is not None: - self.axis.cla() self.stale = True - def cla(self): + def clear(self): """Clear the current spine.""" - self._position = None # clear position + self._clear() if self.axis is not None: - self.axis.cla() + self.axis.clear() + + def _clear(self): + """ + Clear things directly related to the spine. + + In this way it is possible to avoid clearing the Axis as well when calling + from library code where it is known that the Axis is cleared separately. + """ + self._position = None # clear position def _adjust_location(self): """Automatically set spine bounds to the view interval.""" @@ -243,64 +238,14 @@ def _adjust_location(self): if self.spine_type == 'circle': return - if self._bounds is None: - if self.spine_type in ('left', 'right'): - low, high = self.axes.viewLim.intervaly - elif self.spine_type in ('top', 'bottom'): - low, high = self.axes.viewLim.intervalx - else: - raise ValueError('unknown spine spine_type: %s' % - self.spine_type) - - if self._smart_bounds: # deprecated in 3.2 - # attempt to set bounds in sophisticated way - - # handle inverted limits - viewlim_low, viewlim_high = sorted([low, high]) - - if self.spine_type in ('left', 'right'): - datalim_low, datalim_high = self.axes.dataLim.intervaly - ticks = self.axes.get_yticks() - elif self.spine_type in ('top', 'bottom'): - datalim_low, datalim_high = self.axes.dataLim.intervalx - ticks = self.axes.get_xticks() - # handle inverted limits - ticks = np.sort(ticks) - datalim_low, datalim_high = sorted([datalim_low, datalim_high]) - - if datalim_low < viewlim_low: - # Data extends past view. Clip line to view. - low = viewlim_low - else: - # Data ends before view ends. - cond = (ticks <= datalim_low) & (ticks >= viewlim_low) - tickvals = ticks[cond] - if len(tickvals): - # A tick is less than or equal to lowest data point. - low = tickvals[-1] - else: - # No tick is available - low = datalim_low - low = max(low, viewlim_low) - - if datalim_high > viewlim_high: - # Data extends past view. Clip line to view. - high = viewlim_high - else: - # Data ends before view ends. - cond = (ticks >= datalim_high) & (ticks <= viewlim_high) - tickvals = ticks[cond] - if len(tickvals): - # A tick is greater than or equal to highest data - # point. - high = tickvals[0] - else: - # No tick is available - high = datalim_high - high = min(high, viewlim_high) - - else: + if self._bounds is not None: low, high = self._bounds + elif self.spine_type in ('left', 'right'): + low, high = self.axes.viewLim.intervaly + elif self.spine_type in ('top', 'bottom'): + low, high = self.axes.viewLim.intervalx + else: + raise ValueError(f'unknown spine spine_type: {self.spine_type}') if self._patch_type == 'arc': if self.spine_type in ('bottom', 'top'): @@ -366,8 +311,12 @@ def set_position(self, position): Additionally, shorthand notations define a special positions: - * 'center' -> ('axes', 0.5) - * 'zero' -> ('data', 0.0) + * 'center' -> ``('axes', 0.5)`` + * 'zero' -> ``('data', 0.0)`` + + Examples + -------- + :doc:`/gallery/spines/spine_placement_demo` """ if position in ('center', 'zero'): # special positions pass @@ -400,8 +349,8 @@ def get_spine_transform(self): position = ('data', 0) assert len(position) == 2, 'position should be 2-tuple' position_type, amount = position - cbook._check_in_list(['axes', 'outward', 'data'], - position_type=position_type) + _api.check_in_list(['axes', 'outward', 'data'], + position_type=position_type) if self.spine_type in ['left', 'right']: base_transform = self.axes.get_yaxis_transform(which='grid') elif self.spine_type in ['top', 'bottom']: @@ -420,7 +369,7 @@ def get_spine_transform(self): offset_dots = amount * np.array(offset_vec) / 72 return (base_transform + mtransforms.ScaledTranslation( - *offset_dots, self.figure.dpi_scale_trans)) + *offset_dots, self.get_figure(root=False).dpi_scale_trans)) elif position_type == 'axes': if self.spine_type in ['left', 'right']: # keep y unchanged, fix x at amount @@ -496,7 +445,7 @@ def linear_spine(cls, axes, spine_type, **kwargs): else: raise ValueError('unable to make path for spine "%s"' % spine_type) result = cls(axes, spine_type, path, **kwargs) - result.set_visible(rcParams['axes.spines.{0}'.format(spine_type)]) + result.set_visible(mpl.rcParams[f'axes.spines.{spine_type}']) return result @@ -524,7 +473,7 @@ def set_color(self, c): Parameters ---------- - c : color + c : :mpltype:`color` Notes ----- @@ -534,3 +483,114 @@ def set_color(self, c): """ self.set_edgecolor(c) self.stale = True + + +class SpinesProxy: + """ + A proxy to broadcast ``set_*()`` and ``set()`` method calls to contained `.Spines`. + + The proxy cannot be used for any other operations on its members. + + The supported methods are determined dynamically based on the contained + spines. If not all spines support a given method, it's executed only on + the subset of spines that support it. + """ + def __init__(self, spine_dict): + self._spine_dict = spine_dict + + def __getattr__(self, name): + broadcast_targets = [spine for spine in self._spine_dict.values() + if hasattr(spine, name)] + if (name != 'set' and not name.startswith('set_')) or not broadcast_targets: + raise AttributeError( + f"'SpinesProxy' object has no attribute '{name}'") + + def x(_targets, _funcname, *args, **kwargs): + for spine in _targets: + getattr(spine, _funcname)(*args, **kwargs) + x = functools.partial(x, broadcast_targets, name) + x.__doc__ = broadcast_targets[0].__doc__ + return x + + def __dir__(self): + names = [] + for spine in self._spine_dict.values(): + names.extend(name + for name in dir(spine) if name.startswith('set_')) + return list(sorted(set(names))) + + +class Spines(MutableMapping): + r""" + The container of all `.Spine`\s in an Axes. + + The interface is dict-like mapping names (e.g. 'left') to `.Spine` objects. + Additionally, it implements some pandas.Series-like features like accessing + elements by attribute:: + + spines['top'].set_visible(False) + spines.top.set_visible(False) + + Multiple spines can be addressed simultaneously by passing a list:: + + spines[['top', 'right']].set_visible(False) + + Use an open slice to address all spines:: + + spines[:].set_visible(False) + + The latter two indexing methods will return a `SpinesProxy` that broadcasts all + ``set_*()`` and ``set()`` calls to its members, but cannot be used for any other + operation. + """ + def __init__(self, **kwargs): + self._dict = kwargs + + @classmethod + def from_dict(cls, d): + return cls(**d) + + def __getstate__(self): + return self._dict + + def __setstate__(self, state): + self.__init__(**state) + + def __getattr__(self, name): + try: + return self._dict[name] + except KeyError: + raise AttributeError( + f"'Spines' object does not contain a '{name}' spine") + + def __getitem__(self, key): + if isinstance(key, list): + unknown_keys = [k for k in key if k not in self._dict] + if unknown_keys: + raise KeyError(', '.join(unknown_keys)) + return SpinesProxy({k: v for k, v in self._dict.items() + if k in key}) + if isinstance(key, tuple): + raise ValueError('Multiple spines must be passed as a single list') + if isinstance(key, slice): + if key.start is None and key.stop is None and key.step is None: + return SpinesProxy(self._dict) + else: + raise ValueError( + 'Spines does not support slicing except for the fully ' + 'open slice [:] to access all spines.') + return self._dict[key] + + def __setitem__(self, key, value): + # TODO: Do we want to deprecate adding spines? + self._dict[key] = value + + def __delitem__(self, key): + # TODO: Do we want to deprecate deleting spines? + del self._dict[key] + + def __iter__(self): + return iter(self._dict) + + def __len__(self): + return len(self._dict) diff --git a/lib/matplotlib/spines.pyi b/lib/matplotlib/spines.pyi new file mode 100644 index 000000000000..ff2a1a40bf94 --- /dev/null +++ b/lib/matplotlib/spines.pyi @@ -0,0 +1,83 @@ +from collections.abc import Callable, Iterator, MutableMapping +from typing import Literal, TypeVar, overload + +import matplotlib.patches as mpatches +from matplotlib.axes import Axes +from matplotlib.axis import Axis +from matplotlib.path import Path +from matplotlib.transforms import Transform +from matplotlib.typing import ColorType + +class Spine(mpatches.Patch): + axes: Axes + spine_type: str + axis: Axis | None + def __init__(self, axes: Axes, spine_type: str, path: Path, **kwargs) -> None: ... + def set_patch_arc( + self, center: tuple[float, float], radius: float, theta1: float, theta2: float + ) -> None: ... + def set_patch_circle(self, center: tuple[float, float], radius: float) -> None: ... + def set_patch_line(self) -> None: ... + def get_patch_transform(self) -> Transform: ... + def get_path(self) -> Path: ... + def register_axis(self, axis: Axis) -> None: ... + def clear(self) -> None: ... + def set_position( + self, + position: Literal["center", "zero"] + | tuple[Literal["outward", "axes", "data"], float], + ) -> None: ... + def get_position( + self, + ) -> Literal["center", "zero"] | tuple[ + Literal["outward", "axes", "data"], float + ]: ... + def get_spine_transform(self) -> Transform: ... + def set_bounds(self, low: float | None = ..., high: float | None = ...) -> None: ... + def get_bounds(self) -> tuple[float, float]: ... + + _T = TypeVar("_T", bound=Spine) + @classmethod + def linear_spine( + cls: type[_T], + axes: Axes, + spine_type: Literal["left", "right", "bottom", "top"], + **kwargs + ) -> _T: ... + @classmethod + def arc_spine( + cls: type[_T], + axes: Axes, + spine_type: Literal["left", "right", "bottom", "top"], + center: tuple[float, float], + radius: float, + theta1: float, + theta2: float, + **kwargs + ) -> _T: ... + @classmethod + def circular_spine( + cls: type[_T], axes: Axes, center: tuple[float, float], radius: float, **kwargs + ) -> _T: ... + def set_color(self, c: ColorType | None) -> None: ... + +class SpinesProxy: + def __init__(self, spine_dict: dict[str, Spine]) -> None: ... + def __getattr__(self, name: str) -> Callable[..., None]: ... + def __dir__(self) -> list[str]: ... + +class Spines(MutableMapping[str, Spine]): + def __init__(self, **kwargs: Spine) -> None: ... + @classmethod + def from_dict(cls, d: dict[str, Spine]) -> Spines: ... + def __getattr__(self, name: str) -> Spine: ... + @overload + def __getitem__(self, key: str) -> Spine: ... + @overload + def __getitem__(self, key: list[str]) -> SpinesProxy: ... + @overload + def __getitem__(self, key: slice) -> SpinesProxy: ... + def __setitem__(self, key: str, value: Spine) -> None: ... + def __delitem__(self, key: str) -> None: ... + def __iter__(self) -> Iterator[str]: ... + def __len__(self) -> int: ... diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index ce7770cb8e55..bd11558b0da9 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -1,35 +1,36 @@ """ Stacked area plot for 1D arrays inspired by Douglas Y'barbo's stackoverflow answer: -http://stackoverflow.com/questions/2225995/how-can-i-create-stacked-line-graph-with-matplotlib - -(http://stackoverflow.com/users/66549/doug) +https://stackoverflow.com/q/2225995/ +(https://stackoverflow.com/users/66549/doug) """ + +import itertools + import numpy as np -import matplotlib.cbook as cbook +from matplotlib import _api __all__ = ['stackplot'] def stackplot(axes, x, *args, - labels=(), colors=None, baseline='zero', + labels=(), colors=None, hatch=None, baseline='zero', **kwargs): """ - Draw a stacked area plot. + Draw a stacked area plot or a streamgraph. Parameters ---------- - x : 1d array of dimension N + x : (N,) array-like - y : 2d array (dimension MxN), or sequence of 1d arrays (each dimension 1xN) - - The data is assumed to be unstacked. Each of the following + y : (M, N) array-like + The data can be either stacked or unstacked. Each of the following calls is legal:: - stackplot(x, y) # where y is MxN - stackplot(x, y1, y2, y3, y4) # where y1, y2, y3, y4, are all 1xNm + stackplot(x, y) # where y has shape (M, N) e.g. y = [y1, y2, y3, y4] + stackplot(x, y1, y2, y3, y4) # where y1, y2, y3, y4 have length N baseline : {'zero', 'sym', 'wiggle', 'weighted_wiggle'} Method used to calculate the baseline: @@ -42,12 +43,32 @@ def stackplot(axes, x, *args, size of each layer. It is also called 'Streamgraph'-layout. More details can be found at http://leebyron.com/streamgraph/. - labels : Length N sequence of strings - Labels to assign to each data series. + labels : list of str, optional + A sequence of labels to assign to each data series. If unspecified, + then no labels will be applied to artists. + + colors : list of :mpltype:`color`, optional + A sequence of colors to be cycled through and used to color the stacked + areas. The sequence need not be exactly the same length as the number + of provided *y*, in which case the colors will repeat from the + beginning. - colors : Length N sequence of colors - A list or tuple of colors. These will be cycled through and used to - colour the stacked areas. + If not specified, the colors from the Axes property cycle will be used. + + hatch : list of str, default: None + A sequence of hatching styles. See + :doc:`/gallery/shapes_and_collections/hatch_style_reference`. + The sequence will be cycled through for filling the + stacked areas from bottom to top. + It need not be exactly the same length as the number + of provided *y*, in which case the styles will repeat from the + beginning. + + .. versionadded:: 3.9 + Support for list input + + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER **kwargs All other keyword arguments are passed to `.Axes.fill_between`. @@ -59,18 +80,25 @@ def stackplot(axes, x, *args, stacked area plot. """ - y = np.row_stack(args) + y = np.vstack(args) labels = iter(labels) if colors is not None: - axes.set_prop_cycle(color=colors) + colors = itertools.cycle(colors) + else: + colors = (axes._get_lines.get_next_color() for _ in y) + + if hatch is None or isinstance(hatch, str): + hatch = itertools.cycle([hatch]) + else: + hatch = itertools.cycle(hatch) # Assume data passed has not been 'stacked', so stack it here. # We'll need a float buffer for the upcoming calculations. stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32)) - cbook._check_in_list(['zero', 'sym', 'wiggle', 'weighted_wiggle'], - baseline=baseline) + _api.check_in_list(['zero', 'sym', 'wiggle', 'weighted_wiggle'], + baseline=baseline) if baseline == 'zero': first_line = 0. @@ -101,17 +129,19 @@ def stackplot(axes, x, *args, stack += first_line # Color between x = 0 and the first array. - color = axes._get_lines.get_next_color() coll = axes.fill_between(x, first_line, stack[0, :], - facecolor=color, label=next(labels, None), + facecolor=next(colors), + hatch=next(hatch), + label=next(labels, None), **kwargs) coll.sticky_edges.y[:] = [0] r = [coll] # Color between array i-1 and array i for i in range(len(y) - 1): - color = axes._get_lines.get_next_color() r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], - facecolor=color, label=next(labels, None), + facecolor=next(colors), + hatch=next(hatch), + label=next(labels, None), **kwargs)) return r diff --git a/lib/matplotlib/stackplot.pyi b/lib/matplotlib/stackplot.pyi new file mode 100644 index 000000000000..9509f858a4bf --- /dev/null +++ b/lib/matplotlib/stackplot.pyi @@ -0,0 +1,20 @@ +from matplotlib.axes import Axes +from matplotlib.collections import PolyCollection + +from collections.abc import Iterable +from typing import Literal +from numpy.typing import ArrayLike +from matplotlib.typing import ColorType + +def stackplot( + axes: Axes, + x: ArrayLike, + *args: ArrayLike, + labels: Iterable[str] = ..., + colors: Iterable[ColorType] | None = ..., + hatch: Iterable[str] | str | None = ..., + baseline: Literal["zero", "sym", "wiggle", "weighted_wiggle"] = ..., + **kwargs +) -> list[PolyCollection]: ... + +__all__ = ['stackplot'] diff --git a/lib/matplotlib/streamplot.py b/lib/matplotlib/streamplot.py index 3b8ba87a352b..ece8bebf8192 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -5,13 +5,11 @@ import numpy as np -import matplotlib -import matplotlib.cbook as cbook -import matplotlib.cm as cm +import matplotlib as mpl +from matplotlib import _api, cm, patches import matplotlib.colors as mcolors import matplotlib.collections as mcollections import matplotlib.lines as mlines -import matplotlib.patches as patches __all__ = ['streamplot'] @@ -20,14 +18,18 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, cmap=None, norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, transform=None, zorder=None, start_points=None, - maxlength=4.0, integration_direction='both'): + maxlength=4.0, integration_direction='both', + broken_streamlines=True, *, integration_max_step_scale=1.0, + integration_max_error_scale=1.0, num_arrows=1): """ Draw streamlines of a vector flow. Parameters ---------- - x, y : 1D arrays - An evenly spaced grid. + x, y : 1D/2D arrays + Evenly spaced strictly increasing arrays to make a grid. If 2D, all + rows of *x* must be equal and all columns of *y* must be equal; i.e., + they must be as if generated by ``np.meshgrid(x_1d, y_1d)``. u, v : 2D arrays *x* and *y*-velocities. The number of rows and columns must match the length of *y* and *x*, respectively. @@ -38,19 +40,17 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, For different densities in each direction, use a tuple (density_x, density_y). linewidth : float or 2D array - The width of the stream lines. With a 2D array the line width can be + The width of the streamlines. With a 2D array the line width can be varied across the grid. The array must have the same shape as *u* and *v*. - color : color or 2D array + color : :mpltype:`color` or 2D array The streamline color. If given an array, its values are converted to colors using *cmap* and *norm*. The array must have the same shape as *u* and *v*. - cmap : `~matplotlib.colors.Colormap` - Colormap used to plot streamlines and arrows. This is only used if - *color* is an array. - norm : `~matplotlib.colors.Normalize` - Normalize object used to scale luminance data to 0, 1. If ``None``, - stretch (min, max) to (0, 1). This is only used if *color* is an array. + cmap, norm + Data normalization and colormapping parameters for *color*; only used + if *color* is an array of floats. See `~.Axes.imshow` for a detailed + description. arrowsize : float Scaling factor for the arrow size. arrowstyle : str @@ -58,16 +58,45 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, See `~matplotlib.patches.FancyArrowPatch`. minlength : float Minimum length of streamline in axes coordinates. - start_points : Nx2 array + start_points : (N, 2) array Coordinates of starting points for the streamlines in data coordinates (the same coordinates as the *x* and *y* arrays). - zorder : int - The zorder of the stream lines and arrows. + zorder : float + The zorder of the streamlines and arrows. Artists with lower zorder values are drawn first. maxlength : float Maximum length of streamline in axes coordinates. integration_direction : {'forward', 'backward', 'both'}, default: 'both' Integrate the streamline in forward, backward or both directions. + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + broken_streamlines : boolean, default: True + If False, forces streamlines to continue until they + leave the plot domain. If True, they may be terminated if they + come too close to another streamline. + integration_max_step_scale : float, default: 1.0 + Multiplier on the maximum allowable step in the streamline integration routine. + A value between zero and one results in a max integration step smaller than + the default max step, resulting in more accurate streamlines at the cost + of greater computation time; a value greater than one does the converse. Must be + greater than zero. + + .. versionadded:: 3.11 + + integration_max_error_scale : float, default: 1.0 + Multiplier on the maximum allowable error in the streamline integration routine. + A value between zero and one results in a tighter max integration error than + the default max error, resulting in more accurate streamlines at the cost + of greater computation time; a value greater than one does the converse. Must be + greater than zero. + + .. versionadded:: 3.11 + + num_arrows : int + Number of arrows per streamline. The arrows are spaced equally along the steps + each streamline takes. Note that this can be different to being spaced equally + along the distance of the streamline. + Returns ------- @@ -77,7 +106,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, - ``lines``: `.LineCollection` of streamlines - ``arrows``: `.PatchCollection` containing `.FancyArrowPatch` - objects representing the arrows half-way along stream lines. + objects representing the arrows half-way along streamlines. This container will probably change in the future to allow changes to the colormap, alpha, etc. for both lines and arrows, but these @@ -87,6 +116,21 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, mask = StreamMask(density) dmap = DomainMap(grid, mask) + if integration_max_step_scale <= 0.0: + raise ValueError( + "The value of integration_max_step_scale must be > 0, " + + f"got {integration_max_step_scale}" + ) + + if integration_max_error_scale <= 0.0: + raise ValueError( + "The value of integration_max_error_scale must be > 0, " + + f"got {integration_max_error_scale}" + ) + + if num_arrows < 0: + raise ValueError(f"The value of num_arrows must be >= 0, got {num_arrows=}") + if zorder is None: zorder = mlines.Line2D.zorder @@ -97,14 +141,13 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, if color is None: color = axes._get_lines.get_next_color() - if linewidth is None: - linewidth = matplotlib.rcParams['lines.linewidth'] + linewidth = mpl._val_or_rc(linewidth, 'lines.linewidth') line_kw = {} arrow_kw = dict(arrowstyle=arrowstyle, mutation_scale=10 * arrowsize) - cbook._check_in_list(['both', 'forward', 'backward'], - integration_direction=integration_direction) + _api.check_in_list(['both', 'forward', 'backward'], + integration_direction=integration_direction) if integration_direction == 'both': maxlength /= 2. @@ -113,8 +156,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, if use_multicolor_lines: if color.shape != grid.shape: raise ValueError("If 'color' is given, it must match the shape of " - "'Grid(x, y)'") - line_colors = [] + "the (x, y) grid") + line_colors = [[]] # Empty entry allows concatenation of zero arrays. color = np.ma.masked_invalid(color) else: line_kw['color'] = color @@ -123,7 +166,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, if isinstance(linewidth, np.ndarray): if linewidth.shape != grid.shape: raise ValueError("If 'linewidth' is given, it must match the " - "shape of 'Grid(x, y)'") + "shape of the (x, y) grid") line_kw['linewidth'] = [] else: line_kw['linewidth'] = linewidth @@ -134,20 +177,22 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, # Sanity checks. if u.shape != grid.shape or v.shape != grid.shape: - raise ValueError("'u' and 'v' must match the shape of 'Grid(x, y)'") + raise ValueError("'u' and 'v' must match the shape of the (x, y) grid") u = np.ma.masked_invalid(u) v = np.ma.masked_invalid(v) - integrate = get_integrator(u, v, dmap, minlength, maxlength, - integration_direction) + integrate = _get_integrator(u, v, dmap, minlength, maxlength, + integration_direction) trajectories = [] if start_points is None: for xm, ym in _gen_starting_points(mask.shape): if mask[ym, xm] == 0: xg, yg = dmap.mask2grid(xm, ym) - t = integrate(xg, yg) + t = integrate(xg, yg, broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) if t is not None: trajectories.append(t) else: @@ -157,8 +202,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, for xs, ys in sp2: if not (grid.x_origin <= xs <= grid.x_origin + grid.width and grid.y_origin <= ys <= grid.y_origin + grid.height): - raise ValueError("Starting point ({}, {}) outside of data " - "boundaries".format(xs, ys)) + raise ValueError(f"Starting point ({xs}, {ys}) outside of " + "data boundaries") # Convert start_points from data to array coords # Shift the seed points from the bottom left of the data so that @@ -168,51 +213,65 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, for xs, ys in sp2: xg, yg = dmap.data2grid(xs, ys) - t = integrate(xg, yg) + # Floating point issues can cause xg, yg to be slightly out of + # bounds for xs, ys on the upper boundaries. Because we have + # already checked that the starting points are within the original + # grid, clip the xg, yg to the grid to work around this issue + xg = np.clip(xg, 0, grid.nx - 1) + yg = np.clip(yg, 0, grid.ny - 1) + + t = integrate(xg, yg, broken_streamlines, integration_max_step_scale, + integration_max_error_scale) if t is not None: trajectories.append(t) if use_multicolor_lines: if norm is None: norm = mcolors.Normalize(color.min(), color.max()) - if cmap is None: - cmap = cm.get_cmap(matplotlib.rcParams['image.cmap']) - else: - cmap = cm.get_cmap(cmap) + cmap = cm._ensure_cmap(cmap) streamlines = [] arrows = [] for t in trajectories: - tgx = np.array(t[0]) - tgy = np.array(t[1]) + tgx, tgy = t.T # Rescale from grid-coordinates to data-coordinates. - tx, ty = dmap.grid2data(*np.array(t)) + tx, ty = dmap.grid2data(tgx, tgy) tx += grid.x_origin ty += grid.y_origin - points = np.transpose([tx, ty]).reshape(-1, 1, 2) - streamlines.extend(np.hstack([points[:-1], points[1:]])) + # Create multiple tiny segments if varying width or color is given + if isinstance(linewidth, np.ndarray) or use_multicolor_lines: + points = np.transpose([tx, ty]).reshape(-1, 1, 2) + streamlines.extend(np.hstack([points[:-1], points[1:]])) + else: + points = np.transpose([tx, ty]) + streamlines.append(points) - # Add arrows half way along each trajectory. + # Distance along streamline s = np.cumsum(np.hypot(np.diff(tx), np.diff(ty))) - n = np.searchsorted(s, s[-1] / 2.) - arrow_tail = (tx[n], ty[n]) - arrow_head = (np.mean(tx[n:n + 2]), np.mean(ty[n:n + 2])) - if isinstance(linewidth, np.ndarray): line_widths = interpgrid(linewidth, tgx, tgy)[:-1] line_kw['linewidth'].extend(line_widths) - arrow_kw['linewidth'] = line_widths[n] - if use_multicolor_lines: color_values = interpgrid(color, tgx, tgy)[:-1] line_colors.append(color_values) - arrow_kw['color'] = cmap(norm(color_values[n])) - p = patches.FancyArrowPatch( - arrow_tail, arrow_head, transform=transform, **arrow_kw) - axes.add_patch(p) - arrows.append(p) + # Add arrows along each trajectory. + for x in range(1, num_arrows+1): + # Get index of distance along streamline to place arrow + idx = np.searchsorted(s, s[-1] * (x/(num_arrows+1))) + arrow_tail = (tx[idx], ty[idx]) + arrow_head = (np.mean(tx[idx:idx + 2]), np.mean(ty[idx:idx + 2])) + + if isinstance(linewidth, np.ndarray): + arrow_kw['linewidth'] = line_widths[idx] + + if use_multicolor_lines: + arrow_kw['color'] = cmap(norm(color_values[idx])) + + p = patches.FancyArrowPatch( + arrow_tail, arrow_head, transform=transform, **arrow_kw) + arrows.append(p) lc = mcollections.LineCollection( streamlines, transform=transform, **line_kw) @@ -223,22 +282,20 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, lc.set_cmap(cmap) lc.set_norm(norm) axes.add_collection(lc) - axes.autoscale_view() - ac = matplotlib.collections.PatchCollection(arrows) + ac = mcollections.PatchCollection(arrows) + # Adding the collection itself is broken; see #2341. + for p in arrows: + axes.add_patch(p) + + axes.autoscale_view() stream_container = StreamplotSet(lc, ac) return stream_container class StreamplotSet: - def __init__(self, lines, arrows, **kwargs): - if kwargs: - cbook.warn_deprecated( - "3.3", - message="Passing arbitrary keyword arguments to StreamplotSet " - "is deprecated since %(since) and will become an " - "error %(removal)s.") + def __init__(self, lines, arrows): self.lines = lines self.arrows = arrows @@ -280,8 +337,7 @@ def __init__(self, grid, mask): def grid2mask(self, xi, yi): """Return nearest space in mask-coords from given grid-coords.""" - return (int(xi * self.x_grid2mask + 0.5), - int(yi * self.y_grid2mask + 0.5)) + return round(xi * self.x_grid2mask), round(yi * self.y_grid2mask) def mask2grid(self, xm, ym): return xm * self.x_mask2grid, ym * self.y_mask2grid @@ -292,19 +348,19 @@ def data2grid(self, xd, yd): def grid2data(self, xg, yg): return xg / self.x_data2grid, yg / self.y_data2grid - def start_trajectory(self, xg, yg): + def start_trajectory(self, xg, yg, broken_streamlines=True): xm, ym = self.grid2mask(xg, yg) - self.mask._start_trajectory(xm, ym) + self.mask._start_trajectory(xm, ym, broken_streamlines) def reset_start_point(self, xg, yg): xm, ym = self.grid2mask(xg, yg) self.mask._current_xy = (xm, ym) - def update_trajectory(self, xg, yg): + def update_trajectory(self, xg, yg, broken_streamlines=True): if not self.grid.within_grid(xg, yg): raise InvalidIndexError xm, ym = self.grid2mask(xg, yg) - self.mask._update_trajectory(xm, ym) + self.mask._update_trajectory(xm, ym, broken_streamlines) def undo_trajectory(self): self.mask._undo_trajectory() @@ -314,26 +370,32 @@ class Grid: """Grid of data.""" def __init__(self, x, y): - if x.ndim == 1: + if np.ndim(x) == 1: pass - elif x.ndim == 2: - x_row = x[0, :] + elif np.ndim(x) == 2: + x_row = x[0] if not np.allclose(x_row, x): raise ValueError("The rows of 'x' must be equal") x = x_row else: raise ValueError("'x' can have at maximum 2 dimensions") - if y.ndim == 1: + if np.ndim(y) == 1: pass - elif y.ndim == 2: - y_col = y[:, 0] - if not np.allclose(y_col, y.T): + elif np.ndim(y) == 2: + yt = np.transpose(y) # Also works for nested lists. + y_col = yt[0] + if not np.allclose(y_col, yt): raise ValueError("The columns of 'y' must be equal") y = y_col else: raise ValueError("'y' can have at maximum 2 dimensions") + if not (np.diff(x) > 0).all(): + raise ValueError("'x' must be strictly increasing") + if not (np.diff(y) > 0).all(): + raise ValueError("'y' must be strictly increasing") + self.nx = len(x) self.ny = len(y) @@ -356,7 +418,7 @@ def shape(self): return self.ny, self.nx def within_grid(self, xi, yi): - """Return True if point is a valid index of grid.""" + """Return whether (*xi*, *yi*) is a valid index of the grid.""" # Note that xi/yi can be floats; so, for example, we can't simply check # `xi < self.nx` since *xi* can be `self.nx - 1 < xi < self.nx` return 0 <= xi <= self.nx - 1 and 0 <= yi <= self.ny - 1 @@ -388,17 +450,17 @@ def __init__(self, density): def __getitem__(self, args): return self._mask[args] - def _start_trajectory(self, xm, ym): + def _start_trajectory(self, xm, ym, broken_streamlines=True): """Start recording streamline trajectory""" self._traj = [] - self._update_trajectory(xm, ym) + self._update_trajectory(xm, ym, broken_streamlines) def _undo_trajectory(self): """Remove current trajectory from mask""" for t in self._traj: self._mask[t] = 0 - def _update_trajectory(self, xm, ym): + def _update_trajectory(self, xm, ym, broken_streamlines=True): """ Update current trajectory position in mask. @@ -410,7 +472,10 @@ def _update_trajectory(self, xm, ym): self._mask[ym, xm] = 1 self._current_xy = (xm, ym) else: - raise InvalidIndexError + if broken_streamlines: + raise InvalidIndexError + else: + pass class InvalidIndexError(Exception): @@ -424,7 +489,7 @@ class TerminateTrajectory(Exception): # Integrator definitions # ======================= -def get_integrator(u, v, dmap, minlength, maxlength, integration_direction): +def _get_integrator(u, v, dmap, minlength, maxlength, integration_direction): # rescale velocity onto grid-coordinates for integrations. u, v = dmap.data2grid(u, v) @@ -449,7 +514,8 @@ def backward_time(xi, yi): dxi, dyi = forward_time(xi, yi) return -dxi, -dyi - def integrate(x0, y0): + def integrate(x0, y0, broken_streamlines=True, integration_max_step_scale=1.0, + integration_max_error_scale=1.0): """ Return x, y grid-coordinates of trajectory based on starting point. @@ -461,30 +527,31 @@ def integrate(x0, y0): resulting trajectory is None if it is shorter than `minlength`. """ - stotal, x_traj, y_traj = 0., [], [] + stotal, xy_traj = 0., [] try: - dmap.start_trajectory(x0, y0) + dmap.start_trajectory(x0, y0, broken_streamlines) except InvalidIndexError: return None if integration_direction in ['both', 'backward']: - s, xt, yt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength) + s, xyt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength, + broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) stotal += s - x_traj += xt[::-1] - y_traj += yt[::-1] + xy_traj += xyt[::-1] if integration_direction in ['both', 'forward']: dmap.reset_start_point(x0, y0) - s, xt, yt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength) - if len(x_traj) > 0: - xt = xt[1:] - yt = yt[1:] + s, xyt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength, + broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) stotal += s - x_traj += xt - y_traj += yt + xy_traj += xyt[1:] if stotal > minlength: - return x_traj, y_traj + return np.broadcast_arrays(xy_traj, np.empty((1, 2)))[0] else: # reject short trajectories dmap.undo_trajectory() return None @@ -496,7 +563,9 @@ class OutOfBounds(IndexError): pass -def _integrate_rk12(x0, y0, dmap, f, maxlength): +def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True, + integration_max_step_scale=1.0, + integration_max_error_scale=1.0): """ 2nd-order Runge-Kutta algorithm with adaptive step size. @@ -516,14 +585,13 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): timestep is more suited to the problem as this would be very hard to judge automatically otherwise. - This integrator is about 1.5 - 2x as fast as both the RK4 and RK45 - solvers in most setups on my machine. I would recommend removing the - other two to keep things simple. + This integrator is about 1.5 - 2x as fast as RK4 and RK45 solvers (using + similar Python implementations) in most setups. """ # This error is below that needed to match the RK4 integrator. It # is set for visual reasons -- too low and corners start # appearing ugly and jagged. Can be tuned. - maxerror = 0.003 + maxerror = 0.003 * integration_max_error_scale # This limit is important (for all integrators) to avoid the # trajectory skipping some mask cells. We could relax this @@ -532,19 +600,18 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): # nature of the interpolation, this doesn't boost speed by much # for quite a bit of complexity. maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1) + maxds *= integration_max_step_scale ds = maxds stotal = 0 xi = x0 yi = y0 - xf_traj = [] - yf_traj = [] + xyf_traj = [] while True: try: if dmap.grid.within_grid(xi, yi): - xf_traj.append(xi) - yf_traj.append(yi) + xyf_traj.append((xi, yi)) else: raise OutOfBounds @@ -558,9 +625,8 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): # Out of the domain during this step. # Take an Euler step to the boundary to improve neatness # unless the trajectory is currently empty. - if xf_traj: - ds, xf_traj, yf_traj = _euler_step(xf_traj, yf_traj, - dmap, f) + if xyf_traj: + ds, xyf_traj = _euler_step(xyf_traj, dmap, f) stotal += ds break except TerminateTrajectory: @@ -571,7 +637,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): dx2 = ds * 0.5 * (k1x + k2x) dy2 = ds * 0.5 * (k1y + k2y) - nx, ny = dmap.grid.shape + ny, nx = dmap.grid.shape # Error is normalized to the axes coordinates error = np.hypot((dx2 - dx1) / (nx - 1), (dy2 - dy1) / (ny - 1)) @@ -580,7 +646,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): xi += dx2 yi += dy2 try: - dmap.update_trajectory(xi, yi) + dmap.update_trajectory(xi, yi, broken_streamlines) except InvalidIndexError: break if stotal + ds > maxlength: @@ -593,14 +659,13 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): else: ds = min(maxds, 0.85 * ds * (maxerror / error) ** 0.5) - return stotal, xf_traj, yf_traj + return stotal, xyf_traj -def _euler_step(xf_traj, yf_traj, dmap, f): +def _euler_step(xyf_traj, dmap, f): """Simple Euler integration step that extends streamline to boundary.""" ny, nx = dmap.grid.shape - xi = xf_traj[-1] - yi = yf_traj[-1] + xi, yi = xyf_traj[-1] cx, cy = f(xi, yi) if cx == 0: dsx = np.inf @@ -615,9 +680,8 @@ def _euler_step(xf_traj, yf_traj, dmap, f): else: dsy = (ny - 1 - yi) / cy ds = min(dsx, dsy) - xf_traj.append(xi + cx * ds) - yf_traj.append(yi + cy * ds) - return ds, xf_traj, yf_traj + xyf_traj.append((xi + cx * ds, yi + cy * ds)) + return ds, xyf_traj # Utility functions diff --git a/lib/matplotlib/streamplot.pyi b/lib/matplotlib/streamplot.pyi new file mode 100644 index 000000000000..ca3553edc2fd --- /dev/null +++ b/lib/matplotlib/streamplot.pyi @@ -0,0 +1,88 @@ +from matplotlib.axes import Axes +from matplotlib.colors import Normalize, Colormap +from matplotlib.collections import LineCollection, PatchCollection +from matplotlib.patches import ArrowStyle +from matplotlib.transforms import Transform + +from typing import Literal +from numpy.typing import ArrayLike +from .typing import ColorType + +def streamplot( + axes: Axes, + x: ArrayLike, + y: ArrayLike, + u: ArrayLike, + v: ArrayLike, + density: float | tuple[float, float] = ..., + linewidth: float | ArrayLike | None = ..., + color: ColorType | ArrayLike | None = ..., + cmap: str | Colormap | None = ..., + norm: str | Normalize | None = ..., + arrowsize: float = ..., + arrowstyle: str | ArrowStyle = ..., + minlength: float = ..., + transform: Transform | None = ..., + zorder: float | None = ..., + start_points: ArrayLike | None = ..., + maxlength: float = ..., + integration_direction: Literal["forward", "backward", "both"] = ..., + broken_streamlines: bool = ..., + *, + integration_max_step_scale: float = ..., + integration_max_error_scale: float = ..., + num_arrows: int = ..., +) -> StreamplotSet: ... + +class StreamplotSet: + lines: LineCollection + arrows: PatchCollection + def __init__(self, lines: LineCollection, arrows: PatchCollection) -> None: ... + +class DomainMap: + grid: Grid + mask: StreamMask + x_grid2mask: float + y_grid2mask: float + x_mask2grid: float + y_mask2grid: float + x_data2grid: float + y_data2grid: float + def __init__(self, grid: Grid, mask: StreamMask) -> None: ... + def grid2mask(self, xi: float, yi: float) -> tuple[int, int]: ... + def mask2grid(self, xm: float, ym: float) -> tuple[float, float]: ... + def data2grid(self, xd: float, yd: float) -> tuple[float, float]: ... + def grid2data(self, xg: float, yg: float) -> tuple[float, float]: ... + def start_trajectory( + self, xg: float, yg: float, broken_streamlines: bool = ... + ) -> None: ... + def reset_start_point(self, xg: float, yg: float) -> None: ... + def update_trajectory(self, xg, yg, broken_streamlines: bool = ...) -> None: ... + def undo_trajectory(self) -> None: ... + +class Grid: + nx: int + ny: int + dx: float + dy: float + x_origin: float + y_origin: float + width: float + height: float + def __init__(self, x: ArrayLike, y: ArrayLike) -> None: ... + @property + def shape(self) -> tuple[int, int]: ... + def within_grid(self, xi: float, yi: float) -> bool: ... + +class StreamMask: + nx: int + ny: int + shape: tuple[int, int] + def __init__(self, density: float | tuple[float, float]) -> None: ... + def __getitem__(self, args): ... + +class InvalidIndexError(Exception): ... +class TerminateTrajectory(Exception): ... +class OutOfBounds(IndexError): ... + +__all__ = ['streamplot'] diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py index 42d050d22cd0..488c6d6ae1ec 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -1 +1,4 @@ -from .core import use, context, available, library, reload_library +from .core import available, context, library, reload_library, use + + +__all__ = ["available", "context", "library", "reload_library", "use"] diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index e17e110a94ee..e36c3c37a882 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -12,14 +12,14 @@ """ import contextlib +import importlib.resources import logging import os from pathlib import Path -import re import warnings import matplotlib as mpl -from matplotlib import cbook, rc_params_from_file, rcParamsDefault +from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault _log = logging.getLogger(__name__) @@ -30,41 +30,18 @@ # Users may want multiple library paths, so store a list of paths. USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')] STYLE_EXTENSION = 'mplstyle' -STYLE_FILE_PATTERN = re.compile(r'([\S]+).%s$' % STYLE_EXTENSION) - - # A list of rcParams that should not be applied from styles STYLE_BLACKLIST = { - 'interactive', 'backend', 'backend.qt4', 'webagg.port', 'webagg.address', + 'interactive', 'backend', 'webagg.port', 'webagg.address', 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', - 'toolbar', 'timezone', 'datapath', 'figure.max_open_warning', + 'toolbar', 'timezone', 'figure.max_open_warning', 'figure.raise_window', 'savefig.directory', 'tk.window_focus', 'docstring.hardcopy', 'date.epoch'} -def _remove_blacklisted_style_params(d, warn=True): - o = {} - for key in d: # prevent triggering RcParams.__getitem__('backend') - if key in STYLE_BLACKLIST: - if warn: - cbook._warn_external( - "Style includes a parameter, '{0}', that is not related " - "to style. Ignoring".format(key)) - else: - o[key] = d[key] - return o - - -@cbook.deprecated("3.2") -def is_style_file(filename): - """Return True if the filename looks like a style file.""" - return STYLE_FILE_PATTERN.match(filename) is not None - - -def _apply_style(d, warn=True): - mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn)) - - +@_docstring.Substitution( + "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower))) +) def use(style): """ Use Matplotlib style settings from a style specification. @@ -80,51 +57,90 @@ def use(style): Parameters ---------- style : str, dict, Path or list + A style specification. Valid options are: - +------+-------------------------------------------------------------+ - | str | The name of a style or a path/URL to a style file. For a | - | | list of available style names, see `style.available`. | - +------+-------------------------------------------------------------+ - | dict | Dictionary with valid key/value pairs for | - | | `matplotlib.rcParams`. | - +------+-------------------------------------------------------------+ - | Path | A path-like object which is a path to a style file. | - +------+-------------------------------------------------------------+ - | list | A list of style specifiers (str, Path or dict) applied from | - | | first to last in the list. | - +------+-------------------------------------------------------------+ + str + - One of the style names in `.style.available` (a builtin style or + a style installed in the user library path). + + - A dotted name of the form "package.style_name"; in that case, + "package" should be an importable Python package name, e.g. at + ``/path/to/package/__init__.py``; the loaded style file is + ``/path/to/package/style_name.mplstyle``. (Style files in + subpackages are likewise supported.) + + - The path or URL to a style file, which gets loaded by + `.rc_params_from_file`. + + dict + A mapping of key/value pairs for `matplotlib.rcParams`. + + Path + The path to a style file, which gets loaded by + `.rc_params_from_file`. + + list + A list of style specifiers (str, Path or dict), which are applied + from first to last in the list. + + Notes + ----- + The following `.rcParams` are not related to style and will be ignored if + found in a style specification: + %s """ - style_alias = {'mpl20': 'default', - 'mpl15': 'classic'} if isinstance(style, (str, Path)) or hasattr(style, 'keys'): # If name is a single str, Path or dict, make it a single element list. styles = [style] else: styles = style - styles = (style_alias.get(s, s) if isinstance(s, str) else s - for s in styles) + style_alias = {'mpl20': 'default', 'mpl15': 'classic'} + for style in styles: - if not isinstance(style, (str, Path)): - _apply_style(style) - elif style == 'default': - # Deprecation warnings were already handled when creating - # rcParamsDefault, no need to reemit them here. - with cbook._suppress_matplotlib_deprecation_warning(): - _apply_style(rcParamsDefault, warn=False) - elif style in library: - _apply_style(library[style]) - else: + if isinstance(style, str): + style = style_alias.get(style, style) + if style == "default": + # Deprecation warnings were already handled when creating + # rcParamsDefault, no need to reemit them here. + with _api.suppress_matplotlib_deprecation_warning(): + # don't trigger RcParams.__getitem__('backend') + style = {k: rcParamsDefault[k] for k in rcParamsDefault + if k not in STYLE_BLACKLIST} + elif style in library: + style = library[style] + elif "." in style: + pkg, _, name = style.rpartition(".") + try: + path = importlib.resources.files(pkg) / f"{name}.{STYLE_EXTENSION}" + style = _rc_params_in_file(path) + except (ModuleNotFoundError, OSError, TypeError) as exc: + # There is an ambiguity whether a dotted name refers to a + # package.style_name or to a dotted file path. Currently, + # we silently try the first form and then the second one; + # in the future, we may consider forcing file paths to + # either use Path objects or be prepended with "./" and use + # the slash as marker for file paths. + pass + if isinstance(style, (str, Path)): try: - rc = rc_params_from_file(style, use_default_template=False) - _apply_style(rc) - except IOError as err: - raise IOError( - "{!r} not found in the style library and input is not a " - "valid URL or path; see `style.available` for list of " - "available styles".format(style)) from err + style = _rc_params_in_file(style) + except OSError as err: + raise OSError( + f"{style!r} is not a valid package style, path of style " + f"file, URL of style file, or library style name (library " + f"styles are listed in `style.available`)") from err + filtered = {} + for k in style: # don't trigger RcParams.__getitem__('backend') + if k in STYLE_BLACKLIST: + _api.warn_external( + f"Style includes a parameter, {k!r}, that is not " + f"related to style. Ignoring this parameter.") + else: + filtered[k] = style[k] + mpl.rcParams.update(filtered) @contextlib.contextmanager @@ -137,18 +153,28 @@ def context(style, after_reset=False): style : str, dict, Path or list A style specification. Valid options are: - +------+-------------------------------------------------------------+ - | str | The name of a style or a path/URL to a style file. For a | - | | list of available style names, see `style.available`. | - +------+-------------------------------------------------------------+ - | dict | Dictionary with valid key/value pairs for | - | | `matplotlib.rcParams`. | - +------+-------------------------------------------------------------+ - | Path | A path-like object which is a path to a style file. | - +------+-------------------------------------------------------------+ - | list | A list of style specifiers (str, Path or dict) applied from | - | | first to last in the list. | - +------+-------------------------------------------------------------+ + str + - One of the style names in `.style.available` (a builtin style or + a style installed in the user library path). + + - A dotted name of the form "package.style_name"; in that case, + "package" should be an importable Python package name, e.g. at + ``/path/to/package/__init__.py``; the loaded style file is + ``/path/to/package/style_name.mplstyle``. (Style files in + subpackages are likewise supported.) + + - The path or URL to a style file, which gets loaded by + `.rc_params_from_file`. + dict + A mapping of key/value pairs for `matplotlib.rcParams`. + + Path + The path to a style file, which gets loaded by + `.rc_params_from_file`. + + list + A list of style specifiers (str, Path or dict), which are applied + from first to last in the list. after_reset : bool If True, apply style after resetting settings to their defaults; @@ -161,45 +187,20 @@ def context(style, after_reset=False): yield -def load_base_library(): - """Load style library defined in this package.""" - library = read_style_directory(BASE_LIBRARY_PATH) - return library - - -def iter_user_libraries(): - for stylelib_path in USER_LIBRARY_PATHS: - stylelib_path = os.path.expanduser(stylelib_path) - if os.path.exists(stylelib_path) and os.path.isdir(stylelib_path): - yield stylelib_path - - def update_user_library(library): """Update style library with user-defined rc files.""" - for stylelib_path in iter_user_libraries(): + for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS): styles = read_style_directory(stylelib_path) update_nested_dict(library, styles) return library -@cbook.deprecated("3.2") -def iter_style_files(style_dir): - """Yield file path and name of styles in the given directory.""" - for path in os.listdir(style_dir): - filename = os.path.basename(path) - if is_style_file(filename): - match = STYLE_FILE_PATTERN.match(filename) - path = os.path.abspath(os.path.join(style_dir, path)) - yield path, match.group(1) - - def read_style_directory(style_dir): """Return dictionary of styles defined in *style_dir*.""" styles = dict() for path in Path(style_dir).glob(f"*.{STYLE_EXTENSION}"): with warnings.catch_warnings(record=True) as warns: - styles[path.stem] = rc_params_from_file( - path, use_default_template=False) + styles[path.stem] = _rc_params_in_file(path) for w in warns: _log.warning('In %s: %s', path, w.message) return styles @@ -221,17 +222,15 @@ def update_nested_dict(main_dict, new_dict): # Load style library # ================== -_base_library = load_base_library() - -library = None - +_base_library = read_style_directory(BASE_LIBRARY_PATH) +library = {} available = [] def reload_library(): """Reload the style library.""" - global library - library = update_user_library(_base_library) + library.clear() + library.update(update_user_library(_base_library)) available[:] = sorted(library.keys()) diff --git a/lib/matplotlib/style/core.pyi b/lib/matplotlib/style/core.pyi new file mode 100644 index 000000000000..5734b017f7c4 --- /dev/null +++ b/lib/matplotlib/style/core.pyi @@ -0,0 +1,21 @@ +from collections.abc import Generator +import contextlib + +from matplotlib import RcParams +from matplotlib.typing import RcStyleType + +USER_LIBRARY_PATHS: list[str] = ... +STYLE_EXTENSION: str = ... + +def use(style: RcStyleType) -> None: ... +@contextlib.contextmanager +def context( + style: RcStyleType, after_reset: bool = ... +) -> Generator[None, None, None]: ... + +library: dict[str, RcParams] +available: list[str] + +def reload_library() -> None: ... + +__all__ = ['use', 'context', 'available', 'library', 'reload_library'] diff --git a/lib/matplotlib/style/meson.build b/lib/matplotlib/style/meson.build new file mode 100644 index 000000000000..03e7972132bb --- /dev/null +++ b/lib/matplotlib/style/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + 'core.py', +] + +typing_sources = [ + 'core.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/style') diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index 87148b1e40c1..370ce9fe922f 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -9,6 +9,11 @@ """ Tables drawing. +.. note:: + The table implementation in Matplotlib is lightly maintained. For a more + featureful table implementation, you may wish to try `blume + `_. + Use the factory function `~matplotlib.table.table` to create a ready-made table from texts. If you need more control, use the `.Table` class and its methods. @@ -19,13 +24,17 @@ Thanks to John Gill for providing the class and table. """ -from . import artist, cbook, docstring +import numpy as np + +from . import _api, _docstring from .artist import Artist, allow_rasterization from .patches import Rectangle from .text import Text from .transforms import Bbox from .path import Path +from .cbook import _is_pandas_dataframe + class Cell(Rectangle): """ @@ -46,13 +55,12 @@ class Cell(Rectangle): 'vertical': 'RL' } - def __init__(self, xy, width, height, + def __init__(self, xy, width, height, *, edgecolor='k', facecolor='w', fill=True, text='', - loc=None, + loc='right', fontproperties=None, - *, visible_edges='closed', ): """ @@ -64,20 +72,21 @@ def __init__(self, xy, width, height, The cell width. height : float The cell height. - edgecolor : color + edgecolor : :mpltype:`color`, default: 'k' The color of the cell border. - facecolor : color + facecolor : :mpltype:`color`, default: 'w' The cell facecolor. - fill : bool + fill : bool, default: True Whether the cell background is filled. - text : str + text : str, optional The cell text. - loc : {'left', 'center', 'right'}, default: 'right' + loc : {'right', 'center', 'left'} The alignment of the text within the cell. - fontproperties : dict + fontproperties : dict, optional A dict defining the font properties of the text. Supported keys and values are the keyword arguments accepted by `.FontProperties`. - visible_edges : str, default: 'closed' + visible_edges : {'closed', 'open', 'horizontal', 'vertical'} or \ +substring of 'BRTL' The cell edges to be drawn with a line: a substring of 'BRTL' (bottom, right, top, left), or one of 'open' (no edges drawn), 'closed' (all edges drawn), 'horizontal' (bottom and top), @@ -91,15 +100,13 @@ def __init__(self, xy, width, height, self.visible_edges = visible_edges # Create text object - if loc is None: - loc = 'right' self._loc = loc self._text = Text(x=xy[0], y=xy[1], clip_on=False, text=text, fontproperties=fontproperties, horizontalalignment=loc, verticalalignment='center') - def set_transform(self, trans): - super().set_transform(trans) + def set_transform(self, t): + super().set_transform(t) # the text does not get the transform! self.stale = True @@ -170,16 +177,16 @@ def get_required_width(self, renderer): l, b, w, h = self.get_text_bounds(renderer) return w * (1.0 + (2.0 * self.PAD)) - @docstring.dedent_interpd + @_docstring.interpd def set_text_props(self, **kwargs): """ Update the text properties. Valid keyword arguments are: - %(Text)s + %(Text:kwdoc)s """ - self._text.update(kwargs) + self._text._internal_update(kwargs) self.stale = True @property @@ -274,12 +281,12 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs): """ Parameters ---------- - ax : `matplotlib.axes.Axes` + ax : `~matplotlib.axes.Axes` The `~.axes.Axes` to plot the table into. - loc : str + loc : str, optional The position of the cell with respect to *ax*. This must be one of the `~.Table.codes`. - bbox : `.Bbox` or None + bbox : `.Bbox` or [xmin, ymin, width, height], optional A bounding box to draw the table into. If this is not *None*, this overrides *loc*. @@ -297,7 +304,7 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs): "Unrecognized location {!r}. Valid locations are\n\t{}" .format(loc, '\n\t'.join(self.codes))) loc = self.codes[loc] - self.set_figure(ax.figure) + self.set_figure(ax.get_figure(root=False)) self._axes = ax self._loc = loc self._bbox = bbox @@ -310,7 +317,7 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs): self._edges = None self._autoColumns = [] self._autoFontsize = True - self.update(kwargs) + self._internal_update(kwargs) self.set_clip_on(False) @@ -342,13 +349,13 @@ def __setitem__(self, position, cell): """ Set a custom cell in a given position. """ - cbook._check_isinstance(Cell, cell=cell) + _api.check_isinstance(Cell, cell=cell) try: row, col = position[0], position[1] except Exception as err: raise KeyError('Only tuples length 2 are accepted as ' 'coordinates') from err - cell.set_figure(self.figure) + cell.set_figure(self.get_figure(root=False)) cell.set_transform(self.get_transform()) cell.set_clip_on(False) self._cells[row, col] = cell @@ -383,7 +390,7 @@ def edges(self, value): self.stale = True def _approx_text_height(self): - return (self.FONTSIZE / 72.0 * self.figure.dpi / + return (self.FONTSIZE / 72.0 * self.get_figure(root=True).dpi / self._axes.bbox.height * 1.2) @allow_rasterization @@ -393,7 +400,7 @@ def draw(self, renderer): # Need a renderer to do hit tests on mouseevent; assume the last one # will do if renderer is None: - renderer = self.figure._cachedRenderer + renderer = self.get_figure(root=True)._get_renderer() if renderer is None: raise RuntimeError('No renderer defined') @@ -422,12 +429,11 @@ def _get_grid_bbox(self, renderer): def contains(self, mouseevent): # docstring inherited - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info + if self._different_canvas(mouseevent): + return False, {} # TODO: Return index of the cell containing the cursor so that the user # doesn't have to bind to each one individually. - renderer = self.figure._cachedRenderer + renderer = self.get_figure(root=True)._get_renderer() if renderer is not None: boxes = [cell.get_window_extent(renderer) for (row, col), cell in self._cells.items() @@ -441,8 +447,10 @@ def get_children(self): """Return the Artists contained by the table.""" return list(self._cells.values()) - def get_window_extent(self, renderer): - """Return the bounding box of the table in window coords.""" + def get_window_extent(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() self._update_positions(renderer) boxes = [cell.get_window_extent(renderer) for cell in self._cells.values()] @@ -488,14 +496,11 @@ def auto_set_column_width(self, col): col : int or sequence of ints The indices of the columns to auto-scale. """ - # check for col possibility on iteration - try: - iter(col) - except (TypeError, AttributeError): - self._autoColumns.append(col) - else: - for cell in col: - self._autoColumns.append(cell) + col1d = np.atleast_1d(col) + if not np.issubdtype(col1d.dtype, np.integer): + raise TypeError("col must be an int or sequence of ints.") + for cell in col1d: + self._autoColumns.append(cell) self.stale = True @@ -587,7 +592,10 @@ def _update_positions(self, renderer): if self._bbox is not None: # Position according to bbox - rl, rb, rw, rh = self._bbox + if isinstance(self._bbox, Bbox): + rl, rb, rw, rh = self._bbox.bounds + else: + rl, rb, rw, rh = self._bbox self.scale(rw / w, rh / h) ox = rl - l oy = rb - b @@ -639,10 +647,7 @@ def get_celld(self): return self._cells -docstring.interpd.update(Table=artist.kwdoc(Table)) - - -@docstring.dedent_interpd +@_docstring.interpd def table(ax, cellText=None, cellColours=None, cellLoc='right', colWidths=None, @@ -663,20 +668,20 @@ def table(ax, *colLoc* respectively. For finer grained control over tables, use the `.Table` class and add it to - the axes with `.Axes.add_table`. + the Axes with `.Axes.add_table`. Parameters ---------- - cellText : 2D list of str, optional + cellText : 2D list of str or pandas.DataFrame, optional The texts to place into the table cells. *Note*: Line breaks in the strings are currently not accounted for and will result in the text exceeding the cell boundaries. - cellColours : 2D list of colors, optional + cellColours : 2D list of :mpltype:`color`, optional The background colors of the cells. - cellLoc : {'left', 'center', 'right'}, default: 'right' + cellLoc : {'right', 'center', 'left'} The alignment of the text within the cells. colWidths : list of float, optional @@ -686,30 +691,30 @@ def table(ax, rowLabels : list of str, optional The text of the row header cells. - rowColours : list of colors, optional + rowColours : list of :mpltype:`color`, optional The colors of the row header cells. - rowLoc : {'left', 'center', 'right'}, default: 'left' + rowLoc : {'left', 'center', 'right'} The text alignment of the row header cells. colLabels : list of str, optional The text of the column header cells. - colColours : list of colors, optional + colColours : list of :mpltype:`color`, optional The colors of the column header cells. - colLoc : {'left', 'center', 'right'}, default: 'left' + colLoc : {'center', 'left', 'right'} The text alignment of the column header cells. - loc : str, optional + loc : str, default: 'bottom' The position of the cell with respect to *ax*. This must be one of the `~.Table.codes`. - bbox : `.Bbox`, optional + bbox : `.Bbox` or [xmin, ymin, width, height], optional A bounding box to draw the table into. If this is not *None*, this overrides *loc*. - edges : substring of 'BRTL' or {'open', 'closed', 'horizontal', 'vertical'} + edges : {'closed', 'open', 'horizontal', 'vertical'} or substring of 'BRTL' The cell edges to be drawn with a line. See also `~.Cell.visible_edges`. @@ -723,7 +728,7 @@ def table(ax, **kwargs `.Table` properties. - %(Table)s + %(Table:kwdoc)s """ if cellColours is None and cellText is None: @@ -737,20 +742,35 @@ def table(ax, cols = len(cellColours[0]) cellText = [[''] * cols] * rows + # Check if we have a Pandas DataFrame + if _is_pandas_dataframe(cellText): + # if rowLabels/colLabels are empty, use DataFrame entries. + # Otherwise, throw an error. + if rowLabels is None: + rowLabels = cellText.index + else: + raise ValueError("rowLabels cannot be used alongside Pandas DataFrame") + if colLabels is None: + colLabels = cellText.columns + else: + raise ValueError("colLabels cannot be used alongside Pandas DataFrame") + # Update cellText with only values + cellText = cellText.values + rows = len(cellText) cols = len(cellText[0]) for row in cellText: if len(row) != cols: - raise ValueError("Each row in 'cellText' must have {} columns" - .format(cols)) + raise ValueError(f"Each row in 'cellText' must have {cols} " + "columns") if cellColours is not None: if len(cellColours) != rows: - raise ValueError("'cellColours' must have {} rows".format(rows)) + raise ValueError(f"'cellColours' must have {rows} rows") for row in cellColours: if len(row) != cols: - raise ValueError("Each row in 'cellColours' must have {} " - "columns".format(cols)) + raise ValueError("Each row in 'cellColours' must have " + f"{cols} columns") else: cellColours = ['w' * cols] * rows @@ -770,7 +790,7 @@ def table(ax, if rowLabels is not None: if len(rowLabels) != rows: - raise ValueError("'rowLabels' must be of length {0}".format(rows)) + raise ValueError(f"'rowLabels' must be of length {rows}") # If we have column labels, need to shift # the text and colour arrays down 1 row @@ -818,5 +838,9 @@ def table(ax, if rowLabelWidth == 0: table.auto_set_column_width(-1) + # set_fontsize is only effective after cells are added + if "fontsize" in kwargs: + table.set_fontsize(kwargs["fontsize"]) + ax.add_table(table) return table diff --git a/lib/matplotlib/table.pyi b/lib/matplotlib/table.pyi new file mode 100644 index 000000000000..167d98d3c4cb --- /dev/null +++ b/lib/matplotlib/table.pyi @@ -0,0 +1,87 @@ +from .artist import Artist +from .axes import Axes +from .backend_bases import RendererBase +from .patches import Rectangle +from .path import Path +from .text import Text +from .transforms import Bbox +from .typing import ColorType + +from collections.abc import Sequence +from typing import Any, Literal + +from pandas import DataFrame + +class Cell(Rectangle): + PAD: float + def __init__( + self, + xy: tuple[float, float], + width: float, + height: float, + *, + edgecolor: ColorType = ..., + facecolor: ColorType = ..., + fill: bool = ..., + text: str = ..., + loc: Literal["left", "center", "right"] = ..., + fontproperties: dict[str, Any] | None = ..., + visible_edges: str | None = ... + ) -> None: ... + def get_text(self) -> Text: ... + def set_fontsize(self, size: float) -> None: ... + def get_fontsize(self) -> float: ... + def auto_set_font_size(self, renderer: RendererBase) -> float: ... + def get_text_bounds( + self, renderer: RendererBase + ) -> tuple[float, float, float, float]: ... + def get_required_width(self, renderer: RendererBase) -> float: ... + def set_text_props(self, **kwargs) -> None: ... + @property + def visible_edges(self) -> str: ... + @visible_edges.setter + def visible_edges(self, value: str | None) -> None: ... + def get_path(self) -> Path: ... + +CustomCell = Cell + +class Table(Artist): + codes: dict[str, int] + FONTSIZE: float + AXESPAD: float + def __init__( + self, ax: Axes, loc: str | None = ..., bbox: Bbox | None = ..., **kwargs + ) -> None: ... + def add_cell(self, row: int, col: int, *args, **kwargs) -> Cell: ... + def __setitem__(self, position: tuple[int, int], cell: Cell) -> None: ... + def __getitem__(self, position: tuple[int, int]) -> Cell: ... + @property + def edges(self) -> str | None: ... + @edges.setter + def edges(self, value: str | None) -> None: ... + def draw(self, renderer) -> None: ... + def get_children(self) -> list[Artist]: ... + def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... + def auto_set_column_width(self, col: int | Sequence[int]) -> None: ... + def auto_set_font_size(self, value: bool = ...) -> None: ... + def scale(self, xscale: float, yscale: float) -> None: ... + def set_fontsize(self, size: float) -> None: ... + def get_celld(self) -> dict[tuple[int, int], Cell]: ... + +def table( + ax: Axes, + cellText: Sequence[Sequence[str]] | DataFrame | None = ..., + cellColours: Sequence[Sequence[ColorType]] | None = ..., + cellLoc: Literal["left", "center", "right"] = ..., + colWidths: Sequence[float] | None = ..., + rowLabels: Sequence[str] | None = ..., + rowColours: Sequence[ColorType] | None = ..., + rowLoc: Literal["left", "center", "right"] = ..., + colLabels: Sequence[str] | None = ..., + colColours: Sequence[ColorType] | None = ..., + colLoc: Literal["left", "center", "right"] = ..., + loc: str = ..., + bbox: Bbox | None = ..., + edges: str = ..., + **kwargs +) -> Table: ... diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 9c9c728b7c14..d6affb1b039f 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -1,12 +1,18 @@ """ Helper functions for testing. """ - +import itertools import locale import logging +import os +from pathlib import Path +import string +import subprocess +import sys +from tempfile import TemporaryDirectory import matplotlib as mpl -from matplotlib import cbook +from matplotlib import _api _log = logging.getLogger(__name__) @@ -37,10 +43,235 @@ def setup(): mpl.use('Agg') - with cbook._suppress_matplotlib_deprecation_warning(): + with _api.suppress_matplotlib_deprecation_warning(): mpl.rcdefaults() # Start with all defaults # These settings *must* be hardcoded for running the comparison tests and # are not necessarily the default values as specified in rcsetup.py. set_font_settings_for_testing() set_reproducibility_for_testing() + + +def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, + stderr=None, check=False, text=True, + capture_output=False): + """ + Create and run a subprocess. + + Thin wrapper around `subprocess.run`, intended for testing. Will + mark fork() failures on Cygwin as expected failures: not a + success, but not indicating a problem with the code either. + + Parameters + ---------- + args : list of str + env : dict[str, str] + timeout : float + stdout, stderr + check : bool + text : bool + Also called ``universal_newlines`` in subprocess. I chose this + name since the main effect is returning bytes (`False`) vs. str + (`True`), though it also tries to normalize newlines across + platforms. + capture_output : bool + Set stdout and stderr to subprocess.PIPE + + Returns + ------- + proc : subprocess.Popen + + See Also + -------- + subprocess.run + + Raises + ------ + pytest.xfail + If platform is Cygwin and subprocess reports a fork() failure. + """ + if capture_output: + stdout = stderr = subprocess.PIPE + try: + proc = subprocess.run( + command, env=env, + timeout=timeout, check=check, + stdout=stdout, stderr=stderr, + text=text + ) + except BlockingIOError: + if sys.platform == "cygwin": + # Might want to make this more specific + import pytest + pytest.xfail("Fork failure") + raise + return proc + + +def subprocess_run_helper(func, *args, timeout, extra_env=None): + """ + Run a function in a sub-process. + + Parameters + ---------- + func : function + The function to be run. It must be in a module that is importable. + *args : str + Any additional command line arguments to be passed in + the first argument to ``subprocess.run``. + extra_env : dict[str, str] + Any additional environment variables to be set for the subprocess. + """ + target = func.__name__ + module = func.__module__ + file = func.__code__.co_filename + proc = subprocess_run_for_testing( + [ + sys.executable, + "-c", + f"import importlib.util;" + f"_spec = importlib.util.spec_from_file_location({module!r}, {file!r});" + f"_module = importlib.util.module_from_spec(_spec);" + f"_spec.loader.exec_module(_module);" + f"_module.{target}()", + *args + ], + env={**os.environ, "SOURCE_DATE_EPOCH": "0", **(extra_env or {})}, + timeout=timeout, check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + return proc + + +def _check_for_pgf(texsystem): + """ + Check if a given TeX system + pgf is available + + Parameters + ---------- + texsystem : str + The executable name to check + """ + with TemporaryDirectory() as tmpdir: + tex_path = Path(tmpdir, "test.tex") + tex_path.write_text(r""" + \documentclass{article} + \usepackage{pgf} + \begin{document} + \typeout{pgfversion=\pgfversion} + \makeatletter + \@@end + """, encoding="utf-8") + try: + subprocess.check_call( + [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except (OSError, subprocess.CalledProcessError): + return False + return True + + +def _has_tex_package(package): + try: + mpl.dviread.find_tex_file(f"{package}.sty") + return True + except FileNotFoundError: + return False + + +def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backends): + import pytest + IPython = pytest.importorskip("IPython") + + if sys.platform == "win32": + pytest.skip("Cannot change backend running IPython in subprocess on Windows") + + if (IPython.version_info[:3] == (8, 24, 0) and + requested_backend_or_gui_framework == "osx"): + pytest.skip("Bug using macosx backend in IPython 8.24.0 fixed in 8.24.1") + + # This code can be removed when Python 3.12, the latest version supported + # by IPython < 8.24, reaches end-of-life in late 2028. + for min_version, backend in all_expected_backends.items(): + if IPython.version_info[:2] >= min_version: + expected_backend = backend + break + + code = ("import matplotlib as mpl, matplotlib.pyplot as plt;" + "fig, ax=plt.subplots(); ax.plot([1, 3, 2]); mpl.get_backend()") + proc = subprocess_run_for_testing( + [ + "ipython", + "--no-simple-prompt", + f"--matplotlib={requested_backend_or_gui_framework}", + "-c", code, + ], + check=True, + capture_output=True, + ) + + assert proc.stdout.strip().endswith(f"'{expected_backend}'") + + +def is_ci_environment(): + # Common CI variables + ci_environment_variables = [ + 'CI', # Generic CI environment variable + 'CONTINUOUS_INTEGRATION', # Generic CI environment variable + 'TRAVIS', # Travis CI + 'CIRCLECI', # CircleCI + 'JENKINS', # Jenkins + 'GITLAB_CI', # GitLab CI + 'GITHUB_ACTIONS', # GitHub Actions + 'TEAMCITY_VERSION' # TeamCity + # Add other CI environment variables as needed + ] + + for env_var in ci_environment_variables: + if os.getenv(env_var): + return True + + return False + + +def _gen_multi_font_text(): + """ + Generate text intended for use with multiple fonts to exercise font fallbacks. + + Returns + ------- + fonts : list of str + The names of the fonts used to render the test string, sorted by intended + priority. This should be set as the font family for the Figure or Text artist. + text : str + The test string. + """ + # These fonts are serif and sans-serif, and would not normally be combined, but that + # should make it easier to see which glyph is from which font. + fonts = ['cmr10', 'DejaVu Sans'] + # cmr10 does not contain accented characters, so they should fall back to DejaVu + # Sans. However, some accented capital A versions *are* in cmr10 with non-standard + # glyph shapes, so don't test those (otherwise this Latin1 supplement group would + # start at 0xA0.) + start = 0xC5 + latin1_supplement = [chr(x) for x in range(start, 0xFF+1)] + latin_extended_A = [chr(x) for x in range(0x100, 0x17F+1)] + latin_extended_B = [chr(x) for x in range(0x180, 0x24F+1)] + count = itertools.count(start - 0xA0) + non_basic_characters = '\n'.join( + ''.join(line) + for _, line in itertools.groupby( # Replace with itertools.batched for Py3.12+. + [*latin1_supplement, *latin_extended_A, *latin_extended_B], + key=lambda x: next(count) // 32) # 32 characters per line. + ) + test_str = f"""There are basic characters +{string.ascii_uppercase} {string.ascii_lowercase} +{string.digits} {string.punctuation} +and accented characters +{non_basic_characters} +in between!""" + # The resulting string contains 491 unique characters. Some file formats use 8-bit + # tables, which the large number of characters exercises twice over. + return fonts, test_str diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi new file mode 100644 index 000000000000..7763cb6a9769 --- /dev/null +++ b/lib/matplotlib/testing/__init__.pyi @@ -0,0 +1,55 @@ +from collections.abc import Callable +import subprocess +from typing import Any, IO, Literal, overload + +def set_font_settings_for_testing() -> None: ... +def set_reproducibility_for_testing() -> None: ... +def setup() -> None: ... +@overload +def subprocess_run_for_testing( + command: list[str], + env: dict[str, str] | None = ..., + timeout: float | None = ..., + stdout: int | IO[Any] | None = ..., + stderr: int | IO[Any] | None = ..., + check: bool = ..., + *, + text: Literal[True], + capture_output: bool = ..., +) -> subprocess.CompletedProcess[str]: ... +@overload +def subprocess_run_for_testing( + command: list[str], + env: dict[str, str] | None = ..., + timeout: float | None = ..., + stdout: int | IO[Any] | None = ..., + stderr: int | IO[Any] | None = ..., + check: bool = ..., + text: Literal[False] = ..., + capture_output: bool = ..., +) -> subprocess.CompletedProcess[bytes]: ... +@overload +def subprocess_run_for_testing( + command: list[str], + env: dict[str, str] | None = ..., + timeout: float | None = ..., + stdout: int | IO[Any] | None = ..., + stderr: int | IO[Any] | None = ..., + check: bool = ..., + text: bool = ..., + capture_output: bool = ..., +) -> subprocess.CompletedProcess[bytes] | subprocess.CompletedProcess[str]: ... +def subprocess_run_helper( + func: Callable[[], None], + *args: Any, + timeout: float, + extra_env: dict[str, str] | None = ..., +) -> subprocess.CompletedProcess[str]: ... +def _check_for_pgf(texsystem: str) -> bool: ... +def _has_tex_package(package: str) -> bool: ... +def ipython_in_subprocess( + requested_backend_or_gui_framework: str, + all_expected_backends: dict[tuple[int, int], str], +) -> None: ... +def is_ci_environment() -> bool: ... +def _gen_multi_font_text() -> tuple[list[str], str]: ... diff --git a/lib/matplotlib/testing/_markers.py b/lib/matplotlib/testing/_markers.py new file mode 100644 index 000000000000..c7ef8687a8b3 --- /dev/null +++ b/lib/matplotlib/testing/_markers.py @@ -0,0 +1,49 @@ +""" +pytest markers for the internal Matplotlib test suite. +""" + +import logging +import shutil + +import pytest + +import matplotlib.testing +import matplotlib.testing.compare +from matplotlib import _get_executable_info, ExecutableNotFoundError + + +_log = logging.getLogger(__name__) + + +def _checkdep_usetex() -> bool: + if not shutil.which("tex"): + _log.warning("usetex mode requires TeX.") + return False + try: + _get_executable_info("dvipng") + except ExecutableNotFoundError: + _log.warning("usetex mode requires dvipng.") + return False + try: + _get_executable_info("gs") + except ExecutableNotFoundError: + _log.warning("usetex mode requires ghostscript.") + return False + return True + + +needs_ghostscript = pytest.mark.skipif( + "eps" not in matplotlib.testing.compare.converter, + reason="This test needs a ghostscript installation") +needs_pgf_lualatex = pytest.mark.skipif( + not matplotlib.testing._check_for_pgf('lualatex'), + reason='lualatex + pgf is required') +needs_pgf_pdflatex = pytest.mark.skipif( + not matplotlib.testing._check_for_pgf('pdflatex'), + reason='pdflatex + pgf is required') +needs_pgf_xelatex = pytest.mark.skipif( + not matplotlib.testing._check_for_pgf('xelatex'), + reason='xelatex + pgf is required') +needs_usetex = pytest.mark.skipif( + not _checkdep_usetex(), + reason="This test needs a TeX installation") diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 06f2074e0ef7..67897e76edcb 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -3,14 +3,17 @@ """ import atexit +import functools import hashlib +import logging import os from pathlib import Path -import re import shutil import subprocess import sys from tempfile import TemporaryDirectory, TemporaryFile +import weakref +import re import numpy as np from PIL import Image @@ -19,7 +22,9 @@ from matplotlib import cbook from matplotlib.testing.exceptions import ImageComparisonFailure -__all__ = ['compare_images', 'comparable_formats'] +_log = logging.getLogger(__name__) + +__all__ = ['calculate_rms', 'comparable_formats', 'compare_images'] def make_test_filename(fname, purpose): @@ -27,60 +32,34 @@ def make_test_filename(fname, purpose): Make a new filename by inserting *purpose* before the file's extension. """ base, ext = os.path.splitext(fname) - return '%s-%s%s' % (base, purpose, ext) + return f'{base}-{purpose}{ext}' -def get_cache_dir(): +def _get_cache_path(): cache_dir = Path(mpl.get_cachedir(), 'test_cache') cache_dir.mkdir(parents=True, exist_ok=True) - return str(cache_dir) + return cache_dir + + +def get_cache_dir(): + return str(_get_cache_path()) def get_file_hash(path, block_size=2 ** 20): - md5 = hashlib.md5() + sha256 = hashlib.sha256(usedforsecurity=False) with open(path, 'rb') as fd: while True: data = fd.read(block_size) if not data: break - md5.update(data) + sha256.update(data) if Path(path).suffix == '.pdf': - md5.update(str(mpl._get_executable_info("gs").version) - .encode('utf-8')) + sha256.update(str(mpl._get_executable_info("gs").version).encode('utf-8')) elif Path(path).suffix == '.svg': - md5.update(str(mpl._get_executable_info("inkscape").version) - .encode('utf-8')) - - return md5.hexdigest() - + sha256.update(str(mpl._get_executable_info("inkscape").version).encode('utf-8')) -@cbook.deprecated("3.3") -def make_external_conversion_command(cmd): - def convert(old, new): - cmdline = cmd(old, new) - pipe = subprocess.Popen(cmdline, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = pipe.communicate() - errcode = pipe.wait() - if not os.path.exists(new) or errcode: - msg = "Conversion command failed:\n%s\n" % ' '.join(cmdline) - if stdout: - msg += "Standard output:\n%s\n" % stdout - if stderr: - msg += "Standard error:\n%s\n" % stderr - raise IOError(msg) - - return convert - - -# Modified from https://bugs.python.org/issue25567. -_find_unsafe_bytes = re.compile(br'[^a-zA-Z0-9_@%+=:,./-]').search - - -def _shlex_quote_bytes(b): - return (b if _find_unsafe_bytes(b) is None - else b"'" + b.replace(b"'", b"'\"'\"'") + b"'") + return sha256.hexdigest() class _ConverterError(Exception): @@ -112,10 +91,20 @@ def _read_until(self, terminator): while True: c = self._proc.stdout.read(1) if not c: - raise _ConverterError + raise _ConverterError(os.fsdecode(bytes(buf))) buf.extend(c) if buf.endswith(terminator): - return bytes(buf[:-len(terminator)]) + return bytes(buf) + + +class _MagickConverter: + def __call__(self, orig, dest): + try: + subprocess.run( + [mpl._get_executable_info("magick").executable, orig, dest], + check=True) + except subprocess.CalledProcessError as e: + raise _ConverterError() from e class _GSConverter(_Converter): @@ -123,13 +112,13 @@ def __call__(self, orig, dest): if not self._proc: self._proc = subprocess.Popen( [mpl._get_executable_info("gs").executable, - "-dNOSAFER", "-dNOPAUSE", "-sDEVICE=png16m"], + "-dNOSAFER", "-dNOPAUSE", "-dEPSCrop", "-sDEVICE=png16m"], # As far as I can see, ghostscript never outputs to stderr. stdin=subprocess.PIPE, stdout=subprocess.PIPE) try: self._read_until(b"\nGS") - except _ConverterError as err: - raise OSError("Failed to start Ghostscript") from err + except _ConverterError as e: + raise OSError(f"Failed to start Ghostscript:\n\n{e.args[0]}") from None def encode_and_escape(name): return (os.fsencode(name) @@ -145,25 +134,32 @@ def encode_and_escape(name): + b") run flush\n") self._proc.stdin.flush() # GS> if nothing left on the stack; GS if n items left on the stack. - err = self._read_until(b"GS") - stack = self._read_until(b">") + err = self._read_until((b"GS<", b"GS>")) + stack = self._read_until(b">") if err.endswith(b"GS<") else b"" if stack or not os.path.exists(dest): - stack_size = int(stack[1:]) if stack else 0 + stack_size = int(stack[:-1]) if stack else 0 self._proc.stdin.write(b"pop\n" * stack_size) # Using the systemencoding should at least get the filenames right. raise ImageComparisonFailure( - (err + b"GS" + stack + b">") - .decode(sys.getfilesystemencoding(), "replace")) + (err + stack).decode(sys.getfilesystemencoding(), "replace")) class _SVGConverter(_Converter): def __call__(self, orig, dest): - old_inkscape = mpl._get_executable_info("inkscape").version < "1" + old_inkscape = mpl._get_executable_info("inkscape").version.major < 1 terminator = b"\n>" if old_inkscape else b"> " if not hasattr(self, "_tmpdir"): self._tmpdir = TemporaryDirectory() + # On Windows, we must make sure that self._proc has terminated + # (which __del__ does) before clearing _tmpdir. + weakref.finalize(self._tmpdir, self.__del__) if (not self._proc # First run. or self._proc.poll() is not None): # Inkscape terminated. + if self._proc is not None and self._proc.poll() is not None: + for stream in filter(None, [self._proc.stdin, + self._proc.stdout, + self._proc.stderr]): + stream.close() env = { **os.environ, # If one passes e.g. a png file to Inkscape, it will try to @@ -173,7 +169,7 @@ def __call__(self, orig, dest): # just be reported as a regular exception below). "DISPLAY": "", # Do not load any user options. - "INKSCAPE_PROFILE_DIR": os.devnull, + "INKSCAPE_PROFILE_DIR": self._tmpdir.name, } # Old versions of Inkscape (e.g. 0.48.3.1) seem to sometimes # deadlock when stderr is redirected to a pipe, so we redirect it @@ -190,8 +186,9 @@ def __call__(self, orig, dest): try: self._read_until(terminator) except _ConverterError as err: - raise OSError("Failed to start Inkscape in interactive " - "mode") from err + raise OSError( + "Failed to start Inkscape in interactive mode:\n\n" + + err.args[0]) from err # Inkscape's shell mode does not support escaping metacharacters in the # filename ("\n", and ":;" for inkscape>=1). Avoid any problems by @@ -225,7 +222,28 @@ def __del__(self): self._tmpdir.cleanup() +class _SVGWithMatplotlibFontsConverter(_SVGConverter): + """ + A SVG converter which explicitly adds the fonts shipped by Matplotlib to + Inkspace's font search path, to better support `svg.fonttype = "none"` + (which is in particular used by certain mathtext tests). + """ + + def __call__(self, orig, dest): + if not hasattr(self, "_tmpdir"): + self._tmpdir = TemporaryDirectory() + shutil.copytree(cbook._get_data_path("fonts/ttf"), + Path(self._tmpdir.name, "fonts")) + return super().__call__(orig, dest) + + def _update_converter(): + try: + mpl._get_executable_info("magick") + except mpl.ExecutableNotFoundError: + pass + else: + converter['gif'] = _MagickConverter() try: mpl._get_executable_info("gs") except mpl.ExecutableNotFoundError: @@ -240,12 +258,11 @@ def _update_converter(): converter['svg'] = _SVGConverter() -#: A dictionary that maps filename extensions to functions which -#: themselves map arguments `old` and `new` (filenames) to a list of strings. -#: The list can then be passed to Popen to convert files with that -#: extension to png format. +#: A dictionary that maps filename extensions to functions which themselves +#: convert between arguments `old` and `new` (filenames). converter = {} _update_converter() +_svg_with_matplotlib_fonts_converter = _SVGWithMatplotlibFontsConverter() def comparable_formats(): @@ -268,12 +285,13 @@ def convert(filename, cache): If *cache* is True, the result of the conversion is cached in `matplotlib.get_cachedir() + '/test_cache/'`. The caching is based on a - hash of the exact contents of the input file. There is no limit on the - size of the cache, so it may need to be manually cleared periodically. + hash of the exact contents of the input file. Old cache entries are + automatically deleted as needed to keep the size of the cache capped to + twice the size of all baseline images. """ path = Path(filename) if not path.exists(): - raise IOError(f"{path} does not exist") + raise OSError(f"{path} does not exist") if path.suffix[1:] not in converter: import pytest pytest.skip(f"Don't know how to convert {path.suffix} files to png") @@ -282,23 +300,75 @@ def convert(filename, cache): # Only convert the file if the destination doesn't already exist or # is out of date. if not newpath.exists() or newpath.stat().st_mtime < path.stat().st_mtime: - cache_dir = Path(get_cache_dir()) if cache else None + cache_dir = _get_cache_path() if cache else None if cache_dir is not None: + _register_conversion_cache_cleaner_once() hash_value = get_file_hash(path) cached_path = cache_dir / (hash_value + newpath.suffix) if cached_path.exists(): + _log.debug("For %s: reusing cached conversion.", filename) shutil.copyfile(cached_path, newpath) return str(newpath) - converter[path.suffix[1:]](path, newpath) + _log.debug("For %s: converting to png.", filename) + convert = converter[path.suffix[1:]] + if path.suffix == ".svg": + contents = path.read_text(encoding="utf-8") + # NOTE: This check should be kept in sync with font styling in + # `lib/matplotlib/backends/backend_svg.py`. If it changes, then be sure to + # re-generate any SVG test files using this mode, or else such tests will + # fail to use the converter for the expected images (but will for the + # results), and the tests will fail strangely. + if re.search( + # searches for attributes : + # style=[font|font-size|font-weight| + # font-family|font-variant|font-style] + # taking care of the possibility of multiple style attributes + # before the font styling (i.e. opacity) + r'style="[^"]*font(|-size|-weight|-family|-variant|-style):', + contents # raw contents of the svg file + ): + # for svg.fonttype = none, we explicitly patch the font search + # path so that fonts shipped by Matplotlib are found. + convert = _svg_with_matplotlib_fonts_converter + convert(path, newpath) if cache_dir is not None: + _log.debug("For %s: caching conversion result.", filename) shutil.copyfile(newpath, cached_path) return str(newpath) +def _clean_conversion_cache(): + # This will actually ignore mpl_toolkits baseline images, but they're + # relatively small. + baseline_images_size = sum( + path.stat().st_size + for path in Path(mpl.__file__).parent.glob("**/baseline_images/**/*")) + # 2x: one full copy of baselines, and one full copy of test results + # (actually an overestimate: we don't convert png baselines and results). + max_cache_size = 2 * baseline_images_size + # Reduce cache until it fits. + with cbook._lock_path(_get_cache_path()): + cache_stat = { + path: path.stat() for path in _get_cache_path().glob("*")} + cache_size = sum(stat.st_size for stat in cache_stat.values()) + paths_by_atime = sorted( # Oldest at the end. + cache_stat, key=lambda path: cache_stat[path].st_atime, + reverse=True) + while cache_size > max_cache_size: + path = paths_by_atime.pop() + cache_size -= cache_stat[path].st_size + path.unlink() + + +@functools.cache # Ensure this is only registered once. +def _register_conversion_cache_cleaner_once(): + atexit.register(_clean_conversion_cache) + + def crop_to_same(actual_path, actual_image, expected_path, expected_image): # clip the images to the same size -- this is useful only when # comparing eps to pdf @@ -316,8 +386,8 @@ def calculate_rms(expected_image, actual_image): """ if expected_image.shape != actual_image.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {} " - "actual size {}".format(expected_image.shape, actual_image.shape)) + f"Image sizes do not match expected size: {expected_image.shape} " + f"actual size {actual_image.shape}") # Convert to float to avoid overflowing finite integer types. return np.sqrt(((expected_image - actual_image).astype(float) ** 2).mean()) @@ -326,12 +396,22 @@ def calculate_rms(expected_image, actual_image): # 16-bit depth, as Pillow converts these to RGB incorrectly. +def _load_image(path): + img = Image.open(path) + # In an RGBA image, if the smallest value in the alpha channel is 255, all + # values in it must be 255, meaning that the image is opaque. If so, + # discard the alpha channel so that it may compare equal to an RGB image. + if img.mode != "RGBA" or img.getextrema()[3][0] == 255: + img = img.convert("RGB") + return np.asarray(img) + + def compare_images(expected, actual, tol, in_decorator=False): """ Compare two "image" files checking differences within a tolerance. The two given filenames may point to files which are convertible to - PNG via the `.converter` dictionary. The underlying RMS is calculated + PNG via the `!converter` dictionary. The underlying RMS is calculated with the `.calculate_rms` function. Parameters @@ -377,22 +457,22 @@ def compare_images(expected, actual, tol, in_decorator=False): """ actual = os.fspath(actual) if not os.path.exists(actual): - raise Exception("Output image %s does not exist." % actual) + raise Exception(f"Output image {actual} does not exist.") if os.stat(actual).st_size == 0: - raise Exception("Output image file %s is empty." % actual) + raise Exception(f"Output image file {actual} is empty.") # Convert the image to png expected = os.fspath(expected) if not os.path.exists(expected): - raise IOError('Baseline image %r does not exist.' % expected) + raise OSError(f'Baseline image {expected!r} does not exist.') extension = expected.split('.')[-1] if extension != 'png': - actual = convert(actual, cache=False) + actual = convert(actual, cache=True) expected = convert(expected, cache=True) - # open the image files and remove the alpha channel (if it exists) - expected_image = np.asarray(Image.open(expected).convert("RGB")) - actual_image = np.asarray(Image.open(actual).convert("RGB")) + # open the image files + expected_image = _load_image(expected) + actual_image = _load_image(actual) actual_image, expected_image = crop_to_same( actual, actual_image, expected, expected_image) @@ -441,32 +521,23 @@ def save_diff_image(expected, actual, output): output : str File path to save difference image to. """ - # Drop alpha channels, similarly to compare_images. - expected_image = np.asarray(Image.open(expected).convert("RGB")) - actual_image = np.asarray(Image.open(actual).convert("RGB")) + expected_image = _load_image(expected) + actual_image = _load_image(actual) actual_image, expected_image = crop_to_same( actual, actual_image, expected, expected_image) - expected_image = np.array(expected_image).astype(float) - actual_image = np.array(actual_image).astype(float) + expected_image = np.array(expected_image, float) + actual_image = np.array(actual_image, float) if expected_image.shape != actual_image.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {} " - "actual size {}".format(expected_image.shape, actual_image.shape)) - abs_diff_image = np.abs(expected_image - actual_image) + f"Image sizes do not match expected size: {expected_image.shape} " + f"actual size {actual_image.shape}") + abs_diff = np.abs(expected_image - actual_image) # expand differences in luminance domain - abs_diff_image *= 255 * 10 - save_image_np = np.clip(abs_diff_image, 0, 255).astype(np.uint8) - height, width, depth = save_image_np.shape - - # The PDF renderer doesn't produce an alpha channel, but the - # matplotlib PNG writer requires one, so expand the array - if depth == 3: - with_alpha = np.empty((height, width, 4), dtype=np.uint8) - with_alpha[:, :, 0:3] = save_image_np - save_image_np = with_alpha + abs_diff *= 10 + abs_diff = np.clip(abs_diff, 0, 255).astype(np.uint8) - # Hard-code the alpha channel to fully solid - save_image_np[:, :, 3] = 255 + if abs_diff.shape[2] == 4: # Hard-code the alpha channel to fully solid + abs_diff[:, :, 3] = 255 - Image.fromarray(save_image_np).save(output, format="png") + Image.fromarray(abs_diff).save(output, format="png") diff --git a/lib/matplotlib/testing/compare.pyi b/lib/matplotlib/testing/compare.pyi new file mode 100644 index 000000000000..8f11b3bebc1a --- /dev/null +++ b/lib/matplotlib/testing/compare.pyi @@ -0,0 +1,32 @@ +from collections.abc import Callable +from typing import Literal, overload + +from numpy.typing import NDArray + +__all__ = ["calculate_rms", "comparable_formats", "compare_images"] + +def make_test_filename(fname: str, purpose: str) -> str: ... +def get_cache_dir() -> str: ... +def get_file_hash(path: str, block_size: int = ...) -> str: ... + +converter: dict[str, Callable[[str, str], None]] = {} + +def comparable_formats() -> list[str]: ... +def convert(filename: str, cache: bool) -> str: ... +def crop_to_same( + actual_path: str, actual_image: NDArray, expected_path: str, expected_image: NDArray +) -> tuple[NDArray, NDArray]: ... +def calculate_rms(expected_image: NDArray, actual_image: NDArray) -> float: ... +@overload +def compare_images( + expected: str, actual: str, tol: float, in_decorator: Literal[True] +) -> None | dict[str, float | str]: ... +@overload +def compare_images( + expected: str, actual: str, tol: float, in_decorator: Literal[False] +) -> None | str: ... +@overload +def compare_images( + expected: str, actual: str, tol: float, in_decorator: bool = ... +) -> None | str | dict[str, float | str]: ... +def save_diff_image(expected: str, actual: str, output: str) -> None: ... diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index de8a61bdfd64..2961e7f02f3f 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -1,7 +1,7 @@ import pytest import sys import matplotlib -from matplotlib import cbook +from matplotlib import _api def pytest_configure(config): @@ -13,11 +13,14 @@ def pytest_configure(config): ("markers", "flaky: (Provided by pytest-rerunfailures.)"), ("markers", "timeout: (Provided by pytest-timeout.)"), ("markers", "backend: Set alternate Matplotlib backend temporarily."), - ("markers", "style: Set alternate Matplotlib style temporarily."), ("markers", "baseline_images: Compare output against references."), ("markers", "pytz: Tests that require pytz to be installed."), - ("markers", "network: Tests that reach out to the network."), ("filterwarnings", "error"), + ("filterwarnings", + "ignore:.*The py23 module has been deprecated:DeprecationWarning"), + ("filterwarnings", + r"ignore:DynamicImporter.find_spec\(\) not found; " + r"falling back to find_module\(\):ImportWarning"), ]: config.addinivalue_line(key, value) @@ -38,48 +41,21 @@ def mpl_test_settings(request): backend = None backend_marker = request.node.get_closest_marker('backend') + prev_backend = matplotlib.get_backend() if backend_marker is not None: assert len(backend_marker.args) == 1, \ "Marker 'backend' must specify 1 backend." backend, = backend_marker.args skip_on_importerror = backend_marker.kwargs.get( 'skip_on_importerror', False) - prev_backend = matplotlib.get_backend() # special case Qt backend importing to avoid conflicts - if backend.lower().startswith('qt4'): - if any(k in sys.modules for k in ('PyQt5', 'PySide2')): - pytest.skip('Qt5 binding already imported') - try: - import PyQt4 - # RuntimeError if PyQt5 already imported. - except (ImportError, RuntimeError): - try: - import PySide - except ImportError: - pytest.skip("Failed to import a Qt4 binding.") - elif backend.lower().startswith('qt5'): - if any(k in sys.modules for k in ('PyQt4', 'PySide')): + if backend.lower().startswith('qt5'): + if any(sys.modules.get(k) for k in ('PyQt4', 'PySide')): pytest.skip('Qt4 binding already imported') - try: - import PyQt5 - # RuntimeError if PyQt4 already imported. - except (ImportError, RuntimeError): - try: - import PySide2 - except ImportError: - pytest.skip("Failed to import a Qt5 binding.") - - # Default of cleanup and image_comparison too. - style = ["classic", "_classic_test_patch"] - style_marker = request.node.get_closest_marker('style') - if style_marker is not None: - assert len(style_marker.args) == 1, \ - "Marker 'style' must specify 1 style." - style, = style_marker.args matplotlib.testing.setup() - with cbook._suppress_matplotlib_deprecation_warning(): + with _api.suppress_matplotlib_deprecation_warning(): if backend is not None: # This import must come after setup() so it doesn't load the # default backend prematurely. @@ -90,43 +66,36 @@ def mpl_test_settings(request): # Should only occur for the cairo backend tests, if neither # pycairo nor cairocffi are installed. if 'cairo' in backend.lower() or skip_on_importerror: - pytest.skip("Failed to switch to backend {} ({})." - .format(backend, exc)) + pytest.skip("Failed to switch to backend " + f"{backend} ({exc}).") else: raise - matplotlib.style.use(style) + # Default of cleanup and image_comparison too. + matplotlib.style.use(["classic", "_classic_test_patch"]) try: yield finally: if backend is not None: - plt.switch_backend(prev_backend) + plt.close("all") + matplotlib.use(prev_backend) @pytest.fixture -def mpl_image_comparison_parameters(request, extension): - # This fixture is applied automatically by the image_comparison decorator. - # - # The sole purpose of this fixture is to provide an indirect method of - # obtaining parameters *without* modifying the decorated function - # signature. In this way, the function signature can stay the same and - # pytest won't get confused. - # We annotate the decorated function with any parameters captured by this - # fixture so that they can be used by the wrapper in image_comparison. - baseline_images, = request.node.get_closest_marker('baseline_images').args - if baseline_images is None: - # Allow baseline image list to be produced on the fly based on current - # parametrization. - baseline_images = request.getfixturevalue('baseline_images') - - func = request.function - with cbook._setattr_cm(func.__wrapped__, - parameters=(baseline_images, extension)): - yield +def pd(): + """ + Fixture to import and configure pandas. Using this fixture, the test is skipped when + pandas is not installed. Use this fixture instead of importing pandas in test files. + Examples + -------- + Request the pandas fixture by passing in ``pd`` as an argument to the test :: -@pytest.fixture -def pd(): - """Fixture to import and configure pandas.""" + def test_matshow_pandas(pd): + + df = pd.DataFrame({'x':[1,2,3], 'y':[4,5,6]}) + im = plt.figure().subplots().matshow(df) + np.testing.assert_array_equal(im.get_array(), df) + """ pd = pytest.importorskip('pandas') try: from pandas.plotting import ( @@ -135,3 +104,75 @@ def pd(): except ImportError: pass return pd + + +@pytest.fixture +def xr(): + """ + Fixture to import xarray so that the test is skipped when xarray is not installed. + Use this fixture instead of importing xrray in test files. + + Examples + -------- + Request the xarray fixture by passing in ``xr`` as an argument to the test :: + + def test_imshow_xarray(xr): + + ds = xr.DataArray(np.random.randn(2, 3)) + im = plt.figure().subplots().imshow(ds) + np.testing.assert_array_equal(im.get_array(), ds) + """ + + xr = pytest.importorskip('xarray') + return xr + + +@pytest.fixture +def text_placeholders(monkeypatch): + """ + Replace texts with placeholder rectangles. + + The rectangle size only depends on the font size and the number of characters. It is + thus insensitive to font properties and rendering details. This should be used for + tests that depend on text geometries but not the actual text rendering, e.g. layout + tests. + """ + from matplotlib.patches import Rectangle + + def patched_get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi): + """ + Replace ``_get_text_metrics_with_cache`` with fixed results. + + The usual ``renderer.get_text_width_height_descent`` would depend on font + metrics; instead the fixed results are based on font size and the length of the + string only. + """ + # While get_window_extent returns pixels and font size is in points, font size + # includes ascenders and descenders. Leaving out this factor and setting + # descent=0 ends up with a box that is relatively close to DejaVu Sans. + height = fontprop.get_size() + width = len(text) * height / 1.618 # Golden ratio for character size. + descent = 0 + return width, height, descent + + def patched_text_draw(self, renderer): + """ + Replace ``Text.draw`` with a fixed bounding box Rectangle. + + The bounding box corresponds to ``Text.get_window_extent``, which ultimately + depends on the above patched ``_get_text_metrics_with_cache``. + """ + if renderer is not None: + self._renderer = renderer + if not self.get_visible(): + return + if self.get_text() == '': + return + bbox = self.get_window_extent() + rect = Rectangle(bbox.p0, bbox.width, bbox.height, + facecolor=self.get_color(), edgecolor='none') + rect.draw(renderer) + + monkeypatch.setattr('matplotlib.text._get_text_metrics_with_cache', + patched_get_text_metrics_with_cache) + monkeypatch.setattr('matplotlib.text.Text.draw', patched_text_draw) diff --git a/lib/matplotlib/testing/conftest.pyi b/lib/matplotlib/testing/conftest.pyi new file mode 100644 index 000000000000..f5d90bc88f73 --- /dev/null +++ b/lib/matplotlib/testing/conftest.pyi @@ -0,0 +1,14 @@ +from types import ModuleType + +import pytest + +def pytest_configure(config: pytest.Config) -> None: ... +def pytest_unconfigure(config: pytest.Config) -> None: ... +@pytest.fixture +def mpl_test_settings(request: pytest.FixtureRequest) -> None: ... +@pytest.fixture +def pd() -> ModuleType: ... +@pytest.fixture +def xr() -> ModuleType: ... +@pytest.fixture +def text_placeholders(monkeypatch: pytest.MonkeyPatch) -> None: ... diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 15d79f9fbc84..17509449e768 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -1,23 +1,20 @@ import contextlib -from distutils.version import StrictVersion import functools import inspect import os +from platform import uname from pathlib import Path import shutil import string import sys -import unittest import warnings +from packaging.version import parse as parse_version + import matplotlib.style import matplotlib.units import matplotlib.testing -from matplotlib import cbook -from matplotlib import ft2font -from matplotlib import pyplot as plt -from matplotlib import ticker - +from matplotlib import _pylab_helpers, cbook, ft2font, pyplot as plt, ticker from .compare import comparable_formats, compare_images, make_test_filename from .exceptions import ImageComparisonFailure @@ -34,84 +31,33 @@ def _cleanup_cm(): plt.close("all") -class CleanupTestCase(unittest.TestCase): - """A wrapper for unittest.TestCase that includes cleanup operations.""" - @classmethod - def setUpClass(cls): - cls._cm = _cleanup_cm().__enter__() - - @classmethod - def tearDownClass(cls): - cls._cm.__exit__(None, None, None) - - -def cleanup(style=None): - """ - A decorator to ensure that any global state is reset before - running a test. - - Parameters - ---------- - style : str, dict, or list, optional - The style(s) to apply. Defaults to ``["classic", - "_classic_test_patch"]``. - """ - - # If cleanup is used without arguments, *style* will be a callable, and we - # pass it directly to the wrapper generator. If cleanup if called with an - # argument, it is a string naming a style, and the function will be passed - # as an argument to what we return. This is a confusing, but somewhat - # standard, pattern for writing a decorator with optional arguments. - - def make_cleanup(func): - if inspect.isgeneratorfunction(func): - @functools.wraps(func) - def wrapped_callable(*args, **kwargs): - with _cleanup_cm(), matplotlib.style.context(style): - yield from func(*args, **kwargs) - else: - @functools.wraps(func) - def wrapped_callable(*args, **kwargs): - with _cleanup_cm(), matplotlib.style.context(style): - func(*args, **kwargs) - - return wrapped_callable - - if callable(style): - result = make_cleanup(style) - # Default of mpl_test_settings fixture and image_comparison too. - style = ["classic", "_classic_test_patch"] - return result - else: - return make_cleanup - - -def check_freetype_version(ver): +def _check_freetype_version(ver): if ver is None: return True if isinstance(ver, str): ver = (ver, ver) - ver = [StrictVersion(x) for x in ver] - found = StrictVersion(ft2font.__freetype_version__) + ver = [parse_version(x) for x in ver] + found = parse_version(ft2font.__freetype_version__) return ver[0] <= found <= ver[1] def _checked_on_freetype_version(required_freetype_version): import pytest - reason = ("Mismatched version of freetype. " - "Test requires '%s', you have '%s'" % - (required_freetype_version, ft2font.__freetype_version__)) return pytest.mark.xfail( - not check_freetype_version(required_freetype_version), - reason=reason, raises=ImageComparisonFailure, strict=False) + not _check_freetype_version(required_freetype_version), + reason=f"Mismatched version of freetype. " + f"Test requires '{required_freetype_version}', " + f"you have '{ft2font.__freetype_version__}'", + raises=ImageComparisonFailure, strict=False) def remove_ticks_and_titles(figure): figure.suptitle("") null_formatter = ticker.NullFormatter() - for ax in figure.get_axes(): + def remove_ticks(ax): + """Remove ticks in *ax* and all its child Axes.""" ax.set_title("") ax.xaxis.set_major_formatter(null_formatter) ax.xaxis.set_minor_formatter(null_formatter) @@ -122,6 +68,33 @@ def remove_ticks_and_titles(figure): ax.zaxis.set_minor_formatter(null_formatter) except AttributeError: pass + for child in ax.child_axes: + remove_ticks(child) + for ax in figure.get_axes(): + remove_ticks(ax) + + +@contextlib.contextmanager +def _collect_new_figures(): + """ + After:: + + with _collect_new_figures() as figs: + some_code() + + the list *figs* contains the figures that have been created during the + execution of ``some_code``, sorted by figure number. + """ + managers = _pylab_helpers.Gcf.figs + preexisting = [manager for manager in managers.values()] + new_figs = [] + try: + yield new_figs + finally: + new_managers = sorted([manager for manager in managers.values() + if manager not in preexisting], + key=lambda manager: manager.num) + new_figs[:] = [manager.canvas.figure for manager in new_managers] def _raise_on_image_difference(expected, actual, tol): @@ -136,32 +109,6 @@ def _raise_on_image_difference(expected, actual, tol): '\n\t%(actual)s\n\t%(expected)s\n\t%(diff)s') % err) -def _skip_if_format_is_uncomparable(extension): - import pytest - return pytest.mark.skipif( - extension not in comparable_formats(), - reason='Cannot compare {} files on this system'.format(extension)) - - -def _mark_skip_if_format_is_uncomparable(extension): - import pytest - if isinstance(extension, str): - name = extension - marks = [] - elif isinstance(extension, tuple): - # Extension might be a pytest ParameterSet instead of a plain string. - # Unfortunately, this type is not exposed, so since it's a namedtuple, - # check for a tuple instead. - name, = extension.values - marks = [*extension.marks] - else: - # Extension might be a pytest marker instead of a plain string. - name, = extension.args - marks = [extension.mark] - return pytest.param(name, - marks=[*marks, _skip_if_format_is_uncomparable(name)]) - - class _ImageComparisonBase: """ Image comparison base class @@ -189,6 +136,8 @@ def copy_baseline(self, baseline, extension): with contextlib.suppress(OSError): os.remove(expected_fname) try: + if 'microsoft' in uname().release.lower(): + raise OSError # On WSL, symlink breaks silently os.symlink(orig_expected_path, expected_fname) except OSError: # On Windows, symlink *may* be unavailable. shutil.copyfile(orig_expected_path, expected_fname) @@ -199,10 +148,8 @@ def copy_baseline(self, baseline, extension): f"{orig_expected_path}") from err return expected_fname - def compare(self, idx, baseline, extension, *, _lock=False): + def compare(self, fig, baseline, extension, *, _lock=False): __tracebackhide__ = True - fignum = plt.get_fignums()[idx] - fig = plt.figure(fignum) if self.remove_text: remove_ticks_and_titles(fig) @@ -217,7 +164,12 @@ def compare(self, idx, baseline, extension, *, _lock=False): lock = (cbook._lock_path(actual_path) if _lock else contextlib.nullcontext()) with lock: - fig.savefig(actual_path, **kwargs) + try: + fig.savefig(actual_path, **kwargs) + finally: + # Matplotlib has an autouse fixture to close figures, but this + # makes things more convenient for third-party users. + plt.close(fig) expected_path = self.copy_baseline(baseline, extension) _raise_on_image_difference(expected_path, actual_path, self.tol) @@ -233,7 +185,6 @@ def _pytest_image_comparison(baseline_images, extensions, tol, """ import pytest - extensions = map(_mark_skip_if_format_is_uncomparable, extensions) KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY def decorator(func): @@ -241,7 +192,7 @@ def decorator(func): @functools.wraps(func) @pytest.mark.parametrize('extension', extensions) - @pytest.mark.style(style) + @matplotlib.style.context(style) @_checked_on_freetype_version(freetype_version) @functools.wraps(func) def wrapper(*args, extension, request, **kwargs): @@ -251,10 +202,21 @@ def wrapper(*args, extension, request, **kwargs): if 'request' in old_sig.parameters: kwargs['request'] = request + if extension not in comparable_formats(): + reason = { + 'gif': 'because ImageMagick is not installed', + 'pdf': 'because Ghostscript is not installed', + 'eps': 'because Ghostscript is not installed', + 'svg': 'because Inkscape is not installed', + }.get(extension, 'on this system') + pytest.skip(f"Cannot compare {extension} files {reason}") + img = _ImageComparisonBase(func, tol=tol, remove_text=remove_text, savefig_kwargs=savefig_kwargs) matplotlib.testing.set_font_settings_for_testing() - func(*args, **kwargs) + + with _collect_new_figures() as figs: + func(*args, **kwargs) # If the test is parametrized in any way other than applied via # this decorator, then we need to use a lock to prevent two @@ -271,11 +233,11 @@ def wrapper(*args, extension, request, **kwargs): our_baseline_images = request.getfixturevalue( 'baseline_images') - assert len(plt.get_fignums()) == len(our_baseline_images), ( - "Test generated {} images but there are {} baseline images" - .format(len(plt.get_fignums()), len(our_baseline_images))) - for idx, baseline in enumerate(our_baseline_images): - img.compare(idx, baseline, extension, _lock=needs_lock) + assert len(figs) == len(our_baseline_images), ( + f"Test generated {len(figs)} images but there are " + f"{len(our_baseline_images)} baseline images") + for fig, baseline in zip(figs, our_baseline_images): + img.compare(fig, baseline, extension, _lock=needs_lock) parameters = list(old_sig.parameters.values()) if 'extension' not in old_sig.parameters: @@ -302,7 +264,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, style=("classic", "_classic_test_patch")): """ Compare images generated by the test with those specified in - *baseline_images*, which must correspond, else an `ImageComparisonFailure` + *baseline_images*, which must correspond, else an `.ImageComparisonFailure` exception will be raised. Parameters @@ -318,7 +280,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, extensions : None or list of str The list of extensions to test, e.g. ``['png', 'pdf']``. - If *None*, defaults to all supported extensions: png, pdf, and svg. + If *None*, defaults to: png, pdf, and svg. When testing a single extension, it can be directly included in the names passed to *baseline_images*. In that case, *extensions* must not @@ -385,7 +347,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, savefig_kwargs=savefig_kwarg, style=style) -def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0): +def check_figures_equal(*, extensions=("png", ), tol=0): """ Decorator for test cases that generate and compare two figures. @@ -398,11 +360,22 @@ def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0): Parameters ---------- - extensions : list, default: ["png", "pdf", "svg"] - The extensions to test. + extensions : list, default: ["png"] + The extensions to test. Supported extensions are "png", "pdf", "svg". + + Testing with the one default extension is sufficient if the output is not + format dependent, e.g. if you test that a ``bar()`` plot yields the same + result as some manually placed Rectangles. You should use all extensions + if a renderer property is involved, e.g. correct alpha blending. tol : float The RMS threshold above which the test is considered failed. + Raises + ------ + RuntimeError + If any new figures are created (and not subsequently closed) inside + the test function. + Examples -------- Check that calling `.Axes.plot` with a single argument plots it against @@ -416,6 +389,7 @@ def test_plot(fig_test, fig_ref): """ ALLOWED_CHARS = set(string.digits + string.ascii_letters + '_-[]()') KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY + def decorator(func): import pytest @@ -424,7 +398,7 @@ def decorator(func): if not {"fig_test", "fig_ref"}.issubset(old_sig.parameters): raise ValueError("The decorated function must have at least the " - "parameters 'fig_ref' and 'fig_test', but your " + "parameters 'fig_test' and 'fig_ref', but your " f"function has the signature {old_sig}") @pytest.mark.parametrize("ext", extensions) @@ -439,7 +413,14 @@ def wrapper(*args, ext, request, **kwargs): try: fig_test = plt.figure("test") fig_ref = plt.figure("reference") - func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs) + with _collect_new_figures() as figs: + func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs) + if figs: + raise RuntimeError('Number of open figures changed during ' + 'test. Make sure you are plotting to ' + 'fig_test or fig_ref, or if this is ' + 'deliberate explicitly close the ' + 'new figure(s) inside the test.') test_image_path = result_dir / (file_name + "." + ext) ref_image_path = result_dir / (file_name + "-expected." + ext) fig_test.savefig(test_image_path) @@ -482,7 +463,7 @@ def _image_directories(func): ``$(pwd)/result_images/test_baz``. The result directory is created if it doesn't exist. """ - module_path = Path(sys.modules[func.__module__].__file__) + module_path = Path(inspect.getfile(func)) baseline_dir = module_path.parent / "baseline_images" / module_path.stem result_dir = Path().resolve() / "result_images" / module_path.stem result_dir.mkdir(parents=True, exist_ok=True) diff --git a/lib/matplotlib/testing/decorators.pyi b/lib/matplotlib/testing/decorators.pyi new file mode 100644 index 000000000000..f1b6c5e595cb --- /dev/null +++ b/lib/matplotlib/testing/decorators.pyi @@ -0,0 +1,25 @@ +from collections.abc import Callable, Sequence +from pathlib import Path +from typing import Any, TypeVar +from typing_extensions import ParamSpec + +from matplotlib.figure import Figure +from matplotlib.typing import RcStyleType + +_P = ParamSpec("_P") +_R = TypeVar("_R") + +def remove_ticks_and_titles(figure: Figure) -> None: ... +def image_comparison( + baseline_images: list[str] | None, + extensions: list[str] | None = ..., + tol: float = ..., + freetype_version: tuple[str, str] | str | None = ..., + remove_text: bool = ..., + savefig_kwarg: dict[str, Any] | None = ..., + style: RcStyleType = ..., +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... +def check_figures_equal( + *, extensions: Sequence[str] = ..., tol: float = ... +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... +def _image_directories(func: Callable) -> tuple[Path, Path]: ... diff --git a/lib/matplotlib/testing/jpl_units/Duration.py b/lib/matplotlib/testing/jpl_units/Duration.py index 723cbea1dfcd..052c5a47c0fd 100644 --- a/lib/matplotlib/testing/jpl_units/Duration.py +++ b/lib/matplotlib/testing/jpl_units/Duration.py @@ -1,8 +1,9 @@ """Duration module.""" +import functools import operator -from matplotlib import cbook +from matplotlib import _api class Duration: @@ -21,7 +22,7 @@ def __init__(self, frame, seconds): - frame The frame of the duration. Must be 'ET' or 'UTC' - seconds The number of seconds in the Duration. """ - cbook._check_in_list(self.allowed, frame=frame) + _api.check_in_list(self.allowed, frame=frame) self._frame = frame self._seconds = seconds @@ -44,38 +45,20 @@ def seconds(self): def __bool__(self): return self._seconds != 0 - def __eq__(self, rhs): - return self._cmp(rhs, operator.eq) - - def __ne__(self, rhs): - return self._cmp(rhs, operator.ne) - - def __lt__(self, rhs): - return self._cmp(rhs, operator.lt) - - def __le__(self, rhs): - return self._cmp(rhs, operator.le) - - def __gt__(self, rhs): - return self._cmp(rhs, operator.gt) - - def __ge__(self, rhs): - return self._cmp(rhs, operator.ge) - - def _cmp(self, rhs, op): + def _cmp(self, op, rhs): """ - Compare two Durations. - - = INPUT VARIABLES - - rhs The Duration to compare against. - - op The function to do the comparison - - = RETURN VALUE - - Returns op(self, rhs) + Check that *self* and *rhs* share frames; compare them using *op*. """ self.checkSameFrame(rhs, "compare") return op(self._seconds, rhs._seconds) + __eq__ = functools.partialmethod(_cmp, operator.eq) + __ne__ = functools.partialmethod(_cmp, operator.ne) + __lt__ = functools.partialmethod(_cmp, operator.lt) + __le__ = functools.partialmethod(_cmp, operator.le) + __gt__ = functools.partialmethod(_cmp, operator.gt) + __ge__ = functools.partialmethod(_cmp, operator.ge) + def __add__(self, rhs): """ Add two Durations. @@ -126,25 +109,15 @@ def __mul__(self, rhs): """ return Duration(self._frame, self._seconds * float(rhs)) - def __rmul__(self, lhs): - """ - Scale a Duration by a value. - - = INPUT VARIABLES - - lhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration(self._frame, self._seconds * float(lhs)) + __rmul__ = __mul__ def __str__(self): """Print the Duration.""" - return "%g %s" % (self._seconds, self._frame) + return f"{self._seconds:g} {self._frame}" def __repr__(self): """Print the Duration.""" - return "Duration('%s', %g)" % (self._frame, self._seconds) + return f"Duration('{self._frame}', {self._seconds:g})" def checkSameFrame(self, rhs, func): """ diff --git a/lib/matplotlib/testing/jpl_units/Epoch.py b/lib/matplotlib/testing/jpl_units/Epoch.py index 613846b3a06d..501b7fa38c79 100644 --- a/lib/matplotlib/testing/jpl_units/Epoch.py +++ b/lib/matplotlib/testing/jpl_units/Epoch.py @@ -1,10 +1,11 @@ """Epoch module.""" +import functools import operator import math import datetime as DT -from matplotlib import cbook +from matplotlib import _api from matplotlib.dates import date2num @@ -59,7 +60,7 @@ def __init__(self, frame, sec=None, jd=None, daynum=None, dt=None): "dnum= %s\n" "dt = %s" % (sec, jd, daynum, dt)) - cbook._check_in_list(self.allowed, frame=frame) + _api.check_in_list(self.allowed, frame=frame) self._frame = frame if dt is not None: @@ -106,44 +107,22 @@ def secondsPast(self, frame, jd): delta = t._jd - jd return t._seconds + delta * 86400 - def __eq__(self, rhs): - return self._cmp(rhs, operator.eq) - - def __ne__(self, rhs): - return self._cmp(rhs, operator.ne) - - def __lt__(self, rhs): - return self._cmp(rhs, operator.lt) - - def __le__(self, rhs): - return self._cmp(rhs, operator.le) - - def __gt__(self, rhs): - return self._cmp(rhs, operator.gt) - - def __ge__(self, rhs): - return self._cmp(rhs, operator.ge) - - def _cmp(self, rhs, op): - """ - Compare two Epoch's. - - = INPUT VARIABLES - - rhs The Epoch to compare against. - - op The function to do the comparison - - = RETURN VALUE - - Returns op(self, rhs) - """ + def _cmp(self, op, rhs): + """Compare Epochs *self* and *rhs* using operator *op*.""" t = self if self._frame != rhs._frame: t = self.convert(rhs._frame) - if t._jd != rhs._jd: return op(t._jd, rhs._jd) - return op(t._seconds, rhs._seconds) + __eq__ = functools.partialmethod(_cmp, operator.eq) + __ne__ = functools.partialmethod(_cmp, operator.ne) + __lt__ = functools.partialmethod(_cmp, operator.lt) + __le__ = functools.partialmethod(_cmp, operator.le) + __gt__ = functools.partialmethod(_cmp, operator.gt) + __ge__ = functools.partialmethod(_cmp, operator.ge) + def __add__(self, rhs): """ Add a duration to an Epoch. @@ -195,7 +174,7 @@ def __sub__(self, rhs): def __str__(self): """Print the Epoch.""" - return "%22.15e %s" % (self.julianDate(self._frame), self._frame) + return f"{self.julianDate(self._frame):22.15e} {self._frame}" def __repr__(self): """Print the Epoch.""" diff --git a/lib/matplotlib/testing/jpl_units/EpochConverter.py b/lib/matplotlib/testing/jpl_units/EpochConverter.py index 32926b18bbff..1edc2acf2b24 100644 --- a/lib/matplotlib/testing/jpl_units/EpochConverter.py +++ b/lib/matplotlib/testing/jpl_units/EpochConverter.py @@ -1,7 +1,6 @@ """EpochConverter module containing class EpochConverter.""" -from matplotlib import cbook -import matplotlib.units as units +from matplotlib import cbook, units import matplotlib.dates as date_ticker __all__ = ['EpochConverter'] @@ -13,9 +12,7 @@ class EpochConverter(units.ConversionInterface): classes. """ - # julian date reference for "Jan 1, 0001" minus 1 day because - # Matplotlib really wants "Jan 0, 0001" - jdRef = 1721425.5 - 1 + jdRef = 1721425.5 @staticmethod def axisinfo(unit, axis): @@ -81,8 +78,6 @@ def convert(value, unit, axis): if not cbook.is_scalar_or_string(value): return [EpochConverter.convert(x, unit, axis) for x in value] - if units.ConversionInterface.is_numlike(value): - return value if unit is None: unit = EpochConverter.default_units(value, axis) if isinstance(value, U.Duration): diff --git a/lib/matplotlib/testing/jpl_units/StrConverter.py b/lib/matplotlib/testing/jpl_units/StrConverter.py index a47bcdd343a2..a62d4981dc79 100644 --- a/lib/matplotlib/testing/jpl_units/StrConverter.py +++ b/lib/matplotlib/testing/jpl_units/StrConverter.py @@ -27,15 +27,12 @@ def axisinfo(unit, axis): def convert(value, unit, axis): # docstring inherited - if units.ConversionInterface.is_numlike(value): - return value - if value == []: return [] # we delay loading to make matplotlib happy ax = axis.axes - if axis is ax.get_xaxis(): + if axis is ax.xaxis: isXAxis = True else: isXAxis = False diff --git a/lib/matplotlib/testing/jpl_units/UnitDbl.py b/lib/matplotlib/testing/jpl_units/UnitDbl.py index 14d77cd8faf7..5226c06ad54b 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDbl.py +++ b/lib/matplotlib/testing/jpl_units/UnitDbl.py @@ -1,8 +1,9 @@ """UnitDbl module.""" +import functools import operator -from matplotlib import cbook +from matplotlib import _api class UnitDbl: @@ -48,7 +49,7 @@ def __init__(self, value, units): - value The numeric value of the UnitDbl. - units The string name of the units the value is in. """ - data = cbook._check_getitem(self.allowed, units=units) + data = _api.check_getitem(self.allowed, units=units) self._value = float(value * data[0]) self._units = data[1] @@ -68,7 +69,7 @@ def convert(self, units): """ if self._units == units: return self._value - data = cbook._check_getitem(self.allowed, units=units) + data = _api.check_getitem(self.allowed, units=units) if self._units != data[1]: raise ValueError(f"Error trying to convert to different units.\n" f" Invalid conversion requested.\n" @@ -88,107 +89,40 @@ def __bool__(self): """Return the truth value of a UnitDbl.""" return bool(self._value) - def __eq__(self, rhs): - return self._cmp(rhs, operator.eq) - - def __ne__(self, rhs): - return self._cmp(rhs, operator.ne) - - def __lt__(self, rhs): - return self._cmp(rhs, operator.lt) - - def __le__(self, rhs): - return self._cmp(rhs, operator.le) - - def __gt__(self, rhs): - return self._cmp(rhs, operator.gt) - - def __ge__(self, rhs): - return self._cmp(rhs, operator.ge) - - def _cmp(self, rhs, op): - """ - Compare two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to compare against. - - op The function to do the comparison - - = RETURN VALUE - - Returns op(self, rhs) - """ + def _cmp(self, op, rhs): + """Check that *self* and *rhs* share units; compare them using *op*.""" self.checkSameUnits(rhs, "compare") return op(self._value, rhs._value) - def __add__(self, rhs): - """ - Add two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to add. - - = RETURN VALUE - - Returns the sum of ourselves and the input UnitDbl. - """ - self.checkSameUnits(rhs, "add") - return UnitDbl(self._value + rhs._value, self._units) - - def __sub__(self, rhs): - """ - Subtract two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to subtract. - - = RETURN VALUE - - Returns the difference of ourselves and the input UnitDbl. - """ - self.checkSameUnits(rhs, "subtract") - return UnitDbl(self._value - rhs._value, self._units) + __eq__ = functools.partialmethod(_cmp, operator.eq) + __ne__ = functools.partialmethod(_cmp, operator.ne) + __lt__ = functools.partialmethod(_cmp, operator.lt) + __le__ = functools.partialmethod(_cmp, operator.le) + __gt__ = functools.partialmethod(_cmp, operator.gt) + __ge__ = functools.partialmethod(_cmp, operator.ge) - def __mul__(self, rhs): - """ - Scale a UnitDbl by a value. + def _binop_unit_unit(self, op, rhs): + """Check that *self* and *rhs* share units; combine them using *op*.""" + self.checkSameUnits(rhs, op.__name__) + return UnitDbl(op(self._value, rhs._value), self._units) - = INPUT VARIABLES - - rhs The scalar to multiply by. + __add__ = functools.partialmethod(_binop_unit_unit, operator.add) + __sub__ = functools.partialmethod(_binop_unit_unit, operator.sub) - = RETURN VALUE - - Returns the scaled UnitDbl. - """ - return UnitDbl(self._value * rhs, self._units) + def _binop_unit_scalar(self, op, scalar): + """Combine *self* and *scalar* using *op*.""" + return UnitDbl(op(self._value, scalar), self._units) - def __rmul__(self, lhs): - """ - Scale a UnitDbl by a value. - - = INPUT VARIABLES - - lhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled UnitDbl. - """ - return UnitDbl(self._value * lhs, self._units) + __mul__ = functools.partialmethod(_binop_unit_scalar, operator.mul) + __rmul__ = functools.partialmethod(_binop_unit_scalar, operator.mul) def __str__(self): """Print the UnitDbl.""" - return "%g *%s" % (self._value, self._units) + return f"{self._value:g} *{self._units}" def __repr__(self): """Print the UnitDbl.""" - return "UnitDbl(%g, '%s')" % (self._value, self._units) + return f"UnitDbl({self._value:g}, '{self._units}')" def type(self): """Return the type of UnitDbl data.""" diff --git a/lib/matplotlib/testing/jpl_units/UnitDblConverter.py b/lib/matplotlib/testing/jpl_units/UnitDblConverter.py index 37734e5ced60..23065379f581 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDblConverter.py +++ b/lib/matplotlib/testing/jpl_units/UnitDblConverter.py @@ -2,8 +2,7 @@ import numpy as np -from matplotlib import cbook -import matplotlib.units as units +from matplotlib import cbook, units import matplotlib.projections.polar as polar __all__ = ['UnitDblConverter'] @@ -66,11 +65,6 @@ def convert(value, unit, axis): # docstring inherited if not cbook.is_scalar_or_string(value): return [UnitDblConverter.convert(x, unit, axis) for x in value] - # If the incoming value behaves like a number, - # then just return it because we don't know how to convert it - # (or it is already converted) - if units.ConversionInterface.is_numlike(value): - return value # If no units were specified, then get the default units to use. if unit is None: unit = UnitDblConverter.default_units(value, axis) diff --git a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py index da262eae3e2d..30a9914015bc 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py +++ b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py @@ -17,12 +17,12 @@ def __call__(self, x, pos=None): if len(self.locs) == 0: return '' else: - return '{:.12}'.format(x) + return f'{x:.12}' def format_data_short(self, value): # docstring inherited - return '{:.12}'.format(value) + return f'{value:.12}' def format_data(self, value): # docstring inherited - return '{:.12}'.format(value) + return f'{value:.12}' diff --git a/lib/matplotlib/testing/jpl_units/meson.build b/lib/matplotlib/testing/jpl_units/meson.build new file mode 100644 index 000000000000..c950f0bfa4dd --- /dev/null +++ b/lib/matplotlib/testing/jpl_units/meson.build @@ -0,0 +1,16 @@ +python_sources = [ + '__init__.py', + 'Duration.py', + 'EpochConverter.py', + 'Epoch.py', + 'StrConverter.py', + 'UnitDblConverter.py', + 'UnitDblFormatter.py', + 'UnitDbl.py', +] + +typing_sources = [ +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/testing/jpl_units') diff --git a/lib/matplotlib/testing/meson.build b/lib/matplotlib/testing/meson.build new file mode 100644 index 000000000000..1016f81941ca --- /dev/null +++ b/lib/matplotlib/testing/meson.build @@ -0,0 +1,22 @@ +python_sources = [ + '__init__.py', + '_markers.py', + 'compare.py', + 'conftest.py', + 'decorators.py', + 'exceptions.py', + 'widgets.py', +] + +typing_sources = [ + '__init__.pyi', + 'compare.pyi', + 'conftest.pyi', + 'decorators.pyi', + 'widgets.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/testing') + +subdir('jpl_units') diff --git a/lib/matplotlib/testing/widgets.py b/lib/matplotlib/testing/widgets.py index 7c77e68327a1..3962567aa7c0 100644 --- a/lib/matplotlib/testing/widgets.py +++ b/lib/matplotlib/testing/widgets.py @@ -2,56 +2,118 @@ ======================== Widget testing utilities ======================== -Functions that are useful for testing widgets. -See also matplotlib.tests.test_widgets + +See also :mod:`matplotlib.tests.test_widgets`. """ -import matplotlib.pyplot as plt + from unittest import mock +import matplotlib.pyplot as plt + def get_ax(): - """Creates plot and returns its axes""" + """Create a plot and return its Axes.""" fig, ax = plt.subplots(1, 1) ax.plot([0, 200], [0, 200]) ax.set_aspect(1.0) - ax.figure.canvas.draw() + fig.canvas.draw() return ax -def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1): - """ - Trigger an event +def noop(*args, **kwargs): + pass + + +def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1): + r""" + Create a mock event that can stand in for `.Event` and its subclasses. + + This event is intended to be used in tests where it can be passed into + event handling functions. Parameters ---------- - tool : matplotlib.widgets.RectangleSelector - etype - the event to trigger - xdata : int - x coord of mouse in data coords - ydata : int - y coord of mouse in data coords - button : int or str - button pressed None, 1, 2, 3, 'up', 'down' (up and down are used - for scroll events) - key - the key depressed when the mouse event triggered (see - :class:`KeyEvent`) + ax : `~matplotlib.axes.Axes` + The Axes the event will be in. + xdata : float + x coord of mouse in data coords. + ydata : float + y coord of mouse in data coords. + button : None or `MouseButton` or {'up', 'down'} + The mouse button pressed in this event (see also `.MouseEvent`). + key : None or str + The key pressed when the mouse event triggered (see also `.KeyEvent`). step : int - number of scroll steps (positive for 'up', negative for 'down') + Number of scroll steps (positive for 'up', negative for 'down'). + + Returns + ------- + event + A `.Event`\-like Mock instance. """ event = mock.Mock() event.button = button - ax = tool.ax event.x, event.y = ax.transData.transform([(xdata, ydata), (xdata, ydata)])[0] event.xdata, event.ydata = xdata, ydata event.inaxes = ax - event.canvas = ax.figure.canvas + event.canvas = ax.get_figure(root=True).canvas event.key = key event.step = step event.guiEvent = None event.name = 'Custom' + return event + + +def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1): + """ + Trigger an event on the given tool. + Parameters + ---------- + tool : matplotlib.widgets.AxesWidget + etype : str + The event to trigger. + xdata : float + x coord of mouse in data coords. + ydata : float + y coord of mouse in data coords. + button : None or `MouseButton` or {'up', 'down'} + The mouse button pressed in this event (see also `.MouseEvent`). + key : None or str + The key pressed when the mouse event triggered (see also `.KeyEvent`). + step : int + Number of scroll steps (positive for 'up', negative for 'down'). + """ + event = mock_event(tool.ax, button, xdata, ydata, key, step) func = getattr(tool, etype) func(event) + + +def click_and_drag(tool, start, end, key=None): + """ + Helper to simulate a mouse drag operation. + + Parameters + ---------- + tool : `~matplotlib.widgets.Widget` + start : [float, float] + Starting point in data coordinates. + end : [float, float] + End point in data coordinates. + key : None or str + An optional key that is pressed during the whole operation + (see also `.KeyEvent`). + """ + if key is not None: + # Press key + do_event(tool, 'on_key_press', xdata=start[0], ydata=start[1], + button=1, key=key) + # Click, move, and release mouse + do_event(tool, 'press', xdata=start[0], ydata=start[1], button=1) + do_event(tool, 'onmove', xdata=end[0], ydata=end[1], button=1) + do_event(tool, 'release', xdata=end[0], ydata=end[1], button=1) + if key is not None: + # Release key + do_event(tool, 'on_key_release', xdata=end[0], ydata=end[1], + button=1, key=key) diff --git a/lib/matplotlib/testing/widgets.pyi b/lib/matplotlib/testing/widgets.pyi new file mode 100644 index 000000000000..858ff4571582 --- /dev/null +++ b/lib/matplotlib/testing/widgets.pyi @@ -0,0 +1,31 @@ +from typing import Any, Literal + +from matplotlib.axes import Axes +from matplotlib.backend_bases import Event, MouseButton +from matplotlib.widgets import AxesWidget, Widget + +def get_ax() -> Axes: ... +def noop(*args: Any, **kwargs: Any) -> None: ... +def mock_event( + ax: Axes, + button: MouseButton | int | Literal["up", "down"] | None = ..., + xdata: float = ..., + ydata: float = ..., + key: str | None = ..., + step: int = ..., +) -> Event: ... +def do_event( + tool: AxesWidget, + etype: str, + button: MouseButton | int | Literal["up", "down"] | None = ..., + xdata: float = ..., + ydata: float = ..., + key: str | None = ..., + step: int = ..., +) -> None: ... +def click_and_drag( + tool: Widget, + start: tuple[float, float], + end: tuple[float, float], + key: str | None = ..., +) -> None: ... diff --git a/lib/matplotlib/tests/README b/lib/matplotlib/tests/README index 0f243229a0c6..0613afcc85a3 100644 --- a/lib/matplotlib/tests/README +++ b/lib/matplotlib/tests/README @@ -4,6 +4,5 @@ About Matplotlib Testing Infrastructure Information on the testing infrastructure is provided in the Testing section of the Matplotlib Developers’ Guide: -* http://matplotlib.org/devel/testing.html +* https://matplotlib.org/devel/testing.html * /doc/devel/coding_guide.rst (equivalent, but in reST format) - diff --git a/lib/matplotlib/tests/__init__.py b/lib/matplotlib/tests/__init__.py index 7c4c5e946b6b..8cce4fe4558d 100644 --- a/lib/matplotlib/tests/__init__.py +++ b/lib/matplotlib/tests/__init__.py @@ -3,7 +3,7 @@ # Check that the test directories exist. if not (Path(__file__).parent / 'baseline_images').exists(): - raise IOError( + raise OSError( 'The baseline image directory does not exist. ' 'This is most likely because the test data is not installed. ' 'You may need to install matplotlib from source to get the ' diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.map b/lib/matplotlib/tests/baseline_images/dviread/test.map index 96a4ca6f51cb..67ceb014b415 100644 --- a/lib/matplotlib/tests/baseline_images/dviread/test.map +++ b/lib/matplotlib/tests/baseline_images/dviread/test.map @@ -1,10 +1,33 @@ % used by test_dviread.py -TeXfont1 PSfont1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.pdf b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.pdf index 054fe8d8264f..cac3b8f7751e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.pdf and b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.png b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.png index cf2ebc38391d..1846832dc3f3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.png and b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.svg b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.svg index e6743bd2a79b..eb2fc6501453 100644 --- a/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.svg +++ b/lib/matplotlib/tests/baseline_images/test_artist/clip_path_clipping.svg @@ -1,12 +1,23 @@ - - + + + + + + 2025-05-18T15:59:59.749730 + image/svg+xml + + + Matplotlib v3.11.0.dev842+g991ee94077, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 274.909091 388.8 L 274.909091 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#p4234805953)" style="fill: url(#h8da01be9d9); fill-opacity: 0.7; stroke: #0000ff; stroke-opacity: 0.7; stroke-width: 5"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -162,94 +173,94 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -262,10 +273,10 @@ L 518.4 388.8 L 518.4 43.2 L 315.490909 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#p1824667f16)" style="fill: url(#h8da01be9d9); opacity: 0.7; stroke: #0000ff; stroke-width: 5; stroke-linejoin: miter"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -390,84 +401,84 @@ L 518.4 43.2 - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -475,7 +486,7 @@ L 518.4 43.2 - + - + - + + +z +" style="fill: #0000ff; stroke: #0000ff; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 0.7"/> diff --git a/lib/matplotlib/tests/baseline_images/test_artist/hatching.pdf b/lib/matplotlib/tests/baseline_images/test_artist/hatching.pdf index c812f811812a..df8dcbeed8e6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_artist/hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_artist/hatching.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks.png b/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks.png deleted file mode 100644 index 71dfb1aa4a0d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks_log.png b/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks_log.png deleted file mode 100644 index 7a31434f501f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/auto_numticks_log.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.pdf b/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.pdf deleted file mode 100644 index 42652378a5d0..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.svg b/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.svg deleted file mode 100644 index e87ecb06a6bd..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.svg +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.pdf b/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.pdf deleted file mode 100644 index d034617daa24..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.svg b/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.svg deleted file mode 100644 index 7b05c11742e5..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/axhspan_epoch.svg +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png b/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png new file mode 100644 index 000000000000..3937cdf5b34c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axis_options.png b/lib/matplotlib/tests/baseline_images/test_axes/axis_options.png new file mode 100644 index 000000000000..b45b153fbb38 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/axis_options.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.pdf b/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.pdf deleted file mode 100644 index 2d9006680410..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.svg b/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.svg deleted file mode 100644 index 54ce8e9308f5..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.svg +++ /dev/null @@ -1,719 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf deleted file mode 100644 index 55264db5d1f9..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg deleted file mode 100644 index 6774f852dc9e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png index 05ae97248c8f..14675de05163 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png index 2ef5936b2db7..c6f11f0411ae 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_custom_capwidths.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_custom_capwidths.png new file mode 100644 index 000000000000..6282584ca548 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_custom_capwidths.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png index 1a7553254c60..fc91c7911723 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png index ec7107aea080..803db84f2dc2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf deleted file mode 100644 index 57e0fb494244..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png index 61507b39c9cc..944f9451285c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg deleted file mode 100644 index 054af5b4f2f3..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png index 7037ca022975..9ac36a98b2ae 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png index c9a1e8826741..a3095f5dc26f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index 3bf1c27df947..d830e2355492 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png index fdaf2e157961..44de05620d19 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidth.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidth.png new file mode 100644 index 000000000000..2e7c530beecf Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidth.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidths.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidths.png new file mode 100644 index 000000000000..1d8e44ccbecd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidths.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png index 19e3731575c7..587ac5e68b43 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png index cced1e51566e..3323a7e4e1ec 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png index 698bc40f15c2..901309e6fa62 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png index e1aafcd346ed..234f2941bdc0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png index 4333291d745f..6c9d4a8e2a67 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png index d7c586645809..efd3a3e136c9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png index 63f37ded24c0..d7d8bb264281 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png index 0c2c9935f29e..210dea241397 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png index 87bb56b464b6..df216d72ca12 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png index 89a7112dc853..11a23c2ecedf 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png index 4d8b9c38e087..76bb606ba6c5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png index 8b6a49c84dc4..9b027f3bacd2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png index 93921f74e49f..29853ae8d778 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png index bb1997317252..d9dc4379c6ae 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png index aaa884632195..38a0563aeb0f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png index a47dc0a47d21..f864ac1c4267 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png index b65a77e86687..af0fb83ddd22 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png index 45a0fac0fe81..fe7b0d37fb12 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png index 966cc5a559cd..279a9dd1c8b5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png index 9d47676beee5..ad938d741ac9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png index 1eae658a2ac0..63542a4007bd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf index 8c1f477a66b1..d38d94962848 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png index 002557764760..8fd8e5c018d6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg index c5b68c60ecd0..bf0b1f15812d 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-05-08T08:38:16.254819 + image/svg+xml + + + Matplotlib v3.8.0.dev1017+g22694d6944.d20230508, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 343.296 307.584 L 343.296 41.472 L 57.6 41.472 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - + - - - - + - - - - - + - - - - - + - - - - - + - - - - - + - - - - + - - - - + - - - + - - - + +" clip-path="url(#p20b471bf05)" style="fill: #053061"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - + + - + - - - - +" transform="scale(0.015625)"/> + + + - + - - + + - - - - +" transform="scale(0.015625)"/> + + + - + - - + + - - - - +" transform="scale(0.015625)"/> + + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - + - - + + - + - - + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + @@ -14677,133 +14611,133 @@ z - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - - + + + - + - - - + + + - + - - - + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - + + + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - - - + - - - - +L 57.6 116.623916 +" clip-path="url(#p20b471bf05)" style="fill: none; stroke: #bfbf00; stroke-width: 2"/> - - + - - - - - + - - - - - + - - - - + - - - - + - - - + - - - + +z +" clip-path="url(#p20b471bf05)" style="fill: none; stroke: #00bfbf; stroke-width: 2"/> + - +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="fill: #ffffff"/> + + + + + + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p835c633be6)" style="fill: #053061"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - + + - + - - - - - - +" transform="scale(0.015625)"/> + + + + + - + - - - - - + + + + + - + - - + + - - - - - - +" transform="scale(0.015625)"/> + + + + + - + - - - - - + + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - + +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> - - + + - - + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf index ac6f579cdafc..6ad6ca0de11f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg index 2cfa55ec7696..3b9c65fe8cb9 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-05-08T08:36:18.041547 + image/svg+xml + + + Matplotlib v3.8.0.dev1017+gf5c408d00b.d20230508, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 414.72 307.584 L 414.72 41.472 L 57.6 41.472 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + - - - +" clip-path="url(#p65d51b06d9)" style="fill: url(#hccf00b61cb); fill-opacity: 0.5"/> - - + - - - +" clip-path="url(#p65d51b06d9)" style="fill: url(#hde0a47f0b8); fill-opacity: 0.5"/> - - + - - - +" clip-path="url(#p65d51b06d9)" style="fill: url(#hf0e9e6139c); fill-opacity: 0.5"/> - - + - - - - +" clip-path="url(#p65d51b06d9)" style="fill: url(#hd472800a41); fill-opacity: 0.5"/> - - + - +" clip-path="url(#p65d51b06d9)" style="fill: url(#he78dc33525); fill-opacity: 0.5"/> - - + - - +" clip-path="url(#p65d51b06d9)" style="fill: url(#h9a9d4f9427); fill-opacity: 0.5"/> - - + - +" clip-path="url(#p65d51b06d9)" style="fill: url(#hc6e131b2d7); fill-opacity: 0.5"/> - - + +" clip-path="url(#p65d51b06d9)" style="fill: url(#h8604b2d15b); fill-opacity: 0.5"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + - + - + - + @@ -6136,68 +6130,68 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + - + - + - + @@ -6205,33 +6199,33 @@ L -3.5 0 +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> - - + + - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> - - + + +" style="fill: #000000; stroke: #000000; stroke-width: 1.0; stroke-linecap: butt; stroke-linejoin: miter"/> diff --git a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x.png b/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x.png deleted file mode 100644 index b193acac1bab..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x_and_y.png b/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x_and_y.png deleted file mode 100644 index d39489636a7b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_x_and_y.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_y.png b/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_y.png deleted file mode 100644 index e6b1b9ae0e95..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/date_timezone_y.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.pdf b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.pdf deleted file mode 100644 index 2058620b35d2..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.svg deleted file mode 100644 index e3940abeca5b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.svg +++ /dev/null @@ -1,1186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf deleted file mode 100644 index 503bdf11dabc..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg deleted file mode 100644 index 8ea6f573d94b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg +++ /dev/null @@ -1,1673 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.pdf b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.pdf deleted file mode 100644 index f4da53e4890f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.png b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.png index 91d5a7e89bca..9f3ff4488a99 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.png and b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg deleted file mode 100644 index 7302d4e74273..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg +++ /dev/null @@ -1,2365 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.pdf b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.pdf deleted file mode 100644 index 10773bfe9083..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.svg deleted file mode 100644 index 5e25759468d1..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_zorder.svg +++ /dev/null @@ -1,1015 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/eventplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/eventplot.pdf deleted file mode 100644 index e98c3f6d6b34..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/eventplot.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/eventplot.svg b/lib/matplotlib/tests/baseline_images/test_axes/eventplot.svg deleted file mode 100644 index dcc145527b7c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/eventplot.svg +++ /dev/null @@ -1,2628 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png b/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png new file mode 100644 index 000000000000..28bde8bf76ec Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.pdf b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.pdf deleted file mode 100644 index eeb8969fa702..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png index 59c32a9084d7..007007ec6ee8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png and b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg deleted file mode 100644 index 35a003c97c21..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg +++ /dev/null @@ -1,1256 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.pdf b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.pdf deleted file mode 100644 index 4042ec1c9fba..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.svg b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.svg deleted file mode 100644 index 857e8c92f833..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_decreasing.svg +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.png b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.png new file mode 100644 index 000000000000..35cf06ee4850 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.pdf b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.pdf deleted file mode 100644 index 43a2c30a0368..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.svg b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.svg deleted file mode 100644 index 9b2b29fd4e98..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_001.svg +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.pdf b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.pdf deleted file mode 100644 index 3465f1bd42d0..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.svg b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.svg deleted file mode 100644 index 03adfb5247d3..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_002.svg +++ /dev/null @@ -1,905 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.pdf b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.pdf deleted file mode 100644 index f071d453dc7b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.svg b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.svg deleted file mode 100644 index 9aa79e5a566f..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_003.svg +++ /dev/null @@ -1,905 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.pdf b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.pdf deleted file mode 100644 index ad0fa75eafae..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.svg b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.svg deleted file mode 100644 index d4c6c2b72e4c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_004.svg +++ /dev/null @@ -1,796 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.pdf b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.pdf deleted file mode 100644 index 21f9705474e8..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.svg b/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.svg deleted file mode 100644 index d1eea00c3208..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/formatter_ticker_005.svg +++ /dev/null @@ -1,798 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/grouped_bar.png b/lib/matplotlib/tests/baseline_images/test_axes/grouped_bar.png new file mode 100644 index 000000000000..19d676a6b662 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/grouped_bar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hexbin_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/hexbin_linear.png new file mode 100644 index 000000000000..824ea49b0599 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/hexbin_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hexbin_log.png b/lib/matplotlib/tests/baseline_images/test_axes/hexbin_log.png index febefb870918..466519461aac 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hexbin_log.png and b/lib/matplotlib/tests/baseline_images/test_axes/hexbin_log.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist2d.pdf deleted file mode 100644 index 8343cc2efd97..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist2d.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist2d.svg deleted file mode 100644 index 025441b800db..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist2d.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf deleted file mode 100644 index d7a58f772a40..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg deleted file mode 100644 index 8111cb56486a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_log.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_log.pdf deleted file mode 100644 index 9c6e73351b0d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_log.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_log.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_log.svg deleted file mode 100644 index d0825b425ba3..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_log.svg +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.pdf deleted file mode 100644 index 45cc03fb511f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.svg deleted file mode 100644 index e3865b8cf8bf..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_offset.svg +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.pdf deleted file mode 100644 index 8c1e835c2729..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.svg deleted file mode 100644 index c5bcbc473301..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_bar.svg +++ /dev/null @@ -1,1485 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.pdf deleted file mode 100644 index c63109d24640..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.svg deleted file mode 100644 index a48c7fdd2eda..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_normed.svg +++ /dev/null @@ -1,657 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.pdf deleted file mode 100644 index a9e7412b235b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.svg deleted file mode 100644 index 19e4ea8a8969..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_step.svg +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.pdf deleted file mode 100644 index 4c55a0fa010d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.svg deleted file mode 100644 index 5be44c0cf6b7..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled.svg +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.pdf deleted file mode 100644 index 182020177eda..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.svg deleted file mode 100644 index 7d595a98a5ef..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_stepfilled_alpha.svg +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.pdf deleted file mode 100644 index f1c383b62486..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.svg deleted file mode 100644 index 452839806945..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist_stacked_weights.svg +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf b/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf index 875868fff1e7..183b072fc312 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.pdf b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.pdf index c33c7c4e29ca..f4bbc73544a5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.png b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.png index e68b7c3c0baf..cde64b03c7f6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.png and b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg index b5ebff4427b2..d1169e860808 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg @@ -1,12 +1,23 @@ - + + + + + 2021-03-02T20:09:49.859581 + image/svg+xml + + + Matplotlib v3.3.4.post2495+g8432e3164, https://matplotlib.org/ + + + + + - + @@ -26,9 +37,9 @@ L 103.104 41.472 z " style="fill:#ffffff;"/> - - + + @@ -36,38 +47,38 @@ iVBORw0KGgoAAAANSUhEUgAAAXIAAAFyCAYAAADoJFEJAAAABHNCSVQICAgIfAhkiAAAIABJREFUeJzt +" id="m0bf9183b9d" style="stroke:#000000;stroke-width:0.8;"/> - + - - + + - - +" id="DejaVuSans-30" transform="scale(0.015625)"/> + @@ -75,38 +86,38 @@ z - + - - - + + + @@ -115,31 +126,33 @@ z - + - - + + - - +" id="DejaVuSans-34" transform="scale(0.015625)"/> + @@ -148,44 +161,44 @@ z - + - - + + - - +" id="DejaVuSans-36" transform="scale(0.015625)"/> + @@ -194,53 +207,53 @@ z - + - - + + - - +" id="DejaVuSans-38" transform="scale(0.015625)"/> + @@ -253,10 +266,10 @@ z +" id="m09e66e4baf" style="stroke:#000000;stroke-width:0.8;"/> - + @@ -269,7 +282,7 @@ L -3.5 0 - + @@ -283,7 +296,7 @@ L -3.5 0 - + @@ -297,7 +310,7 @@ L -3.5 0 - + @@ -311,7 +324,7 @@ L -3.5 0 - + @@ -323,17 +336,23 @@ L -3.5 0 - - + @@ -455,7 +585,7 @@ L 369.216 41.472 - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/inset_polar.png b/lib/matplotlib/tests/baseline_images/test_axes/inset_polar.png new file mode 100644 index 000000000000..b7b7faf198ec Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/inset_polar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.pdf b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.pdf deleted file mode 100644 index c76b653c33fb..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png deleted file mode 100644 index e305de1d9ac7..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg deleted file mode 100644 index 0a29c9d0af21..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg +++ /dev/null @@ -1,670 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery.pdf deleted file mode 100644 index 8a2a4887d2d7..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery.svg deleted file mode 100644 index 8ae4cdac28a1..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery.svg +++ /dev/null @@ -1,987 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.pdf deleted file mode 100644 index 9bff9fcd374d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.svg deleted file mode 100644 index db71bd78fa57..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_line.svg +++ /dev/null @@ -1,1407 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.pdf deleted file mode 100644 index e0f266c1670c..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.svg deleted file mode 100644 index 760903302ecd..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales.svg +++ /dev/null @@ -1,3177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.png b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.png new file mode 100644 index 000000000000..2eb087944ec4 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.pdf deleted file mode 100644 index 103c8c292503..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.svg deleted file mode 100644 index 746e66f65dfb..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_zoomed.svg +++ /dev/null @@ -1,3420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.pdf deleted file mode 100644 index d2c84bf64c66..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.svg deleted file mode 100644 index 1c1ee14a1282..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_log_scales.svg +++ /dev/null @@ -1,6491 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf deleted file mode 100644 index b69860d339e9..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg deleted file mode 100644 index 19730045f101..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg +++ /dev/null @@ -1,4181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/mixed_errorbar_polar_caps.png b/lib/matplotlib/tests/baseline_images/test_axes/mixed_errorbar_polar_caps.png new file mode 100644 index 000000000000..bbe879779df4 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/mixed_errorbar_polar_caps.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.pdf b/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.pdf deleted file mode 100644 index c9306e718556..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.svg b/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.svg deleted file mode 100644 index 64359dcf6376..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/mollweide_grid.svg +++ /dev/null @@ -1,1851 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/nonfinite_limits.svg b/lib/matplotlib/tests/baseline_images/test_axes/nonfinite_limits.svg index c1a886b6ff5f..77cfb8afaffa 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/nonfinite_limits.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/nonfinite_limits.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-07-07T03:36:38.117527 + image/svg+xml + + + Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#pe2e2378c9e)" style="fill: none; stroke: #0000ff; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - - + + - - +M 2034 4750 +Q 2819 4750 3233 4129 +Q 3647 3509 3647 2328 +Q 3647 1150 3233 529 +Q 2819 -91 2034 -91 +Q 1250 -91 836 529 +Q 422 1150 422 2328 +Q 422 3509 836 4129 +Q 1250 4750 2034 4750 +z +" transform="scale(0.015625)"/> + + - - + + - + - + - - + + - - +" transform="scale(0.015625)"/> + - - + + - + - + - - + + - - +" transform="scale(0.015625)"/> + - - + + - + - + - + - - + + - + - + - - - - + + + + - - + + - + - + - + - - + + - + - + - - - - + + + + - - + + @@ -387,149 +405,152 @@ Q 46.96875 40.921875 40.578125 39.3125 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - + + - - +" transform="scale(0.015625)"/> + - + - + - + - - + + - - +" transform="scale(0.015625)"/> + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -537,17 +558,17 @@ z - + - + - + @@ -556,8 +577,8 @@ z - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.pdf b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.pdf index e45b4650f8a6..609fe5506fd0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.png b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.png index 1e4db29adcae..dbaa310eba74 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.png and b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg index c94b782c5ee0..1bc36cd8ffed 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-06-04T11:45:04.891764 + image/svg+xml + + + Matplotlib v3.8.0.dev1211+gdcb8180edc.d20230604, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,1628 +35,1628 @@ L 203.294118 388.8 L 203.294118 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +" clip-path="url(#pb51c0ef5e5)" style="fill: #ff7700; stroke: #000000; stroke-width: 0.5"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + @@ -1654,118 +1665,118 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1778,1618 +1789,1618 @@ L 360.847059 388.8 L 360.847059 43.2 L 229.552941 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +" clip-path="url(#pd48800f88c)" style="fill: #ff7700; stroke: #0000ff; stroke-width: 2"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + @@ -3398,108 +3409,108 @@ L 360.847059 43.2 - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3512,12 +3523,12 @@ L 518.4 388.8 L 518.4 43.2 L 387.105882 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - + - + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + @@ -23866,108 +9717,108 @@ L 518.4 43.2 - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -23975,14 +9826,14 @@ L 518.4 43.2 - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_small.eps b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_small.eps new file mode 100644 index 000000000000..f60cc23924d1 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_small.eps @@ -0,0 +1,294 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 +%%Title: pcolormesh_small.eps +%%Creator: Matplotlib v3.8.0.dev1213+gf7674f7ec5.d20230605, https://matplotlib.org/ +%%CreationDate: Mon Jun 5 12:57:17 2023 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 8 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/sc { setcachedevice } _d +end +%%EndProlog +mpldict begin +18 180 translate +0 0 576 432 rectclip +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1 setgray +fill +grestore +0.5 setlinewidth +1 setlinejoin +0 setlinecap +[] 0 setdash +0 setgray +gsave +72 231.709 202.909 157.091 rectclip +183.6 231.905848 m +89.322065 271.080197 l +145.210611 279.491161 l +239.488546 240.316813 l +183.6 231.905848 l +gsave +0.5 0 0 setrgbcolor +fill +grestore +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +89.322065 271.080197 m +183.6 310.254545 l +239.488546 318.66551 l +145.210611 279.491161 l +89.322065 271.080197 l +gsave +0 0 0.5 setrgbcolor +fill +grestore +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +239.488546 240.316813 m +145.210611 279.491161 l +179.068684 298.648661 l +273.346619 259.474312 l +239.488546 240.316813 l +gsave +0.161 1 0.806 setrgbcolor +fill +grestore +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +145.210611 279.491161 m +239.488546 318.66551 l +273.346619 337.82301 l +179.068684 298.648661 l +145.210611 279.491161 l +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +273.346619 259.474312 m +179.068684 298.648661 l +179.068684 321.86043 l +273.346619 282.686081 l +273.346619 259.474312 l +gsave +0 0 0.714 setrgbcolor +fill +grestore +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +179.068684 298.648661 m +273.346619 337.82301 l +273.346619 361.034778 l +179.068684 321.86043 l +179.068684 298.648661 l +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +273.346619 282.686081 m +179.068684 321.86043 l +145.210611 341.01793 l +239.488546 301.843581 l +273.346619 282.686081 l +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +179.068684 321.86043 m +273.346619 361.034778 l +239.488546 380.192278 l +145.210611 341.01793 l +179.068684 321.86043 l +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +239.488546 301.843581 m +145.210611 341.01793 l +89.322065 349.428894 l +183.6 310.254545 l +239.488546 301.843581 l +stroke +grestore +gsave +72 231.709 202.909 157.091 rectclip +145.210611 341.01793 m +239.488546 380.192278 l +183.6 388.603243 l +89.322065 349.428894 l +145.210611 341.01793 l +stroke +grestore +2 setlinewidth +0 0 1 setrgbcolor +gsave +315.491 231.709 202.909 157.091 rectclip +427.090909 231.905848 m +332.812974 271.080197 l +388.70152 279.491161 l +482.979455 240.316813 l +427.090909 231.905848 l +gsave +0.5 0 0 setrgbcolor +fill +grestore +stroke +grestore +1 setgray +gsave +315.491 231.709 202.909 157.091 rectclip +332.812974 271.080197 m +427.090909 310.254545 l +482.979455 318.66551 l +388.70152 279.491161 l +332.812974 271.080197 l +gsave +0 0 0.5 setrgbcolor +fill +grestore +stroke +grestore +0 0 1 setrgbcolor +gsave +315.491 231.709 202.909 157.091 rectclip +482.979455 240.316813 m +388.70152 279.491161 l +422.559593 298.648661 l +516.837528 259.474312 l +482.979455 240.316813 l +gsave +0.161 1 0.806 setrgbcolor +fill +grestore +stroke +grestore +1 setgray +gsave +315.491 231.709 202.909 157.091 rectclip +388.70152 279.491161 m +482.979455 318.66551 l +516.837528 337.82301 l +422.559593 298.648661 l +388.70152 279.491161 l +stroke +grestore +0 0 1 setrgbcolor +gsave +315.491 231.709 202.909 157.091 rectclip +516.837528 259.474312 m +422.559593 298.648661 l +422.559593 321.86043 l +516.837528 282.686081 l +516.837528 259.474312 l +gsave +0 0 0.714 setrgbcolor +fill +grestore +stroke +grestore +1 setgray +gsave +315.491 231.709 202.909 157.091 rectclip +422.559593 298.648661 m +516.837528 337.82301 l +516.837528 361.034778 l +422.559593 321.86043 l +422.559593 298.648661 l +stroke +grestore +0 0 1 setrgbcolor +gsave +315.491 231.709 202.909 157.091 rectclip +516.837528 282.686081 m +422.559593 321.86043 l +388.70152 341.01793 l +482.979455 301.843581 l +516.837528 282.686081 l +stroke +grestore +1 setgray +gsave +315.491 231.709 202.909 157.091 rectclip +422.559593 321.86043 m +516.837528 361.034778 l +482.979455 380.192278 l +388.70152 341.01793 l +422.559593 321.86043 l +stroke +grestore +0 0 1 setrgbcolor +gsave +315.491 231.709 202.909 157.091 rectclip +482.979455 301.843581 m +388.70152 341.01793 l +332.812974 349.428894 l +427.090909 310.254545 l +482.979455 301.843581 l +stroke +grestore +1 setgray +gsave +315.491 231.709 202.909 157.091 rectclip +388.70152 341.01793 m +482.979455 380.192278 l +427.090909 388.603243 l +332.812974 349.428894 l +388.70152 341.01793 l +stroke +grestore +gsave +<< /ShadingType 4 + /ColorSpace [/DeviceRGB] + /BitsPerCoordinate 32 + /BitsPerComponent 8 + /BitsPerFlag 8 + /AntiAlias true + /Decode [ -3763.19 4612.84 -4013.43 4296.09 0 1 0 1 0 1 ] + /DataSource < +007d3020007e309000feed00008011c7707f6586637f0000007f7b98517eec36359f8a3f008011c7707f6586637f00000081c710a27fa7dc6bff6b00007f7b98 +517eec36359f8a3f0081c710a27fa7dc6bff6b00007ee569317e72e60800d0ff007f7b98517eec36359f8a3f007ee569317e72e60800d0ff007d3020007e3090 +00feed00007f7b98517eec36359f8a3f007ee569317e72e60800d0ff0081c710a27fa7dc6bff6b000080dab1e87f58ed0f7f865f0081c710a27fa7dc6bff6b00 +0082cffa9e803ef415ffde000080dab1e87f58ed0f7f865f0082cffa9e803ef415ffde00007fee532d7f09fdb200007f0080dab1e87f58ed0f7f865f007fee53 +2d7f09fdb200007f007ee569317e72e60800d0ff0080dab1e87f58ed0f7f865f007fee532d7f09fdb200007f0082cffa9e803ef415ffde0000815f26e5800001 +947f6f3f0082cffa9e803ef415ffde000082cffa9e80f60576ffde0000815f26e5800001947f6f3f0082cffa9e80f60576ffde00007fee532d7fc10f1300007f +00815f26e5800001947f6f3f007fee532d7fc10f1300007f007fee532d7f09fdb200007f00815f26e5800001947f6f3f0082cffa9e7e8c18b0ffde00007fee53 +2d7fc10f1300007f0080dab1e87f721fb77f865f007fee532d7fc10f1300007f007ee56931805826bd00d0ff0080dab1e87f721fb77f865f007ee56931805826 +bd00d0ff0081c710a27f23305aff6b000080dab1e87f721fb77f865f0081c710a27f23305aff6b000082cffa9e7e8c18b0ffde000080dab1e87f721fb77f865f +007fee532d7fc10f1300007f0082cffa9e80f60576ffde000080dab1e880a7161a7f865f0082cffa9e80f60576ffde000081c710a2818d1d20ff6b000080dab1 +e880a7161a7f865f0081c710a2818d1d20ff6b00007ee56931805826bd00d0ff0080dab1e880a7161a7f865f007ee56931805826bd00d0ff007fee532d7fc10f +1300007f0080dab1e880a7161a7f865f0081c710a27f23305aff6b00007ee56931805826bd00d0ff007f7b98517fded6909f8a3f007ee56931805826bd00d0ff +007d302000809a7cc6feed00007f7b98517fded6909f8a3f007d302000809a7cc6feed00008011c7707f6586637f0000007f7b98517fded6909f8a3f008011c7 +707f6586637f00000081c710a27f23305aff6b00007f7b98517fded6909f8a3f007ee56931805826bd00d0ff0081c710a2818d1d20ff6b00007f7b98518113cc +f39f8a3f0081c710a2818d1d20ff6b00008011c77081cf73297f0000007f7b98518113ccf39f8a3f008011c77081cf73297f0000007d302000809a7cc6feed00 +007f7b98518113ccf39f8a3f007d302000809a7cc6feed00007ee56931805826bd00d0ff007f7b98518113ccf39f8a3f +> +>> +shfill +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png index 8af5f62b5526..c5236a34b9e1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png index 1f925b75d9d3..64b2244711f9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png index 1c0c6c2c0577..f3935a9e159a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png index 4eea9fb7c157..4e4edbeed0ed 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png index 18acc069100e..e814e061205a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png index 68fc84a4d596..e12d743fbc45 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png index d2d651e421dc..c6fd5262acce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png index 91664207dee7..d5875752c3cd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png new file mode 100644 index 000000000000..fc8076486661 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/preset_clip_paths.png b/lib/matplotlib/tests/baseline_images/test_axes/preset_clip_paths.png new file mode 100644 index 000000000000..0b60b60f4849 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/preset_clip_paths.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png b/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png index bbf9f9e13211..8398034d1891 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png and b/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/single_date.png b/lib/matplotlib/tests/baseline_images/test_axes/single_date.png deleted file mode 100644 index 9df3334340c2..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/single_date.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/single_point.svg b/lib/matplotlib/tests/baseline_images/test_axes/single_point.svg index 5f940bb5a83c..de3b541c4f8a 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/single_point.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/single_point.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-07-07T03:36:36.453826 + image/svg+xml + + + Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,11 +35,11 @@ L 518.4 200.290909 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - - - - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - + + - - + - - - +M 2034 4750 +Q 2819 4750 3233 4129 +Q 3647 3509 3647 2328 +Q 3647 1150 3233 529 +Q 2819 -91 2034 -91 +Q 1250 -91 836 529 +Q 422 1150 422 2328 +Q 422 3509 836 4129 +Q 1250 4750 2034 4750 +z +" transform="scale(0.015625)"/> + + + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - - + + - - +" transform="scale(0.015625)"/> + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - - - - + + + + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + @@ -410,224 +389,196 @@ L 518.4 43.2 - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - - + + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pbfdf9b07b4)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + @@ -640,324 +591,302 @@ L 518.4 388.8 L 518.4 231.709091 L 72 231.709091 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - - - - + + + + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - - - - + + + + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - - + + - - +" transform="scale(0.015625)"/> + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + @@ -965,211 +894,183 @@ L 518.4 231.709091 - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + - - - - - +" clip-path="url(#pc45172f364)" style="fill: none; stroke-dasharray: 1,3; stroke-dashoffset: 0; stroke: #000000; stroke-width: 0.5"/> - + - + - + - - - + + + @@ -1177,11 +1078,11 @@ L 518.4 231.709091 - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.pdf b/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.pdf deleted file mode 100644 index 531c9c5e9b3f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.svg b/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.svg deleted file mode 100644 index ec64d7cdbf4e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_baseline.svg +++ /dev/null @@ -1,3359 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.pdf b/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.pdf deleted file mode 100644 index 8f08637012e4..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.svg b/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.svg deleted file mode 100644 index 421dc4448593..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/stackplot_test_image.svg +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stem.png b/lib/matplotlib/tests/baseline_images/test_axes/stem.png index 4c6b3af3205b..2e6968b6183a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/stem.png and b/lib/matplotlib/tests/baseline_images/test_axes/stem.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png new file mode 100644 index 000000000000..a3fb13d0716a Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png new file mode 100644 index 000000000000..a2e185c2769d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_centered_bar_label_nonlinear.svg b/lib/matplotlib/tests/baseline_images/test_axes/test_centered_bar_label_nonlinear.svg new file mode 100644 index 000000000000..cea1050932a6 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/test_centered_bar_label_nonlinear.svg @@ -0,0 +1,166 @@ + + + + + + + + 2022-09-10T15:01:10.033044 + image/svg+xml + + + Matplotlib v3.5.0.dev5765+gcb3beb2f91.d20220910, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png new file mode 100644 index 000000000000..fa499047b0f8 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png new file mode 100644 index 000000000000..3367067f3605 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf b/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf index d1245169e994..305bcb90ab99 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.pdf b/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.pdf deleted file mode 100644 index f189df616a33..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.svg b/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.svg deleted file mode 100644 index 37a6b88f3e73..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.svg +++ /dev/null @@ -1,1243 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.png b/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.png new file mode 100644 index 000000000000..c1c8074ed80c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png b/lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png new file mode 100644 index 000000000000..f30bc46b8c5c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.pdf b/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.pdf deleted file mode 100644 index e6974a409dca..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.svg b/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.svg deleted file mode 100644 index a2eaa040cbb3..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/vline_hline_zorder.svg +++ /dev/null @@ -1,1011 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/vlines_hlines_blended_transform.png b/lib/matplotlib/tests/baseline_images/test_axes/vlines_hlines_blended_transform.png new file mode 100644 index 000000000000..bcaee389dffe Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/vlines_hlines_blended_transform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-bitstream-charter.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-bitstream-charter.pdf new file mode 100644 index 000000000000..c8f9411fb3d9 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-bitstream-charter.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-dejavusans.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-dejavusans.pdf new file mode 100644 index 000000000000..fd907dee6687 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-dejavusans.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-heuristica.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-heuristica.pdf new file mode 100644 index 000000000000..ca9b38d09b89 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/font-heuristica.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/grayscale_alpha.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/grayscale_alpha.pdf index e893648cd0f2..93e850ca8bdb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pdf/grayscale_alpha.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/grayscale_alpha.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/hatching_legend.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/hatching_legend.pdf index 146d4dd92d4d..57fc311ee81b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pdf/hatching_legend.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/hatching_legend.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/kerning.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/kerning.pdf new file mode 100644 index 000000000000..90bf2a5c9845 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/kerning.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf new file mode 100644 index 000000000000..a1e01accabdd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf new file mode 100644 index 000000000000..8e6826719910 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_ttconv/truetype-conversion.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/truetype-conversion.pdf similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_ttconv/truetype-conversion.pdf rename to lib/matplotlib/tests/baseline_images/test_backend_pdf/truetype-conversion.pdf diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_document_font_size.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_document_font_size.pdf new file mode 100644 index 000000000000..9f060419a2a7 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_document_font_size.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_mixedmode.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_mixedmode.pdf index fdbb20349c42..fd7cf7a5c0d1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_mixedmode.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_mixedmode.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf index f9418746d453..c93b5de52674 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf index 02fe1fcef54c..fbf9f7271e49 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf index b5f395422463..e5f9cd6e8e94 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf index 819886986a8b..aff1d4d6dd28 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/colorbar_shift.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/colorbar_shift.eps new file mode 100644 index 000000000000..b88e23a33c42 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/colorbar_shift.eps @@ -0,0 +1,897 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: colorbar_shift.eps +%%Creator: Matplotlib v3.7.0.dev1597+g613b343238.d20230210, https://matplotlib.org/ +%%CreationDate: Fri Feb 10 16:16:04 2023 +%%Orientation: portrait +%%BoundingBox: 110 245 502 547 +%%HiResBoundingBox: 110.097762 245.509625 501.902238 546.490375 +%%EndComments +%%BeginProlog +/mpldict 11 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +%!PS-Adobe-3.0 Resource-Font +%%Creator: Converted from TrueType to Type 3 by Matplotlib. +10 dict begin +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix [0.00048828125 0 0 0.00048828125 0 0] def +/FontBBox [-2090 -948 3673 2524] def +/FontType 3 def +/Encoding [/period /zero /one /two /minus /four /five /six /eight /nine] def +/CharStrings 11 dict dup begin +/.notdef 0 def +/period{651 0 219 0 430 254 sc +219 254 m +430 254 l +430 0 l +219 0 l +219 254 l + +ce} _d +/zero{1303 0 135 -29 1167 1520 sc +651 1360 m +547 1360 469 1309 416 1206 c +364 1104 338 950 338 745 c +338 540 364 387 416 284 c +469 182 547 131 651 131 c +756 131 834 182 886 284 c +939 387 965 540 965 745 c +965 950 939 1104 886 1206 c +834 1309 756 1360 651 1360 c + +651 1520 m +818 1520 946 1454 1034 1321 c +1123 1189 1167 997 1167 745 c +1167 494 1123 302 1034 169 c +946 37 818 -29 651 -29 c +484 -29 356 37 267 169 c +179 302 135 494 135 745 c +135 997 179 1189 267 1321 c +356 1454 484 1520 651 1520 c + +ce} _d +/one{1303 0 225 0 1114 1493 sc +254 170 m +584 170 l +584 1309 l +225 1237 l +225 1421 l +582 1493 l +784 1493 l +784 170 l +1114 170 l +1114 0 l +254 0 l +254 170 l + +ce} _d +/two{1303 0 150 0 1098 1520 sc +393 170 m +1098 170 l +1098 0 l +150 0 l +150 170 l +227 249 331 356 463 489 c +596 623 679 709 713 748 c +778 821 823 882 848 932 c +874 983 887 1032 887 1081 c +887 1160 859 1225 803 1275 c +748 1325 675 1350 586 1350 c +523 1350 456 1339 385 1317 c +315 1295 240 1262 160 1217 c +160 1421 l +241 1454 317 1478 388 1495 c +459 1512 523 1520 582 1520 c +737 1520 860 1481 952 1404 c +1044 1327 1090 1223 1090 1094 c +1090 1033 1078 974 1055 919 c +1032 864 991 800 930 725 c +913 706 860 650 771 557 c +682 465 556 336 393 170 c + +ce} _d +/minus{1716 0 217 557 1499 727 sc +217 727 m +1499 727 l +1499 557 l +217 557 l +217 727 l + +ce} _d +/four{1303 0 100 0 1188 1493 sc +774 1317 m +264 520 l +774 520 l +774 1317 l + +721 1493 m +975 1493 l +975 520 l +1188 520 l +1188 352 l +975 352 l +975 0 l +774 0 l +774 352 l +100 352 l +100 547 l +721 1493 l + +ce} _d +/five{1303 0 158 -29 1124 1493 sc +221 1493 m +1014 1493 l +1014 1323 l +406 1323 l +406 957 l +435 967 465 974 494 979 c +523 984 553 987 582 987 c +749 987 881 941 978 850 c +1075 759 1124 635 1124 479 c +1124 318 1074 193 974 104 c +874 15 733 -29 551 -29 c +488 -29 424 -24 359 -13 c +294 -2 227 14 158 35 c +158 238 l +218 205 280 181 344 165 c +408 149 476 141 547 141 c +662 141 754 171 821 232 c +888 293 922 375 922 479 c +922 583 888 665 821 726 c +754 787 662 817 547 817 c +493 817 439 811 385 799 c +332 787 277 768 221 743 c +221 1493 l + +ce} _d +/six{1303 0 143 -29 1174 1520 sc +676 827 m +585 827 513 796 460 734 c +407 672 381 587 381 479 c +381 372 407 287 460 224 c +513 162 585 131 676 131 c +767 131 838 162 891 224 c +944 287 971 372 971 479 c +971 587 944 672 891 734 c +838 796 767 827 676 827 c + +1077 1460 m +1077 1276 l +1026 1300 975 1318 923 1331 c +872 1344 821 1350 770 1350 c +637 1350 535 1305 464 1215 c +394 1125 354 989 344 807 c +383 865 433 909 492 940 c +551 971 617 987 688 987 c +838 987 956 941 1043 850 c +1130 759 1174 636 1174 479 c +1174 326 1129 203 1038 110 c +947 17 827 -29 676 -29 c +503 -29 371 37 280 169 c +189 302 143 494 143 745 c +143 981 199 1169 311 1309 c +423 1450 573 1520 762 1520 c +813 1520 864 1515 915 1505 c +967 1495 1021 1480 1077 1460 c + +ce} _d +/eight{1303 0 139 -29 1163 1520 sc +651 709 m +555 709 479 683 424 632 c +369 581 342 510 342 420 c +342 330 369 259 424 208 c +479 157 555 131 651 131 c +747 131 823 157 878 208 c +933 260 961 331 961 420 c +961 510 933 581 878 632 c +823 683 748 709 651 709 c + +449 795 m +362 816 295 857 246 916 c +198 975 174 1048 174 1133 c +174 1252 216 1347 301 1416 c +386 1485 503 1520 651 1520 c +800 1520 916 1485 1001 1416 c +1086 1347 1128 1252 1128 1133 c +1128 1048 1104 975 1055 916 c +1007 857 940 816 854 795 c +951 772 1027 728 1081 662 c +1136 596 1163 515 1163 420 c +1163 275 1119 164 1030 87 c +942 10 816 -29 651 -29 c +486 -29 360 10 271 87 c +183 164 139 275 139 420 c +139 515 166 596 221 662 c +276 728 352 772 449 795 c + +375 1114 m +375 1037 399 976 447 933 c +496 890 564 868 651 868 c +738 868 805 890 854 933 c +903 976 928 1037 928 1114 c +928 1191 903 1252 854 1295 c +805 1338 738 1360 651 1360 c +564 1360 496 1338 447 1295 c +399 1252 375 1191 375 1114 c + +ce} _d +/nine{1303 0 129 -29 1159 1520 sc +225 31 m +225 215 l +276 191 327 173 379 160 c +431 147 482 141 532 141 c +665 141 767 186 837 275 c +908 365 948 501 958 684 c +919 627 870 583 811 552 c +752 521 686 506 614 506 c +465 506 346 551 259 641 c +172 732 129 855 129 1012 c +129 1165 174 1288 265 1381 c +356 1474 476 1520 627 1520 c +800 1520 931 1454 1022 1321 c +1113 1189 1159 997 1159 745 c +1159 510 1103 322 991 181 c +880 41 730 -29 541 -29 c +490 -29 439 -24 387 -14 c +335 -4 281 11 225 31 c + +627 664 m +718 664 789 695 842 757 c +895 819 922 904 922 1012 c +922 1119 895 1204 842 1266 c +789 1329 718 1360 627 1360 c +536 1360 464 1329 411 1266 c +358 1204 332 1119 332 1012 c +332 904 358 819 411 757 c +464 695 536 664 627 664 c + +ce} _d +end readonly def + +/BuildGlyph { + exch begin + CharStrings exch + 2 copy known not {pop /.notdef} if + true 3 1 roll get exec + end +} _d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +} _d + +FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +110.098 245.51 translate +391.804 300.981 0 0 clipbox +gsave +0 0 m +391.804475 0 l +391.804475 300.98075 l +0 300.98075 l +cl +1.000 setgray +fill +grestore +gsave +36.465625 23.871875 m +322.161625 23.871875 l +322.161625 289.983875 l +36.465625 289.983875 l +cl +1.000 setgray +fill +grestore +/p0_0 { +newpath +translate +0 -3 m +0.795609 -3 1.55874 -2.683901 2.12132 -2.12132 c +2.683901 -1.55874 3 -0.795609 3 0 c +3 0.795609 2.683901 1.55874 2.12132 2.12132 c +1.55874 2.683901 0.795609 3 0 3 c +-0.795609 3 -1.55874 2.683901 -2.12132 2.12132 c +-2.683901 1.55874 -3 0.795609 -3 0 c +-3 -0.795609 -2.683901 -1.55874 -2.12132 -2.12132 c +-1.55874 -2.683901 -0.795609 -3 0 -3 c +cl + +} bind def +1.000 setlinewidth +1 setlinejoin +0 setlinecap +[] 0 setdash +0.000 0.500 0.000 setrgbcolor +gsave +285.696 266.112 36.466 23.872 clipbox +49.4518 156.928 p0_0 +gsave +fill +grestore +stroke +grestore +0.000 0.000 1.000 setrgbcolor +gsave +285.696 266.112 36.466 23.872 clipbox +309.175 156.928 p0_0 +gsave +fill +grestore +stroke +grestore +0.800 setlinewidth +0.000 setgray +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +49.4518 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +41.4987 9.27812 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +101.397 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +93.4434 9.27812 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /two glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +153.341 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +145.388 9.27812 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /four glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +205.286 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +197.333 9.27812 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /six glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +257.231 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +249.278 9.27812 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /eight glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +0 -3.5 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +309.175 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +301.222 9.27812 translate +0 rotate +0 0 m /one glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +-0 0 m +-3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +36.4656 60.1599 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +7.2 56.363 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /nine glyphshow +15.9033 0 m /six glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +-0 0 m +-3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +36.4656 108.544 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +7.2 104.747 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /nine glyphshow +15.9033 0 m /eight glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +-0 0 m +-3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +36.4656 156.928 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +7.2 153.131 translate +0 rotate +0 0 m /one glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +15.9033 0 m /zero glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +-0 0 m +-3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +36.4656 205.312 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +7.2 201.515 translate +0 rotate +0 0 m /one glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +15.9033 0 m /two glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +-0 0 m +-3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +36.4656 253.696 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +7.2 249.899 translate +0 rotate +0 0 m /one glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +15.9033 0 m /four glyphshow +grestore +0 setlinejoin +2 setlinecap +gsave +36.465625 23.871875 m +36.465625 289.983875 l +stroke +grestore +gsave +322.161625 23.871875 m +322.161625 289.983875 l +stroke +grestore +gsave +36.465625 23.871875 m +322.161625 23.871875 l +stroke +grestore +gsave +36.465625 289.983875 m +322.161625 289.983875 l +stroke +grestore +gsave +340.017625 23.871875 m +353.323225 23.871875 l +353.323225 289.983875 l +340.017625 289.983875 l +cl +1.000 setgray +fill +grestore +gsave +13.306 266.112 340.018 23.872 clipbox +340.017625 23.871875 m +353.323225 23.871875 l +353.323225 112.575875 l +340.017625 112.575875 l +340.017625 23.871875 l +1.000 0.000 0.000 setrgbcolor +fill +grestore +gsave +13.306 266.112 340.018 23.872 clipbox +340.017625 112.575875 m +353.323225 112.575875 l +353.323225 201.279875 l +340.017625 201.279875 l +340.017625 112.575875 l +0.000 0.500 0.000 setrgbcolor +fill +grestore +gsave +13.306 266.112 340.018 23.872 clipbox +340.017625 201.279875 m +353.323225 201.279875 l +353.323225 289.983875 l +340.017625 289.983875 l +340.017625 201.279875 l +0.000 0.000 1.000 setrgbcolor +fill +grestore +1 setlinejoin +0 setlinecap +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +353.323 23.8719 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +360.323 20.075 translate +0 rotate +0 0 m /minus glyphshow +8.37891 0 m /one glyphshow +14.7412 0 m /period glyphshow +17.9199 0 m /zero glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +353.323 112.576 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +360.323 108.779 translate +0 rotate +0 0 m /minus glyphshow +8.37891 0 m /zero glyphshow +14.7412 0 m /period glyphshow +17.9199 0 m /five glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +353.323 201.28 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +360.323 197.483 translate +0 rotate +0 0 m /zero glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /five glyphshow +grestore +gsave +/o { +gsave +newpath +translate +0.8 setlinewidth +1 setlinejoin + +0 setlinecap + +0 0 m +3.5 0 l + +gsave +0.000 setgray +fill +grestore +stroke +grestore +} bind def +353.323 289.984 o +grestore +/DejaVuSans 10.000 selectfont +gsave + +360.323 286.187 translate +0 rotate +0 0 m /one glyphshow +6.3623 0 m /period glyphshow +9.54102 0 m /zero glyphshow +grestore +0 setlinejoin +2 setlinecap +gsave +340.017625 23.871875 m +346.670425 23.871875 l +353.323225 23.871875 l +353.323225 289.983875 l +346.670425 289.983875 l +340.017625 289.983875 l +340.017625 23.871875 l +cl +stroke +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/coloredhatcheszerolw.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/coloredhatcheszerolw.eps new file mode 100644 index 000000000000..c0994b3116a5 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/coloredhatcheszerolw.eps @@ -0,0 +1,216 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: coloredhatcheszerolw.eps +%%Creator: Matplotlib v3.6.0.dev1993+g86a08ee.d20220407, https://matplotlib.org/ +%%CreationDate: Thu Apr 7 11:52:41 2022 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 10 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +1.000 0.000 0.000 setrgbcolor +gsave +446.4 345.6 72 43.2 clipbox +72 -129.6 m +131.193332 -129.6 187.970227 -111.392702 229.826234 -78.988052 c +271.68224 -46.583402 295.2 -2.627096 295.2 43.2 c +295.2 89.027096 271.68224 132.983402 229.826234 165.388052 c +187.970227 197.792702 131.193332 216 72 216 c +12.806668 216 -43.970227 197.792702 -85.826234 165.388052 c +-127.68224 132.983402 -151.2 89.027096 -151.2 43.2 c +-151.2 -2.627096 -127.68224 -46.583402 -85.826234 -78.988052 c +-43.970227 -111.392702 12.806668 -129.6 72 -129.6 c +cl + << /PatternType 1 + /PaintType 2 + /TilingType 2 + /BBox[0 0 72 72] + /XStep 72 + /YStep 72 + + /PaintProc { + pop + 1 setlinewidth +-36 36 m +36 108 l +-24 24 m +48 96 l +-12 12 m +60 84 l +0 0 m +72 72 l +12 -12 m +84 60 l +24 -24 m +96 48 l +36 -36 m +108 36 l + + gsave + fill + grestore + stroke + } bind + >> + matrix + 0 432 translate + makepattern + /H0 exch def +gsave +1.000000 0.000000 0.000000 H0 setpattern fill grestore +grestore +0.200 setlinewidth +0 setlinejoin +0 setlinecap +[] 0 setdash +0.000 0.500 0.000 setrgbcolor +gsave +446.4 345.6 72 43.2 clipbox +295.2 129.6 m +324.796666 129.6 353.185114 138.703649 374.113117 154.905974 c +395.04112 171.108299 406.8 193.086452 406.8 216 c +406.8 238.913548 395.04112 260.891701 374.113117 277.094026 c +353.185114 293.296351 324.796666 302.4 295.2 302.4 c +265.603334 302.4 237.214886 293.296351 216.286883 277.094026 c +195.35888 260.891701 183.6 238.913548 183.6 216 c +183.6 193.086452 195.35888 171.108299 216.286883 154.905974 c +237.214886 138.703649 265.603334 129.6 295.2 129.6 c +cl + << /PatternType 1 + /PaintType 2 + /TilingType 2 + /BBox[0 0 72 72] + /XStep 72 + /YStep 72 + + /PaintProc { + pop + 1 setlinewidth +0 6 m +72 6 l +0 18 m +72 18 l +0 30 m +72 30 l +0 42 m +72 42 l +0 54 m +72 54 l +0 66 m +72 66 l +6 0 m +6 72 l +18 0 m +18 72 l +30 0 m +30 72 l +42 0 m +42 72 l +54 0 m +54 72 l +66 0 m +66 72 l + + gsave + fill + grestore + stroke + } bind + >> + matrix + 0 432 translate + makepattern + /H1 exch def +gsave +0.000000 0.500000 0.000000 H1 setpattern fill grestore +stroke +grestore +0.000 0.000 1.000 setrgbcolor +gsave +446.4 345.6 72 43.2 clipbox +518.4 250.56 m +536.158 250.56 553.191068 265.125838 565.74787 291.049559 c +578.304672 316.973279 585.36 352.138323 585.36 388.8 c +585.36 425.461677 578.304672 460.626721 565.74787 486.550441 c +553.191068 512.474162 536.158 527.04 518.4 527.04 c +500.642 527.04 483.608932 512.474162 471.05213 486.550441 c +458.495328 460.626721 451.44 425.461677 451.44 388.8 c +451.44 352.138323 458.495328 316.973279 471.05213 291.049559 c +483.608932 265.125838 500.642 250.56 518.4 250.56 c +cl + << /PatternType 1 + /PaintType 2 + /TilingType 2 + /BBox[0 0 72 72] + /XStep 72 + /YStep 72 + + /PaintProc { + pop + 1 setlinewidth +-36 36 m +36 -36 l +-24 48 m +48 -24 l +-12 60 m +60 -12 l +0 72 m +72 0 l +12 84 m +84 12 l +24 96 m +96 24 l +36 108 m +108 36 l + + gsave + fill + grestore + stroke + } bind + >> + matrix + 0 432 translate + makepattern + /H2 exch def +gsave +0.000000 0.000000 1.000000 H2 setpattern fill grestore +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps new file mode 100644 index 000000000000..540d1d54bd18 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps @@ -0,0 +1,5713 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 +%%Title: multi_font_type3.eps +%%Creator: Matplotlib v3.10.0.dev856+g03f7095b8c, https://matplotlib.org/ +%%CreationDate: Wed Oct 16 16:10:34 2024 +%%Orientation: portrait +%%BoundingBox: 0 0 576 432 +%%HiResBoundingBox: 0.000000 0.000000 576.000000 432.000000 +%%EndComments +%%BeginProlog +/mpldict 10 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/sc { setcachedevice } _d +%!PS-Adobe-3.0 Resource-Font +%%Creator: Converted from TrueType to Type 3 by Matplotlib. +10 dict begin +/FontName /Cmr10 def +/PaintType 0 def +/FontMatrix [0.00048828125 0 0 0.00048828125 0 0] def +/FontBBox [-90 -512 2066 1536] def +/FontType 3 def +/Encoding [/space /exclam /quotedblright /numbersign /dollar /percent /ampersand /quoteright /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash /zero /one /two /three /four /five /six /seven /eight /nine /colon /semicolon /exclamdown /equal /questiondown /question /at /A /B /C /D /E /F /G /H /I /J /K /L /M /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft /quotedblleft /bracketright /circumflex /dotaccent /quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m /n /o /p /q /r /s /t /u /v /w /x /y /z /emdash /endash /hungarumlaut /tilde] def +/CharStrings 96 dict dup begin +/.notdef 0 def +/space{682 0 0 0 0 0 sc +ce} _d +/exclam{567 0 172 0 397 1466 sc +172 113 m +172 144 183 170 206 192 c +229 214 255 225 285 225 c +304 225 322 220 340 210 c +358 200 372 186 382 168 c +392 150 397 132 397 113 c +397 83 386 57 364 34 c +342 11 316 0 285 0 c +255 0 229 11 206 34 c +183 57 172 83 172 113 c + +256 408 m +172 1352 l +172 1364 l +172 1393 183 1417 206 1436 c +229 1456 256 1466 285 1466 c +315 1466 341 1456 363 1436 c +386 1417 397 1393 397 1364 c +397 1352 l +315 408 l +315 403 313 399 309 395 c +305 391 301 389 297 389 c +272 389 l +269 389 265 391 261 395 c +258 400 256 404 256 408 c + +ce} _d +/quotedblright{1024 0 68 799 719 1421 sc +98 827 m +98 833 101 839 106 844 c +157 888 196 942 225 1006 c +254 1070 268 1136 268 1204 c +268 1218 267 1228 266 1235 c +247 1209 218 1196 180 1196 c +149 1196 123 1207 101 1229 c +79 1251 68 1278 68 1309 c +68 1341 79 1368 101 1389 c +123 1410 149 1421 180 1421 c +214 1421 242 1410 263 1387 c +284 1364 299 1336 308 1302 c +317 1268 322 1235 322 1204 c +322 1129 306 1055 273 984 c +241 913 197 852 141 803 c +136 800 132 799 129 799 c +122 799 115 802 108 808 c +101 814 98 820 98 827 c + +496 827 m +496 833 499 839 504 844 c +556 889 596 943 624 1006 c +652 1069 666 1135 666 1204 c +666 1218 665 1228 664 1235 c +644 1209 615 1196 578 1196 c +547 1196 520 1207 498 1229 c +476 1251 465 1278 465 1309 c +465 1341 476 1368 498 1389 c +520 1410 547 1421 578 1421 c +611 1421 639 1410 660 1387 c +681 1364 696 1336 705 1302 c +714 1268 719 1235 719 1204 c +719 1129 703 1055 670 984 c +638 913 594 853 539 803 c +534 800 529 799 526 799 c +519 799 513 802 506 808 c +499 814 496 820 496 827 c + +ce} _d +/numbersign{1706 0 115 -397 1589 1421 sc +342 -356 m +342 -353 343 -351 344 -348 c +510 272 l +154 272 l +143 272 133 276 126 285 c +119 294 115 303 115 313 c +115 324 119 334 126 342 c +133 350 143 354 154 354 c +535 354 l +616 670 l +154 670 l +143 670 133 674 126 682 c +119 690 115 700 115 711 c +115 721 119 730 126 739 c +133 748 143 752 154 752 c +641 752 l +813 1391 l +815 1400 819 1407 826 1412 c +833 1418 842 1421 852 1421 c +863 1421 873 1417 881 1409 c +889 1401 893 1391 893 1380 c +893 1372 l +725 752 l +1110 752 l +1282 1391 l +1284 1400 1288 1407 1295 1412 c +1302 1418 1311 1421 1321 1421 c +1332 1421 1342 1417 1350 1409 c +1358 1401 1362 1391 1362 1380 c +1362 1372 l +1194 752 l +1552 752 l +1563 752 1571 748 1578 739 c +1585 730 1589 721 1589 711 c +1589 700 1585 690 1578 682 c +1571 674 1563 670 1552 670 c +1169 670 l +1087 354 l +1552 354 l +1563 354 1571 350 1578 342 c +1585 334 1589 324 1589 313 c +1589 303 1585 294 1578 285 c +1571 276 1563 272 1552 272 c +1063 272 l +893 -367 l +886 -387 872 -397 852 -397 c +841 -397 831 -393 823 -385 c +815 -377 811 -367 811 -356 c +811 -353 812 -351 813 -348 c +979 272 l +594 272 l +424 -367 l +417 -387 403 -397 383 -397 c +372 -397 362 -393 354 -385 c +346 -377 342 -367 342 -356 c + +618 354 m +1004 354 l +1085 670 l +700 670 l +618 354 l + +ce} _d +/dollar{1024 0 115 -115 907 1536 sc +475 -115 m +475 -20 l +402 -20 339 -2 284 34 c +230 70 188 118 159 179 c +130 240 115 306 115 377 c +115 404 125 427 144 446 c +163 465 186 475 213 475 c +240 475 263 465 282 446 c +301 427 311 404 311 377 c +311 350 301 327 282 308 c +263 289 240 279 213 279 c +211 279 l +202 279 195 280 190 281 c +189 282 187 282 186 282 c +185 283 185 283 184 283 c +193 240 212 200 241 164 c +270 128 306 100 347 80 c +388 61 431 51 475 51 c +475 649 l +435 660 406 669 389 674 c +372 679 355 686 336 695 c +318 704 300 714 283 725 c +266 736 248 751 229 770 c +153 847 115 940 115 1047 c +115 1049 l +115 1051 l +115 1101 125 1150 145 1197 c +165 1245 193 1288 229 1327 c +258 1356 296 1382 343 1406 c +390 1430 434 1442 475 1442 c +475 1536 l +547 1536 l +547 1444 l +615 1444 677 1428 732 1395 c +787 1363 830 1319 861 1262 c +892 1206 907 1143 907 1073 c +907 1046 897 1023 878 1004 c +859 985 836 975 809 975 c +782 975 759 985 740 1004 c +721 1023 711 1046 711 1073 c +711 1100 721 1123 740 1142 c +759 1161 782 1171 809 1171 c +811 1171 l +820 1171 827 1170 831 1169 c +836 1169 l +824 1210 803 1245 774 1275 c +745 1305 710 1328 669 1345 c +629 1362 588 1370 547 1370 c +547 827 l +603 814 649 800 684 783 c +719 767 753 743 784 711 c +824 671 854 624 875 570 c +896 517 907 461 907 403 c +907 399 l +907 344 897 291 877 239 c +858 187 830 141 793 102 c +760 69 720 40 674 16 c +629 -8 586 -20 547 -20 c +547 -115 l +475 -115 l + +547 51 m +593 57 635 74 673 102 c +712 131 742 166 763 209 c +784 252 795 295 795 340 c +795 393 785 438 764 477 c +743 516 714 549 677 575 c +640 601 597 620 547 633 c +547 51 l + +475 846 m +475 1370 l +433 1365 392 1351 353 1326 c +314 1302 284 1271 261 1233 c +238 1196 227 1155 227 1110 c +227 977 310 889 475 846 c + +ce} _d +/percent{1706 0 115 -115 1589 1536 sc +285 -74 m +285 -66 287 -59 291 -53 c +1219 1329 l +1137 1283 1047 1260 948 1260 c +841 1260 739 1288 641 1343 c +668 1278 682 1205 682 1124 c +682 1080 677 1034 666 987 c +656 940 640 896 619 854 c +598 813 570 778 536 751 c +503 724 463 711 418 711 c +354 711 299 732 253 775 c +207 818 172 871 149 935 c +126 999 115 1062 115 1124 c +115 1185 126 1247 149 1311 c +172 1376 207 1429 253 1472 c +299 1515 354 1536 418 1536 c +469 1536 515 1516 557 1475 c +667 1367 797 1313 948 1313 c +1029 1313 1104 1331 1174 1366 c +1244 1402 1301 1453 1346 1520 c +1353 1531 1363 1536 1378 1536 c +1390 1536 1400 1532 1407 1525 c +1415 1518 1419 1508 1419 1495 c +1419 1488 1417 1481 1413 1475 c +356 -102 l +350 -111 340 -115 326 -115 c +315 -115 305 -111 297 -102 c +289 -93 285 -84 285 -74 c + +418 764 m +485 764 536 804 571 884 c +606 965 623 1045 623 1124 c +623 1169 616 1219 601 1276 c +587 1333 565 1382 534 1422 c +503 1463 465 1483 418 1483 c +350 1483 305 1445 283 1369 c +261 1294 250 1211 250 1122 c +250 1036 261 955 284 878 c +307 802 351 764 418 764 c + +1325 -115 m +1261 -115 1206 -94 1160 -51 c +1114 -8 1079 45 1056 109 c +1033 174 1022 237 1022 299 c +1022 360 1033 422 1056 486 c +1079 551 1114 604 1160 647 c +1206 690 1261 711 1325 711 c +1384 711 1434 688 1474 643 c +1514 598 1543 544 1561 481 c +1580 418 1589 357 1589 299 c +1589 255 1584 210 1573 163 c +1563 116 1547 72 1526 29 c +1505 -13 1478 -47 1444 -74 c +1411 -101 1371 -115 1325 -115 c + +1325 -61 m +1371 -61 1410 -41 1441 0 c +1472 41 1495 90 1509 147 c +1523 204 1530 254 1530 299 c +1530 378 1513 457 1478 537 c +1443 617 1392 657 1325 657 c +1257 657 1212 619 1190 543 c +1168 468 1157 386 1157 297 c +1157 210 1168 129 1191 53 c +1214 -23 1258 -61 1325 -61 c + +ce} _d +/ampersand{1591 0 86 -45 1489 1466 sc +86 266 m +86 343 113 408 168 463 c +412 717 l +386 785 366 855 351 926 c +337 998 330 1069 330 1139 c +330 1193 342 1245 365 1295 c +389 1346 423 1387 466 1418 c +510 1450 561 1466 618 1466 c +681 1466 726 1437 755 1380 c +784 1323 799 1259 799 1190 c +799 1129 776 1067 729 1002 c +683 937 622 864 547 782 c +566 735 587 690 609 648 c +631 606 656 563 684 518 c +713 473 744 428 777 382 c +811 336 845 291 879 248 c +920 296 955 343 985 390 c +1016 437 1059 507 1114 600 c +1165 690 l +1172 698 1176 710 1176 725 c +1176 757 1161 779 1132 792 c +1103 805 1069 811 1032 811 c +1032 883 l +1489 883 l +1489 811 l +1366 811 1280 771 1233 690 c +1167 578 l +1122 499 1080 431 1042 372 c +1005 314 963 258 918 205 c +963 154 1007 111 1052 77 c +1097 44 1145 27 1194 27 c +1233 27 1270 37 1304 57 c +1339 77 1366 104 1386 137 c +1407 171 1417 208 1417 248 c +1477 248 l +1477 197 1464 149 1439 104 c +1414 59 1379 23 1336 -4 c +1293 -31 1245 -45 1194 -45 c +1062 -45 940 7 827 111 c +713 7 589 -45 455 -45 c +395 -45 336 -32 279 -7 c +222 18 175 55 139 102 c +104 150 86 205 86 266 c + +473 27 m +585 27 688 69 782 152 c +715 222 650 303 587 395 c +524 487 473 577 434 664 c +352 580 l +293 519 264 435 264 330 c +264 284 271 238 286 191 c +301 145 324 106 355 74 c +387 43 426 27 473 27 c + +526 838 m +588 907 639 970 679 1027 c +719 1084 739 1139 739 1192 c +739 1225 736 1257 729 1290 c +722 1323 710 1351 691 1376 c +673 1401 649 1413 618 1413 c +583 1413 554 1401 531 1377 c +508 1354 492 1325 481 1290 c +470 1256 465 1223 465 1190 c +465 1072 485 955 526 838 c + +ce} _d +/quoteright{567 0 172 799 426 1421 sc +203 827 m +203 833 206 839 211 844 c +263 889 303 943 331 1006 c +359 1069 373 1135 373 1204 c +373 1218 372 1228 371 1235 c +351 1209 322 1196 285 1196 c +254 1196 227 1207 205 1229 c +183 1251 172 1278 172 1309 c +172 1341 183 1368 205 1389 c +227 1410 254 1421 285 1421 c +318 1421 346 1410 367 1387 c +388 1364 403 1336 412 1302 c +421 1268 426 1235 426 1204 c +426 1129 410 1055 377 984 c +345 913 301 853 246 803 c +241 800 236 799 233 799 c +226 799 220 802 213 808 c +206 814 203 820 203 827 c + +ce} _d +/parenleft{795 0 199 -512 680 1536 sc +635 -508 m +559 -448 493 -379 438 -301 c +383 -224 338 -141 303 -53 c +268 35 242 127 225 223 c +208 319 199 415 199 512 c +199 610 208 707 225 803 c +242 899 269 991 304 1080 c +340 1169 386 1252 441 1329 c +496 1406 561 1474 635 1532 c +635 1535 638 1536 645 1536 c +664 1536 l +668 1536 672 1534 675 1530 c +678 1527 680 1523 680 1518 c +680 1512 679 1508 676 1505 c +609 1440 554 1370 509 1295 c +465 1220 429 1141 402 1056 c +375 972 356 885 344 794 c +332 704 326 610 326 512 c +326 78 442 -252 674 -477 c +678 -481 680 -487 680 -494 c +680 -497 678 -501 674 -505 c +671 -510 667 -512 664 -512 c +645 -512 l +638 -512 635 -511 635 -508 c + +ce} _d +/parenright{795 0 115 -512 596 1536 sc +133 -512 m +121 -512 115 -506 115 -494 c +115 -488 116 -484 119 -481 c +352 -253 469 78 469 512 c +469 946 354 1276 123 1501 c +118 1504 115 1510 115 1518 c +115 1523 117 1527 120 1530 c +124 1534 128 1536 133 1536 c +152 1536 l +156 1536 159 1535 162 1532 c +260 1455 342 1361 407 1250 c +472 1139 520 1021 550 896 c +581 771 596 643 596 512 c +596 415 588 320 571 226 c +555 133 529 41 493 -50 c +458 -141 413 -225 358 -302 c +303 -379 238 -448 162 -508 c +159 -511 156 -512 152 -512 c +133 -512 l + +ce} _d +/asterisk{1024 0 133 653 889 1536 sc +193 844 m +178 844 164 850 151 863 c +139 876 133 891 133 907 c +133 930 143 946 162 956 c +457 1096 l +162 1233 l +143 1243 133 1259 133 1282 c +133 1299 139 1314 151 1327 c +163 1340 177 1346 193 1346 c +204 1346 214 1342 223 1335 c +483 1145 l +453 1477 l +451 1481 l +451 1496 457 1509 469 1520 c +482 1531 496 1536 512 1536 c +527 1536 540 1531 552 1521 c +565 1511 571 1498 571 1481 c +571 1477 l +539 1145 l +799 1335 l +808 1342 818 1346 829 1346 c +846 1346 860 1340 871 1327 c +883 1314 889 1299 889 1282 c +889 1259 879 1243 860 1233 c +565 1096 l +860 956 l +879 946 889 930 889 907 c +889 891 883 876 871 863 c +860 850 846 844 829 844 c +818 844 808 847 799 854 c +539 1044 l +571 713 l +571 709 l +571 693 565 680 552 669 c +540 658 527 653 512 653 c +496 653 482 658 469 669 c +457 680 451 694 451 709 c +453 713 l +483 1044 l +223 854 l +215 847 205 844 193 844 c + +ce} _d +/plus{1591 0 115 -170 1477 1194 sc +154 471 m +143 471 133 475 126 484 c +119 493 115 502 115 512 c +115 522 119 531 126 540 c +133 549 143 553 154 553 c +756 553 l +756 1157 l +756 1168 760 1176 768 1183 c +776 1190 786 1194 797 1194 c +807 1194 816 1190 825 1183 c +834 1176 838 1168 838 1157 c +838 553 l +1440 553 l +1450 553 1459 549 1466 540 c +1473 531 1477 522 1477 512 c +1477 502 1473 493 1466 484 c +1459 475 1450 471 1440 471 c +838 471 l +838 -133 l +838 -144 834 -152 825 -159 c +816 -166 807 -170 797 -170 c +786 -170 776 -166 768 -159 c +760 -152 756 -144 756 -133 c +756 471 l +154 471 l + +ce} _d +/comma{567 0 172 -397 420 225 sc +203 -369 m +203 -363 206 -357 211 -352 c +260 -305 299 -250 326 -188 c +353 -126 367 -61 367 8 c +367 33 l +345 11 318 0 285 0 c +254 0 227 11 205 33 c +183 55 172 82 172 113 c +172 145 183 172 205 193 c +227 214 254 225 285 225 c +334 225 368 202 389 157 c +410 112 420 63 420 8 c +420 -68 405 -140 374 -208 c +344 -277 301 -338 246 -393 c +241 -396 236 -397 233 -397 c +226 -397 220 -394 213 -388 c +206 -382 203 -376 203 -369 c + +ce} _d +/hyphen{682 0 23 379 565 506 sc +23 379 m +23 506 l +565 506 l +565 379 l +23 379 l + +ce} _d +/period{567 0 172 0 397 225 sc +172 113 m +172 144 183 170 206 192 c +229 214 255 225 285 225 c +304 225 322 220 340 210 c +358 200 372 186 382 168 c +392 150 397 132 397 113 c +397 83 386 57 364 34 c +342 11 316 0 285 0 c +255 0 229 11 206 34 c +183 57 172 83 172 113 c + +ce} _d +/slash{1024 0 115 -512 907 1536 sc +115 -471 m +115 -467 116 -464 117 -463 c +829 1511 l +832 1519 836 1525 843 1529 c +850 1534 857 1536 866 1536 c +878 1536 888 1532 895 1525 c +903 1518 907 1508 907 1495 c +907 1487 l +195 -487 l +187 -504 174 -512 156 -512 c +145 -512 135 -508 127 -500 c +119 -492 115 -482 115 -471 c + +ce} _d +/zero{1024 0 80 -45 942 1364 sc +512 -45 m +345 -45 231 24 170 161 c +110 299 80 463 80 653 c +80 772 91 883 112 988 c +134 1093 177 1181 241 1254 c +306 1327 396 1364 512 1364 c +602 1364 676 1342 733 1298 c +790 1254 834 1197 864 1127 c +894 1058 914 983 925 903 c +936 824 942 740 942 653 c +942 536 931 426 909 323 c +888 221 845 134 782 62 c +719 -9 629 -45 512 -45 c + +512 8 m +588 8 645 47 682 125 c +719 203 742 289 751 384 c +760 479 764 579 764 686 c +764 789 760 883 751 970 c +742 1057 719 1135 682 1205 c +645 1276 589 1311 512 1311 c +435 1311 377 1276 340 1205 c +303 1134 280 1056 271 969 c +262 883 258 789 258 686 c +258 610 260 538 263 471 c +267 404 277 334 293 262 c +309 191 335 130 370 81 c +406 32 453 8 512 8 c + +ce} _d +/one{1024 0 178 0 862 1364 sc +190 0 m +190 72 l +361 72 446 94 446 137 c +446 1212 l +375 1178 286 1161 178 1161 c +178 1233 l +345 1233 472 1277 557 1364 c +586 1364 l +591 1364 595 1362 599 1358 c +604 1355 606 1351 606 1346 c +606 137 l +606 94 691 72 862 72 c +862 0 l +190 0 l + +ce} _d +/two{1024 0 102 0 920 1364 sc +102 0 m +102 55 l +102 58 103 62 106 66 c +424 418 l +472 470 511 514 541 549 c +571 584 601 625 630 671 c +659 717 682 764 699 811 c +716 859 725 910 725 963 c +725 1019 715 1072 694 1123 c +673 1174 642 1215 601 1246 c +560 1277 511 1292 453 1292 c +394 1292 340 1274 293 1238 c +246 1203 212 1157 193 1100 c +198 1101 206 1102 215 1102 c +246 1102 272 1092 293 1071 c +315 1050 326 1024 326 991 c +326 960 315 933 293 911 c +272 890 246 879 215 879 c +183 879 156 890 134 912 c +113 935 102 961 102 991 c +102 1042 112 1090 131 1135 c +150 1180 178 1220 214 1255 c +251 1290 292 1317 337 1336 c +383 1355 432 1364 483 1364 c +561 1364 634 1347 701 1314 c +768 1281 822 1235 861 1174 c +900 1114 920 1044 920 963 c +920 904 907 847 881 794 c +855 741 822 692 781 648 c +740 605 688 555 625 500 c +562 445 520 408 500 389 c +268 166 l +465 166 l +562 166 642 167 707 168 c +772 170 807 173 811 176 c +827 193 843 256 860 365 c +920 365 l +862 0 l +102 0 l + +ce} _d +/three{1024 0 86 -45 936 1364 sc +195 158 m +227 111 270 77 324 54 c +378 31 436 20 498 20 c +577 20 634 54 667 121 c +700 189 717 266 717 352 c +717 391 713 429 706 468 c +699 507 688 543 671 576 c +654 609 631 636 602 656 c +573 676 538 686 496 686 c +360 686 l +348 686 342 692 342 705 c +342 723 l +342 734 348 739 360 739 c +473 748 l +521 748 561 766 592 802 c +624 838 647 882 662 933 c +677 985 684 1034 684 1081 c +684 1146 669 1200 638 1242 c +607 1284 561 1305 498 1305 c +446 1305 396 1295 349 1275 c +302 1256 264 1226 236 1186 c +239 1187 241 1187 243 1187 c +245 1188 247 1188 250 1188 c +281 1188 306 1177 327 1156 c +348 1135 358 1109 358 1079 c +358 1050 348 1024 327 1003 c +306 982 281 971 250 971 c +220 971 194 982 173 1003 c +152 1024 141 1050 141 1079 c +141 1138 159 1189 194 1232 c +229 1275 275 1308 330 1330 c +386 1353 442 1364 498 1364 c +539 1364 583 1358 629 1345 c +675 1333 717 1315 754 1292 c +791 1269 822 1240 845 1204 c +869 1168 881 1127 881 1081 c +881 1024 868 971 842 922 c +817 873 782 831 737 796 c +692 761 643 734 590 717 c +649 706 706 683 759 650 c +812 617 855 574 887 522 c +920 470 936 414 936 354 c +936 279 915 210 874 149 c +833 88 778 41 711 6 c +644 -28 573 -45 498 -45 c +434 -45 370 -33 305 -8 c +241 16 188 52 147 101 c +106 150 86 208 86 276 c +86 310 97 338 120 361 c +143 384 171 395 205 395 c +227 395 247 390 265 379 c +284 369 298 355 308 336 c +319 317 324 297 324 276 c +324 243 312 215 289 192 c +266 169 238 158 205 158 c +195 158 l + +ce} _d +/four{1024 0 57 0 965 1364 sc +57 338 m +57 410 l +690 1354 l +695 1361 702 1364 711 1364 c +741 1364 l +756 1364 764 1356 764 1341 c +764 410 l +965 410 l +965 338 l +764 338 l +764 137 l +764 109 784 91 824 83 c +864 76 910 72 963 72 c +963 0 l +399 0 l +399 72 l +452 72 498 76 538 83 c +578 91 598 109 598 137 c +598 338 l +57 338 l + +125 410 m +610 410 l +610 1135 l +125 410 l + +ce} _d +/five{1024 0 102 -45 920 1364 sc +178 233 m +192 193 213 157 242 124 c +271 91 306 66 345 47 c +385 29 426 20 469 20 c +568 20 636 58 673 135 c +710 212 729 305 729 414 c +729 461 728 501 726 533 c +725 566 720 597 713 627 c +700 675 678 717 646 753 c +615 789 576 807 530 807 c +484 807 444 800 411 786 c +378 772 352 756 331 737 c +310 718 292 699 276 678 c +260 657 250 646 246 645 c +223 645 l +220 645 215 647 210 651 c +205 656 203 660 203 664 c +203 1348 l +203 1351 205 1355 209 1358 c +214 1362 218 1364 223 1364 c +229 1364 l +321 1320 419 1298 522 1298 c +623 1298 721 1320 815 1364 c +821 1364 l +826 1364 830 1362 834 1359 c +838 1356 840 1352 840 1348 c +840 1329 l +840 1322 839 1319 836 1319 c +789 1257 731 1209 660 1174 c +590 1139 517 1122 442 1122 c +387 1122 331 1130 274 1145 c +274 758 l +319 795 360 821 395 836 c +431 852 477 860 532 860 c +607 860 675 838 734 795 c +794 752 840 695 872 625 c +904 556 920 485 920 412 c +920 330 900 254 859 184 c +819 114 764 58 695 17 c +626 -24 550 -45 469 -45 c +402 -45 340 -28 283 7 c +227 42 183 88 150 147 c +118 206 102 268 102 334 c +102 365 112 390 132 409 c +152 428 177 438 207 438 c +237 438 262 428 282 408 c +303 389 313 364 313 334 c +313 305 303 280 282 259 c +262 239 237 229 207 229 c +202 229 197 229 191 230 c +185 231 181 232 178 233 c + +ce} _d +/six{1024 0 86 -45 936 1364 sc +512 -45 m +427 -45 357 -23 300 22 c +243 67 199 126 168 197 c +137 269 116 344 104 423 c +92 502 86 581 86 662 c +86 770 107 878 149 987 c +191 1096 253 1186 334 1257 c +416 1328 513 1364 625 1364 c +672 1364 715 1355 755 1337 c +796 1320 827 1294 850 1259 c +873 1225 885 1184 885 1135 c +885 1107 875 1083 856 1064 c +837 1045 814 1036 786 1036 c +759 1036 736 1046 717 1065 c +698 1084 688 1108 688 1135 c +688 1162 698 1185 717 1204 c +736 1223 759 1233 786 1233 c +797 1233 l +780 1258 755 1276 723 1287 c +692 1299 659 1305 625 1305 c +584 1305 545 1296 510 1278 c +475 1260 444 1236 416 1205 c +388 1174 365 1140 346 1103 c +327 1066 313 1024 302 977 c +292 930 286 885 283 844 c +280 803 279 751 279 688 c +303 744 337 790 381 825 c +425 861 475 879 530 879 c +591 879 646 867 696 842 c +746 817 789 783 825 739 c +861 696 888 646 907 590 c +926 534 936 477 936 420 c +936 340 918 264 882 191 c +847 119 797 62 732 19 c +667 -24 594 -45 512 -45 c + +512 20 m +565 20 607 32 639 56 c +671 80 694 112 709 151 c +724 191 734 231 737 271 c +741 312 743 361 743 420 c +743 497 739 563 732 618 c +725 673 705 721 672 762 c +639 804 589 825 522 825 c +467 825 421 806 385 769 c +350 732 324 684 307 627 c +291 570 283 516 283 463 c +283 445 284 431 285 422 c +285 420 285 418 284 417 c +284 416 284 414 283 412 c +283 353 289 294 301 234 c +313 174 336 123 370 82 c +404 41 451 20 512 20 c + +ce} _d +/seven{1024 0 115 -45 993 1384 sc +356 53 m +356 129 363 203 376 276 c +389 349 409 420 434 491 c +460 562 491 632 527 700 c +564 769 604 833 647 893 c +834 1153 l +600 1153 l +357 1153 232 1150 225 1143 c +207 1121 190 1058 174 954 c +115 954 l +182 1384 l +242 1384 l +242 1378 l +242 1353 284 1337 367 1330 c +450 1323 532 1319 612 1319 c +993 1319 l +993 1266 l +993 1265 993 1264 992 1263 c +992 1262 992 1261 991 1260 c +709 864 l +640 761 596 647 579 522 c +562 397 553 240 553 53 c +553 26 543 3 524 -16 c +505 -35 482 -45 455 -45 c +428 -45 404 -35 385 -16 c +366 3 356 26 356 53 c + +ce} _d +/eight{1024 0 86 -45 936 1364 sc +86 311 m +86 393 113 465 167 528 c +221 591 290 644 375 686 c +299 735 l +252 766 214 806 185 857 c +156 908 141 962 141 1018 c +141 1083 158 1142 192 1195 c +227 1248 272 1289 329 1319 c +386 1349 447 1364 512 1364 c +573 1364 631 1352 687 1327 c +744 1302 790 1267 826 1221 c +863 1175 881 1120 881 1057 c +881 1011 870 968 848 929 c +827 890 797 854 759 823 c +722 792 682 765 639 743 c +756 668 l +810 633 853 586 886 529 c +919 472 936 411 936 348 c +936 274 916 207 876 146 c +837 85 784 38 719 5 c +654 -28 585 -45 512 -45 c +441 -45 373 -31 307 -2 c +242 27 188 68 147 122 c +106 177 86 240 86 311 c + +197 311 m +197 257 212 208 241 163 c +271 118 310 83 359 58 c +408 33 459 20 512 20 c +591 20 663 43 728 89 c +793 136 825 197 825 274 c +825 300 820 326 809 351 c +799 377 785 400 766 421 c +748 442 728 460 705 473 c +430 651 l +387 628 348 600 312 565 c +277 530 249 491 228 448 c +207 405 197 359 197 311 c + +338 936 m +586 776 l +643 809 690 850 727 897 c +764 944 782 998 782 1057 c +782 1103 769 1145 743 1183 c +718 1222 684 1252 643 1273 c +602 1294 557 1305 510 1305 c +469 1305 427 1297 385 1281 c +343 1265 308 1241 281 1209 c +254 1178 240 1141 240 1098 c +240 1034 273 980 338 936 c + +ce} _d +/nine{1024 0 86 -45 936 1364 sc +231 86 m +268 42 333 20 426 20 c +478 20 526 38 571 73 c +616 108 651 152 676 203 c +705 261 723 323 731 388 c +739 454 743 536 743 633 c +720 578 686 532 642 497 c +599 462 549 444 492 444 c +413 444 342 465 279 508 c +217 551 169 608 136 678 c +103 749 86 824 86 903 c +86 985 105 1061 142 1132 c +179 1203 231 1260 297 1301 c +363 1343 438 1364 522 1364 c +605 1364 674 1341 729 1296 c +785 1251 828 1193 857 1122 c +886 1051 907 976 918 897 c +930 818 936 739 936 662 c +936 557 917 449 878 339 c +839 230 781 138 704 65 c +627 -8 535 -45 426 -45 c +345 -45 277 -26 221 12 c +165 50 137 107 137 184 c +137 212 146 235 165 254 c +184 273 208 283 236 283 c +263 283 286 273 305 254 c +324 235 334 212 334 184 c +334 157 324 134 305 115 c +286 96 263 86 236 86 c +231 86 l + +500 498 m +556 498 602 517 637 554 c +673 592 699 639 715 695 c +731 751 739 807 739 862 c +739 901 l +739 909 l +739 1012 724 1103 694 1184 c +664 1265 607 1305 522 1305 c +468 1305 424 1293 390 1269 c +357 1246 332 1214 316 1175 c +300 1136 290 1094 285 1049 c +281 1004 279 956 279 903 c +279 826 283 760 290 705 c +297 650 317 602 350 560 c +383 519 433 498 500 498 c + +ce} _d +/colon{567 0 172 0 397 883 sc +172 113 m +172 144 183 170 206 192 c +229 214 255 225 285 225 c +304 225 322 220 340 210 c +358 200 372 186 382 168 c +392 150 397 132 397 113 c +397 83 386 57 364 34 c +342 11 316 0 285 0 c +255 0 229 11 206 34 c +183 57 172 83 172 113 c + +172 770 m +172 789 177 808 187 825 c +197 842 211 856 228 867 c +246 878 265 883 285 883 c +304 883 323 878 340 867 c +358 856 372 842 382 825 c +392 808 397 789 397 770 c +397 739 386 713 364 690 c +343 668 316 657 285 657 c +254 657 228 668 205 690 c +183 713 172 739 172 770 c + +ce} _d +/semicolon{567 0 172 -397 403 883 sc +203 -369 m +203 -363 204 -359 207 -356 c +302 -253 350 -131 350 8 c +350 18 l +330 6 308 0 285 0 c +254 0 227 11 205 33 c +183 55 172 82 172 113 c +172 145 183 172 205 193 c +227 214 254 225 285 225 c +332 225 364 203 379 159 c +395 116 403 65 403 8 c +403 -40 397 -87 385 -134 c +374 -181 356 -226 332 -271 c +309 -316 281 -356 248 -391 c +244 -395 239 -397 233 -397 c +226 -397 220 -394 213 -388 c +206 -382 203 -376 203 -369 c + +172 770 m +172 789 177 808 187 825 c +197 842 211 856 228 867 c +246 878 265 883 285 883 c +304 883 323 878 340 867 c +358 856 372 842 382 825 c +392 808 397 789 397 770 c +397 739 386 713 364 690 c +343 668 316 657 285 657 c +254 657 228 668 205 690 c +183 713 172 739 172 770 c + +ce} _d +/exclamdown{567 0 172 -442 397 1024 sc +172 -340 m +172 -328 l +256 616 l +256 620 257 624 260 628 c +263 633 267 635 272 635 c +297 635 l +302 635 306 633 309 629 c +313 625 315 621 315 616 c +397 -328 l +397 -340 l +397 -369 386 -393 363 -412 c +341 -432 315 -442 285 -442 c +256 -442 229 -432 206 -412 c +183 -393 172 -369 172 -340 c + +172 911 m +172 941 183 967 206 990 c +229 1013 255 1024 285 1024 c +304 1024 322 1019 340 1009 c +358 999 372 985 382 967 c +392 949 397 930 397 911 c +397 882 386 856 364 833 c +342 810 316 799 285 799 c +255 799 229 810 206 833 c +183 856 172 882 172 911 c + +ce} _d +/equal{1591 0 115 272 1477 752 sc +154 272 m +143 272 133 276 126 285 c +119 294 115 303 115 313 c +115 324 119 334 126 342 c +133 350 143 354 154 354 c +1440 354 l +1450 354 1459 350 1466 342 c +1473 334 1477 324 1477 313 c +1477 303 1473 294 1466 285 c +1459 276 1450 272 1440 272 c +154 272 l + +154 670 m +143 670 133 674 126 682 c +119 690 115 700 115 711 c +115 721 119 730 126 739 c +133 748 143 752 154 752 c +1440 752 l +1450 752 1459 748 1466 739 c +1473 730 1477 721 1477 711 c +1477 700 1473 690 1466 682 c +1459 674 1450 670 1440 670 c +154 670 l + +ce} _d +/questiondown{967 0 115 -420 850 1024 sc +115 -141 m +115 -102 123 -63 138 -26 c +153 11 176 41 207 66 c +253 103 292 146 324 193 c +357 240 382 291 399 346 c +417 401 426 457 426 516 c +426 616 l +426 620 427 624 430 628 c +433 633 437 635 442 635 c +467 635 l +472 635 476 633 479 629 c +483 625 485 621 485 616 c +485 512 l +485 425 472 340 447 255 c +422 170 385 94 336 27 c +307 -12 293 -68 293 -143 c +293 -193 296 -233 302 -264 c +308 -295 323 -320 346 -339 c +370 -358 406 -367 455 -367 c +516 -367 575 -357 631 -337 c +688 -317 731 -285 762 -240 c +752 -240 l +725 -240 701 -230 682 -211 c +663 -192 653 -168 653 -141 c +653 -114 663 -91 682 -72 c +701 -53 725 -43 752 -43 c +779 -43 802 -53 821 -72 c +840 -91 850 -114 850 -141 c +850 -204 830 -256 789 -298 c +748 -341 697 -372 636 -391 c +575 -410 514 -420 455 -420 c +358 -420 277 -397 212 -351 c +147 -305 115 -235 115 -141 c + +342 911 m +342 941 353 967 376 990 c +399 1013 425 1024 455 1024 c +474 1024 492 1019 510 1009 c +528 999 542 985 552 967 c +562 949 567 930 567 911 c +567 882 556 856 534 833 c +512 810 486 799 455 799 c +425 799 399 810 376 833 c +353 856 342 882 342 911 c + +ce} _d +/question{967 0 115 0 850 1444 sc +342 113 m +342 144 353 170 376 192 c +399 214 425 225 455 225 c +474 225 492 220 510 210 c +528 200 542 186 552 168 c +562 150 567 132 567 113 c +567 83 556 57 534 34 c +512 11 486 0 455 0 c +425 0 399 11 376 34 c +353 57 342 83 342 113 c + +426 408 m +426 512 l +426 601 442 689 475 775 c +508 861 554 935 614 997 c +634 1018 649 1044 658 1073 c +667 1103 672 1134 672 1167 c +672 1223 666 1267 653 1299 c +640 1331 618 1354 587 1369 c +556 1384 512 1391 455 1391 c +402 1391 353 1380 307 1359 c +262 1338 226 1306 201 1264 c +213 1264 l +240 1264 263 1254 282 1235 c +301 1216 311 1193 311 1165 c +311 1138 301 1115 282 1096 c +263 1077 240 1067 213 1067 c +186 1067 163 1077 144 1096 c +125 1115 115 1138 115 1165 c +115 1221 131 1270 164 1312 c +197 1355 240 1387 293 1410 c +346 1433 400 1444 455 1444 c +520 1444 582 1435 642 1418 c +703 1401 752 1372 791 1330 c +830 1288 850 1233 850 1165 c +850 1125 841 1086 822 1049 c +803 1012 777 982 743 958 c +665 903 602 837 555 758 c +508 679 485 596 485 508 c +485 408 l +485 403 483 399 479 395 c +475 391 471 389 467 389 c +442 389 l +439 389 435 391 431 395 c +428 400 426 404 426 408 c + +ce} _d +/at{1591 0 115 -23 1477 1444 sc +797 -23 m +700 -23 609 -3 526 36 c +443 76 371 131 309 200 c +247 270 199 349 165 437 c +132 526 115 617 115 711 c +115 805 132 896 165 984 c +199 1073 247 1152 309 1221 c +371 1291 443 1346 526 1385 c +609 1424 700 1444 797 1444 c +894 1444 984 1424 1067 1385 c +1150 1346 1223 1291 1284 1221 c +1346 1152 1394 1073 1427 984 c +1460 896 1477 805 1477 711 c +1477 586 1464 479 1439 391 c +1414 304 1355 260 1264 260 c +1216 260 1172 272 1132 296 c +1092 320 1067 354 1057 397 c +1025 356 986 322 940 297 c +895 272 847 260 797 260 c +718 260 648 281 586 323 c +524 366 475 422 440 492 c +405 562 387 635 387 711 c +387 786 405 858 440 928 c +475 999 524 1055 586 1097 c +648 1140 718 1161 797 1161 c +855 1161 910 1145 961 1112 c +1012 1079 1054 1037 1085 985 c +1188 985 l +1192 985 1196 983 1199 979 c +1202 976 1204 972 1204 967 c +1204 430 l +1204 402 1209 375 1219 350 c +1229 325 1246 313 1270 313 c +1333 313 1373 353 1390 434 c +1408 515 1417 606 1417 709 c +1417 794 1402 879 1371 962 c +1340 1046 1298 1120 1243 1183 c +1189 1247 1123 1298 1045 1335 c +968 1372 885 1391 797 1391 c +709 1391 626 1372 548 1334 c +471 1297 404 1246 349 1183 c +294 1120 251 1048 220 965 c +189 882 174 798 174 711 c +174 624 189 540 219 459 c +250 378 293 305 349 240 c +405 175 472 124 550 87 c +628 50 711 31 799 31 c +864 31 928 36 993 46 c +1058 57 1122 72 1185 91 c +1248 110 1309 135 1368 164 c +1460 164 l +1464 164 1468 162 1471 158 c +1475 154 1477 150 1477 145 c +1477 136 1473 130 1464 127 c +1251 27 1028 -23 797 -23 c + +797 313 m +852 313 902 331 948 366 c +994 402 1030 447 1055 502 c +1055 920 l +1038 955 1017 986 991 1014 c +966 1043 936 1065 901 1082 c +867 1099 832 1108 797 1108 c +753 1108 714 1095 681 1068 c +648 1042 621 1009 600 969 c +579 929 564 886 553 839 c +542 793 537 750 537 711 c +537 655 546 596 565 534 c +584 472 613 420 652 377 c +691 334 739 313 797 313 c + +ce} _d +/A{1536 0 66 0 1468 1466 sc +66 0 m +66 72 l +188 72 263 112 291 193 c +721 1444 l +725 1459 736 1466 754 1466 c +780 1466 l +798 1466 809 1459 813 1444 c +1262 137 l +1275 108 1299 90 1334 83 c +1370 76 1415 72 1468 72 c +1468 0 l +897 0 l +897 72 l +1010 72 1067 90 1067 127 c +1067 137 l +956 457 l +459 457 l +367 193 l +366 188 365 181 365 172 c +365 138 381 113 413 96 c +446 80 481 72 518 72 c +518 0 l +66 0 l + +483 528 m +932 528 l +707 1182 l +483 528 l + +ce} _d +/B{1450 0 70 0 1333 1399 sc +70 0 m +70 72 l +211 72 281 94 281 137 c +281 1262 l +281 1305 211 1327 70 1327 c +70 1399 l +823 1399 l +892 1399 962 1385 1033 1358 c +1104 1331 1162 1291 1208 1238 c +1255 1185 1278 1123 1278 1051 c +1278 968 1244 898 1175 841 c +1107 785 1027 748 936 731 c +995 731 1056 715 1119 682 c +1182 649 1233 606 1273 551 c +1313 496 1333 438 1333 377 c +1333 302 1311 236 1266 179 c +1222 122 1165 77 1094 46 c +1023 15 952 0 881 0 c +70 0 l + +459 137 m +459 108 467 89 484 82 c +501 75 527 72 563 72 c +823 72 l +876 72 926 86 971 114 c +1017 142 1053 179 1080 226 c +1107 273 1120 324 1120 377 c +1120 429 1109 480 1087 529 c +1065 579 1033 620 992 652 c +951 684 905 700 852 700 c +459 700 l +459 137 l + +459 754 m +766 754 l +807 754 846 761 882 776 c +919 791 951 813 980 841 c +1009 870 1032 902 1047 937 c +1063 973 1071 1011 1071 1051 c +1071 1086 1065 1120 1053 1153 c +1042 1186 1025 1215 1002 1242 c +979 1269 953 1289 922 1304 c +891 1319 858 1327 823 1327 c +563 1327 l +538 1327 519 1326 505 1324 c +492 1322 481 1316 472 1306 c +463 1297 459 1282 459 1262 c +459 754 l + +ce} _d +/C{1479 0 115 -45 1362 1444 sc +467 219 m +514 160 572 113 642 78 c +712 44 785 27 860 27 c +923 27 982 39 1036 64 c +1090 89 1137 124 1177 169 c +1218 214 1249 264 1270 321 c +1292 378 1303 437 1303 500 c +1303 511 1309 516 1321 516 c +1346 516 l +1357 516 1362 509 1362 496 c +1362 425 1348 356 1321 289 c +1294 223 1255 165 1205 114 c +1155 64 1097 25 1032 -3 c +967 -31 900 -45 829 -45 c +731 -45 638 -25 550 14 c +463 54 386 108 321 177 c +256 246 206 326 169 417 c +133 508 115 602 115 700 c +115 798 133 892 169 983 c +206 1074 256 1154 321 1223 c +386 1292 463 1346 550 1385 c +638 1424 731 1444 829 1444 c +899 1444 966 1429 1030 1398 c +1094 1368 1150 1325 1198 1270 c +1317 1438 l +1325 1442 1330 1444 1331 1444 c +1346 1444 l +1350 1444 1354 1442 1357 1439 c +1360 1436 1362 1432 1362 1427 c +1362 874 l +1362 862 1357 856 1346 856 c +1309 856 l +1296 856 1290 862 1290 874 c +1290 913 1282 957 1266 1006 c +1251 1055 1231 1101 1206 1144 c +1182 1188 1154 1227 1122 1260 c +1045 1335 957 1372 858 1372 c +783 1372 710 1355 641 1321 c +572 1287 514 1240 467 1180 c +417 1116 382 1043 363 961 c +344 880 334 793 334 700 c +334 607 344 520 363 438 c +382 356 417 283 467 219 c + +ce} _d +/D{1563 0 68 0 1448 1399 sc +68 0 m +68 72 l +209 72 279 94 279 137 c +279 1262 l +279 1305 209 1327 68 1327 c +68 1399 l +823 1399 l +916 1399 1002 1379 1079 1338 c +1156 1298 1222 1244 1277 1177 c +1332 1110 1374 1033 1403 948 c +1433 863 1448 775 1448 686 c +1448 599 1433 515 1403 433 c +1373 352 1330 278 1273 212 c +1217 147 1150 95 1073 57 c +996 19 913 0 823 0 c +68 0 l + +463 137 m +463 108 471 89 488 82 c +505 75 531 72 567 72 c +770 72 l +840 72 906 87 969 117 c +1032 148 1085 190 1126 244 c +1169 301 1198 365 1213 436 c +1228 507 1235 591 1235 686 c +1235 785 1228 872 1213 947 c +1198 1022 1169 1088 1126 1147 c +1085 1205 1034 1249 971 1280 c +908 1311 841 1327 770 1327 c +567 1327 l +542 1327 523 1326 509 1324 c +496 1322 485 1316 476 1306 c +467 1297 463 1282 463 1262 c +463 137 l + +ce} _d +/E{1393 0 63 0 1335 1399 sc +63 0 m +63 72 l +204 72 274 94 274 137 c +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +1221 1399 l +1278 930 l +1219 930 l +1204 1048 1184 1134 1157 1187 c +1131 1240 1089 1277 1031 1297 c +973 1317 886 1327 770 1327 c +569 1327 l +544 1327 525 1326 511 1324 c +498 1322 487 1316 478 1306 c +469 1297 465 1282 465 1262 c +465 762 l +616 762 l +685 762 737 768 771 779 c +806 791 829 813 842 846 c +855 879 862 931 862 1001 c +922 1001 l +922 451 l +862 451 l +862 520 855 572 842 605 c +829 638 806 661 771 672 c +737 684 685 690 616 690 c +465 690 l +465 137 l +465 108 473 89 490 82 c +507 75 533 72 569 72 c +786 72 l +881 72 957 79 1015 94 c +1074 109 1119 134 1151 169 c +1184 204 1209 250 1226 305 c +1244 361 1261 438 1276 537 c +1335 537 l +1249 0 l +63 0 l + +ce} _d +/F{1335 0 63 0 1249 1399 sc +63 0 m +63 72 l +204 72 274 94 274 137 c +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +1192 1399 l +1249 930 l +1190 930 l +1181 1016 1168 1084 1152 1133 c +1137 1183 1114 1222 1084 1250 c +1054 1279 1014 1299 963 1310 c +913 1321 844 1327 756 1327 c +569 1327 l +544 1327 525 1326 511 1324 c +498 1322 487 1316 478 1306 c +469 1297 465 1282 465 1262 c +465 735 l +610 735 l +680 735 731 741 764 753 c +797 766 819 788 831 821 c +844 854 850 905 850 975 c +909 975 l +909 424 l +850 424 l +850 494 844 545 831 578 c +819 611 797 633 764 645 c +731 658 680 664 610 664 c +465 664 l +465 137 l +465 94 552 72 727 72 c +727 0 l +63 0 l + +ce} _d +/G{1606 0 115 -45 1505 1444 sc +471 219 m +520 159 580 112 651 78 c +722 44 797 27 874 27 c +950 27 1019 46 1081 85 c +1143 124 1174 179 1174 250 c +1174 422 l +1174 465 1089 487 918 487 c +918 559 l +1505 559 l +1505 487 l +1462 487 1427 483 1402 476 c +1377 469 1364 451 1364 422 c +1364 18 l +1364 13 1362 9 1358 5 c +1355 2 1351 0 1346 0 c +1333 0 1312 15 1282 45 c +1253 75 1229 102 1212 125 c +1179 68 1126 25 1055 -3 c +984 -31 909 -45 831 -45 c +698 -45 577 -11 468 57 c +359 126 273 217 210 331 c +147 446 115 569 115 700 c +115 797 133 891 170 982 c +207 1073 258 1154 323 1223 c +389 1292 466 1346 553 1385 c +640 1424 733 1444 831 1444 c +902 1444 968 1429 1031 1398 c +1094 1368 1151 1325 1200 1270 c +1319 1438 l +1327 1442 1332 1444 1333 1444 c +1348 1444 l +1352 1444 1356 1442 1359 1439 c +1362 1436 1364 1432 1364 1427 c +1364 874 l +1364 862 1359 856 1348 856 c +1311 856 l +1298 856 1292 862 1292 874 c +1292 913 1284 957 1268 1006 c +1253 1055 1233 1101 1208 1144 c +1184 1188 1156 1227 1124 1260 c +1047 1335 959 1372 860 1372 c +784 1372 711 1355 642 1321 c +573 1287 514 1240 467 1180 c +417 1116 382 1043 363 961 c +344 880 334 793 334 700 c +334 491 380 330 471 219 c + +ce} _d +/H{1536 0 63 0 1470 1399 sc +63 0 m +63 72 l +204 72 274 94 274 137 c +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +676 1399 l +676 1327 l +535 1327 465 1305 465 1262 c +465 764 l +1069 764 l +1069 1262 l +1069 1305 999 1327 858 1327 c +858 1399 l +1470 1399 l +1470 1327 l +1330 1327 1260 1305 1260 1262 c +1260 137 l +1260 94 1330 72 1470 72 c +1470 0 l +858 0 l +858 72 l +999 72 1069 94 1069 137 c +1069 692 l +465 692 l +465 137 l +465 94 535 72 676 72 c +676 0 l +63 0 l + +ce} _d +/I{739 0 53 0 686 1399 sc +53 0 m +53 72 l +200 72 274 94 274 137 c +274 1262 l +274 1305 200 1327 53 1327 c +53 1399 l +686 1399 l +686 1327 l +539 1327 465 1305 465 1262 c +465 137 l +465 94 539 72 686 72 c +686 0 l +53 0 l + +ce} _d +/J{1051 0 76 -45 952 1399 sc +186 115 m +211 80 243 54 282 35 c +321 17 363 8 408 8 c +452 8 489 23 519 53 c +550 84 572 121 587 166 c +602 211 610 255 610 297 c +610 1262 l +610 1305 519 1327 336 1327 c +336 1399 l +952 1399 l +952 1327 l +906 1327 868 1323 839 1316 c +810 1309 795 1291 795 1262 c +795 289 l +795 224 776 166 738 115 c +701 64 652 25 592 -3 c +533 -31 471 -45 408 -45 c +353 -45 300 -34 249 -11 c +198 11 157 43 124 85 c +92 128 76 177 76 233 c +76 267 87 295 110 318 c +133 341 161 352 195 352 c +217 352 237 347 255 336 c +273 326 287 312 297 293 c +308 274 313 254 313 233 c +313 200 302 172 279 149 c +256 126 228 115 195 115 c +186 115 l + +ce} _d +/K{1591 0 63 0 1507 1399 sc +63 0 m +63 72 l +204 72 274 94 274 137 c +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +676 1399 l +676 1327 l +535 1327 465 1305 465 1262 c +465 598 l +1098 1206 l +1115 1225 1124 1243 1124 1262 c +1124 1283 1115 1299 1096 1310 c +1078 1321 1057 1327 1034 1327 c +1034 1399 l +1479 1399 l +1479 1327 l +1367 1327 1269 1287 1184 1206 c +821 858 l +1270 193 l +1307 139 1339 105 1366 92 c +1394 79 1441 72 1507 72 c +1507 0 l +971 0 l +971 72 l +1004 72 1031 75 1053 82 c +1076 89 1087 104 1087 129 c +1087 146 1078 167 1061 193 c +694 737 l +465 516 l +465 137 l +465 94 535 72 676 72 c +676 0 l +63 0 l + +ce} _d +/L{1280 0 63 0 1192 1399 sc +63 0 m +63 72 l +204 72 274 94 274 137 c +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +727 1399 l +727 1327 l +552 1327 465 1305 465 1262 c +465 137 l +465 108 473 89 490 82 c +507 75 533 72 569 72 c +727 72 l +829 72 908 91 963 128 c +1018 165 1057 216 1080 280 c +1103 345 1121 430 1133 537 c +1192 537 l +1135 0 l +63 0 l + +ce} _d +/M{1876 0 72 0 1804 1399 sc +72 0 m +72 72 l +213 72 283 112 283 193 c +283 1262 l +283 1305 213 1327 72 1327 c +72 1399 l +459 1399 l +476 1399 487 1391 492 1376 c +938 217 l +1384 1376 l +1389 1391 1400 1399 1417 1399 c +1804 1399 l +1804 1327 l +1663 1327 1593 1305 1593 1262 c +1593 137 l +1593 94 1663 72 1804 72 c +1804 0 l +1208 0 l +1208 72 l +1349 72 1419 94 1419 137 c +1419 1329 l +915 23 l +909 8 898 0 881 0 c +864 0 852 8 846 23 c +348 1313 l +348 193 l +348 112 418 72 559 72 c +559 0 l +72 0 l + +ce} _d +/N{1536 0 63 0 1470 1399 sc +63 0 m +63 72 l +204 72 274 112 274 193 c +274 1315 l +229 1323 159 1327 63 1327 c +63 1399 l +455 1399 l +462 1399 466 1396 469 1391 c +1194 324 l +1194 1206 l +1194 1287 1124 1327 983 1327 c +983 1399 l +1470 1399 l +1470 1327 l +1330 1327 1260 1287 1260 1206 c +1260 18 l +1260 14 1257 10 1252 6 c +1247 2 1242 0 1239 0 c +1214 0 l +1207 0 1203 3 1200 8 c +340 1274 l +340 193 l +340 112 410 72 551 72 c +551 0 l +63 0 l + +ce} _d +/O{1591 0 115 -45 1477 1444 sc +797 -45 m +667 -45 550 -11 446 58 c +343 127 262 219 203 333 c +144 448 115 568 115 694 c +115 787 132 880 165 971 c +199 1062 246 1143 306 1214 c +367 1285 439 1341 524 1382 c +609 1423 700 1444 797 1444 c +894 1444 984 1423 1069 1381 c +1154 1340 1227 1283 1288 1212 c +1349 1141 1395 1061 1428 972 c +1461 883 1477 791 1477 694 c +1477 568 1448 448 1389 333 c +1330 219 1249 127 1145 58 c +1041 -11 925 -45 797 -45 c + +457 221 m +497 158 546 108 605 71 c +664 34 728 16 797 16 c +842 16 885 25 928 43 c +971 61 1010 86 1045 117 c +1080 148 1110 183 1135 221 c +1216 344 1257 512 1257 727 c +1257 924 1216 1079 1135 1194 c +1095 1251 1045 1297 985 1332 c +926 1367 863 1384 797 1384 c +730 1384 667 1367 607 1332 c +547 1297 497 1251 457 1194 c +425 1149 400 1102 382 1051 c +365 1001 352 949 345 895 c +338 841 334 785 334 727 c +334 665 337 604 344 545 c +351 486 364 429 383 372 c +402 315 427 265 457 221 c + +ce} _d +/P{1393 0 68 0 1278 1399 sc +68 0 m +68 72 l +209 72 279 94 279 137 c +279 1262 l +279 1305 209 1327 68 1327 c +68 1399 l +797 1399 l +872 1399 946 1384 1021 1353 c +1096 1322 1157 1278 1205 1219 c +1254 1160 1278 1092 1278 1014 c +1278 938 1254 871 1205 814 c +1156 757 1095 713 1021 683 c +947 654 872 639 797 639 c +469 639 l +469 137 l +469 94 539 72 680 72 c +680 0 l +68 0 l + +463 700 m +741 700 l +816 700 877 711 924 732 c +971 754 1005 788 1026 833 c +1048 879 1059 939 1059 1014 c +1059 1125 1034 1205 985 1254 c +936 1303 855 1327 741 1327 c +567 1327 l +542 1327 523 1326 509 1324 c +496 1322 485 1316 476 1306 c +467 1297 463 1282 463 1262 c +463 700 l + +ce} _d +/Q{1591 0 115 -397 1489 1444 sc +797 -45 m +667 -45 550 -11 446 58 c +343 127 262 219 203 333 c +144 448 115 568 115 694 c +115 787 132 880 165 971 c +199 1062 246 1143 306 1214 c +367 1285 439 1341 524 1382 c +609 1423 700 1444 797 1444 c +894 1444 984 1423 1069 1381 c +1154 1340 1227 1283 1288 1212 c +1349 1141 1395 1061 1428 972 c +1461 883 1477 791 1477 694 c +1477 600 1460 508 1427 419 c +1394 330 1346 250 1283 179 c +1220 108 1147 53 1063 14 c +1089 -47 1116 -94 1143 -127 c +1170 -161 1208 -178 1257 -178 c +1308 -178 1352 -160 1389 -125 c +1426 -90 1444 -47 1444 4 c +1444 9 1446 13 1451 17 c +1456 21 1461 23 1466 23 c +1481 23 1489 14 1489 -4 c +1489 -102 1470 -192 1431 -274 c +1393 -356 1330 -397 1243 -397 c +1195 -397 1155 -385 1124 -361 c +1093 -338 1069 -308 1052 -271 c +1036 -235 1023 -193 1014 -145 c +1005 -97 996 -53 989 -14 c +929 -35 865 -45 797 -45 c + +797 14 m +858 14 918 30 975 61 c +962 120 943 166 916 201 c +890 236 850 254 797 254 c +764 254 737 242 714 217 c +691 193 680 165 680 133 c +680 98 691 70 712 47 c +734 25 762 14 797 14 c + +627 133 m +627 163 634 191 649 218 c +664 245 684 266 710 282 c +737 299 766 307 797 307 c +856 307 903 288 938 249 c +973 211 1003 159 1030 94 c +1075 128 1113 168 1144 214 c +1175 261 1198 309 1215 360 c +1232 411 1245 465 1252 522 c +1260 579 1264 637 1264 694 c +1264 789 1254 878 1235 961 c +1216 1044 1184 1119 1139 1186 c +1100 1245 1050 1293 989 1329 c +928 1366 864 1384 797 1384 c +728 1384 664 1366 603 1330 c +543 1295 493 1247 453 1186 c +406 1118 374 1042 355 959 c +337 876 328 787 328 694 c +328 549 353 416 402 297 c +451 178 534 94 649 45 c +634 73 627 102 627 133 c + +ce} _d +/R{1507 0 68 -45 1499 1399 sc +68 0 m +68 72 l +209 72 279 94 279 137 c +279 1262 l +279 1305 209 1327 68 1327 c +68 1399 l +711 1399 l +788 1399 868 1385 952 1357 c +1037 1330 1107 1288 1164 1231 c +1221 1174 1249 1107 1249 1028 c +1249 971 1232 919 1197 874 c +1163 829 1119 792 1065 761 c +1012 731 957 709 901 696 c +962 675 1016 641 1062 594 c +1108 547 1136 494 1145 434 c +1174 252 l +1187 170 1201 109 1216 68 c +1231 28 1263 8 1313 8 c +1356 8 1387 28 1408 67 c +1429 107 1440 150 1440 197 c +1440 202 1442 206 1446 209 c +1451 213 1455 215 1460 215 c +1479 215 l +1492 215 1499 206 1499 188 c +1499 151 1492 114 1477 78 c +1462 43 1441 13 1413 -10 c +1386 -33 1353 -45 1315 -45 c +1216 -45 1131 -21 1060 28 c +989 77 954 149 954 244 c +954 426 l +954 494 930 552 883 601 c +836 650 778 674 709 674 c +463 674 l +463 137 l +463 94 533 72 674 72 c +674 0 l +68 0 l + +463 727 m +682 727 l +795 727 882 750 941 795 c +1000 841 1030 919 1030 1028 c +1030 1137 1001 1214 942 1259 c +883 1304 797 1327 682 1327 c +567 1327 l +542 1327 523 1326 509 1324 c +496 1322 485 1316 476 1306 c +467 1297 463 1282 463 1262 c +463 727 l + +ce} _d +/S{1137 0 115 -45 1022 1444 sc +115 -29 m +115 449 l +115 460 121 465 133 465 c +158 465 l +162 465 166 463 169 460 c +172 457 174 453 174 449 c +174 315 215 211 298 137 c +381 64 490 27 625 27 c +672 27 716 41 756 68 c +796 95 827 131 849 175 c +872 220 883 266 883 313 c +883 354 875 394 858 433 c +841 472 817 506 786 535 c +755 564 719 583 680 592 c +414 657 l +326 680 254 728 198 800 c +143 873 115 954 115 1044 c +115 1115 133 1181 169 1243 c +205 1305 253 1354 314 1390 c +375 1426 441 1444 512 1444 c +649 1444 757 1398 838 1305 c +922 1438 l +926 1442 931 1444 936 1444 c +950 1444 l +954 1444 958 1442 961 1439 c +965 1436 967 1432 967 1427 c +967 952 l +967 940 961 934 950 934 c +926 934 l +913 934 907 940 907 952 c +907 987 901 1025 889 1066 c +878 1107 862 1147 841 1185 c +820 1223 798 1254 774 1278 c +708 1345 621 1378 512 1378 c +465 1378 422 1366 383 1342 c +344 1319 312 1287 289 1246 c +266 1205 254 1163 254 1118 c +254 1059 272 1006 308 958 c +345 911 392 879 451 864 c +717 799 l +761 788 802 769 841 741 c +880 714 913 682 939 645 c +965 608 985 567 1000 522 c +1015 477 1022 431 1022 383 c +1022 309 1005 239 971 173 c +937 108 889 55 827 15 c +766 -25 698 -45 625 -45 c +580 -45 533 -40 486 -30 c +439 -20 395 -5 355 16 c +315 37 279 63 246 96 c +160 -39 l +156 -43 151 -45 145 -45 c +133 -45 l +121 -45 115 -40 115 -29 c + +ce} _d +/T{1479 0 74 0 1403 1399 sc +346 0 m +346 72 l +415 72 481 76 546 83 c +611 91 643 109 643 137 c +643 1262 l +643 1291 635 1309 618 1316 c +602 1323 576 1327 539 1327 c +453 1327 l +340 1327 260 1302 213 1253 c +188 1228 171 1189 160 1134 c +149 1080 140 1012 133 930 c +74 930 l +113 1399 l +1364 1399 l +1403 930 l +1343 930 l +1335 1018 1326 1088 1316 1139 c +1307 1191 1289 1229 1264 1253 c +1216 1302 1136 1327 1024 1327 c +938 1327 l +913 1327 894 1326 880 1324 c +867 1322 856 1316 847 1306 c +838 1297 834 1282 834 1262 c +834 137 l +834 109 866 91 931 83 c +996 76 1062 72 1130 72 c +1130 0 l +346 0 l + +ce} _d +/U{1536 0 63 -45 1470 1399 sc +274 463 m +274 1262 l +274 1305 204 1327 63 1327 c +63 1399 l +676 1399 l +676 1327 l +535 1327 465 1305 465 1262 c +465 471 l +465 395 475 323 496 255 c +517 188 553 133 602 90 c +652 48 717 27 797 27 c +875 27 944 47 1003 88 c +1062 129 1108 184 1140 252 c +1172 320 1188 393 1188 471 c +1188 1206 l +1188 1287 1118 1327 977 1327 c +977 1399 l +1470 1399 l +1470 1327 l +1330 1327 1260 1287 1260 1206 c +1260 463 l +1260 400 1249 338 1226 277 c +1204 216 1172 161 1129 112 c +1087 63 1037 25 980 -3 c +923 -31 862 -45 797 -45 c +706 -45 620 -22 539 23 c +458 68 394 130 346 208 c +298 286 274 371 274 463 c + +ce} _d +/V{1536 0 39 -45 1495 1399 sc +721 -23 m +238 1262 l +226 1291 203 1309 169 1316 c +135 1323 92 1327 39 1327 c +39 1399 l +602 1399 l +602 1327 l +489 1327 432 1309 432 1274 c +433 1272 433 1270 433 1268 c +434 1267 434 1265 434 1262 c +827 217 l +1198 1206 l +1201 1211 1202 1220 1202 1231 c +1202 1264 1187 1289 1156 1304 c +1125 1319 1091 1327 1053 1327 c +1053 1399 l +1495 1399 l +1495 1327 l +1444 1327 1398 1317 1359 1298 c +1320 1279 1293 1249 1276 1206 c +813 -23 l +809 -38 798 -45 780 -45 c +754 -45 l +736 -45 725 -38 721 -23 c + +ce} _d +/W{2103 0 37 -45 2066 1399 sc +637 -23 m +219 1262 l +208 1291 188 1309 157 1316 c +126 1323 86 1327 37 1327 c +37 1399 l +586 1399 l +586 1327 l +471 1327 414 1309 414 1272 c +414 1262 l +741 254 l +1020 1116 l +973 1262 l +962 1291 942 1309 911 1316 c +880 1323 840 1327 791 1327 c +791 1399 l +1339 1399 l +1339 1327 l +1223 1327 1165 1309 1165 1272 c +1165 1270 1166 1267 1167 1263 c +1168 1259 1169 1256 1169 1255 c +1495 254 l +1802 1206 l +1803 1210 1804 1216 1804 1225 c +1804 1261 1785 1287 1747 1303 c +1709 1319 1669 1327 1628 1327 c +1628 1399 l +2066 1399 l +2066 1327 l +1958 1327 1891 1287 1866 1206 c +1466 -23 l +1461 -38 1451 -45 1436 -45 c +1421 -45 l +1406 -45 1396 -38 1391 -23 c +1053 1020 l +713 -23 l +708 -38 698 -45 682 -45 c +668 -45 l +652 -45 642 -38 637 -23 c + +ce} _d +/X{1536 0 47 0 1487 1399 sc +47 0 m +47 72 l +186 72 283 111 338 188 c +678 694 l +301 1268 l +280 1293 251 1309 214 1316 c +178 1323 132 1327 76 1327 c +76 1399 l +649 1399 l +649 1327 l +624 1327 596 1322 564 1312 c +532 1303 516 1289 516 1272 c +516 1269 517 1267 518 1264 c +786 856 l +1022 1208 l +1027 1220 1030 1230 1030 1237 c +1030 1265 1016 1287 988 1303 c +961 1319 932 1327 901 1327 c +901 1399 l +1401 1399 l +1401 1327 l +1262 1327 1165 1288 1110 1210 c +829 791 l +1262 131 l +1285 105 1315 89 1350 82 c +1386 75 1432 72 1487 72 c +1487 0 l +913 0 l +913 72 l +935 72 963 77 996 86 c +1030 96 1047 110 1047 127 c +1047 131 1046 134 1044 135 c +721 629 l +426 190 l +422 182 420 173 420 162 c +420 134 434 112 461 96 c +488 80 517 72 547 72 c +547 0 l +47 0 l + +ce} _d +/Y{1536 0 23 0 1511 1399 sc +463 0 m +463 72 l +604 72 674 94 674 137 c +674 559 l +244 1266 l +224 1293 196 1310 160 1317 c +124 1324 78 1327 23 1327 c +23 1399 l +600 1399 l +600 1327 l +505 1327 457 1311 457 1280 c +457 1277 458 1272 461 1264 c +831 653 l +1169 1208 l +1178 1221 1182 1234 1182 1249 c +1182 1276 1170 1296 1146 1308 c +1123 1321 1096 1327 1067 1327 c +1067 1399 l +1511 1399 l +1511 1327 l +1458 1327 1408 1317 1362 1298 c +1317 1279 1281 1250 1255 1210 c +858 559 l +858 137 l +858 94 928 72 1069 72 c +1069 0 l +463 0 l + +ce} _d +/Z{1251 0 115 0 1147 1399 sc +137 0 m +122 0 115 8 115 23 c +115 51 l +115 56 116 60 119 63 c +915 1327 l +627 1327 l +531 1327 452 1314 389 1289 c +327 1264 280 1222 248 1164 c +217 1106 201 1028 201 930 c +141 930 l +164 1399 l +1112 1399 l +1127 1399 1135 1391 1135 1376 c +1135 1352 l +1135 1346 1134 1342 1133 1339 c +336 78 l +637 78 l +710 78 775 85 833 98 c +891 111 940 137 979 176 c +1006 203 1028 238 1043 279 c +1058 320 1068 360 1073 399 c +1078 438 1082 490 1087 555 c +1147 555 l +1112 0 l +137 0 l + +ce} _d +/bracketleft{567 0 242 -512 522 1536 sc +242 -512 m +242 1536 l +522 1536 l +522 1454 l +324 1454 l +324 -430 l +522 -430 l +522 -512 l +242 -512 l + +ce} _d +/quotedblleft{1024 0 303 799 954 1421 sc +444 799 m +395 799 360 821 337 866 c +314 911 303 961 303 1016 c +303 1091 319 1164 350 1235 c +382 1306 426 1366 483 1417 c +488 1420 493 1421 496 1421 c +503 1421 510 1418 516 1411 c +523 1405 526 1399 526 1393 c +526 1387 523 1381 518 1376 c +485 1347 456 1313 431 1273 c +406 1233 387 1191 374 1147 c +362 1104 356 1060 356 1016 c +356 1002 357 992 358 985 c +378 1011 407 1024 444 1024 c +465 1024 484 1019 501 1009 c +518 999 532 986 542 969 c +552 952 557 933 557 911 c +557 880 546 853 524 831 c +503 810 476 799 444 799 c + +842 799 m +793 799 757 821 734 866 c +711 911 700 961 700 1016 c +700 1068 707 1118 722 1166 c +737 1215 758 1261 785 1304 c +813 1348 845 1386 881 1417 c +886 1420 890 1421 893 1421 c +900 1421 906 1418 913 1411 c +920 1405 924 1399 924 1393 c +924 1386 921 1381 915 1376 c +882 1347 854 1313 829 1274 c +804 1235 786 1194 773 1149 c +760 1104 754 1060 754 1016 c +754 1002 755 992 756 985 c +775 1011 804 1024 842 1024 c +875 1024 901 1013 922 991 c +943 970 954 943 954 911 c +954 880 943 854 922 832 c +901 810 874 799 842 799 c + +ce} _d +/bracketright{567 0 45 -512 326 1536 sc +45 -512 m +45 -430 l +244 -430 l +244 1454 l +45 1454 l +45 1536 l +326 1536 l +326 -512 l +45 -512 l + +ce} _d +/circumflex{1024 0 236 1102 786 1421 sc +276 1102 m +236 1145 l +512 1421 l +786 1145 l +745 1102 l +512 1307 l +276 1102 l + +ce} _d +/dotaccent{567 0 172 1145 397 1370 sc +172 1257 m +172 1287 183 1313 206 1336 c +229 1359 255 1370 285 1370 c +304 1370 322 1365 340 1355 c +358 1345 372 1331 382 1313 c +392 1295 397 1276 397 1257 c +397 1228 386 1202 364 1179 c +342 1156 316 1145 285 1145 c +255 1145 229 1156 206 1179 c +183 1202 172 1228 172 1257 c + +ce} _d +/quoteleft{567 0 143 799 397 1421 sc +285 799 m +236 799 200 821 177 866 c +154 911 143 961 143 1016 c +143 1068 150 1118 165 1166 c +180 1215 201 1261 228 1304 c +256 1348 288 1386 324 1417 c +329 1420 333 1421 336 1421 c +343 1421 349 1418 356 1411 c +363 1405 367 1399 367 1393 c +367 1388 364 1382 358 1376 c +325 1347 297 1313 272 1274 c +247 1235 229 1194 216 1149 c +203 1104 197 1060 197 1016 c +197 1002 198 992 199 985 c +218 1011 247 1024 285 1024 c +318 1024 344 1013 365 991 c +386 970 397 943 397 911 c +397 880 386 854 365 832 c +344 810 317 799 285 799 c + +ce} _d +/a{1024 0 82 -23 1010 918 sc +82 201 m +82 282 114 348 178 399 c +242 450 319 486 408 507 c +498 528 583 539 664 539 c +664 623 l +664 662 655 700 638 737 c +621 774 596 805 563 828 c +530 852 494 864 455 864 c +364 864 295 844 248 803 c +274 803 295 793 312 773 c +329 754 338 731 338 705 c +338 678 328 654 309 635 c +290 616 267 606 240 606 c +213 606 189 616 170 635 c +151 654 141 678 141 705 c +141 777 174 830 239 865 c +304 900 376 918 455 918 c +510 918 566 906 622 882 c +678 859 724 825 759 781 c +795 737 813 686 813 627 c +813 166 l +813 139 819 115 830 92 c +841 70 859 59 883 59 c +906 59 922 70 933 93 c +944 116 950 140 950 166 c +950 297 l +1010 297 l +1010 166 l +1010 135 1002 106 986 78 c +970 51 948 29 921 12 c +894 -4 865 -12 834 -12 c +794 -12 759 3 730 34 c +701 65 685 102 682 145 c +657 94 619 53 570 22 c +521 -8 468 -23 412 -23 c +360 -23 309 -15 258 0 c +208 15 166 39 132 72 c +99 105 82 148 82 201 c + +248 201 m +248 153 266 113 301 80 c +336 47 378 31 426 31 c +470 31 510 42 546 64 c +582 86 611 116 632 154 c +653 192 664 232 664 274 c +664 487 l +602 487 538 477 473 456 c +408 436 355 404 312 361 c +269 318 248 264 248 201 c + +ce} _d +/b{1137 0 53 -23 1069 1421 sc +213 0 m +213 1212 l +213 1249 207 1275 196 1291 c +185 1308 170 1318 149 1321 c +128 1325 96 1327 53 1327 c +53 1399 l +356 1421 l +356 780 l +379 805 405 827 435 846 c +466 865 498 880 533 890 c +568 900 603 905 639 905 c +700 905 757 893 809 868 c +862 843 907 809 946 766 c +985 723 1015 673 1036 616 c +1058 560 1069 502 1069 442 c +1069 359 1049 282 1008 211 c +968 140 913 83 843 40 c +774 -2 697 -23 614 -23 c +562 -23 512 -10 463 17 c +414 44 374 79 342 123 c +272 0 l +213 0 l + +362 201 m +385 151 417 110 460 78 c +503 47 550 31 602 31 c +673 31 730 51 773 92 c +817 133 848 184 865 246 c +882 308 891 373 891 442 c +891 557 876 645 846 705 c +833 732 814 756 791 779 c +768 802 743 819 714 832 c +686 845 656 852 625 852 c +570 852 520 837 473 808 c +426 779 389 741 362 692 c +362 201 l + +ce} _d +/c{909 0 68 -23 850 918 sc +510 -23 m +427 -23 352 -2 285 41 c +218 84 165 142 126 213 c +87 285 68 361 68 442 c +68 523 87 600 125 674 c +164 748 217 807 284 851 c +352 896 427 918 510 918 c +590 918 663 902 728 871 c +794 840 827 788 827 717 c +827 690 817 667 798 647 c +779 628 756 618 729 618 c +702 618 678 628 659 647 c +640 667 631 690 631 717 c +631 741 639 762 654 779 c +669 797 688 808 711 813 c +664 843 597 858 512 858 c +447 858 394 836 354 793 c +314 750 286 696 270 632 c +254 568 246 505 246 442 c +246 376 256 312 275 250 c +295 189 327 138 370 97 c +414 57 469 37 535 37 c +600 37 655 57 700 96 c +745 136 776 188 793 252 c +793 260 798 264 809 264 c +834 264 l +838 264 842 262 845 258 c +848 255 850 251 850 246 c +850 240 l +829 159 788 95 727 48 c +666 1 593 -23 510 -23 c + +ce} _d +/d{1137 0 68 -23 1083 1421 sc +500 -23 m +419 -23 346 -1 279 42 c +212 86 160 144 123 215 c +86 286 68 362 68 442 c +68 525 88 601 128 672 c +169 743 224 800 293 842 c +362 884 439 905 522 905 c +572 905 619 894 664 873 c +709 852 747 823 780 786 c +780 1212 l +780 1249 774 1275 763 1291 c +752 1308 737 1318 716 1321 c +696 1325 664 1327 621 1327 c +621 1399 l +924 1421 l +924 186 l +924 150 929 124 940 107 c +951 91 967 81 987 77 c +1008 74 1040 72 1083 72 c +1083 0 l +774 -23 l +774 106 l +739 65 697 34 648 11 c +599 -12 550 -23 500 -23 c + +291 178 m +314 133 345 98 384 71 c +423 44 466 31 512 31 c +569 31 621 47 668 80 c +715 113 751 155 774 207 c +774 698 l +758 728 738 755 713 778 c +689 802 662 820 631 833 c +601 846 569 852 535 852 c +464 852 406 832 363 791 c +320 751 289 700 272 637 c +255 574 246 509 246 440 c +246 385 249 338 254 297 c +260 256 272 217 291 178 c + +ce} _d +/e{909 0 57 -23 850 918 sc +510 -23 m +427 -23 350 -1 280 42 c +211 86 156 144 116 217 c +77 290 57 368 57 449 c +57 529 75 605 111 677 c +148 749 198 807 263 851 c +328 896 401 918 481 918 c +544 918 598 907 644 886 c +691 865 729 836 759 799 c +789 762 812 718 827 667 c +842 616 850 561 850 500 c +850 482 843 473 829 473 c +236 473 l +236 451 l +236 338 259 240 304 159 c +350 78 425 37 528 37 c +570 37 609 46 644 65 c +680 84 711 110 737 143 c +764 176 782 212 791 250 c +792 255 795 259 798 262 c +802 266 806 268 811 268 c +829 268 l +843 268 850 259 850 242 c +831 165 789 101 725 51 c +661 2 589 -23 510 -23 c + +238 524 m +705 524 l +705 575 698 627 683 680 c +669 733 645 776 612 811 c +579 846 535 864 481 864 c +404 864 344 828 301 755 c +259 683 238 606 238 524 c + +ce} _d +/f{625 0 66 0 739 1444 sc +66 0 m +66 72 l +112 72 150 76 180 83 c +210 90 225 108 225 137 c +225 811 l +68 811 l +68 883 l +225 883 l +225 1128 l +225 1172 234 1213 252 1251 c +271 1290 295 1323 326 1352 c +357 1381 393 1403 433 1419 c +474 1436 516 1444 559 1444 c +605 1444 646 1430 683 1403 c +720 1376 739 1339 739 1294 c +739 1268 730 1246 712 1228 c +695 1211 673 1202 647 1202 c +621 1202 599 1211 580 1228 c +562 1246 553 1268 553 1294 c +553 1337 571 1365 608 1380 c +586 1387 566 1391 549 1391 c +509 1391 475 1377 446 1348 c +418 1320 397 1286 383 1245 c +369 1204 362 1164 362 1124 c +362 883 l +598 883 l +598 811 l +369 811 l +369 137 l +369 109 389 91 429 83 c +469 76 515 72 567 72 c +567 0 l +66 0 l + +ce} _d +/g{1024 0 57 -422 993 928 sc +57 -160 m +57 -111 75 -69 110 -32 c +145 4 187 30 236 45 c +209 66 188 92 173 123 c +159 154 152 188 152 223 c +152 287 172 344 213 393 c +150 454 119 525 119 604 c +119 647 128 687 146 724 c +165 761 190 794 223 821 c +256 848 292 869 332 883 c +372 898 413 905 455 905 c +536 905 609 881 674 834 c +702 864 735 887 773 903 c +812 920 852 928 893 928 c +922 928 946 917 965 896 c +984 875 993 850 993 821 c +993 804 987 790 974 777 c +961 764 947 758 930 758 c +913 758 898 764 885 777 c +872 790 866 804 866 821 c +866 846 874 864 891 874 c +820 874 760 850 709 801 c +734 776 753 746 768 710 c +783 675 791 639 791 604 c +791 546 775 494 743 447 c +711 401 669 365 616 339 c +564 314 510 301 455 301 c +380 301 312 321 250 362 c +231 335 221 305 221 272 c +221 236 233 204 256 177 c +280 150 310 137 346 137 c +514 137 l +595 137 669 130 734 115 c +799 100 854 71 898 27 c +943 -17 965 -79 965 -160 c +965 -220 940 -270 889 -309 c +838 -349 778 -378 707 -395 c +637 -413 572 -422 512 -422 c +451 -422 386 -413 315 -395 c +244 -378 184 -349 133 -309 c +82 -270 57 -220 57 -160 c + +172 -160 m +172 -206 191 -244 228 -275 c +265 -306 310 -329 363 -344 c +416 -359 465 -367 512 -367 c +558 -367 607 -359 660 -344 c +713 -329 757 -306 794 -275 c +831 -244 850 -206 850 -160 c +850 -89 817 -42 752 -21 c +687 -0 607 10 514 10 c +346 10 l +315 10 286 3 259 -12 c +233 -27 212 -48 196 -75 c +180 -102 172 -131 172 -160 c + +455 356 m +571 356 629 439 629 604 c +629 675 617 734 592 780 c +567 827 522 850 455 850 c +388 850 343 827 318 780 c +293 734 281 675 281 604 c +281 559 286 518 295 481 c +304 444 322 414 347 391 c +372 368 408 356 455 356 c + +ce} _d +/h{1137 0 61 0 1100 1421 sc +61 0 m +61 72 l +108 72 146 76 176 83 c +206 90 221 108 221 137 c +221 1212 l +221 1249 215 1275 204 1291 c +193 1308 178 1318 157 1321 c +136 1325 104 1327 61 1327 c +61 1399 l +365 1421 l +365 717 l +394 774 434 819 485 853 c +536 888 593 905 655 905 c +750 905 821 882 868 837 c +916 792 940 722 940 629 c +940 137 l +940 108 955 90 985 83 c +1015 76 1053 72 1100 72 c +1100 0 l +631 0 l +631 72 l +678 72 716 76 746 83 c +776 90 791 108 791 137 c +791 623 l +791 690 781 744 762 787 c +743 830 703 852 643 852 c +564 852 498 820 447 757 c +396 694 371 622 371 541 c +371 137 l +371 108 386 90 416 83 c +446 76 484 72 530 72 c +530 0 l +61 0 l + +ce} _d +/i{567 0 63 0 510 1370 sc +63 0 m +63 72 l +110 72 148 76 178 83 c +208 90 223 108 223 137 c +223 696 l +223 749 213 781 192 793 c +172 805 132 811 72 811 c +72 883 l +367 905 l +367 137 l +367 108 380 90 406 83 c +432 76 467 72 510 72 c +510 0 l +63 0 l + +150 1257 m +150 1287 161 1313 184 1336 c +207 1359 233 1370 262 1370 c +281 1370 300 1365 318 1355 c +336 1345 350 1331 360 1313 c +370 1295 375 1276 375 1257 c +375 1228 364 1202 341 1179 c +318 1156 292 1145 262 1145 c +233 1145 207 1156 184 1179 c +161 1202 150 1228 150 1257 c + +ce} _d +/j{625 0 -90 -420 434 1370 sc +41 -344 m +72 -359 108 -367 147 -367 c +201 -367 238 -339 259 -284 c +280 -229 291 -170 291 -106 c +291 696 l +291 733 284 759 271 775 c +258 792 239 802 216 805 c +193 809 160 811 115 811 c +115 883 l +434 905 l +434 -115 l +434 -168 421 -217 395 -264 c +369 -311 334 -349 289 -377 c +244 -406 196 -420 143 -420 c +84 -420 30 -405 -18 -376 c +-66 -347 -90 -305 -90 -252 c +-90 -225 -80 -202 -61 -183 c +-42 -164 -19 -154 8 -154 c +35 -154 58 -164 77 -183 c +96 -202 106 -225 106 -252 c +106 -273 100 -292 88 -309 c +77 -326 61 -338 41 -344 c + +209 1257 m +209 1287 220 1313 243 1336 c +266 1359 292 1370 322 1370 c +341 1370 359 1365 377 1355 c +395 1345 409 1331 419 1313 c +429 1295 434 1276 434 1257 c +434 1228 423 1202 401 1179 c +379 1156 353 1145 322 1145 c +292 1145 266 1156 243 1179 c +220 1202 209 1228 209 1257 c + +ce} _d +/k{1079 0 53 0 1047 1421 sc +53 0 m +53 72 l +100 72 138 76 168 83 c +198 90 213 108 213 137 c +213 1212 l +213 1249 207 1275 196 1291 c +185 1308 170 1318 149 1321 c +128 1325 96 1327 53 1327 c +53 1399 l +356 1421 l +356 449 l +631 690 l +658 716 672 740 672 762 c +672 778 666 790 655 798 c +644 807 630 811 614 811 c +614 883 l +999 883 l +999 811 l +906 811 814 771 721 690 c +575 563 l +836 193 l +872 142 902 109 926 94 c +951 79 991 72 1047 72 c +1047 0 l +639 0 l +639 72 l +686 72 709 86 709 115 c +709 136 697 162 672 193 c +475 473 l +350 365 l +350 137 l +350 108 365 90 395 83 c +426 76 464 72 510 72 c +510 0 l +53 0 l + +ce} _d +/l{567 0 63 0 526 1421 sc +63 0 m +63 72 l +110 72 148 76 178 83 c +208 90 223 108 223 137 c +223 1212 l +223 1249 217 1275 206 1291 c +195 1308 180 1318 159 1321 c +138 1325 106 1327 63 1327 c +63 1399 l +367 1421 l +367 137 l +367 108 382 90 412 83 c +442 76 480 72 526 72 c +526 0 l +63 0 l + +ce} _d +/m{1706 0 61 0 1669 905 sc +61 0 m +61 72 l +108 72 146 76 176 83 c +206 90 221 108 221 137 c +221 696 l +221 733 215 759 204 775 c +193 792 178 802 157 805 c +136 809 104 811 61 811 c +61 883 l +358 905 l +358 705 l +385 764 426 812 479 849 c +533 886 592 905 655 905 c +812 905 905 841 932 713 c +959 770 999 817 1052 852 c +1105 887 1162 905 1225 905 c +1287 905 1339 895 1381 875 c +1424 855 1456 824 1477 783 c +1498 742 1509 691 1509 629 c +1509 137 l +1509 108 1524 90 1554 83 c +1585 76 1623 72 1669 72 c +1669 0 l +1200 0 l +1200 72 l +1247 72 1285 76 1315 83 c +1345 90 1360 108 1360 137 c +1360 623 l +1360 692 1350 747 1331 789 c +1312 831 1272 852 1212 852 c +1133 852 1068 820 1017 757 c +966 694 940 622 940 541 c +940 137 l +940 108 955 90 985 83 c +1015 76 1053 72 1100 72 c +1100 0 l +631 0 l +631 72 l +678 72 716 76 746 83 c +776 90 791 108 791 137 c +791 623 l +791 690 781 744 762 787 c +743 830 703 852 643 852 c +564 852 498 820 447 757 c +396 694 371 622 371 541 c +371 137 l +371 108 386 90 416 83 c +446 76 484 72 530 72 c +530 0 l +61 0 l + +ce} _d +/n{1137 0 61 0 1100 905 sc +61 0 m +61 72 l +108 72 146 76 176 83 c +206 90 221 108 221 137 c +221 696 l +221 733 215 759 204 775 c +193 792 178 802 157 805 c +136 809 104 811 61 811 c +61 883 l +358 905 l +358 705 l +385 764 426 812 479 849 c +533 886 592 905 655 905 c +750 905 821 882 868 837 c +916 792 940 722 940 629 c +940 137 l +940 108 955 90 985 83 c +1015 76 1053 72 1100 72 c +1100 0 l +631 0 l +631 72 l +678 72 716 76 746 83 c +776 90 791 108 791 137 c +791 623 l +791 690 781 744 762 787 c +743 830 703 852 643 852 c +564 852 498 820 447 757 c +396 694 371 622 371 541 c +371 137 l +371 108 386 90 416 83 c +446 76 484 72 530 72 c +530 0 l +61 0 l + +ce} _d +/o{1024 0 57 -23 965 918 sc +512 -23 m +430 -23 354 -2 284 39 c +214 81 159 137 118 207 c +77 277 57 353 57 436 c +57 499 68 559 90 617 c +113 675 145 727 186 772 c +228 818 277 854 332 879 c +387 905 447 918 512 918 c +596 918 672 896 741 851 c +810 807 865 748 905 673 c +945 599 965 520 965 436 c +965 354 945 278 904 207 c +863 137 808 81 738 39 c +669 -2 593 -23 512 -23 c + +512 37 m +621 37 694 77 731 156 c +768 235 786 336 786 459 c +786 528 782 584 775 629 c +768 674 752 715 727 752 c +712 775 692 794 668 811 c +645 828 620 841 593 850 c +567 859 540 864 512 864 c +469 864 429 854 390 835 c +352 816 320 788 295 752 c +270 713 253 671 246 624 c +239 578 236 523 236 459 c +236 382 243 313 256 252 c +269 191 296 140 336 99 c +377 58 435 37 512 37 c + +ce} _d +/p{1137 0 53 -397 1069 905 sc +53 -397 m +53 -326 l +100 -326 138 -322 168 -314 c +198 -306 213 -288 213 -260 c +213 727 l +213 764 200 788 173 797 c +146 806 106 811 53 811 c +53 883 l +356 905 l +356 778 l +393 819 437 851 486 872 c +536 894 589 905 645 905 c +726 905 798 883 863 839 c +928 796 978 738 1014 667 c +1051 596 1069 521 1069 442 c +1069 359 1049 282 1008 211 c +968 140 913 83 843 40 c +774 -2 697 -23 614 -23 c +515 -23 431 17 362 98 c +362 -260 l +362 -288 377 -306 407 -314 c +438 -322 476 -326 522 -326 c +522 -397 l +53 -397 l + +362 199 m +386 150 419 110 462 78 c +505 47 551 31 602 31 c +649 31 691 44 727 69 c +764 94 794 128 819 171 c +844 214 862 258 873 305 c +885 352 891 398 891 442 c +891 497 881 556 861 619 c +842 683 812 737 771 780 c +731 824 682 846 625 846 c +570 846 519 832 472 803 c +426 775 389 737 362 688 c +362 199 l + +ce} _d +/q{1079 0 68 -397 1083 905 sc +614 -397 m +614 -326 l +661 -326 699 -322 729 -314 c +759 -306 774 -288 774 -260 c +774 119 l +742 76 702 42 653 16 c +604 -10 553 -23 500 -23 c +439 -23 382 -10 329 15 c +276 40 230 75 191 118 c +152 161 122 211 100 268 c +79 325 68 383 68 442 c +68 523 88 600 128 671 c +168 743 223 800 292 842 c +362 884 437 905 518 905 c +576 905 630 889 679 856 c +728 823 768 780 797 725 c +870 905 l +924 905 l +924 -260 l +924 -288 939 -306 969 -314 c +999 -322 1037 -326 1083 -326 c +1083 -397 l +614 -397 l + +512 31 m +573 31 627 51 674 91 c +722 132 757 183 780 244 c +780 604 l +766 669 737 726 693 774 c +649 822 596 846 535 846 c +488 846 447 834 410 809 c +373 784 343 751 318 710 c +294 669 276 624 264 576 c +252 528 246 483 246 440 c +246 385 256 326 275 261 c +294 197 324 143 364 98 c +404 53 453 31 512 31 c + +ce} _d +/r{801 0 53 0 745 905 sc +53 0 m +53 72 l +100 72 138 76 168 83 c +198 90 213 108 213 137 c +213 696 l +213 733 207 759 196 775 c +185 792 170 802 149 805 c +128 809 96 811 53 811 c +53 883 l +346 905 l +346 705 l +368 764 399 812 440 849 c +481 886 530 905 588 905 c +629 905 665 893 697 869 c +729 845 745 813 745 774 c +745 749 736 728 718 709 c +701 691 679 682 653 682 c +628 682 606 691 588 709 c +570 727 561 749 561 774 c +561 811 574 837 600 852 c +588 852 l +533 852 487 832 452 792 c +417 752 393 702 378 643 c +363 584 356 527 356 473 c +356 137 l +356 94 422 72 555 72 c +555 0 l +53 0 l + +ce} _d +/s{807 0 68 -23 737 918 sc +68 -6 m +68 328 l +68 339 74 344 86 344 c +111 344 l +119 344 124 339 127 328 c +165 130 257 31 403 31 c +468 31 522 46 565 75 c +609 104 631 150 631 211 c +631 255 614 292 580 323 c +546 354 506 376 459 387 c +322 414 l +276 424 234 439 196 460 c +159 481 128 508 104 542 c +80 577 68 617 68 662 c +68 722 84 771 115 809 c +147 848 188 875 239 892 c +290 909 344 918 403 918 c +473 918 534 899 586 862 c +645 913 l +645 916 648 918 655 918 c +670 918 l +674 918 678 916 681 912 c +684 909 686 905 686 901 c +686 633 l +686 620 681 614 670 614 c +645 614 l +633 614 627 620 627 633 c +627 704 607 762 567 805 c +528 848 472 870 401 870 c +340 870 286 859 241 836 c +196 813 174 774 174 719 c +174 681 190 650 222 625 c +255 601 293 584 336 573 c +475 547 l +522 536 565 518 605 493 c +646 468 678 436 701 397 c +725 358 737 315 737 266 c +737 217 728 174 711 137 c +694 101 671 71 640 47 c +610 23 574 5 533 -6 c +492 -17 448 -23 403 -23 c +318 -23 245 6 184 63 c +109 -18 l +109 -21 105 -23 98 -23 c +86 -23 l +74 -23 68 -17 68 -6 c + +ce} _d +/t{795 0 39 -23 680 1260 sc +209 246 m +209 811 l +39 811 l +39 864 l +128 864 194 906 236 989 c +278 1072 299 1163 299 1260 c +358 1260 l +358 883 l +647 883 l +647 811 l +358 811 l +358 250 l +358 193 367 144 386 101 c +405 58 440 37 489 37 c +536 37 569 59 590 104 c +611 149 621 198 621 250 c +621 371 l +680 371 l +680 246 l +680 203 672 161 656 119 c +641 78 618 44 587 17 c +556 -10 519 -23 475 -23 c +393 -23 328 1 280 50 c +233 99 209 165 209 246 c + +ce} _d +/u{1137 0 61 -23 1100 905 sc +221 244 m +221 696 l +221 733 215 759 204 775 c +193 792 178 802 157 805 c +136 809 104 811 61 811 c +61 883 l +371 905 l +371 244 l +371 191 375 149 382 119 c +390 90 406 68 431 53 c +456 38 496 31 551 31 c +624 31 683 62 726 123 c +769 184 791 254 791 332 c +791 696 l +791 733 785 759 774 775 c +763 792 747 802 726 805 c +706 809 674 811 631 811 c +631 883 l +940 905 l +940 186 l +940 150 945 124 956 107 c +967 91 983 81 1004 77 c +1025 74 1057 72 1100 72 c +1100 0 l +797 -23 l +797 150 l +772 99 736 57 691 25 c +646 -7 596 -23 541 -23 c +443 -23 365 -2 307 39 c +250 81 221 149 221 244 c + +ce} _d +/v{1079 0 39 -23 1040 883 sc +500 0 m +201 752 l +188 777 169 793 142 800 c +116 807 82 811 39 811 c +39 883 l +469 883 l +469 811 l +392 811 354 795 354 762 c +354 757 355 753 356 750 c +586 172 l +793 694 l +797 705 799 716 799 727 c +799 753 789 773 769 788 c +750 803 727 811 700 811 c +700 883 l +1040 883 l +1040 811 l +998 811 961 801 929 782 c +898 763 873 734 856 696 c +580 0 l +575 -15 564 -23 547 -23 c +532 -23 l +515 -23 505 -15 500 0 c + +ce} _d +/w{1479 0 37 -23 1440 883 sc +453 0 m +188 745 l +176 775 159 793 136 800 c +113 807 80 811 37 811 c +37 883 l +459 883 l +459 811 l +378 811 338 794 338 760 c +339 758 339 756 339 754 c +340 752 340 749 340 745 c +537 193 l +707 674 l +680 745 l +669 775 652 793 629 800 c +606 807 573 811 530 811 c +530 883 l +934 883 l +934 811 l +853 811 813 794 813 760 c +813 755 814 750 815 745 c +1020 168 l +1206 690 l +1209 701 1210 710 1210 719 c +1210 748 1198 770 1173 786 c +1149 803 1122 811 1092 811 c +1092 883 l +1440 883 l +1440 811 l +1399 811 1363 800 1333 778 c +1304 757 1282 727 1268 690 c +1024 0 l +1019 -15 1009 -23 993 -23 c +977 -23 l +961 -23 951 -15 946 0 c +739 584 l +532 0 l +525 -15 515 -23 500 -23 c +485 -23 l +468 -23 458 -15 453 0 c + +ce} _d +/x{1079 0 25 0 1057 883 sc +25 0 m +25 72 l +77 72 126 82 172 102 c +218 123 257 153 289 193 c +475 430 l +233 745 l +209 775 183 793 154 800 c +126 807 86 811 35 811 c +35 883 l +461 883 l +461 811 l +443 811 426 807 410 799 c +395 791 387 779 387 764 c +387 759 389 752 393 745 c +557 532 l +680 690 l +697 710 705 730 705 750 c +705 767 699 781 688 793 c +677 805 663 811 645 811 c +645 883 l +1022 883 l +1022 811 l +969 811 920 801 873 780 c +827 760 788 730 756 690 c +594 483 l +856 137 l +882 107 909 89 937 82 c +965 75 1005 72 1057 72 c +1057 0 l +631 0 l +631 72 l +648 72 664 76 679 84 c +694 92 702 104 702 119 c +702 125 700 131 696 137 c +512 381 l +365 193 l +350 176 342 156 342 133 c +342 116 348 102 359 90 c +370 78 384 72 399 72 c +399 0 l +25 0 l + +ce} _d +/y{1079 0 39 -420 1040 883 sc +141 -336 m +167 -357 196 -367 227 -367 c +313 -367 383 -302 438 -172 c +508 0 l +201 752 l +188 777 169 793 143 800 c +117 807 82 811 39 811 c +39 883 l +469 883 l +469 811 l +394 811 356 795 356 762 c +356 757 357 753 358 750 c +586 190 l +791 694 l +795 705 797 716 797 729 c +797 746 792 760 783 772 c +774 785 763 794 748 801 c +734 808 718 811 700 811 c +700 883 l +1040 883 l +1040 811 l +998 811 961 801 929 782 c +898 763 873 734 856 696 c +502 -172 l +483 -216 461 -256 436 -293 c +411 -330 381 -361 345 -384 c +309 -408 270 -420 227 -420 c +177 -420 133 -403 95 -370 c +58 -337 39 -297 39 -248 c +39 -223 48 -201 65 -184 c +82 -167 104 -158 129 -158 c +146 -158 162 -162 175 -169 c +189 -177 200 -188 207 -201 c +215 -214 219 -230 219 -248 c +219 -270 212 -290 197 -307 c +182 -324 164 -334 141 -336 c + +ce} _d +/z{909 0 57 0 821 883 sc +80 0 m +65 0 57 8 57 23 c +57 39 l +57 44 59 49 63 53 c +635 829 l +451 829 l +393 829 345 825 307 818 c +270 811 239 797 214 776 c +190 756 172 728 161 692 c +150 657 145 608 145 545 c +86 545 l +109 883 l +795 883 l +801 883 806 881 810 876 c +815 872 817 867 817 860 c +817 848 l +817 844 816 839 813 834 c +240 59 l +436 59 l +495 59 545 63 584 70 c +624 77 658 95 686 123 c +712 149 730 184 739 228 c +749 272 757 326 762 391 c +821 391 l +786 0 l +80 0 l + +ce} _d +/emdash{1024 0 0 518 1022 571 sc +0 518 m +0 571 l +1022 571 l +1022 518 l +0 518 l + +ce} _d +/endash{2048 0 0 518 2046 571 sc +0 518 m +0 571 l +2046 571 l +2046 518 l +0 518 l + +ce} _d +/hungarumlaut{1024 0 258 1049 860 1434 sc +258 1075 m +369 1382 l +381 1417 403 1434 436 1434 c +449 1434 462 1431 475 1424 c +488 1417 499 1408 506 1395 c +514 1382 518 1370 518 1358 c +518 1344 513 1328 502 1311 c +311 1049 l +258 1075 l + +600 1075 m +711 1382 l +723 1417 745 1434 778 1434 c +791 1434 804 1431 817 1424 c +830 1417 841 1408 848 1395 c +856 1382 860 1370 860 1358 c +860 1344 855 1328 844 1311 c +653 1049 l +600 1075 l + +ce} _d +/tilde{1024 0 170 1171 852 1368 sc +170 1206 m +229 1276 l +282 1337 336 1368 389 1368 c +415 1368 443 1360 474 1345 c +505 1330 536 1314 567 1299 c +598 1284 627 1276 653 1276 c +708 1276 760 1307 811 1368 c +852 1333 l +793 1264 l +739 1202 686 1171 633 1171 c +607 1171 578 1179 547 1194 c +516 1210 485 1226 454 1241 c +423 1256 395 1264 369 1264 c +314 1264 262 1233 211 1171 c +170 1206 l + +ce} _d +end readonly def + +/BuildGlyph { + exch begin + CharStrings exch + 2 copy known not {pop /.notdef} if + true 3 1 roll get exec + end +} _d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +} _d + +FontName currentdict end definefont pop + + %%!PS-TrueTypeFont-1.0-2.3499908 + 10 dict begin + /FontType 42 def + /FontMatrix [1 0 0 1 0 0] def + /FontName /DejaVuSans def + /FontInfo 7 dict dup begin + /FullName (DejaVu Sans) def + /FamilyName (DejaVu Sans) def + /Version (Version 2.35) def + /ItalicAngle 0.0 def + /isFixedPitch false def + /UnderlinePosition -130 def + /UnderlineThickness 90 def + end readonly def + /Encoding StandardEncoding def + /FontBBox [-2090 -948 3673 2524] def + /PaintType 0 def + /CIDMap 0 def + /CharStrings 485 dict dup begin +/.notdef 0 def +/.null 1 def +/nonmarkingreturn 2 def +/space 3 def +/exclam 4 def +/A 5 def +/C 6 def +/D 7 def +/E 8 def +/G 9 def +/H 10 def +/I 11 def +/J 12 def +/K 13 def +/L 14 def +/N 15 def +/O 16 def +/R 17 def +/S 18 def +/T 19 def +/U 20 def +/W 21 def +/Y 22 def +/Z 23 def +/grave 24 def +/a 25 def +/c 26 def +/d 27 def +/e 28 def +/g 29 def +/h 30 def +/i 31 def +/j 32 def +/k 33 def +/l 34 def +/n 35 def +/o 36 def +/r 37 def +/s 38 def +/t 39 def +/u 40 def +/w 41 def +/y 42 def +/z 43 def +/dieresis 44 def +/macron 45 def +/acute 46 def +/periodcentered 47 def +/cedilla 48 def +/Aring 49 def +/AE 50 def +/Ccedilla 51 def +/Egrave 52 def +/Eacute 53 def +/Ecircumflex 54 def +/Edieresis 55 def +/Igrave 56 def +/Iacute 57 def +/Icircumflex 58 def +/Idieresis 59 def +/Eth 60 def +/Ntilde 61 def +/Ograve 62 def +/Oacute 63 def +/Ocircumflex 64 def +/Otilde 65 def +/Odieresis 66 def +/multiply 67 def +/Oslash 68 def +/Ugrave 69 def +/Uacute 70 def +/Ucircumflex 71 def +/Udieresis 72 def +/Yacute 73 def +/Thorn 74 def +/germandbls 75 def +/agrave 76 def +/aacute 77 def +/acircumflex 78 def +/atilde 79 def +/adieresis 80 def +/aring 81 def +/ae 82 def +/ccedilla 83 def +/egrave 84 def +/eacute 85 def +/ecircumflex 86 def +/edieresis 87 def +/igrave 88 def +/iacute 89 def +/icircumflex 90 def +/idieresis 91 def +/eth 92 def +/ntilde 93 def +/ograve 94 def +/oacute 95 def +/ocircumflex 96 def +/otilde 97 def +/odieresis 98 def +/divide 99 def +/oslash 100 def +/ugrave 101 def +/uacute 102 def +/ucircumflex 103 def +/udieresis 104 def +/yacute 105 def +/thorn 106 def +/ydieresis 107 def +/Amacron 108 def +/amacron 109 def +/Abreve 110 def +/abreve 111 def +/Aogonek 112 def +/aogonek 113 def +/Cacute 114 def +/cacute 115 def +/Ccircumflex 116 def +/ccircumflex 117 def +/Cdotaccent 118 def +/cdotaccent 119 def +/Ccaron 120 def +/ccaron 121 def +/Dcaron 122 def +/dcaron 123 def +/Dcroat 124 def +/dcroat 125 def +/Emacron 126 def +/emacron 127 def +/Ebreve 128 def +/ebreve 129 def +/Edotaccent 130 def +/edotaccent 131 def +/Eogonek 132 def +/eogonek 133 def +/Ecaron 134 def +/ecaron 135 def +/Gcircumflex 136 def +/gcircumflex 137 def +/Gbreve 138 def +/gbreve 139 def +/Gdotaccent 140 def +/gdotaccent 141 def +/Gcommaaccent 142 def +/gcommaaccent 143 def +/Hcircumflex 144 def +/hcircumflex 145 def +/Hbar 146 def +/hbar 147 def +/Itilde 148 def +/itilde 149 def +/Imacron 150 def +/imacron 151 def +/Ibreve 152 def +/ibreve 153 def +/Iogonek 154 def +/iogonek 155 def +/Idotaccent 156 def +/dotlessi 157 def +/IJ 158 def +/ij 159 def +/Jcircumflex 160 def +/jcircumflex 161 def +/Kcommaaccent 162 def +/kcommaaccent 163 def +/kgreenlandic 164 def +/Lacute 165 def +/lacute 166 def +/Lcommaaccent 167 def +/lcommaaccent 168 def +/Lcaron 169 def +/lcaron 170 def +/Ldot 171 def +/ldot 172 def +/Lslash 173 def +/lslash 174 def +/Nacute 175 def +/nacute 176 def +/Ncommaaccent 177 def +/ncommaaccent 178 def +/Ncaron 179 def +/ncaron 180 def +/napostrophe 181 def +/Eng 182 def +/eng 183 def +/Omacron 184 def +/omacron 185 def +/Obreve 186 def +/obreve 187 def +/Ohungarumlaut 188 def +/ohungarumlaut 189 def +/OE 190 def +/oe 191 def +/Racute 192 def +/racute 193 def +/Rcommaaccent 194 def +/rcommaaccent 195 def +/Rcaron 196 def +/rcaron 197 def +/Sacute 198 def +/sacute 199 def +/Scircumflex 200 def +/scircumflex 201 def +/Scedilla 202 def +/scedilla 203 def +/Scaron 204 def +/scaron 205 def +/Tcommaaccent 206 def +/tcommaaccent 207 def +/Tcaron 208 def +/tcaron 209 def +/Tbar 210 def +/tbar 211 def +/Utilde 212 def +/utilde 213 def +/Umacron 214 def +/umacron 215 def +/Ubreve 216 def +/ubreve 217 def +/Uring 218 def +/uring 219 def +/Uhungarumlaut 220 def +/uhungarumlaut 221 def +/Uogonek 222 def +/uogonek 223 def +/Wcircumflex 224 def +/wcircumflex 225 def +/Ycircumflex 226 def +/ycircumflex 227 def +/Ydieresis 228 def +/Zacute 229 def +/zacute 230 def +/Zdotaccent 231 def +/zdotaccent 232 def +/Zcaron 233 def +/zcaron 234 def +/longs 235 def +/uni0180 236 def +/uni0181 237 def +/uni0182 238 def +/uni0183 239 def +/uni0184 240 def +/uni0185 241 def +/uni0186 242 def +/uni0187 243 def +/uni0188 244 def +/uni0189 245 def +/uni018A 246 def +/uni018B 247 def +/uni018C 248 def +/uni018D 249 def +/uni018E 250 def +/uni018F 251 def +/uni0190 252 def +/uni0191 253 def +/florin 254 def +/uni0193 255 def +/uni0194 256 def +/uni0195 257 def +/uni0196 258 def +/uni0197 259 def +/uni0198 260 def +/uni0199 261 def +/uni019A 262 def +/uni019B 263 def +/uni019C 264 def +/uni019D 265 def +/uni019E 266 def +/uni019F 267 def +/Ohorn 268 def +/ohorn 269 def +/uni01A2 270 def +/uni01A3 271 def +/uni01A4 272 def +/uni01A5 273 def +/uni01A6 274 def +/uni01A7 275 def +/uni01A8 276 def +/uni01A9 277 def +/uni01AA 278 def +/uni01AB 279 def +/uni01AC 280 def +/uni01AD 281 def +/uni01AE 282 def +/Uhorn 283 def +/uhorn 284 def +/uni01B1 285 def +/uni01B2 286 def +/uni01B3 287 def +/uni01B4 288 def +/uni01B5 289 def +/uni01B6 290 def +/uni01B7 291 def +/uni01B8 292 def +/uni01B9 293 def +/uni01BA 294 def +/uni01BB 295 def +/uni01BC 296 def +/uni01BD 297 def +/uni01BE 298 def +/uni01BF 299 def +/uni01C0 300 def +/uni01C1 301 def +/uni01C2 302 def +/uni01C3 303 def +/uni01C4 304 def +/uni01C5 305 def +/uni01C6 306 def +/uni01C7 307 def +/uni01C8 308 def +/uni01C9 309 def +/uni01CA 310 def +/uni01CB 311 def +/uni01CC 312 def +/uni01CD 313 def +/uni01CE 314 def +/uni01CF 315 def +/uni01D0 316 def +/uni01D1 317 def +/uni01D2 318 def +/uni01D3 319 def +/uni01D4 320 def +/uni01D5 321 def +/uni01D6 322 def +/uni01D7 323 def +/uni01D8 324 def +/uni01D9 325 def +/uni01DA 326 def +/uni01DB 327 def +/uni01DC 328 def +/uni01DD 329 def +/uni01DE 330 def +/uni01DF 331 def +/uni01E0 332 def +/uni01E1 333 def +/uni01E2 334 def +/uni01E3 335 def +/uni01E4 336 def +/uni01E5 337 def +/Gcaron 338 def +/gcaron 339 def +/uni01E8 340 def +/uni01E9 341 def +/uni01EA 342 def +/uni01EB 343 def +/uni01EC 344 def +/uni01ED 345 def +/uni01EE 346 def +/uni01EF 347 def +/uni01F0 348 def +/uni01F1 349 def +/uni01F2 350 def +/uni01F3 351 def +/uni01F4 352 def +/uni01F5 353 def +/uni01F6 354 def +/uni01F7 355 def +/uni01F8 356 def +/uni01F9 357 def +/Aringacute 358 def +/aringacute 359 def +/AEacute 360 def +/aeacute 361 def +/Oslashacute 362 def +/oslashacute 363 def +/uni0200 364 def +/uni0201 365 def +/uni0202 366 def +/uni0203 367 def +/uni0204 368 def +/uni0205 369 def +/uni0206 370 def +/uni0207 371 def +/uni0208 372 def +/uni0209 373 def +/uni020A 374 def +/uni020B 375 def +/uni020C 376 def +/uni020D 377 def +/uni020E 378 def +/uni020F 379 def +/uni0210 380 def +/uni0211 381 def +/uni0212 382 def +/uni0213 383 def +/uni0214 384 def +/uni0215 385 def +/uni0216 386 def +/uni0217 387 def +/Scommaaccent 388 def +/scommaaccent 389 def +/uni021A 390 def +/uni021B 391 def +/uni021C 392 def +/uni021D 393 def +/uni021E 394 def +/uni021F 395 def +/uni0220 396 def +/uni0221 397 def +/uni0222 398 def +/uni0223 399 def +/uni0224 400 def +/uni0225 401 def +/uni0226 402 def +/uni0227 403 def +/uni0228 404 def +/uni0229 405 def +/uni022A 406 def +/uni022B 407 def +/uni022C 408 def +/uni022D 409 def +/uni022E 410 def +/uni022F 411 def +/uni0230 412 def +/uni0231 413 def +/uni0232 414 def +/uni0233 415 def +/uni0234 416 def +/uni0235 417 def +/uni0236 418 def +/dotlessj 419 def +/uni0238 420 def +/uni0239 421 def +/uni023A 422 def +/uni023B 423 def +/uni023C 424 def +/uni023D 425 def +/uni023E 426 def +/uni023F 427 def +/uni0240 428 def +/uni0241 429 def +/uni0242 430 def +/uni0243 431 def +/uni0244 432 def +/uni0245 433 def +/uni0246 434 def +/uni0247 435 def +/uni0248 436 def +/uni0249 437 def +/uni024A 438 def +/uni024B 439 def +/uni024C 440 def +/uni024D 441 def +/uni024E 442 def +/uni024F 443 def +/uni0259 444 def +/uni0292 445 def +/uni02BC 446 def +/circumflex 447 def +/caron 448 def +/breve 449 def +/ring 450 def +/ogonek 451 def +/tilde 452 def +/hungarumlaut 453 def +/uni0307 454 def +/uni030C 455 def +/uni030F 456 def +/uni0311 457 def +/uni0312 458 def +/uni031B 459 def +/uni0326 460 def +/Lambda 461 def +/Sigma 462 def +/eta 463 def +/uni0411 464 def +/quoteright 465 def +/dlLtcaron 466 def +/Dieresis 467 def +/Acute 468 def +/Tilde 469 def +/Grave 470 def +/Circumflex 471 def +/Caron 472 def +/uni0311.case 473 def +/Breve 474 def +/Dotaccent 475 def +/Hungarumlaut 476 def +/Doublegrave 477 def +/Eng.alt 478 def +/uni03080304 479 def +/uni03070304 480 def +/uni03080301 481 def +/uni03080300 482 def +/uni03030304 483 def +/uni0308030C 484 def +end readonly def + /sfnts[<0001000000120100000400204744454603ad02160000012c0000002247504f537feb94760000015000000ec44753 +5542720d76a300001014000000e84d415448093f3384000010fc000000f64f532f326aab715a000011f400000056636d6170 +0048065b0000124c000000586376742000691d39000012a4000001fe6670676d7134766a000014a4000000ab676173700007 +0007000015500000000c676c7966aa1f812c0000155c0000a37c68656164085dc2860000b8d800000036686865610d9f094d +0000b91000000024686d747800d59d920000b9340000078a6c6f6361df7708600000c0c0000003cc6d617870065206710000 +c48c000000206e616d6527ed3dbc0000c4ac000001d4706f7374ee52dc100000c68000000f56707265703b07f1000000d5d8 +0000056800010000000c00000000000000020003000300030001003101bb000101de01de0001000000010000000a002e003c +000244464c54000e6c61746e0018000400000000ffff0000000400000000ffff0001000000016b65726e0008000000010000 +00010004000200000001000800020ace000400000b380c0e0019003700000000000000000000000000000000ff9000000000 +00000000000000000000000000000000000000000000000000000000000000000000ffdc0000000000000000000000000000 +00000000000000000000000000000000000000000000ff9000000000000000000000ff900000000000000000000000000000 +ffb70000ff9a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000ffb70000fee6ff9afef0000000000000ffdc00000000ffdc000000000000ffdcff44000000000000ffdc0000 +ffdcffdc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000ff90000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ff9a0000000000000000ff6b0000ff7d0000ffd30000ffa400000000ffa4 +000000000000ffa4ff900000ff9affd3ffa40000ffa4ffa40000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000ffdc000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +ff9000000000ff9000000000000000000000fee60000fef000000000fef0000000000000ff1500000000ff90fee6fef00000 +fef0ff1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ffd3ffd3ffdcffdcffd3ffdc0000000000000000000000000000ffd30000 +ffd3000000000000ffd300000000000000480000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffdc00000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ffdc00000000ffdc0000ff610000ff6100000000ffdcffdc00000000ffdc +00000000ffdc0000ff75000000000000ffdc0000ffdc0000003900000000ffdc0000ffdcffdcffdcffdc000000000000ffdc +ffdcff6100000000ff90ffadff61ff75000000000000ffdc000000000000ffdc00000000ffdc0000ff610000ff6100000000 +ffdcffdc00000000ffdc00000000ffdc00000000000000000000ffdc0000ffdc0000003900000000ffdc0000ffdcffdcffdc +ffdc000000000000ffdc0000ff6100000000ff90ffadff610000000000000000ffdc00000000000000000000000000000000 +00000000ff900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000ffd3ffd3ffdcffdcffd3ffdc0000000000000000 +000000000000ffd30000ffd3000000000000ffd3000000000000ffdc00000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ff880000000000000000ffdc000000000000feadfea4fea400000000fea4 +fed3fead0000fec9fec10000ff88feadfea40000fea4fec900000000fea40000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001003300320033003c003e003f0040004100420045 +0046004700480049004a004b0054005500560057005c005d005e005f0060006100620069006b006c006e007000720078007a +007c0087008a00a500a900ac00b400c000c100c400c500ca00cc00d000da00e400e90002002300320032000e00330033000f +003e00420003004500480006004900490007004a004a0010004b004b0011005400570009005c005c0012005d005d000a005e +0062000b00690069000d006b006b000d006c006c0013006e006e001300700070001400720072000f00780078000f007c007c +0015008700870009008a008a000100a500a5000200a900a9000200ac00ac001600b400b4000a00c000c0000400c100c1000c +00c400c4000400c500c5001700ca00ca000500cc00cc000500d000d0001800da00da000600e400e4000700e900e900080002 +0067003200320015003300330016003e00420004004500480007004900490008004a004b0003004c004c0017004d004d000a +004e0051001700530053000b00540054001800550055000c005600570018005c005c0019005d005d000e005e005e001a005f +005f000f00600062001a00650065001b00660066001300670068001b006900690014006b006b0014006c006c001c006d006d +001d006e006e001c006f006f001d00700070001c00710071001d00720072000100730073001e00740074001f007500750020 +00760076001f00770077002100780078000100790079001e007a007a0002007b007b0022007d007d0023007f007f00240081 +0081002400830083002400850085002400870087000c00880088001f008a008a0025008b008b000d008c008c001f008e008e +0026009b009b0027009f009f002700a500a5000300a900a9000300b400b4000e00b800b8001f00b900b9002800ba00ba001f +00bb00bb002800bc00bc002900bd00bd002800c000c0000300c100c1001000c300c3002700c400c4000300c500c5001000c6 +00c6000500c800c8000500ca00ca000500cb00cb001100cc00cc000500cd00cd001100ce00ce002a00cf00cf002100d000d0 +000600d100d1001200d200d2002b00d500d5002c00d700d7002c00d900d9002c00da00da000700db00db001300dd00dd002c +00df00df002c00e000e0002d00e100e1002e00e200e2002f00e300e3003000e400e4000800e900e900090132013200200156 +01560031015701570032015801580031015901590032018401840005018601860033018701870020019a019a001f019b019b +0034019d019d0020019e019e0035019f019f003600010000000a00c200d0001444464c54007a6172616200b461726d6e00b4 +6272616900b463616e7300b46368657200b46379726c00b467656f7200b46772656b00b468616e6900b46865627200b46b61 +6e6100b46c616f2000b46c61746e00846d61746800b46e6b6f2000b46f67616d00b472756e7200b474666e6700b474686169 +00b4000400000000ffff00000000000649534d2000284b534d2000284c534d2000284e534d200028534b5320002853534d20 +00280000ffff000100000000000000016c6f636c000800000001000000010004000100000001000800010006012800010001 +00b600010000000a00e000e80050003c0c0007dd00000000028200000460000005d500000000000004600000000000000000 +0000000000000460000000000000016800000460000000550000000000000000000000000000000000000000000000000000 +0000000000000000010e0000027600000000000000000000000000000000000000000000000000000000000000000000005a +0000010e0000005a0000005a0000010e00000000000000000000010e0000005a0000005a0000010e0000005a0000005a0000 +005a000001720000005a0000005a000002380000fb8f0000003c00000000000000000028000a000a00000000000100000000 +0001040e019000050000053305990000011e05330599000003d7006602120000020b06030308040202040000000e00000000 +000000000000000050664564004000c5024f0614fe14019a076d01e30000000100000000000000000003000000030000001c +0000000a0000003c000300010000001c0004002000000004000400010000024fffff000000c5ffffff6c000100000000000c +00000000001c0000000000000001000000c50000024f00000031013500b800cb00cb00c100aa009c01a600b8006600000071 +00cb00a002b20085007500b800c301cb0189022d00cb00a600f000d300aa008700cb03aa0400014a003300cb000000d90502 +00f4015400b4009c01390114013907060400044e04b4045204b804e704cd0037047304cd04600473013303a2055605a60556 +053903c5021200c9001f00b801df007300ba03e9033303bc0444040e00df03cd03aa00e503aa0404000000cb008f00a4007b +00b80014016f007f027b0252008f00c705cd009a009a006f00cb00cd019e01d300f000ba018300d5009803040248009e01d5 +00c100cb00f600830354027f00000333026600d300c700a400cd008f009a0073040005d5010a00fe022b00a400b4009c0000 +0062009c0000001d032d05d505d505d505f0007f007b005400a406b80614072301d300b800cb00a601c301ec069300a000d3 +035c037103db0185042304a80448008f0139011401390360008f05d5019a0614072306660179046004600460047b009c0000 +0277046001aa00e904600762007b00c5007f027b000000b4025205cd006600bc00660077061000cd013b01850389008f007b +0000001d00cd074a042f009c009c0000077d006f0000006f0335006a006f007b00ae00b2002d0396008f027b00f600830354 +063705f6008f009c04e10266008f018d02f600cd03440029006604ee00730000140000960000b707060504030201002c2010 +b002254964b040515820c859212d2cb002254964b040515820c859212d2c20100720b00050b00d7920b8ffff5058041b0559 +b0051cb0032508b0042523e120b00050b00d7920b8ffff5058041b0559b0051cb0032508e12d2c4b505820b0fd454459212d +2cb002254560442d2c4b5358b00225b0022545445921212d2c45442d2cb00225b0022549b00525b005254960b0206368208a +108a233a8a10653a2d000000000200080002ffff0003000201350000020005d5000300090035400f07008304810208070501 +030400000a10fc4bb00b5458b90000ffc038593cec32393931002fe4fccc3001b6000b200b500b035d253315231133110323 +030135cbcbcb14a215fefe05d5fd71fe9b016500000200100000056805d50002000a00c24041001101000405040211050504 +01110a030a0011020003030a0711050406110505040911030a08110a030a4200030795010381090509080706040302010009 +050a0b10d4c4173931002f3ce4d4ec1239304b5358071005ed0705ed071005ed0705ed071008ed071005ed071005ed071008 +ed5922b2200c01015d40420f010f020f070f080f005800760070008c000907010802060309041601190256015802500c6701 +6802780176027c0372047707780887018802800c980299039604175d005d090121013301230321032302bcfeee0225fe7be5 +0239d288fd5f88d5050efd1903aefa2b017ffe8100010073ffe3052705f000190036401a0da10eae0a951101a100ae049517 +91118c1a07190d003014101a10fcec32ec310010e4f4ecf4ec10eef6ee30b40f1b1f1b02015d01152e012320001110002132 +3637150e01232000111000213216052766e782ff00fef00110010082e7666aed84feadfe7a0186015386ed0562d55f5efec7 +fed8fed9fec75e5fd34848019f01670168019f47000200c9000005b005d500080011002e4015009509810195100802100a00 +05190d32001c09041210fcecf4ec113939393931002fecf4ec30b2601301015d011133200011100021252120001110002901 +0193f40135011ffee1fecbfe42019f01b20196fe68fe50fe61052ffb770118012e012c0117a6fe97fe80fe7efe96000100c9 +0000048b05d5000b002e401506950402950081089504ad0a05010907031c00040c10fcec32d4c4c431002fececf4ec10ee30 +b21f0d01015d132115211121152111211521c903b0fd1a02c7fd3902f8fc3e05d5aafe46aafde3aa00010073ffe3058b05f0 +001d0039402000051b0195031b950812a111ae15950e91088c1e02001c1134043318190b101e10fcecfce4fcc4310010e4f4 +ecf4ec10fed4ee11393930251121352111060423200011100021320417152e0123200011100021323604c3feb6021275fee6 +a0fea2fe75018b015e9201076f70fc8bfeeefeed011301126ba8d50191a6fd7f53550199016d016e01994846d75f60fecefe +d1fed2fece25000100c90000053b05d5000b002c4014089502ad0400810a0607031c053809011c00040c10fcec32fcec3231 +002f3ce432fcec30b2500d01015d133311211133112311211123c9ca02decacafd22ca05d5fd9c0264fa2b02c7fd39000001 +00c90000019305d50003002eb700af02011c00040410fc4bb0105458b9000000403859ec31002fec3001400d300540055005 +60058f059f05065d13331123c9caca05d5fa2b000001ff96fe66019305d5000b004240130b0200079505b000810c05080639 +011c00040c10fc4bb0105458b9000000403859ece43939310010e4fcec1139393001400d300d400d500d600d8f0d9f0d065d +13331110062b013533323635c9cacde34d3f866e05d5fa93fef2f4aa96c2000100c90000056a05d5000a00ef402808110506 +0507110606050311040504021105050442080502030300af09060501040608011c00040b10fcec32d4c4113931002f3cec32 +1739304b5358071004ed071005ed071005ed071004ed5922b2080301015d4092140201040209081602280528083702360534 +084702460543085502670276027705830288058f0894029b08e702150603090509061b031907050a030a07180328052b062a +073604360536063507300c41034004450540064007400c62036004680567077705700c8b038b058e068f078f0c9a039d069d +07b603b507c503c507d703d607e803e904e805ea06f703f805f9062c5d71005d711333110121090121011123c9ca029e0104 +fd1b031afef6fd33ca05d5fd890277fd48fce302cffd3100000100c90000046a05d500050025400c0295008104011c033a00 +040610fcecec31002fe4ec304009300750078003800404015d133311211521c9ca02d7fc5f05d5fad5aa000100c900000533 +05d500090079401e071101020102110607064207020300af0805060107021c0436071c00040a10fcecfcec11393931002f3c +ec323939304b5358071004ed071004ed5922b21f0b01015d40303602380748024707690266078002070601090615011a0646 +0149065701580665016906790685018a0695019a069f0b105d005d13210111331121011123c901100296c4fef0fd6ac405d5 +fb1f04e1fa2b04e1fb1f00020073ffe305d905f0000b00170023401306951200950c91128c1809190f33031915101810fcec +fcec310010e4f4ec10ee300122001110003332001110002720001110002120001110000327dcfefd0103dcdc0101feffdc01 +3a0178fe88fec6fec5fe870179054cfeb8fee5fee6feb80148011a011b0148a4fe5bfe9efe9ffe5b01a40162016201a50002 +00c90000055405d50013001c00b14035090807030a061103040305110404034206040015030415950914950d810b04050603 +1109001c160e050a191904113f140a1c0c041d10fcec32fcc4ec1117391139393931002f3cf4ecd4ec123912391239304b53 +58071005ed071005ed1117395922b2401e01015d40427a130105000501050206030704150015011402160317042500250125 +0226032706260726082609201e3601360246014602680575047505771388068807980698071f5d005d011e01171323032e01 +2b01112311212016151406011133323635342623038d417b3ecdd9bf4a8b78dcca01c80100fc83fd89fe9295959202bc1690 +7efe68017f9662fd8905d5d6d88dba024ffdee878383850000010087ffe304a205f00027007e403c0d0c020e0b021e1f1e08 +0902070a021f1f1e420a0b1e1f0415010015a11494189511049500942591118c281e0a0b1f1b0700221b190e2d0719142228 +10dcc4ecfcece4111239393939310010e4f4e4ec10eef6ee10c6111739304b535807100eed11173907100eed1117395922b2 +0f2901015db61f292f294f29035d01152e012322061514161f011e0115140421222627351e013332363534262f012e013534 +24333216044873cc5fa5b377a67ae2d7feddfee76aef807bec72adbc879a7be2ca0117f569da05a4c53736807663651f192b +d9b6d9e0302fd04546887e6e7c1f182dc0abc6e426000001fffa000004e905d50007004a400e0602950081040140031c0040 +050810d4e4fce431002ff4ec3230014bb00a5458bd00080040000100080008ffc03811373859401300091f00100110021f07 +1009400970099f09095d03211521112311210604effdeecbfdee05d5aafad5052b00000100b2ffe3052905d5001100404016 +0802110b0005950e8c09008112081c0a38011c00411210fc4bb0105458b90000ffc03859ecfcec310010e432f4ec11393939 +393001b61f138f139f13035d133311141633323635113311100021200011b2cbaec3c2aecbfedffee6fee5fedf05d5fc75f0 +d3d3f0038bfc5cfedcfed6012a01240000010044000007a605d5000c017b4049051a0605090a09041a0a09031a0a0b0a021a +01020b0b0a061107080705110405080807021103020c000c011100000c420a050203060300af0b080c0b0a09080605040302 +010b07000d10d4cc173931002f3cec32321739304b5358071005ed071008ed071008ed071005ed071008ed071005ed0705ed +071008ed5922b2000e01015d40f206020605020a000a000a120a2805240a200a3e023e05340a300a4c024d05420a400a5902 +6a026b05670a600a7b027f027c057f05800a960295051d070009020803000406050005000601070408000807090009040a0a +0c000e1a0315041508190c100e200421052006200720082309240a250b200e200e3c023a033504330530083609390b3f0c30 +0e460046014a0240044505400542064207420840084009440a4d0c400e400e58025608590c500e6602670361046205600660 +0760086409640a640b770076017b027803770474057906790777087008780c7f0c7f0e860287038804890585098a0b8f0e97 +049f0eaf0e5b5d005d1333090133090133012309012344cc013a0139e3013a0139cdfe89fefec5fec2fe05d5fb1204eefb12 +04eefa2b0510faf00001fffc000004e705d50008009440280311040504021101020505040211030208000801110000084202 +0300af0602070440051c0040070910d4e4fce4123931002fec3239304b5358071005ed071008ed071008ed071005ed5922b2 +000a01015d403c05021402350230023005300846024002400540085102510551086502840293021016011a031f0a26012903 +37013803400a670168037803700a9f0a0d5d005d03330901330111231104d9019e019bd9fdf0cb05d5fd9a0266fcf2fd3902 +c7000001005c0000051f05d500090090401b03110708070811020302420895008103950508030001420400060a10dc4bb009 +544bb00a545b58b90006ffc03859c4d4e411393931002fecf4ec304b5358071005ed071005ed592201404005020a07180729 +02260738074802470748080905030b08000b16031a08100b2f0b350339083f0b47034a084f0b55035908660369086f0b7703 +78087f0b9f0b165d005d13211501211521350121730495fc5003c7fb3d03b0fc6705d59afb6faa9a0491000100aa04f00289 +066600030031400901b400b3040344010410dcec310010f4ec30004bb009544bb00e545b58bd0004ffc00001000400040040 +381137385909012301016f011a99feba0666fe8a01760002007bffe3042d047b000a002500bc4027191f0b17090e00a91706 +b90e1120861fba1cb923b8118c170c001703180d09080b1f030814452610fcecccd4ec323211393931002fc4e4f4fcf4ec10 +c6ee10ee11391139123930406e301d301e301f3020302130223f27401d401e401f402040214022501d501e501f5020502150 +2250277027851d871e871f8720872185229027a027f0271e301e301f30203021401e401f40204021501e501f50205021601e +601f60206021701e701f70207021801e801f80208021185d015d0122061514163332363d01371123350e0123222635343633 +2135342623220607353e0133321602bedfac816f99b9b8b83fbc88accbfdfb0102a79760b65465be5af3f00233667b6273d9 +b4294cfd81aa6661c1a2bdc0127f8b2e2eaa2727fc0000010071ffe303e7047b0019003f401b00860188040e860d880ab911 +04b917b8118c1a07120d004814451a10fce432ec310010e4f4ec10fef4ee10f5ee30400b0f1b101b801b901ba01b05015d01 +152e0123220615141633323637150e0123220011100021321603e74e9d50b3c6c6b3509d4e4da55dfdfed6012d010655a204 +35ac2b2be3cdcde32b2baa2424013e010e0112013a2300020071ffe3045a06140010001c003840191ab9000e14b905088c0e +b801970317040008024711120b451d10fcecf4ec323231002fece4f4c4ec10c4ee30b6601e801ea01e03015d011133112335 +0e0123220211100033321601141633323635342623220603a2b8b83ab17ccbff00ffcb7cb1fdc7a79292a8a89292a703b602 +5ef9eca86461014401080108014461fe15cbe7e7cbcbe7e700020071ffe3047f047b0014001b007040240015010986088805 +15a90105b90c01bb18b912b80c8c1c1b1502081508004b02120f451c10fcecf4ecc4111239310010e4f4ece410ee10ee10f4 +ee1112393040293f1d701da01dd01df01d053f003f013f023f153f1b052c072f082f092c0a6f006f016f026f156f1b095d71 +015d0115211e0133323637150e01232000111000333200072e0123220607047ffcb20ccdb76ac76263d06bfef4fec70129fc +e20107b802a5889ab90e025e5abec73434ae2a2c0138010a01130143feddc497b4ae9e0000020071fe56045a047b000b0028 +004a4023190c1d0912861316b90f03b92623b827bc09b90fbd1a1d261900080c4706121220452910fcc4ecf4ec323231002f +c4e4ece4f4c4ec10fed5ee1112393930b6602a802aa02a03015d01342623220615141633323617100221222627351e013332 +363d010e0123220211101233321617353303a2a59594a5a59495a5b8fefefa61ac51519e52b5b439b27ccefcfcce7cb239b8 +023dc8dcdcc8c7dcdcebfee2fee91d1eb32c2abdbf5b6362013a01030104013a6263aa00000100ba00000464061400130034 +4019030900030e0106870e11b80c970a010208004e0d09080b461410fcec32f4ec31002f3cecf4c4ec1112173930b2601501 +015d0111231134262322061511231133113e013332160464b87c7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870614 +fd9e6564ef00000200c100000179061400030007002b400e06be04b100bc020501080400460810fc3cec3231002fe4fcec30 +400b1009400950096009700905015d1333112311331523c1b8b8b8b80460fba00614e9000002ffdbfe5601790614000b000f +0044401c0b0207000ebe0c078705bd00bc0cb110081005064f0d01080c00461010fc3cec32e4391239310010ece4f4ec10ee +1112393930400b1011401150116011701105015d13331114062b01353332363511331523c1b8a3b54631694cb8b80460fb8c +d6c09c61990628e9000100ba0000049c0614000a00bc40290811050605071106060503110405040211050504420805020303 +bc009709060501040608010800460b10fcec32d4c4113931002f3cece41739304b5358071004ed071005ed071005ed071004 +ed5922b2100c01015d405f04020a081602270229052b0856026602670873027705820289058e08930296059708a302120905 +0906020b030a072803270428052b062b07400c6803600c8903850489058d068f079a039707aa03a705b607c507d607f703f0 +03f704f0041a5d71005d1333110133090123011123bab90225ebfdae026bf0fdc7b90614fc6901e3fdf4fdac0223fddd0001 +00c100000179061400030022b7009702010800460410fcec31002fec30400d10054005500560057005f00506015d13331123 +c1b8b80614f9ec00000100ba00000464047b001300364019030900030e0106870e11b80cbc0a010208004e0d09080b461410 +fcec32f4ec31002f3ce4f4c4ec1112173930b46015cf1502015d0111231134262322061511231133153e013332160464b87c +7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870460ae6564ef00020071ffe30475047b000b0017004a401306b91200 +b90cb8128c1809120f51031215451810fcecf4ec310010e4f4ec10ee3040233f197b007b067f077f087f097f0a7f0b7b0c7f +0d7f0e7f0f7f107f117b12a019f01911015d012206151416333236353426273200111000232200111000027394acab9593ac +ac93f00112feeef0f1feef011103dfe7c9c9e7e8c8c7e99cfec8feecfeedfec70139011301140138000100ba0000034a047b +001100304014060b0700110b03870eb809bc070a06080008461210fcc4ec3231002fe4f4ecc4d4cc11123930b450139f1302 +015d012e012322061511231133153e0133321617034a1f492c9ca7b9b93aba85132e1c03b41211cbbefdb20460ae66630505 +0001006fffe303c7047b002700e7403c0d0c020e0b531f1e080902070a531f1f1e420a0b1e1f041500860189041486158918 +b91104b925b8118c281e0a0b1f1b0700521b080e07081422452810fcc4ecd4ece4111239393939310010e4f4ec10fef5ee10 +f5ee121739304b535807100eed111739070eed1117395922b2002701015d406d1c0a1c0b1c0c2e092c0a2c0b2c0c3b093b0a +3b0b3b0c0b200020012402280a280b2a132f142f152a16281e281f292029212427860a860b860c860d12000000010202060a +060b030c030d030e030f03100319031a031b031c041d09272f293f295f297f2980299029a029f029185d005d7101152e0123 +22061514161f011e0115140623222627351e013332363534262f012e01353436333216038b4ea85a898962943fc4a5f7d85a +c36c66c661828c65ab40ab98e0ce66b4043fae282854544049210e2a99899cb62323be353559514b50250f2495829eac1e00 +00010037000002f2059e0013003840190e05080f03a9001101bc08870a0b08090204000810120e461410fc3cc4fc3cc43239 +3931002fecf43cc4ec3211393930b2af1501015d01112115211114163b01152322263511233533110177017bfe854b73bdbd +d5a28787059efec28ffda0894e9a9fd202608f013e00000200aeffe30458047b00130014003b401c030900030e0106870e11 +8c0a01bc14b80c0d0908140b4e020800461510fcecf439ec3231002fe4e432f4c4ec1112173930b46f15c01502015d131133 +1114163332363511331123350e0123222601aeb87c7c95adb8b843b175c1c801cf01ba02a6fd619f9fbea4027bfba0ac6663 +f003a80000010056000006350460000c01eb404905550605090a0904550a0903550a0b0a025501020b0b0a06110708070511 +0405080807021103020c000c011100000c420a050203060300bf0b080c0b0a09080605040302010b07000d10d44bb00a544b +b011545b4bb012545b4bb013545b4bb00b545b58b9000000403859014bb00c544bb00d545b4bb010545b58b90000ffc03859 +cc173931002f3cec32321739304b5358071005ed071008ed071008ed071005ed071008ed071005ed0705ed071008ed592201 +40ff050216021605220a350a49024905460a400a5b025b05550a500a6e026e05660a79027f0279057f05870299029805940a +bc02bc05ce02c703cf051d0502090306040b050a080b09040b050c1502190316041a051b081b09140b150c25002501230227 +03210425052206220725082709240a210b230c390336043608390c300e460248034604400442054006400740084409440a44 +0b400e400e560056015602500451055206520750085309540a550b6300640165026a0365046a056a066a076e09610b670c6f +0e7500750179027d0378047d057a067f067a077f07780879097f097b0a760b7d0c870288058f0e97009701940293039c049b +05980698079908402f960c9f0ea600a601a402a403ab04ab05a906a907ab08a40caf0eb502b103bd04bb05b809bf0ec402c3 +03cc04ca05795d005d13331b01331b013301230b012356b8e6e5d9e6e5b8fedbd9f1f2d90460fc96036afc96036afba00396 +fc6a0001003dfe56047f0460000f018b40430708020911000f0a110b0a00000f0e110f000f0d110c0d00000f0d110e0d0a0b +0a0c110b0b0a420d0b0910000b058703bd0e0bbc100e0d0c0a09060300080f040f0b1010d44bb00a544bb008545b58b9000b +004038594bb0145458b9000bffc03859c4c4111739310010e432f4ec113911391239304b5358071005ed071008ed071008ed +071005ed071008ed0705ed173259220140f0060005080609030d160a170d100d230d350d490a4f0a4e0d5a095a0a6a0a870d +800d930d120a000a09060b050c0b0e0b0f1701150210041005170a140b140c1a0e1a0f270024012402200420052908280925 +0a240b240c270d2a0e2a0f201137003501350230043005380a360b360c380d390e390f301141004001400240034004400540 +06400740084209450a470d490e490f40115400510151025503500450055606550756085709570a550b550c590e590f501166 +016602680a690e690f60117b08780e780f89008a09850b850c890d890e890f9909950b950c9a0e9a0fa40ba40cab0eab0fb0 +11cf11df11ff11655d005d050e012b01353332363f01013309013302934e947c936c4c543321fe3bc3015e015ec368c87a9a +488654044efc94036c0000010058000003db04600009009d401a081102030203110708074208a900bc03a905080301000401 +060a10dc4bb00b544bb00c545b58b90006ffc038594bb0135458b9000600403859c432c411393931002fecf4ec304b535807 +1005ed071005ed592201404205021602260247024907050b080f0b18031b082b08200b36033908300b400140024503400440 +054308570359085f0b6001600266036004600562087f0b800baf0b1b5d005d1321150121152135012171036afd4c02b4fc7d +02b4fd650460a8fcdb93a8032500000200d7054603290610000300070092400e0602ce0400cd080164000564040810dcfcd4 +ec310010fc3cec3230004bb00a544bb00d545b58bd00080040000100080008ffc03811373859014bb00c544bb00d545b4bb0 +0e545b4bb017545b58bd0008ffc000010008000800403811373859014bb00f544bb019545b58bd00080040000100080008ff +c03811373859401160016002600560067001700270057006085d0133152325331523025ecbcbfe79cbcb0610cacaca000001 +00d50562032b05f60003002fb702ef00ee0401000410d4cc310010fcec30004bb009544bb00e545b58bd0004ffc000010004 +00040040381137385913211521d50256fdaa05f694000001017304ee0352066600030031400902b400b3040344010410d4ec +310010f4ec30004bb009544bb00e545b58bd0004ffc00001000400040040381137385901330123028bc7feba990666fe8800 +000100db024801ae034600030012b7028300040119000410d4ec310010d4ec3013331523dbd3d30346fe00010123fe7502c1 +00000013001f400e09060a0df306001300102703091410dcd4ecd4cc31002fd4fcc4123930211e0115140623222627351e01 +333236353426270254373678762e572b224a2f3b3c2b2d3e6930595b0c0c83110f302e1e573d0003001000000568076d000b +000e002100cb40540c110d0c1b1c1b0e111c1b1e111c1b1d111c1c1b0d11210f210c110e0c0f0f2120110f211f11210f2142 +0c1b0f0d0903c115091e950d098e201c1e1d1c18201f210d12060e180c061b0056181c0f0656121c212210d4c4d4ec3210d4 +ee32113911391112391139391112393931002f3ce6d6ee10d4ee1112393939304b5358071005ed0705ed071008ed071005ed +071005ed0705ed0705ed071008ed5922b2202301015d40201a0c730c9b0c03070f081b5023660d690e750d7b0e791c791d76 +20762180230c5d005d013426232206151416333236030121012e01353436333216151406070123032103230354593f405758 +3f3f5998fef00221fe583d3e9f7372a13f3c0214d288fd5f88d5065a3f5957413f5858fef3fd19034e29734973a0a1724676 +29fa8b017ffe8100000200080000074805d5000f00130087403911110e0f0e10110f0f0e0d110f0e0c110e0f0e420595030b +951101951095008111079503ad0d0911100f0d0c050e0a00040806021c120a0e1410d4d43cec32d4c4c41112173931002f3c +ececc4f4ecec10ee10ee304b5358071005ed0705ed071005ed071005ed5922b2801501015d4013671177107711860c851096 +119015a015bf15095d01152111211521112115211121032301170121110735fd1b02c7fd3902f8fc3dfdf0a0cd02718bfeb6 +01cb05d5aafe46aafde3aa017ffe8105d59efcf00310ffff0073fe75052705f012260006000010070030012d0000ffff00c9 +0000048b076b122600080000100701d6049e0175ffff00c90000048b076b122600080000100701d4049e0175ffff00c90000 +048b076d122600080000110701d7049e017500074003400c015d3100ffff00c90000048b074e122600080000110701d3049e +017500094005400c4010025d3100ffff003b000001ba076b1226000b0000100701d6032f0175ffff00a20000021f076b1226 +000b0000100701d4032f0175fffffffe00000260076d1226000b0000110701d7032f01750008b401060a00072b31ffff0006 +00000258074e1226000b0000110701d3032f01750008b4000a0701072b310002000a000005ba05d5000c0019006740201009 +a90b0d95008112950e0b0707011913040f0d161904320a110d1c0800791a10f43cec32c4f4ec10c4173931002fc632eef6ee +10ee32304028201b7f1bb01b039f099f0a9f0b9f0c9f0e9f0f9f109f11bf09bf0abf0bbf0cbf0ebf0fbf10bf11105d015d13 +21200011100029011123353313112115211133200011100021d301a001b10196fe69fe50fe60c9c9cb0150feb0f30135011f +fee1fecb05d5fe97fe80fe7efe9602bc9001e3fe1d90fdea0118012e012c0117ffff00c900000533075e1226000f00001107 +01d504fe01750014b400132204072b400930133f2210131f22045d31ffff0073ffe305d9076b122600100000100701d60527 +0175ffff0073ffe305d9076b122600100000100701d405270175ffff0073ffe305d9076d122600100000110701d705270175 +0010b40f1a1e15072b40051f1a101e025d31ffff0073ffe305d9075e122600100000110701d5052701750018b40321300907 +2b400d30213f3020212f3010211f30065d31ffff0073ffe305d9074e122600100000110701d3052701750014b4031f1a0907 +2b4009401f4f1a101f1f1a045d3100010119003f059c04c5000b0085404d0a9c0b0a070807099c080807049c030407070605 +9c060706049c0504010201039c0202010b9c0001000a9c090a010100420a080706040201000805030b090c0b0a0907050403 +0108020008060c10d43ccc321739310010d43ccc321739304b5358071008ed071005ed071005ed071008ed071005ed071008 +ed071005ed071008ed59220902070901270901370901059cfe3701c977fe35fe357601c8fe387601cb01cb044cfe35fe3779 +01cbfe357901c901cb79fe3501cb00030066ffba05e5061700090013002b009e403c1d1f1a0d2b2c130a0100040d29262014 +0d042a261e1a0495260d951a91268c2c2b2c2a141710201e23130a0100041d2910071f07192333101917102c10fcecfcecc0 +111239391739123939111239391139310010e4f4ec10ee10c010c011123939123912173912391112393930402a57005a1557 +1955216a1565217b15761c7521094613590056136a006413641c6a287c007313761c7a280b5d015d09011e01333200113426 +272e012322001114161707260235100021321617371707161215100021222627072704b6fd333ea15fdc010127793da15fdc +fefd2727864e4f0179013b82dd57a266aa4e50fe88fec680dd5ba2670458fcb240430148011a70b8b84043feb8fee570bc44 +9e660108a0016201a54d4bbf59c667fef69efe9ffe5b4b4bbf58ffff00b2ffe30529076b122600140000100701d604ee0175 +ffff00b2ffe30529076b122600140000100701d404ee0175ffff00b2ffe30529076d122600140000110701d704ee01750014 +b40a141800072b40092f1420181f141018045d31ffff00b2ffe30529074e122600140000110701d304ee0175001cb4011914 +09072b401150195f1440194f1420192f1410191f14085d31fffffffc000004e7076b122600160000100701d4047301750002 +00c90000048d05d5000c0015003d401b0e95090d9502f600810b150f090304011219063f0d0a011c00041610fcec3232fcec +11173931002ff4fcecd4ec3040090f171f173f175f1704015d1333113332041514042b011123131133323635342623c9cafe +fb0101fefffbfecacafe8d9a998e05d5fef8e1dcdce2feae0427fdd192868691000100baffe304ac0614002f009a40302d27 +210c04060d2000042a1686171ab9132ab90397138c2e0c090d1d2021270908242708061d082410162d081000463010fcc4fc +cc10c6eed4ee10ee1139391239123931002fe4feee10fed5ee12173917393040400f050f060f070f270f288a0c8a0d070a06 +0a070a0b0a0c0a0d0a1f0d200a210c220426190d191f19203a203a214d1f4d20492149226a1f6a20a506a507a620185d015d +133436333216170e011514161f011e0115140623222627351e013332363534262f012e01353436372e01232206151123baef +dad0db0397a83a4139a660e1d3408849508c4174783b655c6057a7970883718288bb0471c8dbe8e00873602f512a256a8e64 +acb71918a41e1d5f5b3f543e373b875b7fac1d67708b83fb9300ffff007bffe3042d0666122600190000110600185200000b +40073f262f261f26035d3100ffff007bffe3042d06661226001900001106002e5200000b40073f262f261f26035d3100ffff +007bffe3042d0666122600190000110601bf52000008b40b282c14072b31ffff007bffe3042d0637122600190000110601c4 +52000014b4142e3c0b072b4009202e2f3c102e1f3c045d31ffff007bffe3042d06101226001900001106002c52000020b414 +2d280b072b40157f286f28502d5f28402d4f28302d3f28002d0f280a5d31ffff007bffe3042d0706122600190000110601c2 +52000025400e262c142c260b0732381438320b072b10c42b10c4310040093f353f2f0f350f2f045d30000003007bffe3076f +047b00060033003e01034043272d253d0e0d0034a925168615881200a90e3a12b91c192e862dba2a03b90ebb07310ab81f19 +8c253f343726060f0025371c07260f1500080d3d26080f2d370822453f10fcecccd4fc3cd4ecc41112393911391112391112 +39310010c4e432f43cc4e4fc3cf4ec10c4ee3210ee10f4ee10ee11391139111239304081302b302c302d302e302f3030402b +402c402d402e402f4030502b502c502d502e502f5030852b853080409040a040b040c040d040e040e040f0401d3f003f063f +0d3f0e3f0f05302c302d302e302f402c402d402e402f502c502d502e502f6f006f066f0d6f0e6f0f602c602d602e602f702c +702d702e702f802c802d802e802f1d5d71015d012e0123220607033e013332001d01211e0133323637150e01232226270e01 +232226353436332135342623220607353e013332160322061514163332363d0106b601a58999b90e444ad484e20108fcb20c +ccb768c86464d06aa7f84d49d88fbdd2fdfb0102a79760b65465be5a8ed5efdfac816f99b9029497b4ae9e01305a5efeddfa +5abfc83535ae2a2c79777878bba8bdc0127f8b2e2eaa272760fe18667b6273d9b429ffff0071fe7503e7047b1226001a0000 +10070030008f0000ffff0071ffe3047f06661226001c000010070018008b0000ffff0071ffe3047f06661226001c00001007 +002e008b0000ffff0071ffe3047f06661226001c0000110701bf008b00000008b4151e221b072b31ffff0071ffe3047f0610 +1226001c00001107002c008b0000000740034020015d3100ffffffc7000001a6066610270018ff1d00001206009d0000ffff +00900000026f06661027002eff1d00001206009d0000ffffffde0000025c06661226009d0000110701bfff1d00000008b401 +070b00072b31fffffff40000024606101226009d00001107002cff1d00000008b4000b0801072b3100020071ffe304750614 +000e00280127405e257b26251e231e247b23231e0f7b231e287b27281e231e262728272524252828272223221f201f212020 +1f42282726252221201f08231e030f2303b91b09b9158c1b23b1292627120c212018282523221f051e0f060c121251061218 +452910fcecf4ec113939173912393911123939310010ecc4f4ec10ee12391239121739304b535807100ec9071008c9071008 +c907100ec9071008ed070eed071005ed071008ed5922b23f2a01015d407616252b1f28222f232f2429252d262d272a283625 +462558205821602060216622752075217522132523252426262627272836243625462445255a205a21622062217f007f017f +027a037b097f0a7f0b7f0c7f0d7f0e7f0f7f107f117f127f137f147b157a1b7a1c7f1d7f1e762076217822a02af02a275d00 +5d012e0123220615141633323635342613161215140023220011340033321617270527252733172517050346325829a7b9ae +9291ae36097e72fee4e6e7fee50114dd12342a9ffec1210119b5e47f014d21fed903931110d8c3bcdedebc7abc01268ffee0 +adfffec9013700fffa01370505b46b635ccc916f6162ffff00ba000004640637122600230000100701c400980000ffff0071 +ffe304750666122600240000100600187300ffff0071ffe3047506661226002400001006002e7300ffff0071ffe304750666 +122600240000110601bf73000008b40f1a1e15072b31ffff0071ffe304750637122600240000110601c473000014b415202e +0f072b400920202f2e10201f2e045d31ffff0071ffe3047506101226002400001106002c73000014b4031f1a09072b400940 +1f4f1a301f3f1a045d31000300d9009605db046f00030007000b0029401400ea0206ea0402089c040a0c090501720400080c +10dcd43cfc3cc4310010d4c4fcc410ee10ee3001331523113315230121152102dff6f6f6f6fdfa0502fafe046ff6fe12f502 +41aa00030048ffa2049c04bc00090013002b00e4403c2b2c261f1d1a130a0100040d292620140d042a261e1a04b9260db91a +b8268c2c2b2c2a141710201e23130a01000410071f1d0712235129101217452c10fcec32f4ec32c011121739123939111239 +391139310010e4f4ec10ee10c010c011123939123912173911393911123930407028013f2d5914561c551d56206a1566217f +007b047f057f067f077f087f097f0a7f0b7f0c7b0d7a157b1a7f1b7f1c7f1d7f1e7f1f7f207b217f227f237f247f257b269b +199525a819a02df02d2659005613551d5a2869006613651c6a287a007413761c7a28891e95189a24a218ad24115d015d0901 +1e01333236353426272e0123220615141617072e01351000333216173717071e011510002322262707270389fe1929674193 +ac145c2a673e97a913147d36360111f15d9f438b5f923536feeef060a13f8b600321fdb02a28e8c84f759a2929ebd3486e2e +974dc577011401383334a84fb34dc678feedfec73433a84effff00aeffe304580666122600280000100600187b00ffff00ae +ffe3045806661226002800001006002e7b00ffff00aeffe304580666122600280000110601bf7b000008b40b171b01072b31 +ffff00aeffe3045806101226002800001106002c7b000018b4021b180a072b400d401b4f18301b3f18001b0f18065d31ffff +003dfe56047f06661226002a00001006002e5e00000200bafe5604a406140010001c003e401b14b905081ab9000e8c08b801 +bd03971d11120b471704000802461d10fcec3232f4ec310010ece4e4f4c4ec10c6ee304009601e801ea01ee01e04015d2511 +231133113e013332001110022322260134262322061514163332360173b9b93ab17bcc00ffffcc7bb10238a79292a7a79292 +a7a8fdae07befda26461febcfef8fef8febc6101ebcbe7e7cbcbe7e7ffff003dfe56047f06101226002a00001106002c5e00 +0016b418171219072b400b30173f1220172f121f12055d31ffff00100000056807311027002d00bc013b1306000500000010 +b40e030209072b400540034f02025d31ffff007bffe3042d05f61026002d4a001306001900000010b41803020f072b40056f +027f03025d31ffff0010000005680792102701c100ce014a1306000500000012b418000813072b310040056f006f08025d30 +ffff007bffe3042d061f102601c14fd71306001900000008b422000819072b31ffff0010fe7505a505d51226000500001007 +01c302e40000ffff007bfe750480047b122600190000100701c301bf0000ffff0073ffe30527076b122600060000100701d4 +052d0175ffff0071ffe303e706661226001a00001007002e00890000ffff0073ffe30527076d102701d7054c017513060006 +00000009b204041e103c3d2f3100ffff0071ffe303e706661226001a0000100701bf00a40000ffff0073ffe3052707501027 +01db054c0175120600060000ffff0071ffe303e70614102701c604a400001206001a0000ffff0073ffe30527076d12260006 +0000110701d8052d0175000740031f1d015d3100ffff0071ffe303e706661226001a0000100701c000890000ffff00c90000 +05b0076d102701d804ec0175120600070000ffff0071ffe305db06141226001b0000110701d205140000000b40075f1d3f1d +1f1d035d3100ffff000a000005ba05d51006003c000000020071ffe304f4061400180024004a40240703d30901f922b90016 +1cb90d108c16b805970b021f0c04030008080a0647191213452510fcecf43cc4fc173cc431002fece4f4c4ec10c4eefd3cee +3230b660268026a02603015d01112135213533153315231123350e0123220211100033321601141633323635342623220603 +a2feba0146b89a9ab83ab17ccbff00ffcb7cb1fdc7a79292a8a89292a703b6014e7d93937dfafca864610144010801080144 +61fe15cbe7e7cbcbe7e7ffff00c90000048b07331226000800001007002d00a1013dffff0071ffe3047f05f61027002d0096 +00001306001c0000000740037000015d3100ffff00c90000048b076d102701da04a10175130600080000000740034000015d +3100ffff0071ffe3047f0648102701c1009600001306001c0000000740037000015d3100ffff00c90000048b0750102701db +049e0175120600080000ffff0071ffe3047f0614102701c6049600001206001c0000ffff00c9fe75048d05d5122600080000 +100701c301cc0000ffff0071fe75047f047b1226001c0000100701c301780000ffff00c90000048b07671226000800001107 +01d804a6016f00074003400c015d3100ffff0071ffe3047f06611226001c0000110701c00094fffb0010b400211d0f072b40 +050f21001d025d31ffff0073ffe3058b076d102701d7055c01751306000900000009b2040415103c3d2f3100ffff0071fe56 +045a0666102601bf68001306001d00000009b204040a103c3d2f3100ffff0073ffe3058b076d122600090000100701da051b +0175ffff0071fe56045a06481226001d0000100701c1008b0000ffff0073ffe3058b0750102701db055c0175130600090000 +00080040033f00015d30ffff0071fe56045a0614102701c6046a00001206001d0000ffff0073fe01058b05f0102701cc055e +ffed120600090000ffff0071fe56045a0634102701ca03e0010c1206001d0000ffff00c90000053b076d102701d705020175 +1306000a00000014b40c020607072b40092f0220061f021006045d31ffffffe500000464076d102701d7031601751306001e +0000002ab414020613072b31004bb00e5158bb0014ffc00013ffc0383859400d901490138014801340144013065d000200c9 +0000068b05d500130017003a401e060212950914110c9515ad0400810e0a070c17041c0538120d14011c001810dcec3232cc +fcec3232cc31002f3ce432fcecdc3232ec3232300133152135331533152311231121112311233533171521350171ca02deca +a8a8cafd22caa8a8ca02de05d5e0e0e0a4fbaf02c7fd390451a4a4e0e000000100780000049f0614001b003e402103090003 +16010e12870d1506871619b810970a010208004e130e11150908100b1c10dc32ec3232ccccf4ec31002f3cecf4c4ecdc32ec +32111217393001112311342623220615112311233533353315211521113e01333216049fb87c7c95acb97d7db90160fea042 +b375c1c602a4fd5c029e9f9ebea4fd8704f6a47a7aa4febc6564ef00ffffffe400000278075e102701d5032e01751306000b +00000008b41e09181f072b31ffffffd3000002670637102701c4ff1d00001306009d00000008b41c08161d072b31ffff0003 +0000025907311027002dff2e013b1306000b00000008b404030205072b31fffffff20000024805f51027002dff1dffff1306 +009d00000008b404030205072b31fffffff500000267076d102701da032e01751306000b00000008b40e00080f072b31ffff +ffe4000002560648102701c1ff1d00001306009d00000008b40e00080f072b31ffff00b0fe75022505d5102701c3ff640000 +1206000b0000ffff0096fe75020b0614102701c3ff4a00001206001f0000ffff00c90000019507501226000b0000110701db +032f01750013b306010700103c103c3100b43f073f06025d3000000200c100000179047b00030004002c400b04b800bf0204 +010800460510fcec3931002fece43040110404340444041006400650066006700608015d1333112313c1b8b85c0460fba004 +7b00ffff00c9fe6603ef05d51027000c025c00001106000b00000008400311040110ec31ffff00c1fe5603b1061410270020 +023800001106001f00000008400319460110ec31ffffff96fe66025f076d102701d7032e01751306000c00000008b4080206 +07072b31ffffffdbfe56025c0666102701bfff1d0000130601a300000008b408020607072b31ffff00c9fe1e056a05d51027 +01cc051b000a1206000d0000ffff00bafe1e049c0614102701cc04ac000a120600210000000100ba0000049c0460000a00bb +4028081105060507110606050311040504021105050442080502030300bc09060501040608010800460b10fcec32d4c41139 +31002f3cec321739304b5358071004ed071005ed071005ed071004ed5922b2100c01015d405f04020a081602270229052b08 +56026602670873027705820289058e08930296059708a3021209050906020b030a072803270428052b062b07400c6803600c +8903850489058d068f079a039707aa03a705b607c507d607f703f003f704f0041a5d71005d1333110133090123011123bab9 +0225ebfdae026bf0fdc7b90460fe1b01e5fdf2fdae0221fddf00ffff00c90000046a076c102701d4036e01761206000e0000 +ffff00c10000024a076c102701d4035a0176130600220000001eb10304103c31004bb00e5158b900000040385940079f008f +004f00035d30ffff00c9fe1e046a05d5102701cc049b000a1206000e0000ffff0088fe1e01ad0614102701cc031e000a1306 +00220000000740034000015d3100ffff00c90000046a05d5102701d2029fffc31206000e0000ffff00c10000030006141027 +01d202390002110600220000000940058f001f00025d3100ffff00c90000046a05d51027002f023100771206000e0000ffff +00c10000028406141027002f00d6007311060022000000174bb00d514bb011534bb018515a5b58b900000040385931000001 +fff20000047505d5000d003f401e0c0b0a040302060006950081080304010b0e000405011c0c073a0900790e10f43cecc4fc +3cc411123911123931002fe4ec11173930b4300f500f02015d1333112517011121152111072737d3cb013950fe7702d7fc5e +944de105d5fd98db6ffeeefde3aa023b6a6e9e0000010002000002480614000b005e401a0a09080403020600970603040109 +0a00047a0501080a7a07000c10d43ce4fc3ce411123911123931002fec173930014bb0105458bd000c00400001000c000cff +c038113738594013100d400d500d600d73047a0a700de00df00d095d133311371707112311072737c7b87d4cc9b87b4ac506 +14fda65a6a8dfce3029a586a8d00ffff00c900000533076c102701d404c501761306000f0000000740034f00015d3100ffff +00ba00000464066d1026002e4207130600230000000940053f004f00025d3100ffff00c9fe1e053305d5102701cc0500000a +1206000f0000ffff00bafe1e0464047b102701cc0490000a120600230000ffff00c900000533075f1226000f0000110701d8 +04f501670014b4040f0b00072b40092f0f200b1f0f100b045d31ffff00ba000004640666122600230000110701c0008d0000 +0010b40019150c072b40050f190015025d31ffff00cd000005b905d51027002301550000100601be1b00000100c9fe560519 +05f0001c003b400d191612181c1c120a051c07411d10fc4bb0105458b90007ffc03859ec32d4fccc113100400c199516b007 +02950e910881072fe4f4ec10f4ec30011021220615112311331536373633321219011407062b0135333236350450fecdb3d7 +caca4e696a99e3e95152b55731664f037f01acffdefcb205d5f1864343fec1feccfc6fd561609c5aa000000100bafe560464 +047b001f003b401c0d13000318150787061087181cb816bc15070d08004e13170816462010fcec32f4ecc431002fe4f4c4ec +d4ec1112173930b46021cf2102015d01111407062b0135333237363511342623220615112311331536373633321716046452 +51b5fee96926267c7c95acb9b942595a75c1636302a4fd48d660609c30319902b29f9ebea4fd870460ae653232777800ffff +0073ffe305d907311027002d0127013b1306001000000010b40d020307072b40051f021003025d31ffff0071ffe3047505f5 +1026002d73ff1306002400000008b413020319072b31ffff0073ffe305d9076d102701da052701751306001000000010b411 +000817072b400510001f08025d31ffff0071ffe304750648102601c173001306002400000008b41d080023072b31ffff0073 +ffe305d9076b102701dc05270175120600100000ffff0071ffe304750666102701c500a00000120600240000000200730000 +080c05d500100019003b401f059503110195008118079503ad091812100a1506021c1100040815190d101a10fcecd4c4c4d4 +ec32123939393931002fecec32f4ec3210ee30011521112115211121152120001110002117232000111000213307fafd1a02 +c7fd3902f8fbd7fe4ffe4101bf01b16781febffec0014001418105d5aafe46aafde3aa017c0170016d017caafee1fee0fedf +fedf00030071ffe307c3047b0006002700330084403107080010860f880c00a9082e0cb916132803b908bb22251fb819138c +340600162231090f0008074b311209512b121c453410fcecf4fcf4ecc4111239391239310010e432f43cc4e4ec3210c4ee32 +10ee10f4ee1112393040253f355f3570359f35cf35d035f035073f003f063f073f083f09056f006f066f076f086f09055d71 +015d012e01232206070515211e0133323637150e01232226270e01232200111000333216173e013332002522061514163332 +36353426070a02a48999b90e0348fcb20cccb76ac86264d06aa0f25147d18cf1feef0111f18cd3424ee88fe20108fab094ac +ab9593acac029498b3ae9e355abec73434ae2a2c6e6d6e6d01390113011401386f6c6b70fedd87e7c9c9e7e8c8c7e900ffff +00c900000554076c102701d404950176120600110000ffff00ba00000394066d1026002e4207120600250000ffff00c9fe1e +055405d5102701cc0510000a120600110000ffff0082fe1e034a047b102701cc0318000a120600250000ffff00c900000554 +075f122600110000110701d8047d016700080040035f1d015d30ffff00ba0000035a0666122600250000110601c01b000010 +b411171309072b40050f170013025d31ffff0087ffe304a2076c102701d404950176120600120000ffff006fffe303c7066d +1026002e4207120600260000ffff0087ffe304a2076d102701d704930175130600120000000bb404201529291049633a3100 +ffff006fffe303c70666102601bf2500130600260000000bb404201529291049633a3100ffff0087fe7504a205f012260012 +000010070030008b0000ffff006ffe7503c7047b122600260000100600301700ffff0087ffe304a2076d1226001200001107 +01d8048b0175000bb42b200e22221049633a3100ffff006fffe303c70666122600260000110701c704270000000bb42b200e +22221049633a3100fffffffafe7504e905d5102600305000120600130000ffff0037fe7502f2059e10260030e10012060027 +0000fffffffa000004e9075f122600130000110701d8047301670010b4010d0900072b310040035f08015d30ffff00370000 +02fe0682122600270000110701d202370070000740038f14015d31000001fffa000004e905d5000f00464018070b95040c09 +030f9500810905014007031c0c00400a0e1010d43ce4ccfc3ce4cc31002ff4ec3210d43cec323001401300111f0010011002 +1f0f1011401170119f11095d032115211121152111231121352111210604effdee0109fef7cbfef70109fdee05d5aafdc0aa +fdbf0241aa02400000010037000002f2059e001d0043401f0816a90517041aa900011bbc0d8710100d0e020608040008171b +15191d461e10fc3c3cc432fc3c3cc4c432393931002fecf43cc4fc3cdc3cec3230b2af1f01015d0111211521152115211514 +17163b0115232227263d0123353335233533110177017bfe85017bfe85252673bdbdd5515187878787059efec28fe98ee989 +27279a504fd2e98ee98f013effff00b2ffe30529075e102701d504ee01751306001400000010b41f091827072b400510091f +18025d31ffff00aeffe304580637102701c4008300001306002800000008b41e081626072b31ffff00b2ffe3052907311027 +002d00ee013b1306001400000014b40503020d072b40092f0220031f021003045d31ffff00aeffe3045805f51027002d0083 +ffff1306002800000008b40603020e072b31ffff00b2ffe30529076d102701da04ee01751306001400000010b40f00081707 +2b400510001f08025d31ffff00aeffe304580648102701c1008300001306002800000008b410000818072b31ffff00b2ffe3 +0529076f122600140000100701c200f00069ffff00aeffe3045806ca122600280000110601c27cc40009400540154021025d +3100ffff00b2ffe30529076b102701dc04ee0175120600140000ffff00aeffe3045e0666102701c500b00000120600280000 +ffff00b2fe75052905d5122600140000100701c300fa0000ffff00aefe7504e8047b122600280000100701c302270000ffff +0044000007a60774102701d705f5017c1306001500000008b415020614072b31ffff005600000635066d102701bf01450007 +1306002900000008b415020614072b31fffffffc000004e70774102701d70472017c1306001600000008b40b020607072b31 +ffff003dfe56047f066d102601bf5e071306002a00000008b418020617072b31fffffffc000004e7074e1226001600001107 +01d3047301750008b400100b04072b31ffff005c0000051f076c102701d404950176120600170000ffff0058000003db066d +1026002e42071206002b0000ffff005c0000051f0750102701db04be0175120600170000ffff0058000003db0614102701c6 +041700001306002b0000000e0140094f0a5f0aaf0adf0a045d31ffff005c0000051f076d122600170000100701d804be0175 +ffff0058000003db06661226002b0000110601c01b000010b4010f0b00072b40050f0f000b025d310001002f000002f80614 +0010002340120b870a970102a905bc010a10080406024c1110fc3cccfccc31002ff4ec10f4ec302123112335333534363b01 +1523220706150198b9b0b0aebdaeb063272603d18f4ebbab9928296700020020ffe304a40614000f002c0044402504b91014 +0cb9201c8c14b8222925a92c242797222e45001218472a20062c2808252327462d10fc3cccec323232ccf4ecec31002ff4dc +3cec3210e4f4c4ec10c6ee300134272623220706151417163332373601363736333217161110070623222726271523112335 +3335331521152103e5535492925453535492925453fd8e3a59587bcc7f80807fcc7b58593ab99a9ab90145febb022fcb7473 +7374cbcb747373740252643031a2a2fef8fef8a2a2313064a805047d93937d000003ff970000055005d50008001100290043 +40231900950a0995128101950aad1f110b080213191f05000e1c1605191c2e09001c12042a10fcec32fcecd4ec1117393939 +31002fececf4ec10ee3930b20f2201015d01112132363534262301112132363534262325213216151406071e011514042321 +1122061d012335343601f70144a39d9da3febc012b94919194fe0b0204e7fa807c95a5fef0fbfde884769cc002c9fddd878b +8c850266fe3e6f727170a6c0b189a21420cb98c8da05305f693146b5a300ffff00c9000004ec05d5120601d00000000200ba +ffe304a40614001600260038401f1bb9000423b9100c8c04b81216a913971228451417120847101f160813462710fcec3232 +f4ecc4ec31002ff4ec10e4f4c4ec10c6ee300136373633321716111007062322272627152311211525013427262322070615 +1417163332373601733a59587bcc7f80807fcc7b58593ab9034efd6b027253549292545353549292545303b6643031a2a2fe +f8fef8a2a2313064a80614a601fcc0cb74737374cbcb7473737400020000000004ec05d5000a00170033400c170b19001910 +2e050b1c15162fdcec32fcecc410cc31400905950cad0b81069514002fece4f4ecb315150b141112392f3001342726232111 +213237360111213204151404232111230104174f4ea3febc0144a34e4ffd7c014efb0110fef0fbfde8c9013801b78b4443fd +dd444304a8fd9adadeddda044401910000020000ffe304a406150012001e003e400d111220131206470d1912080f102fdcec +3232f4ecc410cc31400e0016b903b80e0c1cb9098c11970e002fe4f4ecc410f4ecc4b30f0f110e1112392f30013e01333200 +1110022322262715231123013301342623220615141633323601733ab17bcc00ffffcc7bb13ab9ba0122510272a79292a7a7 +9292a703b66461febcfef8fef8febc6164a8044401d1fc1acbe7e7cbcbe7e70000010073ffe3052705f000190030401b1986 +0088169503911a0d860c881095098c1a1b10131906300d001a10dc3cf4ecec310010f4ecf4ec10f4ecf4ec30133e01332000 +11100021222627351e01332000111000212206077368ed8601530186fe7afead84ed6a66e78201000110fef0ff0082e76605 +624747fe61fe98fe99fe614848d35f5e01390127012801395e5f00010073ffe3065a0764002400444022219520250da10eae +0a951101a100ae04951791118c25200719141b110d003014102510fcfc32ec10ecc4310010e4f4ecf4ec10eef6ee10dcec30 +b40f261f2602015d01152e0123200011100021323637150e0123200011100021321716173637363b0115232206052766e782 +ff00fef00110010082e7666aed84feadfe7a01860153609c0d0c105366e34d3f866e0562d55f5efec7fed8fed9fec75e5fd3 +4848019f01670168019f240304c3627aaa9600010071ffe304cc06140022004e402400860188040e860d880ab91104b917b8 +118c2301871e972307121419081e0d004814452310fcf432ccec10ec310010f4ec10e4f4ec10fef4ee10f5ee30400b0f2410 +2480249024a02405015d01152e0123220615141633323637150e012322001110002132173534363b011523220603e74e9d50 +b3c6c6b3509d4e4da55dfdfed6012d01064746a1b54530694c047ef52b2be3cdcde32b2baa2424013e010e0112013a0c0fd6 +c09c6100ffff000a000005ba05d51006003c00000002ff970000061405d50008001a002e4015009509810195100802100a00 +05190d32001c09041b10fcecf4ec113939393931002fecf4ec30b2601301015d011133200011100021252120001110002901 +1122061d012335343601f7f40135011ffee1fecbfe42019f01b20196fe68fe50fe6184769cc0052ffb770118012e012c0117 +a6fe97fe80fe7efe9605305f693146b5a300000200c9000004ec05d500070014002e400c160804131c0a2e00190e101510fc +ecf4ec32c4c431400c139509810a049512ad03950a002fecf4ec10f4ec300110290111212206112111212224353424332111 +21019e01400144febca39d034efde8fbfef00110fb014efd7c01b7feef0223870393fa2bdadeddda01c000020071ffe3045a +06140012001e003f401d1cb9110e16b905088c0eb80312870197031904110802470013120b451f10fcecc4f4ec323231002f +fcec10e4f4c4ec10c4ee30b660208020a02003015d0135211123350e01232202111000333216171101141633323635342623 +2206010d034db83ab17ccbff00ffcb7cb13afd8da79292a8a89292a7056ea6f9eca864610144010801080144616401b9fcc0 +cbe7e7cbcbe7e70000020071fe560474046300190027005440140d0c0b202945170b12021a12175106201211452810fcecc4 +f4b27f17015decd4ec10ec1112393900400e0d0c1d09060709b9041db914b62810f4ecd4fcd4cc1112393940060025530c0d +0c070e10ec39313025161510212227351633323534252627261110003332000314020336262322061514161716173e01036b +9dfe47dd7866f6f6fef8d0758e0112eff00113019b2701ab9494acbc7e4033636e424f8dfef0469946755c30257087010f01 +0f0139fec7feed9cfefc01a0cbe5e8c3c2c70b060e2adc00000100830000044505d5000b002b40090d05091c000b07020c10 +dcc4c4d4ec32c431400c0a950b8102069507ad039502002fecf4ec10f4ec300111213521112135211121350445fc3e02f8fd +3902c7fd1a05d5fa2baa021daa01baaa00020075ffe305d905f00013001a0044402601140008a107ae040095141795110095 +14ad04950b91118c1b01141a1a190f3314190700101b10fcc4ecf4ec111239310010e4f4ecf4e410ee10ee10f4ee11123930 +13211000212206073536243320001110002120003716003332003775048ffeedfeee8bfc706f010792015e018bfe88fec6fe +b7fe97dc0d00ffcaca00ff0d030c010c0132605fd74648fe67fe92fe9ffe5b01b7ccc3fee4011cc3000100a4ffe3047b05f0 +0028004040240a8609880d9506912900169513ad291f8620881c95238c292a14091f101903191926102910fcecd4ecd4c4c4 +cc310010f4ecf4ec10f4ec3910f4ecf4ec30012e0135342433321617152e012322061514163b011523220615141633323637 +150e0123202435343601d8838e010ce659c97372be5398a39e95b6aea5b9c7be6dc8546ac75efee8fed0a3032521ab7cb2d1 +2020b426247b737077a695848f963231c32525f2dd90c4000001ff96fe66042305d500110041401f1108120d950cb0120695 +040295008104ad12110800070c050107031c00041210fcec32d4c4c411123939310010ecf4ec10ee10f4ec10393930b20f0b +01015d13211521112115211110062b013533323635c9035afd700250fdb0cde34d3f866e05d5aafe48aafd9ffef2f4aa96c2 +0001ff7ffe5602f80614001b00654023130a0f870dbd1d0518011408a906018700971606bc1c021b0700070905081517134c +1c10fc4bb00a5458b90013004038594bb0165458b90013ffc038593cc4fc3cc4c4123939310010e432fcec10ee3212393910 +f4ec39393001b6401d501da01d035d01152322061d012115211114062b013533323635112335333534363302f8b0634d012f +fed1aebdaeb0634db0b0aebd0614995068638ffbebbbab995068042a8f4ebbab00010073ffe3069707640026004940101502 +001c04111c1a34043321190b462710fcecfcf4ec10fcc4c4314018169515270005240195032495081ba11aae1e950e91088c +270010e4f4ecf4ec10fed4ee11393910dcec3025112135211106042320001110002132161734363b01152322061d012e0123 +200011100021323604c3feb6021275fee6a0fea2fe75018b015e5ba344c9e34d3f866e70fc8bfeeefeed011301126ba8d501 +91a6fd7f53550199016d016e01991919bceaaa96c2d75f60fecefed1fed2fece250000020008fe52057605d5000f00250095 +400d27501201120419170c191f242610d4d4ecd4ecd45dc4b510080003040c1112173931400a00951bbd1125122481260010 +e4323232f4ecb31f17081b1112393930400c131111121208232511242408070510ec3c0710ec3cb613110812082408070810 +ecb623110824081208070810ecb410251311230f40101615140317132408222120031f231208040711121739071112173901 +3237363534272627060706151417161301330116171615140706232227263534373637013302bf362c1c1f332c2c331f1c2c +3601d9defdba68432e4b649b9b644b2e4368fdbadefefd2014423949795c5c794939421420037a035efbcfc8ae77428b4157 +57418b4277aec8043100000100ba000007470614002a004f40112c0d120408112a1508264e1f1b081d462b10fcec32f4ecc4 +c4ccd4ec3931004019088709271426008711151b260320111887200923b81e97111c2f3cecf43cc4ec1112173910ec123939 +10ec30253237363534272627351617161114002b012226351134262322061511231133113e013332161511141633054c9554 +574a3e79e06d6ffee0dd46bb9d7c7c95acb9b942b375c1c64c699c62659bde705f21941d8f91feecf5fee6c8ce01089f9ebe +a4fd870614fd9e6564efe8fef2936700000100c9000002c605d5000b002e40100b02000695008107050806011c00040c10fc +4bb0105458b9000000403859ecc4393931002fe4ec113939300113331114163b011523222611c9ca6e863f4de3cd05d5fc2d +c296aaf4010e0001000a0000025205d5000b00454011020b95050800af060305011c0a0800040c10fc3cc44bb0105458bb00 +08004000000040383859ec32c431002fecdc3cf4323001400d300d400d500d600d8f0d9f0d065d1333113315231123112335 +33c9cabfbfcabfbf05d5fd16aafdbf0241aa000100c9000005f705f000170066400e001c0107080f07090b0f1c0e041810fc +ec32d4c4113910d4ec00310040250b110809080a1109090811110708071011080807420b0810030e0c1702059513910eaf0c +092f3cecf4ec393911121739304b5358071004ed071005ed071005ed071004ed592201233534262322070901210111231133 +110136333217161505f7aa49264625fddd031afef6fd33caca026c5571885555044879365023fdf9fce302cffd3105d5fd89 +02434f5c5b6e000100b90000049c0614001200cb400b040d090c0e10090800461310fcec32d4c41139c43100400f42100d0a +030b11069503970bbc110e2f3ce4fce411121739304b5358401410110d0e0d0f110e0e0d0b110c0d0c0a110d0d0c071004ed +071005ed071005ed071004ed59b2101401015d40350b0b0a0f280b270c280d2b0e2b0f4014680b6014890b850c890d8d0e8f +0f9a0b970faa0ba70db60fc50fd60ff70bf00bf70cf00c1a5db4090d090e0271004025040a0a10160a270a290d2b10560a66 +0a6710730a770d820a890d8e10930a960d9710a30a125d1334363b011523220615110133090123011123b9a3b5bfa8694c02 +25ebfdae026bf0fdc7b9047ed6c09c6199fdff01e3fdf4fdac0223fddd000001000a0000022a0614000b0032400705010808 +00460c10fc3cec3231004008020ba905080097062fecd43cec3230400d100d400d500d600d700df00d06015d133311331523 +112311233533c1b8b1b1b8b7b70614fd3890fd4402bc90000001003d0000047f0614000f00a0401308020b05010e070d080c +06090406110c06001010d4c4b28006015dd4c410c4cc11121739b410094009025d3100400f08020b05010e06060004090697 +0d002f3cf4c4c4111217393040320a03a902a90ba90508040c0709040f11000e11010d060100051102110e110f0e01110001 +0d110c070c0b11081107110d060d070510ececec071005ec08ec08ec05ecec070810ec0510ec0708103c3cecec0efc3c3301 +27052725273317251705012309013d01eb47fed42101294bc834013a21fec901edc3fec6fe7e0432bc656363c58a686168fa +d7033cfcc400000100b2ffe3072705d50027004a4012001214201d1c291f50121c14500a1c08042810fcecfcfcfcccfc3c11 +12393100401607140a1c11000621080e18952103248c28121d0881202ff43c3c10f43cc4ec32111217393039250e01232227 +263511331114171633323635113311141716333237363511331123350e012322272603a645c082af5f5fcb2739758fa6cb39 +39777b5353cbcb3fb0797a5655d57c767b7ae2041bfbefba354ebea403ecfbefa24e4d5f60a303ecfa29ae67623e3e000001 +ff96fe66053305d50011008c402907110102010211060706420811000d950cb01207020300af05060107021c04360b0e0c39 +071c00041210fcece43939fcec11393931002fec32393910fcec113939304b5358071004ed071004ed5922b21f0b01015d40 +303602380748024707690266078002070601090615011a06460149065701580665016906790685018a0695019a069f13105d +005d13210111331121011110062b013533323635c901100296c4fef0fd6acde3473f866e05d5fb1f04e1fa2b04e1fb87fef2 +f4aa96c2ffff00bafe560464047b100601cf000000030073ffe305d905f0000b001200190031400b19101906330f13190010 +1a10fcec32f4ec323100400f16950913950fad1a0c950391098c1a10e4f4ec10f4ec10ec3013100021200011100021200001 +220007212602011a0133321213730179013a013b0178fe88fec5fec6fe8702b5caff000c03ac0efefd5608fbdcdcf80802e9 +016201a5fe5bfe9ffe9efe5b01a403c5fee4c3c3011cfd7afefffec2013d0102ffff0067ffe3061d061410260010f4001007 +01cb05a20134ffff0076ffe304d304eb102701cb0458000b10060024050000020073ffe306cf05f00014001f0033401c0495 +10af0015950d91001b95078c0021131c001e1c100418190a102010fcecd43cecdcecc431002ff4ec10f4ec10f4ec30211134 +262311062120001110002132172132161901012200111000333237112606056e7abcfec5fec6fe870179013b70610127e3cd +fc58dcfefd0103dcaf808a03d3c296fb8bd301a40162016201a51bf4fef2fc2d054cfeb8fee6fee5feb86704184600020071 +fe560559047b00160021003a4020058711bc2217b90eb8221db9088c16bd22110105231508011f08051a120b452210fcecd4 +ecdcecc4111239310010e4f4ec10f4ec10f4ec30011134272623110623220011100033321733321716151101220615141633 +3237112604a126266989f0f1feef0111f16452d8b55251fd1a94acab95814054fe560474993130fcbc9d0139011301140138 +1b6060d6fb8c0589e7c9c9e73a02f0360002ff97000004f105d50008001c003a40180195100095098112100a080204000519 +0d3f11001c09041d10fcec32fcec11173931002ff4ecd4ec30400b0f151f153f155f15af1505015d01113332363534262325 +2132041514042b0111231122061d012335343601f7fe8d9a9a8dfe3801c8fb0101fefffbfeca84769cc0052ffdcf92878692 +a6e3dbdde2fda805305f693146b5a300000200b9fe5604a4061400180024004f402423b900171db90e11b8178c01bd25030c +09a90697251a12144706090307200c000802462510fcec3232cc113939f4ec310010f4ec393910e4e4f4c4ec10c4ee304009 +60268026a026e02604015d2511231134363b01152322061d013e013332001110022322260134262322061514163332360173 +baa3b5fee7694c3ab17bcc00ffffcc7bb10238a79292a7a79292a7a8fdae0628d6c09c6199c86461febcfef8fef8febc6101 +ebcbe7e7cbcbe7e7000200c9fef8055405d50015001d005640170506031300091d1810050a1a1904133f0e160a120c041e10 +fcec3232fcc4ec1117391139393931004010001706030417950916950f81040d810b2fecdcf4ecd4ec123939123930014009 +201f401f75047c05025d011e01171323032e012b0111231133113320161514060111333236102623038d417b3ecdd9bf4a8b +78dccacafe0100fc83fd89fe8d9a998e01b416907efe68017f9662fe9105d5fef8d6d88dba024ffdd192010c910000010072 +ffe3048d05f0002100644011071819061d0a0f1d19042d00220a19152210dcece4fcecc41112393939393100401942191807 +06040e21000ea10f940c9511209500940291118c2210e4f4e4ec10eef6ee10ce111739304b5358400a180207060719020606 +0707100eed07100eed591336200410060f010e0114163332371504232027263534363f013637363427262007cce401c60117 +cae27b9a87bcade1f8fefdd6fee79291d7e27aa63c3b595afea1e405a44ce4fe8fc02d181f7cec888bd05f7070d9b6d92b19 +1f3233d940406d0000010064ffe303bc047b002700cf40110a1e1d090d21142108060d0800521a452810fce4ecd4ecc41112 +393939393140191e1d0a09041300862789241486138910b91724b903b8178c280010e4f4ec10fef5ee10f5ee121739304012 +1b1c021a1d53090a201f02211e530a0a09424b535807100eed111739070eed1117395922b2000101015d40112f293f295f29 +7f2980299029a029f029085d4025200020272426281e281d2a152f142f132a12280a2809290829072401861e861d861c861b +12005d40171c1e1c1d1c1c2e1f2c1e2c1d2c1c3b1f3b1e3b1d3b1c0b71133e013332161514060f010e011514163332363715 +0e012322263534363f013e0135342623220607a04cb466cee098ab40ab658c8261c6666cc35ad8f7a5c43f946289895aa84e +043f1e1eac9e8295240f25504b51593535be2323b69c89992a0e2149405454282800ffff00c90000048b05d5100601ce0000 +0002fef2fe5602d706140016001f0036400c1d0e0a1506140108170a4f2010fc32fc32cccc10d4cc3100400f141f87000b1b +87109720048706bd2010fcec10f4ecd43cec3230011114163b01152322263511232035342132171617331525262726232207 +063301774d63b0aebdaebefef2012fb5523512bffe860811216e7c030377046afb3d685099abbb04aed2d860406f9b9a2c18 +3041330000010037fe5602f2059e001d003f400e0e14080802090400081a1c18461e10fc3cc4fc3cdc3239fccc3100401218 +05081903a9001b01bc08871510870ebd152ffcec10ecf43cccec321139393001112115211114163b011514062b0135333237 +363d0122263511233533110177017bfe854b73bda4b446306a2626d5a78787059efec28ffda0894eaed6c09c303199149fd2 +02608f013e0000010018000004e905d5000f005840150d0a0c06029500810400070140031c050b1c0d051010d4d4ec10fce4 +393931002ff4ec32c4393930014bb00a5458bd00100040000100100010ffc03811373859401300111f00100110021f071011 +401170119f11095d012115211123112322061d012335343601ae033bfdeecb5e84769cc005d5aafad5052b5a693146b5a300 +00010037000002f20614001b0049401019160b080417090204000810130e461c10fc3cc4fc3cc43232173931004013130019 +8716970a0e05080f03a91101bc08870a2fecf43cec3211393910f4ec393930b2af1501015d01152115211114163b01152322 +2635112335333534363b01152322060177017bfe854b73bdbdd5a28787aebdaeb0634d04c3638ffda0894e9a9fd202608f4e +bbab99510001fffafe6604e905d5000f0054401407950abd100e0295008110080140031c00400d1010d4e4fce4c4310010f4 +ec3210f4ec30014bb00a5458bd00100040000100100010ffc03811373859401300111f00100110021f0f1011401170119f11 +095d032115211114163b01152322261901210604effdee6e863f4ee3cdfdee05d5aafb3dc296aaf4010e04c3ffff00adfff7 +065f061410260014fb14100701cb05e40134ffff00b0ffe3056904eb102701cb04ee000b1006002802000001004effe305cf +05ca001f003a40101d1a1921100004330a1114190d0a102010fcc4fcc410f4c4ecfcc43100400e0d11011d951e1081201795 +078c2010f4ec10fc3cec32323230012116121510002120001134123721352115060215140033320035340227352105cffec0 +a18efe7ffed1fecffe81919efec10258b2c70109d8d80108c6b1025805188dfed8c2fecbfe77018a013eb8012a8bb2b261fe +b4caeffedd0122f0ca014c61b200000100c9ffe1057605d5001b002d400d10150c070803190c181c15041c10fcecd4ec2f3c +111239310040090816811c0095108c1c10f4ec10ecc430253200353427262735171612151007062127262726190133111416 +3302c6d8010863416eb3a18ec0bffecf4de86167ca6e868d0122f0caa66d5744018dfed8c2fecbc5c40206747a010e03f0fc +10c296000001fffc000005f005f000170064400f131c140c040b070040051c0940071810d4e4fce41239c4392fec3100400b +12151400950e910b09af062fec39f4eccc39393040190c110405040b110a0b0505040b110c0b0809080a11090908424b5358 +071005ed071008ed071008ed071005ed59220122070607011123110133090136333217161d012335342604d739152511fe84 +cbfdf0d9019e014e5aa3885555aa4905470e1819fdbffd3902c7030efd9a01f9885c5b6e837936500001003dfe5605d8047b +001f016a4017120e151b1f1808151f0e0d0c0a09060300081f041f0b2010d44bb00a544bb008545b58b9000b004038594bb0 +145458b9000bffc03859c4c411173910d4ec11391112393100403a0708020911001f0a110b0a00001f0e111d001f0d110c0d +00001f0d110e0d0a0b0a0c110b0b0a420d0b0920000b058703bd201bb912b80bbc172010c4e4f4ec10f4ec11391139123930 +4b5358071005ed071008ed071008ed071005ed071008ed0705ed1732592201408d0a000a09060b050c170115021004100517 +0a140b140c2700240124022004200529082809250a240b240c270d37003501350230043005380a360b360c380d4100400140 +024003400440054006400740084209450a470d5400510151025503500450055606550756085709570a550b550c6601660268 +0a7b0889008a09850b850c890d9909950b950ca40ba40c465d004025060005080609030d160a170d100d230d350d490a4f0a +4e0d5a095a0a6a0a870d800d930d125d050e012b01353332363f01013309013637363332161d012335342623220706070293 +4e947c936c4c543321fe3bc3015e011a1530588783b9b251393929140a68c87a9a488654044efc9402c0343360bf8672723a +542a14190001005c0000051f05d5001100c0403506030207020c0f100b1007110b100b101102070242050d95040e12109500 +810795090c06030f040e04080e00100700014208000a1210dc4bb009544bb00a545b58b9000affc03859c4d4e411393910c4 +10c411173931002fecf4ec10d43cec32304b5358071005ed071005ed0710053c3c0710053c3c592201404005020a0b180b29 +02260b380b4802470b48100905070b10001316071a1010132f13350739103f1347074a104f1355075911660769106f137707 +78107f139f13165d005d132115012115210121152135012135210121730495fe700119fe73fe5403c7fb3d01b9fed5019f01 +83fc6705d59afe1190fdeeaa9a02229001df00010058000003db0460001100c540310c0f100b100603020702101102070207 +110b100b4210a900bc09050da9040e07a90910070f03060c0601000e0408010a1210dc4bb00b544bb00c545b58b9000affc0 +38594bb0135458b9000a00403859c432c4c4c411173931002fecd43cec3210f4ec304b5358071005ed071005ed0710053c3c +0710053c3c59220140420502160226024702490b050b100f1318071b102b1020133607391030134001400245074008400943 +10570759105f136001600266076008600962107f138013af131b5d005d13211503331521012115213501233521012171036a +fbc2fec2fec302b4fc7d012bd40150010dfd650460a8fedc90fe8f93a8015c900139000100a0ffc104f805d500220070400e +0b0e0d080a04190e10160a0d1e2310dcc4c4d439c4ec1239b43f0e4f0e025d111239310040130a0995100f0b950d81231fa1 +1eae00951a8c2310f4ecf4ec10f4ec39d4ec3930400a10110a0b0a0b110f100f071005ec071005ec400e090a370f0205100b +0b15103b0b04015d005d25323736353427262b013501213521150132171617161514070621222726273516171602a8c06364 +5c5da5ae0181fcfc0400fe656a806256519898fee8777d7e866a7f7e6b4b4b8f86494a9801eaaa9afe16382a6d688adc7a79 +131225c3311919000001005cffc104b405d50022005e400f1816151b1f130d1916051f19150d2310dcc4b430154015025dec +d4c4c41139113911123931004013191b951314189516812304a105ae0095098c2310f4ecf4ec10f4ec39d4ec3930400a1311 +1918191811141314071005ec071005ec25323736371506070623202726353437363736330135211521011523220706151417 +1602ac897e7f6a867e7d77fee89898515662806afe650400fcfc0181aea55d5c64636b191931c3251213797adc8a686d2a38 +01ea9aaafe16984a49868f4b4b0000010068fe4c043f0460002000a3400b0006020c121b130306022110dcccc4c4d4ec1112 +393100401a0c1b0018064200a90707032104a9031386149310b918bd03bc2110e4fcecf4ec10ec1112392fecec1112393930 +40080611000511010702070510ec0410ec401b03050500140516002305250037003405460043055b0054057e000d015d401b +040604011406140125062401350137064501460654015c067f060d005d4009061507161a151a12045d090135211521011523 +220706151417163332363715060706232024353437363736025bfe65036afd6501aeaea55d5c6463be6dc8546a64635efee8 +fed05156628001dc01dca893fe0da64a4b848f4b4b3231c3251312f2dd8a686d2a3800010071fe5603e80460002000000132 +37363715060706232011342524353423302101213521150120151005061514027f544d4f5157505661fe200196011cebfede +01e5fd65036afe9e016ffe30e2feee15152cb3200d0e0119ee3525627c023893a8fe64e5feec3118618b000100960000044a +05f00024000025211521350137213521363736353427262322070607353e01333204151407060733152307018902c1fc4c01 +3a73fea701e25f25275354865f696a787ad458e80114221f4a68ec30aaaaaa014075906d484c49774b4b212143cc3132e8c2 +5c52496090310001005dffc104f905d500190035400e1b0308110a0b080700081907461a10fcd4ec10ecd4d4eccc3100400d +169501001a06950d0b9509811a10f4ecd4ec10ccd4ec300110201134262321112115211125241716100f0106070620243501 +26030ab9a5fdf703a1fd2901730100a2513b1c142d98fdc4fed00190fedb01258693032caafe250101d068fee056291d2479 +f2dd00010068fe4c043f0460001a0033400b1c0408120a0c081a08461b10fcc4ecd4d4eccc3100400f0287001a18bd1b0787 +0e0c870abc1b10f4ecd4ec10fccc32ec30171633201134262321112115211133321e01100f0106070621222768aace0196b9 +a5fe9f0319fd9fdd69e4a63b1c142d98fee8bbd4a76301258693032caafe2663d4fee056291d24794a0000010058ffe303a5 +059e0024000001071617161514070621222726273516171633323736373427262b01132335331133113315022102aa706c6e +89feed5551514c49544e50b36339013a56c03e02e5e5cae703e67d1e7773aaba7d9d121123ac281816724185624c72010fa4 +0114feeca400000200bafe5604a4047b000e00170040400b1911080d0417000802461810fcec3232d4eccc3100400c421587 +05098c03bc0001bd1810ecc4f4f4ccec304b5358b617050f8700000e070410ed0010cc590511231133153637363332171615 +100100353427262322070173b9b9348751d2b84d4efccf0272393878dcad7afed0060aaa425231707199fe57fee40190f985 +4241ef00000100c9fe56019305d500030026400a009702bd04010800460410fcec310010ecec30400d100540055005600570 +05f00506015d13331123c9caca05d5f88100ffff00c9fe56032705d51027012c019400001006012c000000010014fe56039c +05d50013003a401d0c09a90f061302a91005050a00970abd14070309050108120d0c10001410d43c3ccc32fc3c3ccc323100 +10ecec11392f3cec32dc3cec323001331121152115211521112311213521352135210173ca015ffea1015ffea1cafea1015f +fea1015f05d5fd97a8f0aafd2c02d4aaf0a8ffff00c90000019405d5100600049400ffff00c900000ad0076d102700e905b1 +0000100600070000ffff00c9000009b00666102700ea05d50000100600070000ffff0071ffe308910666102700ea04b60000 +1006001b0000ffff00c9fe66062405d51027000c049100001006000e0000ffff00c9fe5605de061410270020046500001006 +000e0000ffff00c1fe5602ef06141027002001760000100600220000ffff00c9fe6606f205d51027000c055f00001006000f +0000ffff00c9fe5606b7061410270020053e00001006000f0000ffff00bafe5605de06141027002004650000100600230000 +ffff001000000568076d122600050000110701d804be01750006b10e00103c31ffff007bffe3042d06661226001900001106 +01c05a000008b40b2b2714072b31fffffffe00000260076d1226000b0000110701d8032f0175000bb407200100001049633a +3100ffffffe00000025e06661226009d0000110701c0ff1f0000000bb408200100001049633a3100ffff0073ffe305d9076d +122600100000100701d805270175ffff0071ffe304750666122600240000110601c076000006b11b0c103c31ffff00b2ffe3 +0529076d122600140000110701d804f601750006b11505103c31ffff00aeffe304580666122600280000110601c07600000b +b418200b01011049633a3100ffff00b2ffe305290833102601df3000120600140000ffff00aeffe3045807311027002d007b +013b120600680000ffff00b2ffe30529085a122600140000100601e13600ffff00aeffe304580722122600280000100701e1 +ffbefec8ffff00b2ffe30529085a122600140000100601e43000ffff00aeffe304580722122600280000100701e4ffc4fec8 +ffff00b2ffe305290860122600140000100601e23006ffff00aeffe304580722122600280000100701e2ffbefec8ffff0071 +ffe3047f047b120601bc0000ffff0010000005680833122600050000100601df0000ffff007bffe3042d0731122600500000 +1007002d0052013bffff0010000005680833122600050000100601e00000ffff007bffe3042d06f4122600190000100701e0 +ff93fec1ffff00080000074807341027002d02d7013e120600320000ffff007bffe3076f05f21027002d01e8fffc12060052 +000000010073ffe3060405f00025005440102124221e1c11340200043318190b102610fcecfc3ccce4fcc4c431004018041f +012200051b2395251b950812a111ae15950e91088c2610e4f4ecf4ec10fed4ee113939dcb00b4b5458b1224038593ccc3230 +011133152315060423200011100021320417152e012320001110002132363735233533352135058b797975fee6a0fea2fe75 +018b015e9201076f70fc8bfeeefeed011301126ba843fdfdfeb6030cfed658ff53550199016d016e01994846d75f60fecefe +d1fed2fece2527b55884a60000020071fe5604fa047b000b00340058400e0f22322500080c470612182c453510fcc4ecf4ec +3232c4c43100401b20110e23250c29091886191cb91503b9322fb833bc09b915bd26292fc4e4ece4f4c4ec10fed5ee111239 +39d43ccc3230b660368036a03603015d01342623220615141633323617140733152306070621222627351e01333237363721 +3521363d010e0123220211101233321617353303a2a59594a5a59495a5b813b3c61f3a7ffefa61ac51519e52b55a1511fd84 +029a1639b27ccefcfcce7cb239b8023dc8dcdcc8c7dcdceb6e58465d408c1d1eb32c2a5f171c45475e5b6362013a01030104 +013a6263aa00ffff0073ffe3058b076d122600090000110701d8054a01750010b1210e103c4007942154212421035d31ffff +0071fe56045a0663102601c04afd1206001d0000ffff00c90000056a076d102701d804a201751206000d0000ffffffe90000 +049c076d122600210000110701d8031a0175002ab401100c00072b31004bb00e5158bb0001ffc00000ffc0383859400d9001 +90008001800040014000065dffff0073fe7505d905f0102701c301340000120600100000ffff0071fe750475047b102701c3 +00800000120600240000ffff0073fe7505d907311027002d0127013b120601560000ffff0071fe75047505f51026002d73ff +120601570000ffff00a0ffc104f8076d102701d804be0175120601230000ffff0058fe4c042f0666102601c01b00100601bd +0000ffffffdbfe5602640666102701c0ff250000110601a30000000bb403200807071049633a3100ffff00c900000ad005d5 +1027001705b10000100600070000ffff00c9000009b005d51027002b05d50000100600070000ffff0071ffe3089106141027 +002b04b600001006001b0000ffff0073ffe3058b076c102701d4051b0176120600090000ffff0071fe56045a06631226001d +00001006002e1bfd000100c9ffe3082d05d5001d0035400e0e1c1119031c06381b011c00041e10fcec32fcec32d4ec310040 +0e0f1a9502ad0400811c0a95158c1c2fe4ec10e432fcecc43013331121113311141716173237363511331114070621202726 +3511211123c9ca02deca3e3d9994423eca6460fee6feed6764fd22ca05d5fd9c0264fbec9f504e014f4ba4029ffd5adf8078 +7876e9010dfd3900000200c9fe56050205f0000e00170040400b19111c0d0417001c02041810fcec3232d4eccc3100400c42 +159505098c03810001bd1810ecc4f4f4ccec304b5358b617050f8700000e070410ed0010cc59251123113315363736333217 +1615100100113427262322030193caca389157e2c65354fc9102a13d3c81edba9cfdba077fb9485735787aa4fe37fece01ae +010c8f4746feff00ffff00c900000533076b102701d6051e01751206000f0000ffff00ba0000046406641226002300001007 +00180118fffeffff0010000005680773122600310000100701d4065c017dffff007bffe304dc0773122600510000100701d4 +05ec017dffff000800000748076c102701d4065c0176120600320000ffff007bffe3076f06631226005200001007002e0165 +fffdffff0066ffba05e5076c102701d404fe0176120600440000ffff0048ffa2049c06631226006400001006002e1cfdffff +0010000005680770122600050000100701dd04e5017affff007bffe3042d0664102701c80498fffe120600190000ffff0010 +000005680736122600050000100701d904bc013effff007bffe3042d0648102701c904650000120600190000ffff00c90000 +048b0770122600080000100701dd04a5017affff0071ffe3047f0663102701c804bafffd1206001c0000ffff00c90000048b +0736122600080000100701d904a6013effff0071ffe3047f0648102701c904a900001206001c0000ffffffa7000002730770 +1226000b0000100701dd0359017affffffc3000002810663102701c80366fffd1206009d0000ffff00050000027707361226 +000b0000100701d9033e013effffffe3000002550648102701c9032400001206009d0000ffff0073ffe305d9077012260010 +0000100701dd0541017affff0071ffe304750664102701c8049ffffe120600240000ffff0073ffe305d90736122600100000 +100701d9051c013effff0071ffe304750648102701c904980000120600240000ffff00c70000055407701226001100001007 +01dd0479017affff00820000034a0663102701c80425fffd120600250000ffff00c9000005540736122600110000100701d9 +0480013effff00ba0000035e0648102701c9042d0000120600250000ffff00b2ffe305290770122600140000100701dd0515 +017affff00aeffe304580664102701c804d4fffe120600280000ffff00b2ffe305290736122600140000100701d904ec013e +ffff00aeffe304580648102701c904ab0000120600280000ffff0087fe1404a205f0102701cc04760000120600120000ffff +006ffe1403c7047b102701cc042c0000120600260000fffffffafe1404e905d5102701cc04530000120600130000ffff0037 +fe1402f2059e102701cc040000001206002700000001009cfe52047305f0002e0000010411140e010c01073536243e013534 +2623220f0135373e0335342e03232207353633321e0115140e02033f01346fb9ff00feea99c80131b95c7d705f73a3f83c66 +683d23374b4826b8f3efce83cb7c173a6e02a243fedb70cea0886022a0378c999d4f65843348ab6a1a41638b52375633220c +b8bea456b6803c66717400010047fe4f03bc047b00340000011e0315140e0507353e0435342623220f0135373e0435342e03 +23220607352433321e0115140602a746703e21426c989db3954aa2f59e6328765d3b3fd8df2241573f2d1f3143412345a893 +010a8670b8746701cd08445a58254b8a6c61463d270f822e605b625b33587019568b550d203c4566392c462a1b0a3b5a9a85 +4792616e9900ffff00c90000053b076d102701d8050401751206000a0000fffffff000000464076d102701d8032101751306 +001e0000002ab414050113072b31004bb00e5158bb0014ffc00013ffc0383859400d901490138014801340144013065d0001 +00c9fe56051905f00013002e401203950e91098112b008131c120b061c08411410fc4bb0105458b90008ffc03859ec32d4fc +31002fece4f4ec300134262322061511231133153e0117321219012304509a99b3d7caca51cc9de3e9c9037fd7d5ffdefcb2 +05d5f1878601fec1feccfad900030071ff700644061400070028003400002516333235342722073633321510212227060723 +36372635060706232227261037363332171617113300101716203736102726200704b61125a03434ca6e88f4feaa49352218 +c41d43303a58597ccb807f7f80cb7c59583ab8fcd55354012454545454fedc548205af2d0120b8cefebf0f483a45933c2464 +3031a2a20210a2a2313064025efce6fe6a74737374019674737300020071ffe3052505f0000c003b0057401c240014330418 +103d450a1c28421d181c21383b101c3742041c2f453c10fcecf4ecccb2203b015df4ecccf4ecec1112173931004012243300 +9514ad3c0d3b1c1d913c07082c8c3c10f4ec10f4ccd4cc10f4ec39393001220706101716203736353426030e011514171633 +32373635342726273532171615140607161716151407062027263534373637262726353437362102cbb86a6b6b6a01706b6b +d4f482aa5f3bcca85f604c6d82e4968baa98ac5f609c9bfdba9b9c6061abab43558274010102c54d4dfef24d4d4d4e86879a +0227037c4f45482d4141889e2b4d08646861ba80b2202263638fd974747474d98f6363221f46595882534a0000020071ffe3 +0471050f000d00340043401636450a0818420e3432081028292b08264204081f453510fcecf4eccc32d4eccc32f4ecec3100 +400e3429142200b92ead3507b91c8c3510f4ec10f4ec3939cc32300122070610171620373635342726131615140706071617 +16151407062027263534363726272635343733061417163332373635342702719053525253012053535352fe3a3448829252 +518584fe128485a492903b343fa12b49488382494a2c02c54d4dfef24d4d4d4e86874d4d024a4062994059202263638fd974 +747474d98fc62223564b8e594941e84141414174773e0001005cfe56051f05d50015009f400c0f141112420b081506110d16 +10dc4bb009544bb00a545b58b9000dffc03859c4c4d4ece41139393100400c420795050c0f95118114950c2fecf4ec10dcec +304b5358400a14110e0f0e0f11131413071005ed071005ed5901404005130a0e180e2913260e380e4813470e480f0905140b +0f001716141a0f10172f173514390f3f1747144a0f4f175514590f6614690f6f177714780f7f179f17165d005d051007062b +0135333237363d01213501213521150121051f9e4872fee9692626fbf503b0fc670495fc5003c714fedf50259c303199149a +0491aa9afb6f00010058fe5603db0460001500ac400c0b08150d0f14121112060d1610dc4bb00b544bb00c545b58b9000dff +c038594bb0135458b9000d00403859c4c4b440126012025dc411393910d4b440156015025dec3100400c4207a9050c0fa911 +bc14a90c2fecf4ec10dcec304b5358400a0f1113141314110e0f0e071005ed071005ed590140320513161326134713490e05 +0b0f0f1718141b0f2b0f20173614390f30174514490f5714590f5f176614680f7f178017af17135d005d051007062b013533 +3237363d0121350121352115012103db9e4872fee9692626fd3502b4fd65036afd4c02b414fedf50259c30319914a8032593 +a8fcdb00ffff0010000005680750102701db04bc0175120600050000ffff007bffe3042d0614102701c6044a000012060019 +0000ffff00c9fe75048b05d51226000800001007003000a20000ffff0071fe75047f047b1226001c0000100600307b00ffff +0073ffe305d90833122600100000100601df6200ffff0071ffe3047507311226006200001007002d0073013bffff0073ffe3 +05d90833122600100000100601e36900ffff0071ffe3047506e9122600240000100701e3ffb5feb6ffff0073ffe305d90750 +102701db05270175120600100000ffff0071ffe304750614102701c604730000120600240000ffff0073ffe305d908331226 +00100000100601e06a00ffff0071ffe3047507311226019b00001007002d0073013bfffffffc000004e707311027002d0072 +013b120600160000ffff003dfe56047f05f51026002d5eff1206002a00000002008aff70035c060e00070019000025163332 +3534272207363332151021222706072336372637113301ce1125a03434ca6e88f4feaa49352218c41d433101b88205af2d01 +20b8cefebf0f483a45933c5a0530000200baff70064e047b0007002b00002516333235342722073633321510212227060723 +36372637113426232206151123113315363736333217161504c01125a03434ca6e88f4feaa49352218c41d4331017c7c95ac +b9b942595a75c163638205af2d0120b8cefebf0f483a45933c5a01c09f9ebea4fd870460ae6532327778e80000020037ff70 +0361059e0007002100002516333235342722073633321510212227060723363726351123353311331121152101d31125a034 +34ca6e88f4feaa49362118c41d43318787b9017bfe858205af2d0120b8cefebf0f483a45933c5a02f38f013efec28f000001 +ffdbfe5601790460000b003840150b020700078705bd00bc0c080c05064f010800460c10fcece4391239310010e4f4ec1112 +393930400b100d400d500d600d700d05015d13331114062b013533323635c1b8a3b54631694c0460fb8cd6c09c6199000003 +0071ffe3078c061400090023002f00414013314525121447051b0d082b180e47011221453010fcecf43c3cfc3c3cf4ecec31 +0040102808b90a2e04b9161d8c110ab80d97192fece432f432ec3210ec323000101716203610262007133217113311363332 +0010022322271523350623222726103736001027262007061017162037012f53540124a8a8fedc54b9f572b972f4cc00ffff +ccf472b972f5cb807f7f80055d5354fedc5453535401245402fafe6a7473e70196e773010dc5025efda2c5febcfdf0febcc5 +a8a8c5a2a20210a2a2fce9019674737374fe6a74737300030071fe56078c047b000b0025002f004440133145011224472b11 +1d12070e1e47271217453010fcecf43c3cfc3c3cf4ecec310040120a2ab913042eb9211ab80c138c0fbd1dbc3010e4e4e432 +f43cec3210ec3230001027262007061017162037032227112311062322272610373633321735331536333200100200101716 +20361026200706cd5354fedc54535354012454b9f472b972f5cb807f7f80cbf572b972f4cc00fffffaa253540124a8a8fedc +540164019674737374fe6a747373fef3c5fdae0252c5a2a20210a2a2c5aaaac5febcfdf0febc0317fe6a7473e70196e77300 +0003fffdffba057c06170012001600190000013313011709012303210f012307272337273709013301032103024ae5860161 +66fe70017cd288fdd6cd32463b520201142f0290feee16016fbd015d6a05d5fea101a159fe27fc1b017ff18e464601113804 +c4fd1901b1fe4f011f000002000cffba058a06170022002c0000172713261110373621321716173717071526270116171621 +3237363715060706232027130123262320070611147266dc75c3c3015386763d3a6566632e31fcf4090b880100827473666a +777684feb4c23902d8017482ff00888846580105bb01170168cfd024121b785976bb2b21fc660d0c9d2f2f5fd3482424c701 +15035c2f9c9dfed8ad0000020009ffa2045d04bc0022002b0000172737263510373621321716173717071526270116171633 +32373637150607062322271301262322070615146960bd559796010655512e2d595f761918fdd3070663b3504e4f4e4d5253 +5df0933701ee4747b363635e4ee68dcc01129d9d110a106c4f8f550e0bfd5e08087115162baa2412129001050256117172cd +67000001000a0000046a05d5000d003b40160c050a95020c06950081080305011c073a0c0a00040e10fc3cccecfc3ccc3100 +2fe4ecd43cec3230400d300f500f800780087f047f0306015d1333113315231121152111233533c9cabfbf02d7fc5fbfbf05 +d5fd7790fdeeaa02bc900002ffb2ffba05310617000f001200000115230111231101270111213521371709012104e934fe22 +cbfe0d67025afdee04993866fda6012cfed405693efdccfd090207fdb35802c70252aa4259fe0b0162000001006ffe100419 +047b003d0000013427262f0126272635343633321617152e0123220706151417161f0116171615140706071f011633152322 +27262f012627262726273516171633323736030a3233ab40ab4c4ce0ce66b44c4ea85a8944453131943fc650537b57849f93 +2a4c2754724759ed1e241011616c6663636182464601274b2828250f244a4b829eac1e1eae28282a2a54402524210e2c4b4c +899c5b40139f7e249a3d265bf31e1003021223be351a1b2d2c0000010058fe1004330460001800001321150116170117163b +0115232227262f01262b013d01012171036afd4e5c310108932a4c6c9354724759ed3d5a5e02b4fd650460a8fcdd1031fef8 +7e249a3d265bf33f9c0c0325000100500000048d05d500180036401112130c0b040f000501081916011c040f1910d4d4ecd4 +ec1139391117393100400b0095050f95100b951281022ff4ecd4ecd4ec3001231123113332363534262b0122060735363b01 +3204151404029127caf18d9a9a8dfe45af4f98abfef40108fef7025afda603009187888f2a2cb646dce1d7e7000100500000 +038f047b0018003740100a08060f040c01000412131608000c1910d4d4ecd4ec12391217393100400d16b901170c860d8808 +b90fb8172ff4ecf4ee10d4ec3001333236353427262322070607353633321716151406231123012f648d9a4c55864956564e +98abfb7d84d4c2ca01a691878d414815152bb6466e74dbd5e5fefc000003000a000004ec05d5000c00150028005c401a150f +0c06171d230500121c1a0919202e02040d001c262516042910fc3cccec3232ccfcecd4ec1117393939310040152801952504 +0400051d00950e0d95168105950ead232fececf4ec10ee391112392f3cec3230b20f2a01015d011521152115213236353426 +2301112132363534262325213216151406071e011514042321112335330193015bfea50144a39d9da3febc012b94919194fe +0b0204e7fa807c95a5fef0fbfde8bfbf02c9c990ca878b8c850266fe3e6f727170a6c0b189a21420cb98c8da017090000002 +000cffe305ce05d50014001d005f400f15031c0709053816011c131100411e10fc4bb0105458b90000ffc038593cccec32fc +3cccec32310040161d17100a000714039511091616001a950d8c0400811e10e432f4ec11392f3c3cec323211393939393001 +b61f1f8f1f9f1f035d133311211133113315231510002120001135233533052115141633323635b2cb02e1cba5a5fedffee6 +fee5fedfa6a603acfd1faec3c2ae05d5fd96026afd96a496fedcfed6012a012496a4a47df0d3d3f0ffff00100000056805d5 +100601cd0000000300c9ff42048b069300130017001b00000133073315230321152103211521072337231121011323110113 +211103b8aa41589297010afebcb9022efd9841aa41b002aefe3cb9d9011397fe560693beaafe46aafde3aabebe05d5fad502 +1dfde302c701bafe460000040071ff42047f051e00050026002d00310000012627262703051521031633323637150e012322 +27072313262726111000333217373307161716051326232206071b01231603c702530e106f019afe2b944a616ac76263d06b +7b6350aa6d211c9d0129fc383147aa5c392f83fdbc8714169ab90e5a6fcf0b0294975a100dfef2365afe971c3434ae2a2c21 +c20109171d9c010a0113014309ace0223292c5014a02ae9efe63010eac000001ff96fe66025205d500130059401f0b02070c +010c95120f14079505b010811400110d0508063901111c0c10041410fc4bb0105458b90010004038593cec32e43939c410c4 +310010e4fcec10d43cec32111239393001400d30154015501560158f159f15065d01231110062b0135333236351123353311 +3311330252bfcde34d3f866ebfbfcabf0277fdf1fef2f4aa96c2020fa602b8fd48000002ffdbfe56021c0614001300170053 +402417be14b1180f060b000b8709bd180213a9051000bc180c18090a4f15050108141000461810fc3c3cec3232e439123931 +0010e4dc3ce43210f4ec1112393910f4ec30400b1019401950196019701905015d1333113315231114062b01353332363511 +23353311331523c1b8a3a3a3b54631694cb5b5b8b80460fe08a4fe28d6c09c619901d8a403ace90000020073fe6606b005f1 +0018002400434024030c0d069509b025229500161c950d108c169101af25090608021f0d001c02191913102510fcecd4ec32 +3210cc3939310010ece4f4c4ec10c4ee10e4ec113939300135331114163b011523222611350e012320001110002132160110 +1233321211100223220204b3c46e86454de3cd4deca5fef2feac0154010ea5ecfcdfeacccdebebcdccea04ede8fa93c296aa +f4010e7f848001ab015c015c01ab80fd78fee3febb0145011d011d0145febb0000020071fe560540047b0018002400484022 +188700bd2522b9110e1cb905088c0eb812bc25011718131f041108134719120b452510fcecf4ec323210cc3939310010ece4 +f4c4ec10c4ee10f4ec30b660268026a02603015d012322263d010e012322021110003332161735331114163b010114163332 +36353426232206054046b5a33ab17ccbff00ffcb7cb13ab84c6931fbefa79292a8a89292a7fe56c0d6bc6461014401080108 +01446164aafb8c9961033dcbe7e7cbcbe7e70002000a0000055405d50017002000bb4018050603150900201a12050a1d1904 +153f180a1c0e110c042110fc3cccec32fcc4ec1117391139393931004021090807030a061103040305110404034206040019 +03041019950d09189511810b042f3cf4ecd432ec32123912391239304b5358071005ed071005ed1117395922b2402201015d +40427a1701050005010502060307041500150114021603170425002501250226032706260726082609202236013602460146 +02680575047505771788068807980698071f5d005d011e01171323032e012b01112311233533112120161514060111333236 +35342623038d417b3ecdd9bf4a8b78dccabfbf01c80100fc83fd89fe9295959202bc16907efe68017f9662fd890277a602b8 +d6d88dba024ffdee878383850001000e0000034a047b0018003d400a0a18030806120804461910fc3cc4c4fc3c3c31004010 +12110b15870eb8030818a9050209bc032fe4d43cec3210f4ecc4d4cc30b4501a9f1a02015d0115231123112335331133153e +013332161f012e0123220615021eabb9acacb93aba85132e1c011f492c9ca70268a4fe3c01c4a401f8ae66630505bd1211ce +a1000002fff6000004ec05d500110014000003331721373307331521011123110121353305211704d997020c96d9979cfef5 +fef6cbfef6fef49d0277fed19805d5e0e0e0a4fe76fd3902c7018aa4a4e20002000bfe5604b504600018001b0000050e012b +01353332363f0103213533033313211333033315212b011302934e947c936c4c543321cdfed6f0bec3b8014cb8c3b9effed7 +c1da6d68c87a9a48865401f28f01cdfe3301cdfe338ffef000020071ffe3047f047b0014001b004140240015010986088805 +01a91518b91215bb05b90cb8128c1c02151b1b080f4b15120801451c10fcc4ecf4ec111239310010e4f4ece410ee10ee10f4 +ee111239301335212e0123220607353e01332000111000232200371e013332363771034e0ccdb76ac76263d06b010c0139fe +d7fce2fef9b802a5889ab90e02005abec73434ae2a2cfec8fef6feedfebd0123c497b4ae9e0000010058fe4c042f04600020 +00a9400a1b1f151222061e1f0e2110dcd4c4d4c4ec10ccb2001f1b1112393140161b4200a91a1a1e211da91e0e860d9311b9 +09bd1ebc210010e4fcecf4ec10ec1112392fececb315060009111239393040081b11001c11201a1f070510ec0410ec401b0c +1c0a001b1c19002a1c2a0038003b1c49004c1c54005b1c71000d015d401b041b0420141b1420251b24203520371b4520461b +54205c1b7f1b0d005d4009070b060c1a0c1a0f045d0132171617161514042122272627351e0133323736353427262b013501 +21352115023c6a80625651fed0fee85e63646a54c86dbe63645c5da5ae01aefd65036a01dc382a6d688addf2121325c33132 +4b4b8f844b4aa601f393a800ffff00b203fe01d705d5100601d10000000100c104ee033f066600060037400c040502b400b3 +07040275060710dcec39310010f4ec323930004bb009544bb00e545b58bd0007ffc000010007000700403811373859013313 +2327072301b694f58bb4b48b0666fe88f5f5000100c104ee033f066600060037400c0300b40401b307030575010710dcec39 +310010f43cec3930004bb009544bb00e545b58bd0007ffc0000100070007004038113738590103331737330301b6f58bb4b4 +8bf504ee0178f5f5fe88000100c7052903390648000d0057400e0bf0040700b30e0756080156000e10dcecd4ec310010f43c +d4ec30004bb0095458bd000effc00001000e000e00403811373859004bb00f544bb010545b4bb011545b58bd000e00400001 +000e000effc0381137385913331e0133323637330e01232226c7760b615756600d760a9e91919e06484b4b4a4c8f90900002 +00ee04e103120706000b00170020401103c115f209c10ff11800560c780656121810d4ecf4ec310010f4ecf4ec3001342623 +2206151416333236371406232226353436333216029858404157574140587a9f73739f9f73739f05f43f5857404157584073 +a0a073739f9f0001014cfe7502c1000000130020400f0b0e0a07f30ef40001000a0427111410d4ecc4d4cc31002ffcfcc412 +393021330e0115141633323637150e0123222635343601b8772d2b3736203e1f26441e7a73353d581f2e2e0f0f850a0a575d +3069000100b6051d034a0637001b006340240012070e0b040112070f0b0412c3190704c3150bed1c0f010e00071556167707 +5608761c10f4ecfcec1139393939310010fc3cfcd43cec11123911123911123911123930004bb009544bb00c545b58bd001c +ffc00001001c001c0040381137385901272e0123220607233e013332161f011e0133323637330e0123222601fc3916210d26 +24027d02665b2640253916210d2624027d02665b2640055a371413495287931c21371413495287931c00000200f004ee03ae +066600030007004240110602b40400b3080407030005010305070810d4dcd4cc1139111239310010f43cec3230004bb00954 +4bb00e545b58bd0008ffc000010008000800403811373859013303230333032302fcb2f88781aadf890666fe880178fe8800 +0002fda2047bfe5a0614000300040025400c02be00b104b805040108000510d4ec39310010e4fcec30000140070404340444 +04035d0133152317fda2b8b85e0614e9b0000002fcc5047bff43066600060007003c400f0300b40401b307b8080703057501 +0810dcec3939310010e4f43cec3930004bb009544bb00e545b58bd0007ffc000010007000700403811373859010333173733 +0307fdbaf58bb4b48bf54e04ee0178f5f5fe88730002fc5d04eeff1b066600030007004240110602b40400b3080405010007 +030107050810d4dcd4cc1139111239310010f43cec3230004bb009544bb00e545b58bd0008ffc00001000800080040381137 +38590113230321132303fd0fcd87f80200be89df0666fe880178fe8801780001fcbf0529ff310648000c0018b50756080156 +002fecd4ec3100b40af00400072f3cdcec3003232e0123220607233e012016cf760b615756600d760a9e01229e05294b4b4a +4c8f90900001fe1f03e9ff4405280003000a40030201040010d4cc3001231333fef2d3a48103e9013f000001fef0036b007b +04e000130031400607560e0411002f4bb00c544bb00d545b4bb00e545b58b9000000403859dc32dcec310040050a04c10011 +2fc4fccc3001351e0133323635342627331e01151406232226fef03d581f2e2e0f0f850a0a575d306903d7772d2b3736203e +1f26441e7a7335000001fd6afe14fe8fff540003000a40030300040010d4cc3005330323fdbcd3a481acfec0000100100000 +056805d50006003c400b420695028105010804010710d4c4c431002f3cf4ec304b5358401206110302010511040403061102 +0011010102050710ec10ec0710ec0810ec5933230133012301e5d5023ae50239d2fe2605d5fa2b050e00000100c90000048b +05d5000b00464011420a06950781000495030d01080407040c10fc3cd43ccc31002fec32f4ec32304b535840120b11050504 +0a110606050b11050011040504050710ec10ec0710ec0810ec5925211521350901352115210101b102dafc3e01dffe2103b0 +fd3801dfaaaaaa02700211aaaafdf300000100bafe560464047b00150031401606870e12b80cbc02bd0b17460308004e090d +080c461610fcec32f4ecec31002fece4f4c4ec304005a017801702015d011123113426232206151123113315363736333217 +160464b87c7c95acb9b942595a75c1636302a4fbb204489f9ebea4fd870460ae653232777800000200c9000004ec05d50008 +0015002e400c17090019102e040b1c15041610fcec32f4ecc4cc3100400c0b9515811404950cad0595142fecf4ec10f4ec30 +0134262321112132361315211121320415140429011104179da3febc0144a39d6cfd10014efb0110fef9fefcfde801b78b87 +fddd8704a8a6fe40dadeddda05d5000100b203fe01d705d500050018400b039e00810603040119000610dcecd4cc310010f4 +ec300133150323130104d3a4815205d598fec1013f000001ffb9049a00c706120003000a40030003040010d4cc3011330323 +c775990612fe88000002fcd7050eff2905d90003000700a5400d0400ce0602080164000564040810d4fcdcec310010d43cec +3230004bb00e544bb011545b58bd00080040000100080008ffc03811373859014bb00e544bb00d545b4bb017545b58bd0008 +ffc000010008000800403811373859014bb011544bb019545b58bd00080040000100080008ffc03811373859004bb0185458 +bd0008ffc00001000800080040381137385940116001600260056006700170027005700608015d0133152325331523fe5ecb +cbfe79cbcb05d9cbcbcb0001fd7304eefef005f60003007f40110203000301000003420002fa040103030410c410c0310010 +f4cc304b5358071005c9071005c95922004bb00c5458bd0004ffc000010004000400403811373859004bb00e5458bd000400 +40000100040004ffc03811373859402006021502250125023602460256026a016702090f000f011f001f012f002f01065d01 +5d01330323fe37b9e49905f6fef80001fcb6050eff4a05e9001d0075402116100f03130c0701000308170cc30413c31b08fa +1e10010f00071656180756091e10d4ecd4ec1139393939310010f43cecd4ec321217391112173930004bb00c5458bd001eff +c00001001e001e00403811373859004bb00e5458bd001e00400001001e001effc03811373859b4100b1f1a025d01272e0123 +22061d012334363332161f011e013332363d01330e01232226fdfc39191f0c24287d6756243d303917220f20287d02675422 +3b0539210e0b322d066576101b1e0d0c3329066477100001fd0c04eefe8b05f60003008940110102030200030302420001fa +040103030410c410c0310010f4cc304b5358071005c9071005c95922004bb00c5458bd0004ffc00001000400040040381137 +3859004bb00e5458bd00040040000100040004ffc03811373859402a06000601160012012400240135014301550055019f00 +9f01af00af010e0f000f031f001f032f002f03065d015d01132303fdc7c499e605f6fef801080001fccf04eeff3105f80006 +0077400a04000502fa070402060710d4c439310010f43cc43930004bb00c5458bd0007ffc000010007000700403811373859 +004bb00e5458bd00070040000100070007ffc03811373859014bb00e5458bd0007ffc0000100070007004038113738594013 +0f000f010c041f001f011d042f002f012d0409005d01331323270723fda2bcd38ba6a68b05f8fef6b2b20001fccf04eeff31 +05f800060086400a03040100fa070305010710d4c439310010f4c4323930004bb00c544bb009545b4bb00a545b4bb00b545b +58bd0007ffc000010007000700403811373859004bb00e5458bd00070040000100070007ffc03811373859014bb00e5458bd +0007ffc000010007000700403811373859401300000303000610001203100620002203200609005d01033317373303fda2d3 +8ba6a68bd304ee010ab2b2fef6000001fcc70506ff3905f8000d000003232e0123220607233e01333216c7760d6353526110 +760aa08f909f050636393738777b7a000001fcc70506ff3905f8000d006a400e070004c30bfa0e0756080156000e10d4ecd4 +ec310010f4fccc3230004bb00c5458bd000effc00001000e000e00403811373859004bb00e5458bd000e00400001000e000e +ffc03811373859014bb00e544bb00f545b58bd000effc00001000e000e0040381137385901331e0133323637330e01232226 +fcc7760d6353526110760aa08f909f05f836393738777b7a0001fd9a050efe6605db00030047b700ce02040164000410d4ec +310010d4ec30004bb00e544bb011545b58bd00040040000100040004ffc03811373859004bb0185458bd0004ffc000010004 +00040040381137385901331523fd9acccc05dbcd0002fce604eeffb205f600030007001340070004030708000410cc310010 +d43ccc32300133032303330323fef9b9e4998bb9e49905f6fef80108fef80002fc4e04eeff1a05f600030007000001132303 +21132303fd07c499e40208c499e405f6fef80108fef80108000100d5fe56052705d50013004a402111110102010211101110 +420b950a11020300af10130b100111021c0436111c001410dcecfcec113939cc31002f3cec323939dcec304b5358071004ed +071004ed5922b21f1501015d1333011133111407062b01353332373635011123d5b802e2b85251b5fee9692626fd1eb805d5 +fb83047dfa17d660609c3031ad047dfb8300ffff0192066303e808331027002d00bd023d100701d304bc0155ffff0192065e +03e80833102701db04bc01501007002d00bd023dffff0193066303e5085a102701d404f00264100701d304bc0155ffff0193 +066303e5085a102701d6048c0264100701d304bc0155ffff0176066a040a0833102701d504c0015c1007002d00bd023dffff +018b066303ed085a102701d804bc0262100701d304bc0155000100000002599939a3946a5f0f3cf5001f080000000000d17e +0ee400000000d17e0ee4f7d6fc4c0e5909dc00000008000000000000000000010000076dfe1d00000efef7d6fa510e590001 +000000000000000000000000000001e004cd00660000000002aa0000028b0000033501350579001005960073062900c9050e +00c906330073060400c9025c00c9025cff96053f00c9047500c905fc00c9064c0073058f00c90514008704e3fffa05db00b2 +07e9004404e3fffc057b005c040000aa04e7007b046600710514007104ec007105140071051200ba023900c10239ffdb04a2 +00ba023900c1051200ba04e50071034a00ba042b006f03230037051200ae068b005604bc003d04330058040000d7040000d5 +04000173028b00db040001230579001007cb000805960073050e00c9050e00c9050e00c9050e00c9025c003b025c00a2025c +fffe025c00060633000a05fc00c9064c0073064c0073064c0073064c0073064c007306b40119064c006605db00b205db00b2 +05db00b205db00b204e3fffc04d700c9050a00ba04e7007b04e7007b04e7007b04e7007b04e7007b04e7007b07db007b0466 +007104ec007104ec007104ec007104ec00710239ffc7023900900239ffde0239fff404e50071051200ba04e5007104e50071 +04e5007104e5007104e5007106b400d904e50048051200ae051200ae051200ae051200ae04bc003d051400ba04bc003d0579 +001004e7007b0579001004e7007b0579001004e7007b05960073046600710596007304660071059600730466007105960073 +04660071062900c9051400710633000a05140071050e00c904ec0071050e00c904ec0071050e00c904ec0071050e00c904ec +0071050e00c904ec00710633007305140071063300730514007106330073051400710633007305140071060400c90512ffe5 +075400c9058f0078025cffe40239ffd3025c00030239fff2025cfff50239ffe4025c00b002390096025c00c9023900c104b8 +00c9047200c1025cff960239ffdb053f00c904a200ba04a200ba047500c9023900c1047500c902390088047500c9030000c1 +047500c902bc00c1047ffff20246000205fc00c9051200ba05fc00c9051200ba05fc00c9051200ba068200cd05fc00c90512 +00ba064c007304e50071064c007304e50071064c007304e50071088f0073082f0071058f00c9034a00ba058f00c9034a0082 +058f00c9034a00ba05140087042b006f05140087042b006f05140087042b006f05140087042b006f04e3fffa0323003704e3 +fffa0323003704e3fffa0323003705db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2 +051200ae05db00b2051200ae07e90044068b005604e3fffc04bc003d04e3fffc057b005c04330058057b005c04330058057b +005c0433005802d1002f0514002005e1ff97057d00c9051400ba057d00000514000005a0007305960073046600710633000a +068dff97057d00c90514007104e50071050e0083064c007504ea00a4049aff9602d1ff7f06330073057e000807df00ba02d4 +00c9025c000a05f700c904a200b90239000a04bc003d07cb00b205fcff96051200ba064c0073074e006704e5007607970073 +061300710537ff97051400b9058f00c905140072042b0064050e00c902b0fef20323003704e300180323003704e3fffa06dd +00ad051200b0061d004e05c400c905f3fffc05d8003d057b005c04330058055400a00554005c049f00680433007105170096 +0554005d049f006804150058051400ba025c00c903f000c903ac0014025d00c90b6000c90a6400c9093c007106af00c9064b +00c903a700c1077300c9076400c9066100ba0579001004e7007b025cfffe0239ffe0064c007304e5007105db00b2051200ae +05db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2051200ae04ec00710579001004e7007b0579001004e7 +007b07cb000807db007b06330073051400710633007305140071053f00c904a2ffe9064c007304e50071064c007304e50071 +055400a0049f00580239ffdb0b6000c90a6400c9093c0071063300730514007108e700c9057500c905fc00c9051200ba0579 +001004e7007b07cb000807db007b064c006604e500480579001004e7007b0579001004e7007b050e00c904ec0071050e00c9 +04ec0071025cffa70239ffc3025c00050239ffe3064c007304e50071064c007304e50071058f00c7034a0082058f00c9034a +00ba05db00b2051200ae05db00b2051200ae05140087042b006f04e3fffa032300370504009c042c0047060400c90512fff0 +05e200c906b400710596007104e20071057b005c043300580579001004e7007b050e00c904ec0071064c007304e50071064c +007304e50071064c007304e50071064c007304e5007104e3fffc04bc003d03cc008a06be00ba03d100370239ffdb07fc0071 +07fc00710579fffd0596000c046600090475000a04e3ffb2042b006f0433005804d3005003d50050057d000a05db000c0579 +0010050e00c904ec0071025cff960239ffdb0640007305140071058f000a034a000e04e3fff604bc000b04ec0071049f0058 +028b00b2040000c1040000c1040000c7040000ee0400014c040000b6040000f00000fda20000fcc50000fc5d0000fcbf0000 +fe1f0000fef00000fd6a05790010050e00c9051200ba057d00c9028b00b20000ffb90000fcd70000fd730000fcb60000fd0c +0000fccf0000fccf0000fcc70000fcc70000fd9a0000fce60000fc4e05fc00d5057801920192019301930176018b00000000 +0000000000000000003100ae00f90138016701ba01e8020c024302d602f8034c0391041b049704cf051105ee064f06ae06d6 +076c07b70803086d08d1090d0935097209ea0a080a440a950acc0b7b0bb80bfa0d0c0df10e570eb30ed80eff0f140f440fe4 +104f105b106710731084109610a210ae10bf10d01135114c115811641179119211a9120d12a912b512c112d812f312ff1342 +13d513e713f91409141f143b145a15371543154f155b156c157d1589159515a615b7168f169b16a616b116c116d716ed171b +17d517e017eb17fb1813181e186d1884189918ad18c318d318df18eb18f7190319151921192d1939194a195619621975197d +19db19e719f81a091a1a1a261a321a3e1a4a1a5b1a701a821a931a9f1aab1abc1ac81ad41ae01af71b191b5c1ba61bb71bc8 +1bd91bea1bfb1c0c1c181c241c3b1c611c721c831c941ca51cb11cbd1d351d411d5d1d691d7a1d861d981da41dbd1dfa1e42 +1e531e641e701e7c1e931ea81eb41eff1f4d1f621f721f871f971fa31faf1ffe2092209e20a920b520c120d220e620f220fd +21102122212e2139214c215f216a2175218a219b21dc2229223e224f22662277228c229d22a922ba22c622d222de22ea22fb +230c231d232d233e234a2355236123752381239523c12427248a249224ec2532258525cd262d268a269226dc271a276d27d9 +2807285e28ba28f9295429b92a432aaa2ad72b0f2b6d2bf62c252c992cf92d602d682db92dc52dd12e242e792ec42f242f82 +2fec308f309730e43130317831c5320b32173223327932bf331c34043488350d357d35e4366b36a136da3723376937a237ec +380c38183857385f386b38773883388f389b38a738b338bf38cb38db38eb38fe3911391d392c393c394e395939653970397c +39873993399e39aa39b239bd39c939d439e039ec39f83a613adb3af03afb3b073b293b353b413b4d3b583b643b6f3b823b8e +3b9a3ba63bb23bbd3c083c533c5f3c6b3c773c833c8f3c9b3ca73cb23cbe3cca3cd63ce23cee3cfa3d063d123d1e3d2a3d36 +3d423d4e3d5a3d663d723d7e3d8a3d963da23dae3dba3dc63dd23dde3dea3df63e023e483e913e9d3ebf3ef83f4a3fd04042 +40b74133413f414b41574162416d417941844190419c41a841b341bf41cb41d642004241427542a74317438943c0440b4452 +448844b1450d4538457a45bd462b468b469346c7471c476947b7481748744907494d497449a349f54a7e4a864ab34ae14b26 +4b5c4b8c4beb4c214c434c764cad4cd24ce54d1f4d314d624da04ddd4e1c4e394e4b4eb04efd4f654fb85005505b507550c4 +50f4511251285170517d518a519751a451b151be0001000001e50354002b0068000c00020010009900080000041502160008 +000400000007005a000300010409000001300000000300010409000100160130000300010409000200080146000300010409 +00030016013000030001040900040016013000030001040900050018014e0003000104090006001401660043006f00700079 +0072006900670068007400200028006300290020003200300030003300200062007900200042006900740073007400720065 +0061006d002c00200049006e0063002e00200041006c006c0020005200690067006800740073002000520065007300650072 +007600650064002e000a0043006f007000790072006900670068007400200028006300290020003200300030003600200062 +00790020005400610076006d006a006f006e00670020004200610068002e00200041006c006c002000520069006700680074 +0073002000520065007300650072007600650064002e000a00440065006a0061005600750020006300680061006e00670065 +0073002000610072006500200069006e0020007000750062006c0069006300200064006f006d00610069006e000a00440065 +006a006100560075002000530061006e00730042006f006f006b00560065007200730069006f006e00200032002e00330035 +00440065006a00610056007500530061006e00730002000000000000ff7e005a000000000000000000000000000000000000 +000001e5000000010002000300040024002600270028002a002b002c002d002e002f003100320035003600370038003a003c +003d00430044004600470048004a004b004c004d004e004f005100520055005600570058005a005c005d008e00da008d00c3 +00de00630090006400cb006500c800ca00cf00cc00cd00ce00e9006600d300d000d100af006700f0009100d600d400d50068 +00eb00ed0089006a0069006b006d006c006e00a0006f0071007000720073007500740076007700ea0078007a0079007b007d +007c00b800a1007f007e0080008100ec00ee00ba01020103010401050106010700fd00fe01080109010a010b00ff0100010c +010d010e0101010f0110011101120113011401150116011701180119011a00f800f9011b011c011d011e011f012001210122 +0123012401250126012701280129012a00fa00d7012b012c012d012e012f0130013101320133013401350136013701380139 +00e200e3013a013b013c013d013e013f01400141014201430144014501460147014800b000b10149014a014b014c014d014e +014f01500151015200fb00fc00e400e50153015401550156015701580159015a015b015c015d015e015f0160016101620163 +0164016501660167016800bb0169016a016b016c00e600e7016d016e016f0170017101720173017401750176017701780179 +017a017b017c017d017e017f00a60180018101820183018401850186018701880189018a018b018c018d018e018f01900191 +01920193019401950196019701980199019a019b019c019d019e019f01a001a101a201a301a401a501a601a701a801a901aa +01ab01ac01ad01ae01af01b001b101b201b301b401b501b601b701b801b901ba01bb01bc01bd01be01bf01c001c101c201c3 +01c401c501c601c701c801c901ca01cb01cc01cd01ce01cf01d001d101d201d301d401d501d601d701d801d901da01db01dc +01dd01de01df01e001e101e201e301e401e501e601e701e801e901ea01eb01ec01ed01ee01ef01f001f101f201f301f401f5 +01f601f701f801f901fa01fb01fc01fd01fe01ff0200020102020203020402050206020702080209020a020b020c020d020e +020f0210021102120213021402150216021702180219021a021b021c021d021e021f02200221022202230224022502260227 +02280229022a022b022c022d022e022f0230023102320233023402350236023702380239023a023b023c023d023e023f00d8 +00e100db00dd00e000d900df0240024102420243024402450246024702480249024a00b7024b024c024d024e024f02500251 +02520253025402550256025702580259025a025b025c025d07416d6163726f6e07616d6163726f6e06416272657665066162 +7265766507416f676f6e656b07616f676f6e656b0b4363697263756d666c65780b6363697263756d666c65780a43646f7461 +6363656e740a63646f74616363656e7406446361726f6e06646361726f6e064463726f617407456d6163726f6e07656d6163 +726f6e06456272657665066562726576650a45646f74616363656e740a65646f74616363656e7407456f676f6e656b07656f +676f6e656b06456361726f6e06656361726f6e0b4763697263756d666c65780b6763697263756d666c65780a47646f746163 +63656e740a67646f74616363656e740c47636f6d6d61616363656e740c67636f6d6d61616363656e740b4863697263756d66 +6c65780b6863697263756d666c657804486261720468626172064974696c6465066974696c646507496d6163726f6e07696d +6163726f6e064962726576650669627265766507496f676f6e656b07696f676f6e656b02494a02696a0b4a63697263756d66 +6c65780b6a63697263756d666c65780c4b636f6d6d61616363656e740c6b636f6d6d61616363656e740c6b677265656e6c61 +6e646963064c6163757465066c61637574650c4c636f6d6d61616363656e740c6c636f6d6d61616363656e74064c6361726f +6e066c6361726f6e044c646f74046c646f74064e6163757465066e61637574650c4e636f6d6d61616363656e740c6e636f6d +6d61616363656e74064e6361726f6e066e6361726f6e0b6e61706f7374726f70686503456e6703656e67074f6d6163726f6e +076f6d6163726f6e064f6272657665066f62726576650d4f68756e676172756d6c6175740d6f68756e676172756d6c617574 +06526163757465067261637574650c52636f6d6d61616363656e740c72636f6d6d61616363656e7406526361726f6e067263 +61726f6e06536163757465067361637574650b5363697263756d666c65780b7363697263756d666c65780c54636f6d6d6161 +6363656e740c74636f6d6d61616363656e7406546361726f6e06746361726f6e04546261720474626172065574696c646506 +7574696c646507556d6163726f6e07756d6163726f6e0655627265766506756272657665055572696e67057572696e670d55 +68756e676172756d6c6175740d7568756e676172756d6c61757407556f676f6e656b07756f676f6e656b0b5763697263756d +666c65780b7763697263756d666c65780b5963697263756d666c65780b7963697263756d666c6578065a6163757465067a61 +637574650a5a646f74616363656e740a7a646f74616363656e74056c6f6e677307756e693031383007756e69303138310775 +6e693031383207756e693031383307756e693031383407756e693031383507756e693031383607756e693031383707756e69 +3031383807756e693031383907756e693031384107756e693031384207756e693031384307756e693031384407756e693031 +384507756e693031384607756e693031393007756e693031393107756e693031393307756e693031393407756e6930313935 +07756e693031393607756e693031393707756e693031393807756e693031393907756e693031394107756e69303139420775 +6e693031394307756e693031394407756e693031394507756e6930313946054f686f726e056f686f726e07756e6930314132 +07756e693031413307756e693031413407756e693031413507756e693031413607756e693031413707756e69303141380775 +6e693031413907756e693031414107756e693031414207756e693031414307756e693031414407756e69303141450555686f +726e0575686f726e07756e693031423107756e693031423207756e693031423307756e693031423407756e69303142350775 +6e693031423607756e693031423707756e693031423807756e693031423907756e693031424107756e693031424207756e69 +3031424307756e693031424407756e693031424507756e693031424607756e693031433007756e693031433107756e693031 +433207756e693031433307756e693031433407756e693031433507756e693031433607756e693031433707756e6930314338 +07756e693031433907756e693031434107756e693031434207756e693031434307756e693031434407756e69303143450775 +6e693031434607756e693031443007756e693031443107756e693031443207756e693031443307756e693031443407756e69 +3031443507756e693031443607756e693031443707756e693031443807756e693031443907756e693031444107756e693031 +444207756e693031444307756e693031444407756e693031444507756e693031444607756e693031453007756e6930314531 +07756e693031453207756e693031453307756e693031453407756e693031453506476361726f6e06676361726f6e07756e69 +3031453807756e693031453907756e693031454107756e693031454207756e693031454307756e693031454407756e693031 +454507756e693031454607756e693031463007756e693031463107756e693031463207756e693031463307756e6930314634 +07756e693031463507756e693031463607756e693031463707756e693031463807756e69303146390a4172696e6761637574 +650a6172696e676163757465074145616375746507616561637574650b4f736c61736861637574650b6f736c617368616375 +746507756e693032303007756e693032303107756e693032303207756e693032303307756e693032303407756e6930323035 +07756e693032303607756e693032303707756e693032303807756e693032303907756e693032304107756e69303230420775 +6e693032304307756e693032304407756e693032304507756e693032304607756e693032313007756e693032313107756e69 +3032313207756e693032313307756e693032313407756e693032313507756e693032313607756e69303231370c53636f6d6d +61616363656e740c73636f6d6d61616363656e7407756e693032314107756e693032314207756e693032314307756e693032 +314407756e693032314507756e693032314607756e693032323007756e693032323107756e693032323207756e6930323233 +07756e693032323407756e693032323507756e693032323607756e693032323707756e693032323807756e69303232390775 +6e693032324107756e693032324207756e693032324307756e693032324407756e693032324507756e693032324607756e69 +3032333007756e693032333107756e693032333207756e693032333307756e693032333407756e693032333507756e693032 +333608646f746c6573736a07756e693032333807756e693032333907756e693032334107756e693032334207756e69303233 +4307756e693032334407756e693032334507756e693032334607756e693032343007756e693032343107756e693032343207 +756e693032343307756e693032343407756e693032343507756e693032343607756e693032343707756e693032343807756e +693032343907756e693032344107756e693032344207756e693032344307756e693032344407756e693032344507756e6930 +32344607756e693032353907756e693032393207756e693032424307756e693033303707756e693033304307756e69303330 +4607756e693033313107756e693033313207756e693033314207756e6930333236064c616d626461055369676d6103657461 +07756e693034313109646c4c746361726f6e0844696572657369730541637574650554696c64650547726176650a43697263 +756d666c6578054361726f6e0c756e69303331312e6361736505427265766509446f74616363656e740c48756e676172756d +6c6175740b446f75626c65677261766507456e672e616c740b756e6930333038303330340b756e6930333037303330340b75 +6e6930333038303330310b756e6930333038303330300b756e6930333033303330340b756e6930333038303330430000b802 +8040fffbfe03fa1403f92503f83203f79603f60e03f5fe03f4fe03f32503f20e03f19603f02503ef8a4105effe03ee9603ed +9603ecfa03ebfa03eafe03e93a03e84203e7fe03e63203e5e45305e59603e48a4105e45303e3e22f05e3fa03e22f03e1fe03 +e0fe03df3203de1403dd9603dcfe03db1203da7d03d9bb03d8fe03d68a4105d67d03d5d44705d57d03d44703d3d21b05d3fe +03d21b03d1fe03d0fe03cffe03cefe03cd9603cccb1e05ccfe03cb1e03ca3203c9fe03c6851105c61c03c51603c4fe03c3fe +03c2fe03c1fe03c0fe03bffe03befe03bdfe03bcfe03bbfe03ba1103b9862505b9fe03b8b7bb05b8fe03b7b65d05b7bb03b7 +8004b6b52505b65d40ff03b64004b52503b4fe03b39603b2fe03b1fe03b0fe03affe03ae6403ad0e03acab2505ac6403abaa +1205ab2503aa1203a98a4105a9fa03a8fe03a7fe03a6fe03a51203a4fe03a3a20e05a33203a20e03a16403a08a4105a09603 +9ffe039e9d0c059efe039d0c039c9b19059c64039b9a10059b19039a1003990a0398fe0397960d0597fe03960d03958a4105 +95960394930e05942803930e0392fa039190bb0591fe03908f5d0590bb039080048f8e25058f5d038f40048e25038dfe038c +8b2e058cfe038b2e038a8625058a410389880b05891403880b03878625058764038685110586250385110384fe0383821105 +83fe0382110381fe0380fe037ffe0340ff7e7d7d057efe037d7d037c64037b5415057b25037afe0379fe03780e03770c0376 +0a0375fe0374fa0373fa0372fa0371fa0370fe036ffe036efe036c21036bfe036a1142056a530369fe03687d036711420566 +fe0365fe0364fe0363fe0362fe03613a0360fa035e0c035dfe035bfe035afe0359580a0559fa03580a035716190557320356 +fe035554150555420354150353011005531803521403514a130551fe03500b034ffe034e4d10054efe034d10034cfe034b4a +13054bfe034a4910054a1303491d0d05491003480d0347fe0346960345960344fe0343022d0543fa0342bb03414b0340fe03 +3ffe033e3d12053e14033d3c0f053d12033c3b0d053c40ff0f033b0d033afe0339fe033837140538fa033736100537140336 +350b05361003350b03341e03330d0332310b0532fe03310b03302f0b05300d032f0b032e2d09052e10032d09032c32032b2a +25052b64032a2912052a25032912032827250528410327250326250b05260f03250b0324fe0323fe03220f03210110052112 +032064031ffa031e1d0d051e64031d0d031c1142051cfe031bfa031a42031911420519fe031864031716190517fe03160110 +0516190315fe0314fe0313fe031211420512fe0311022d05114203107d030f64030efe030d0c16050dfe030c0110050c1603 +0bfe030a100309fe0308022d0508fe030714030664030401100504fe03401503022d0503fe0302011005022d0301100300fe +0301b80164858d012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d00>]def + FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +0 0 translate +0 0 576 432 rectclip +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1 setgray +fill +grestore +0 setgray +/Cmr10 16.000 selectfont +gsave + +195.836 385.058 translate +0 rotate +0 0 m /T glyphshow +11.5547 0 m /h glyphshow +20.4375 0 m /e glyphshow +27.5391 0 m /r glyphshow +33.7969 0 m /e glyphshow +40.8984 0 m /space glyphshow +46.2266 0 m /a glyphshow +54.2266 0 m /r glyphshow +60.4844 0 m /e glyphshow +67.5859 0 m /space glyphshow +72.9141 0 m /b glyphshow +81.7969 0 m /a glyphshow +89.7969 0 m /s glyphshow +96.1016 0 m /i glyphshow +100.531 0 m /c glyphshow +107.633 0 m /space glyphshow +112.961 0 m /c glyphshow +120.062 0 m /h glyphshow +128.945 0 m /a glyphshow +136.945 0 m /r glyphshow +143.203 0 m /a glyphshow +151.203 0 m /c glyphshow +158.305 0 m /t glyphshow +164.516 0 m /e glyphshow +171.617 0 m /r glyphshow +177.875 0 m /s glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +34.5859 368.205 translate +0 rotate +0 0 m /A glyphshow +12 0 m /B glyphshow +23.3281 0 m /C glyphshow +34.8828 0 m /D glyphshow +47.0938 0 m /E glyphshow +57.9766 0 m /F glyphshow +68.4062 0 m /G glyphshow +80.9531 0 m /H glyphshow +92.9531 0 m /I glyphshow +98.7266 0 m /J glyphshow +106.938 0 m /K glyphshow +119.367 0 m /L glyphshow +129.367 0 m /M glyphshow +144.023 0 m /N glyphshow +156.023 0 m /O glyphshow +168.453 0 m /P glyphshow +179.336 0 m /Q glyphshow +191.766 0 m /R glyphshow +203.539 0 m /S glyphshow +212.422 0 m /T glyphshow +223.977 0 m /U glyphshow +235.977 0 m /V glyphshow +247.977 0 m /W glyphshow +264.406 0 m /X glyphshow +276.406 0 m /Y glyphshow +288.406 0 m /Z glyphshow +298.18 0 m /space glyphshow +303.508 0 m /a glyphshow +311.508 0 m /b glyphshow +320.391 0 m /c glyphshow +327.492 0 m /d glyphshow +336.375 0 m /e glyphshow +343.477 0 m /f glyphshow +348.359 0 m /g glyphshow +356.359 0 m /h glyphshow +365.242 0 m /i glyphshow +369.672 0 m /j glyphshow +374.555 0 m /k glyphshow +382.984 0 m /l glyphshow +387.414 0 m /m glyphshow +400.742 0 m /n glyphshow +409.625 0 m /o glyphshow +417.625 0 m /p glyphshow +426.508 0 m /q glyphshow +434.938 0 m /r glyphshow +441.195 0 m /s glyphshow +447.5 0 m /t glyphshow +453.711 0 m /u glyphshow +462.594 0 m /v glyphshow +471.023 0 m /w glyphshow +482.578 0 m /x glyphshow +491.008 0 m /y glyphshow +499.438 0 m /z glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +122.281 350.508 translate +0 rotate +0 0 m /zero glyphshow +8 0 m /one glyphshow +16 0 m /two glyphshow +24 0 m /three glyphshow +32 0 m /four glyphshow +40 0 m /five glyphshow +48 0 m /six glyphshow +56 0 m /seven glyphshow +64 0 m /eight glyphshow +72 0 m /nine glyphshow +80 0 m /space glyphshow +85.3281 0 m /exclam glyphshow +89.7578 0 m /quotedblright glyphshow +97.7578 0 m /numbersign glyphshow +111.086 0 m /dollar glyphshow +119.086 0 m /percent glyphshow +132.414 0 m /ampersand glyphshow +144.844 0 m /quoteright glyphshow +149.273 0 m /parenleft glyphshow +155.484 0 m /parenright glyphshow +161.695 0 m /asterisk glyphshow +169.695 0 m /plus glyphshow +182.125 0 m /comma glyphshow +186.555 0 m /hyphen glyphshow +191.883 0 m /period glyphshow +196.312 0 m /slash glyphshow +204.312 0 m /colon glyphshow +208.742 0 m /semicolon glyphshow +213.172 0 m /exclamdown glyphshow +217.602 0 m /equal glyphshow +230.031 0 m /questiondown glyphshow +237.586 0 m /question glyphshow +245.141 0 m /at glyphshow +257.57 0 m /bracketleft glyphshow +262 0 m /quotedblleft glyphshow +270 0 m /bracketright glyphshow +274.43 0 m /circumflex glyphshow +282.43 0 m /dotaccent glyphshow +286.859 0 m /quoteleft glyphshow +291.289 0 m /emdash glyphshow +299.289 0 m /endash glyphshow +315.289 0 m /hungarumlaut glyphshow +323.289 0 m /tilde glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +203.922 333.177 translate +0 rotate +0 0 m /a glyphshow +8 0 m /n glyphshow +16.8828 0 m /d glyphshow +25.7656 0 m /space glyphshow +31.0938 0 m /a glyphshow +39.0938 0 m /c glyphshow +46.1953 0 m /c glyphshow +53.2969 0 m /e glyphshow +60.3984 0 m /n glyphshow +69.2812 0 m /t glyphshow +75.4922 0 m /e glyphshow +82.5938 0 m /d glyphshow +91.4766 0 m /space glyphshow +96.8047 0 m /c glyphshow +103.906 0 m /h glyphshow +112.789 0 m /a glyphshow +120.789 0 m /r glyphshow +127.047 0 m /a glyphshow +135.047 0 m /c glyphshow +142.148 0 m /t glyphshow +148.359 0 m /e glyphshow +155.461 0 m /r glyphshow +161.719 0 m /s glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +144.602 312.161 translate +0 rotate +0 0 m /Aring glyphshow +10.9453 0 m /AE glyphshow +26.5312 0 m /Ccedilla glyphshow +37.7031 0 m /Egrave glyphshow +47.8125 0 m /Eacute glyphshow +57.9219 0 m /Ecircumflex glyphshow +68.0312 0 m /Edieresis glyphshow +78.1406 0 m /Igrave glyphshow +82.8594 0 m /Iacute glyphshow +87.5781 0 m /Icircumflex glyphshow +92.2969 0 m /Idieresis glyphshow +97.0156 0 m /Eth glyphshow +109.414 0 m /Ntilde glyphshow +121.383 0 m /Ograve glyphshow +133.977 0 m /Oacute glyphshow +146.57 0 m /Ocircumflex glyphshow +159.164 0 m /Otilde glyphshow +171.758 0 m /Odieresis glyphshow +184.352 0 m /multiply glyphshow +197.758 0 m /Oslash glyphshow +210.352 0 m /Ugrave glyphshow +222.062 0 m /Uacute glyphshow +233.773 0 m /Ucircumflex glyphshow +245.484 0 m /Udieresis glyphshow +257.195 0 m /Yacute glyphshow +266.969 0 m /Thorn glyphshow +276.648 0 m /germandbls glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +136.82 292.195 translate +0 rotate +0 0 m /agrave glyphshow +9.80469 0 m /aacute glyphshow +19.6094 0 m /acircumflex glyphshow +29.4141 0 m /atilde glyphshow +39.2188 0 m /adieresis glyphshow +49.0234 0 m /aring glyphshow +58.8281 0 m /ae glyphshow +74.5391 0 m /ccedilla glyphshow +83.3359 0 m /egrave glyphshow +93.1797 0 m /eacute glyphshow +103.023 0 m /ecircumflex glyphshow +112.867 0 m /edieresis glyphshow +122.711 0 m /igrave glyphshow +127.156 0 m /iacute glyphshow +131.602 0 m /icircumflex glyphshow +136.047 0 m /idieresis glyphshow +140.492 0 m /eth glyphshow +150.281 0 m /ntilde glyphshow +160.422 0 m /ograve glyphshow +170.211 0 m /oacute glyphshow +180 0 m /ocircumflex glyphshow +189.789 0 m /otilde glyphshow +199.578 0 m /odieresis glyphshow +209.367 0 m /divide glyphshow +222.773 0 m /oslash glyphshow +232.562 0 m /ugrave glyphshow +242.703 0 m /uacute glyphshow +252.844 0 m /ucircumflex glyphshow +262.984 0 m /udieresis glyphshow +273.125 0 m /yacute glyphshow +282.594 0 m /thorn glyphshow +292.75 0 m /ydieresis glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +121.945 270.192 translate +0 rotate +0 0 m /Amacron glyphshow +10.9453 0 m /amacron glyphshow +20.75 0 m /Abreve glyphshow +31.6953 0 m /abreve glyphshow +41.5 0 m /Aogonek glyphshow +52.4453 0 m /aogonek glyphshow +62.25 0 m /Cacute glyphshow +73.4219 0 m /cacute glyphshow +82.2188 0 m /Ccircumflex glyphshow +93.3906 0 m /ccircumflex glyphshow +102.188 0 m /Cdotaccent glyphshow +113.359 0 m /cdotaccent glyphshow +122.156 0 m /Ccaron glyphshow +133.328 0 m /ccaron glyphshow +142.125 0 m /Dcaron glyphshow +154.445 0 m /dcaron glyphshow +164.602 0 m /Dcroat glyphshow +177 0 m /dcroat glyphshow +187.156 0 m /Emacron glyphshow +197.266 0 m /emacron glyphshow +207.109 0 m /Ebreve glyphshow +217.219 0 m /ebreve glyphshow +227.062 0 m /Edotaccent glyphshow +237.172 0 m /edotaccent glyphshow +247.016 0 m /Eogonek glyphshow +257.125 0 m /eogonek glyphshow +266.969 0 m /Ecaron glyphshow +277.078 0 m /ecaron glyphshow +286.922 0 m /Gcircumflex glyphshow +299.32 0 m /gcircumflex glyphshow +309.477 0 m /Gbreve glyphshow +321.875 0 m /gbreve glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +164.969 248.939 translate +0 rotate +0 0 m /Gdotaccent glyphshow +12.3984 0 m /gdotaccent glyphshow +22.5547 0 m /Gcommaaccent glyphshow +34.9531 0 m /gcommaaccent glyphshow +45.1094 0 m /Hcircumflex glyphshow +57.1406 0 m /hcircumflex glyphshow +67.2812 0 m /Hbar glyphshow +81.9375 0 m /hbar glyphshow +93.0547 0 m /Itilde glyphshow +97.7734 0 m /itilde glyphshow +102.219 0 m /Imacron glyphshow +106.938 0 m /imacron glyphshow +111.383 0 m /Ibreve glyphshow +116.102 0 m /ibreve glyphshow +120.547 0 m /Iogonek glyphshow +125.266 0 m /iogonek glyphshow +129.711 0 m /Idotaccent glyphshow +134.43 0 m /dotlessi glyphshow +138.875 0 m /IJ glyphshow +148.312 0 m /ij glyphshow +157.203 0 m /Jcircumflex glyphshow +161.922 0 m /jcircumflex glyphshow +166.367 0 m /Kcommaaccent glyphshow +176.859 0 m /kcommaaccent glyphshow +186.125 0 m /kgreenlandic glyphshow +195.391 0 m /Lacute glyphshow +204.305 0 m /lacute glyphshow +208.75 0 m /Lcommaaccent glyphshow +217.664 0 m /lcommaaccent glyphshow +222.109 0 m /Lcaron glyphshow +231.023 0 m /lcaron glyphshow +237.023 0 m /Ldot glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +123.125 227.17 translate +0 rotate +0 0 m /ldot glyphshow +5.46875 0 m /Lslash glyphshow +14.4609 0 m /lslash glyphshow +19.0078 0 m /Nacute glyphshow +30.9766 0 m /nacute glyphshow +41.1172 0 m /Ncommaaccent glyphshow +53.0859 0 m /ncommaaccent glyphshow +63.2266 0 m /Ncaron glyphshow +75.1953 0 m /ncaron glyphshow +85.3359 0 m /napostrophe glyphshow +98.3516 0 m /Eng glyphshow +110.32 0 m /eng glyphshow +120.461 0 m /Omacron glyphshow +133.055 0 m /omacron glyphshow +142.844 0 m /Obreve glyphshow +155.438 0 m /obreve glyphshow +165.227 0 m /Ohungarumlaut glyphshow +177.82 0 m /ohungarumlaut glyphshow +187.609 0 m /OE glyphshow +204.727 0 m /oe glyphshow +221.094 0 m /Racute glyphshow +232.211 0 m /racute glyphshow +238.789 0 m /Rcommaaccent glyphshow +249.906 0 m /rcommaaccent glyphshow +256.484 0 m /Rcaron glyphshow +267.602 0 m /rcaron glyphshow +274.18 0 m /Sacute glyphshow +284.336 0 m /sacute glyphshow +292.672 0 m /Scircumflex glyphshow +302.828 0 m /scircumflex glyphshow +311.164 0 m /Scedilla glyphshow +321.32 0 m /scedilla glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +128.219 205.27 translate +0 rotate +0 0 m /Scaron glyphshow +10.1562 0 m /scaron glyphshow +18.4922 0 m /Tcommaaccent glyphshow +28.2656 0 m /tcommaaccent glyphshow +34.5391 0 m /Tcaron glyphshow +44.3125 0 m /tcaron glyphshow +50.5859 0 m /Tbar glyphshow +60.3594 0 m /tbar glyphshow +66.6328 0 m /Utilde glyphshow +78.3438 0 m /utilde glyphshow +88.4844 0 m /Umacron glyphshow +100.195 0 m /umacron glyphshow +110.336 0 m /Ubreve glyphshow +122.047 0 m /ubreve glyphshow +132.188 0 m /Uring glyphshow +143.898 0 m /uring glyphshow +154.039 0 m /Uhungarumlaut glyphshow +165.75 0 m /uhungarumlaut glyphshow +175.891 0 m /Uogonek glyphshow +187.602 0 m /uogonek glyphshow +197.742 0 m /Wcircumflex glyphshow +213.562 0 m /wcircumflex glyphshow +226.648 0 m /Ycircumflex glyphshow +236.422 0 m /ycircumflex glyphshow +245.891 0 m /Ydieresis glyphshow +255.664 0 m /Zacute glyphshow +266.625 0 m /zacute glyphshow +275.023 0 m /Zdotaccent glyphshow +285.984 0 m /zdotaccent glyphshow +294.383 0 m /Zcaron glyphshow +305.344 0 m /zcaron glyphshow +313.742 0 m /longs glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +120.906 184.205 translate +0 rotate +0 0 m /uni0180 glyphshow +10.1562 0 m /uni0181 glyphshow +21.9141 0 m /uni0182 glyphshow +32.8906 0 m /uni0183 glyphshow +43.0469 0 m /uni0184 glyphshow +54.0234 0 m /uni0185 glyphshow +64.1797 0 m /uni0186 glyphshow +75.4297 0 m /uni0187 glyphshow +86.6016 0 m /uni0188 glyphshow +95.3984 0 m /uni0189 glyphshow +107.797 0 m /uni018A glyphshow +120.898 0 m /uni018B glyphshow +131.875 0 m /uni018C glyphshow +142.031 0 m /uni018D glyphshow +151.82 0 m /uni018E glyphshow +161.93 0 m /uni018F glyphshow +174.523 0 m /uni0190 glyphshow +184.352 0 m /uni0191 glyphshow +193.555 0 m /florin glyphshow +199.188 0 m /uni0193 glyphshow +211.586 0 m /uni0194 glyphshow +222.57 0 m /uni0195 glyphshow +238.312 0 m /uni0196 glyphshow +243.969 0 m /uni0197 glyphshow +248.688 0 m /uni0198 glyphshow +260.617 0 m /uni0199 glyphshow +269.883 0 m /uni019A glyphshow +274.328 0 m /uni019B glyphshow +283.797 0 m /uni019C glyphshow +299.383 0 m /uni019D glyphshow +311.352 0 m /uni019E glyphshow +321.492 0 m /uni019F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +124.211 166.258 translate +0 rotate +0 0 m /Ohorn glyphshow +14.6094 0 m /ohorn glyphshow +24.3984 0 m /uni01A2 glyphshow +39.5781 0 m /uni01A3 glyphshow +51.7266 0 m /uni01A4 glyphshow +62.1562 0 m /uni01A5 glyphshow +72.3125 0 m /uni01A6 glyphshow +83.4297 0 m /uni01A7 glyphshow +93.5859 0 m /uni01A8 glyphshow +101.922 0 m /uni01A9 glyphshow +112.031 0 m /uni01AA glyphshow +117.406 0 m /uni01AB glyphshow +123.68 0 m /uni01AC glyphshow +133.453 0 m /uni01AD glyphshow +139.727 0 m /uni01AE glyphshow +149.5 0 m /Uhorn glyphshow +163.227 0 m /uhorn glyphshow +173.367 0 m /uni01B1 glyphshow +185.594 0 m /uni01B2 glyphshow +197.125 0 m /uni01B3 glyphshow +209.023 0 m /uni01B4 glyphshow +220.711 0 m /uni01B5 glyphshow +231.672 0 m /uni01B6 glyphshow +240.07 0 m /uni01B7 glyphshow +250.727 0 m /uni01B8 glyphshow +261.383 0 m /uni01B9 glyphshow +270.625 0 m /uni01BA glyphshow +279.023 0 m /uni01BB glyphshow +289.203 0 m /uni01BC glyphshow +299.859 0 m /uni01BD glyphshow +309.102 0 m /uni01BE glyphshow +317.266 0 m /uni01BF glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +110.68 142.527 translate +0 rotate +0 0 m /uni01C0 glyphshow +4.71875 0 m /uni01C1 glyphshow +12.5938 0 m /uni01C2 glyphshow +19.9375 0 m /uni01C3 glyphshow +24.6641 0 m /uni01C4 glyphshow +47.4141 0 m /uni01C5 glyphshow +68.1953 0 m /uni01C6 glyphshow +86.6641 0 m /uni01C7 glyphshow +100.031 0 m /uni01C8 glyphshow +112.617 0 m /uni01C9 glyphshow +119.922 0 m /uni01CA glyphshow +134.82 0 m /uni01CB glyphshow +149.602 0 m /uni01CC glyphshow +162.359 0 m /uni01CD glyphshow +173.305 0 m /uni01CE glyphshow +183.109 0 m /uni01CF glyphshow +187.828 0 m /uni01D0 glyphshow +192.273 0 m /uni01D1 glyphshow +204.867 0 m /uni01D2 glyphshow +214.656 0 m /uni01D3 glyphshow +226.367 0 m /uni01D4 glyphshow +236.508 0 m /uni01D5 glyphshow +248.219 0 m /uni01D6 glyphshow +258.359 0 m /uni01D7 glyphshow +270.07 0 m /uni01D8 glyphshow +280.211 0 m /uni01D9 glyphshow +291.922 0 m /uni01DA glyphshow +302.062 0 m /uni01DB glyphshow +313.773 0 m /uni01DC glyphshow +323.914 0 m /uni01DD glyphshow +333.758 0 m /uni01DE glyphshow +344.703 0 m /uni01DF glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +90.0078 120.092 translate +0 rotate +0 0 m /uni01E0 glyphshow +10.9453 0 m /uni01E1 glyphshow +20.75 0 m /uni01E2 glyphshow +36.3359 0 m /uni01E3 glyphshow +52.0469 0 m /uni01E4 glyphshow +64.4453 0 m /uni01E5 glyphshow +74.6016 0 m /Gcaron glyphshow +87 0 m /gcaron glyphshow +97.1562 0 m /uni01E8 glyphshow +107.648 0 m /uni01E9 glyphshow +116.914 0 m /uni01EA glyphshow +129.508 0 m /uni01EB glyphshow +139.297 0 m /uni01EC glyphshow +151.891 0 m /uni01ED glyphshow +161.68 0 m /uni01EE glyphshow +172.336 0 m /uni01EF glyphshow +181.578 0 m /uni01F0 glyphshow +186.023 0 m /uni01F1 glyphshow +208.773 0 m /uni01F2 glyphshow +229.555 0 m /uni01F3 glyphshow +248.023 0 m /uni01F4 glyphshow +260.422 0 m /uni01F5 glyphshow +270.578 0 m /uni01F6 glyphshow +288.383 0 m /uni01F7 glyphshow +299.297 0 m /uni01F8 glyphshow +311.266 0 m /uni01F9 glyphshow +321.406 0 m /Aringacute glyphshow +332.352 0 m /aringacute glyphshow +342.156 0 m /AEacute glyphshow +357.742 0 m /aeacute glyphshow +373.453 0 m /Oslashacute glyphshow +386.047 0 m /oslashacute glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +138.602 98.7609 translate +0 rotate +0 0 m /uni0200 glyphshow +10.9453 0 m /uni0201 glyphshow +20.75 0 m /uni0202 glyphshow +31.6953 0 m /uni0203 glyphshow +41.5 0 m /uni0204 glyphshow +51.6094 0 m /uni0205 glyphshow +61.4531 0 m /uni0206 glyphshow +71.5625 0 m /uni0207 glyphshow +81.4062 0 m /uni0208 glyphshow +86.125 0 m /uni0209 glyphshow +90.5703 0 m /uni020A glyphshow +95.2891 0 m /uni020B glyphshow +99.7344 0 m /uni020C glyphshow +112.328 0 m /uni020D glyphshow +122.117 0 m /uni020E glyphshow +134.711 0 m /uni020F glyphshow +144.5 0 m /uni0210 glyphshow +155.617 0 m /uni0211 glyphshow +162.195 0 m /uni0212 glyphshow +173.312 0 m /uni0213 glyphshow +179.891 0 m /uni0214 glyphshow +191.602 0 m /uni0215 glyphshow +201.742 0 m /uni0216 glyphshow +213.453 0 m /uni0217 glyphshow +223.594 0 m /Scommaaccent glyphshow +233.75 0 m /scommaaccent glyphshow +242.086 0 m /uni021A glyphshow +251.859 0 m /uni021B glyphshow +258.133 0 m /uni021C glyphshow +268.164 0 m /uni021D glyphshow +276.508 0 m /uni021E glyphshow +288.539 0 m /uni021F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +118.953 75.8109 translate +0 rotate +0 0 m /uni0220 glyphshow +11.7656 0 m /uni0221 glyphshow +25.1719 0 m /uni0222 glyphshow +36.3438 0 m /uni0223 glyphshow +46.1094 0 m /uni0224 glyphshow +57.0703 0 m /uni0225 glyphshow +65.4688 0 m /uni0226 glyphshow +76.4141 0 m /uni0227 glyphshow +86.2188 0 m /uni0228 glyphshow +96.3281 0 m /uni0229 glyphshow +106.172 0 m /uni022A glyphshow +118.766 0 m /uni022B glyphshow +128.555 0 m /uni022C glyphshow +141.148 0 m /uni022D glyphshow +150.938 0 m /uni022E glyphshow +163.531 0 m /uni022F glyphshow +173.32 0 m /uni0230 glyphshow +185.914 0 m /uni0231 glyphshow +195.703 0 m /uni0232 glyphshow +205.477 0 m /uni0233 glyphshow +214.945 0 m /uni0234 glyphshow +222.539 0 m /uni0235 glyphshow +236.023 0 m /uni0236 glyphshow +243.656 0 m /dotlessj glyphshow +248.102 0 m /uni0238 glyphshow +264.07 0 m /uni0239 glyphshow +280.039 0 m /uni023A glyphshow +290.984 0 m /uni023B glyphshow +302.156 0 m /uni023C glyphshow +310.953 0 m /uni023D glyphshow +319.867 0 m /uni023E glyphshow +329.641 0 m /uni023F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +213.938 56.1484 translate +0 rotate +0 0 m /uni0240 glyphshow +8.39844 0 m /uni0241 glyphshow +18.0469 0 m /uni0242 glyphshow +25.7109 0 m /uni0243 glyphshow +36.6875 0 m /uni0244 glyphshow +48.3984 0 m /uni0245 glyphshow +59.3438 0 m /uni0246 glyphshow +69.4531 0 m /uni0247 glyphshow +79.2969 0 m /uni0248 glyphshow +84.0156 0 m /uni0249 glyphshow +88.4609 0 m /uni024A glyphshow +100.961 0 m /uni024B glyphshow +111.117 0 m /uni024C glyphshow +122.234 0 m /uni024D glyphshow +128.812 0 m /uni024E glyphshow +138.586 0 m /uni024F glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +248.008 38.9422 translate +0 rotate +0 0 m /i glyphshow +4.42969 0 m /n glyphshow +13.3125 0 m /space glyphshow +18.6406 0 m /b glyphshow +27.5234 0 m /e glyphshow +34.625 0 m /t glyphshow +40.8359 0 m /w glyphshow +52.3906 0 m /e glyphshow +59.4922 0 m /e glyphshow +66.5938 0 m /n glyphshow +75.4766 0 m /exclam glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps new file mode 100644 index 000000000000..e448344deeb9 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps @@ -0,0 +1,2830 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 +%%Title: multi_font_type42.eps +%%Creator: Matplotlib v3.10.0.dev856+g03f7095b8c, https://matplotlib.org/ +%%CreationDate: Wed Oct 16 16:10:36 2024 +%%Orientation: portrait +%%BoundingBox: 0 0 576 432 +%%HiResBoundingBox: 0.000000 0.000000 576.000000 432.000000 +%%EndComments +%%BeginProlog +/mpldict 10 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/sc { setcachedevice } _d + + %%!PS-TrueTypeFont-1.0-1.0000000 + 10 dict begin + /FontType 42 def + /FontMatrix [1 0 0 1 0 0] def + /FontName /Cmr10 def + /FontInfo 7 dict dup begin + /FullName (cmr10) def + /FamilyName (cmr10) def + /Version (1.1/12-Nov-94) def + /ItalicAngle 0.0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + end readonly def + /Encoding StandardEncoding def + /FontBBox [-90 -512 2066 1536] def + /PaintType 0 def + /CIDMap 0 def + /CharStrings 99 dict dup begin +/.notdef 0 def +/.null 1 def +/nonmarkingreturn 2 def +/Xi 3 def +/comma 4 def +/nine 5 def +/M 6 def +/Z 7 def +/quoteleft 8 def +/dotaccent 9 def +/g 10 def +/t 11 def +/exclam 12 def +/period 13 def +/semicolon 14 def +/B 15 def +/O 16 def +/quotedblright 17 def +/i 18 def +/v 19 def +/numbersign 20 def +/zero 21 def +/equal 22 def +/D 23 def +/Q 24 def +/k 25 def +/x 26 def +/hungarumlaut 27 def +/percent 28 def +/two 29 def +/question 30 def +/F 31 def +/questiondown 32 def +/S 33 def +/m 34 def +/z 35 def +/quoteright 36 def +/four 37 def +/H 38 def +/U 39 def +/bracketleft 40 def +/b 41 def +/o 42 def +/parenright 43 def +/six 44 def +/J 45 def +/W 46 def +/bracketright 47 def +/d 48 def +/q 49 def +/tilde 50 def +/plus 51 def +/eight 52 def +/L 53 def +/Y 54 def +/f 55 def +/s 56 def +/space 57 def +/hyphen 58 def +/colon 59 def +/A 60 def +/N 61 def +/quotedblleft 62 def +/h 63 def +/u 64 def +/slash 65 def +/C 66 def +/P 67 def +/j 68 def +/w 69 def +/dollar 70 def +/one 71 def +/E 72 def +/exclamdown 73 def +/R 74 def +/l 75 def +/y 76 def +/ampersand 77 def +/three 78 def +/at 79 def +/G 80 def +/T 81 def +/a 82 def +/n 83 def +/emdash 84 def +/endash 85 def +/parenleft 86 def +/five 87 def +/I 88 def +/V 89 def +/c 90 def +/circumflex 91 def +/p 92 def +/asterisk 93 def +/seven 94 def +/K 95 def +/X 96 def +/e 97 def +/r 98 def +end readonly def + /sfnts[<00010000000d0080000300504f532f321350119e00004a2c0000004e636d61700a140a77000000dc000000ea6376 +74204d184f4a000001c8000000da6670676d0211c261000002a4000001d8676c7966725a810d000008040000405668656164 +5f1a847b0000047c00000036686865610d5f066f00004a0800000024686d7478ab9523030000485c0000018c6c6f6361048f +1630000004b4000000c86d617870015200cb000049e8000000206e616d651c3b34c20000069000000174706f73740f7d757c +000005a4000000eb70726570ef5692620000057c0000002800000001000300010000000c000400de00000004000400010000 +007effff00000020ffff00000001000400000039000c001100140046001c004d00240056002b005d00330004003a000d0041 +00150047001d004e00250057002c005e00340005003b000e004900160020001e004f003c000f004200170048001f00500026 +0058002d005f00350006003d001000430018004a0021005100270059002e0060003600070028003e002f005b000900080052 +0029005a003000610037000a003f001200440019004b00220053002a005c003100620038000b004000130045001a004c0023 +00540055001b0032000000060008000e001d002b0042fe5afe73ffd30000037303a0057705a401cd00e100db00d500b000a8 +00a600a400980093008d007f006d006a0068005e00560052004e004a00480042003d003b003700350033002f002107fe07ee +05ec05c305b005a0057b0552050804df040803fe03e9031902fc02f402e302aa026d025a0227021f01e901c10185017f016d +012500ee00e100df00db00d900d500cf00c500c300c100be00ba00b800b400b200ae00a600a400a200a000960091008f008b +0087007f007d00790073006f006a0062005200480042003b00350021000040161514131211100f0e0d0c0b0a090807060504 +030201002cb200800043208a628a234266562d2cb22a0000435478b0002b58173959b0002b58173c59b0002b58b00a2a59b0 +014310b0002b58173c59b0002b58b00a2a592d2c2b2d2c2bb0022a2d2cb0022a2d2cb00162b0002342b10103254220462068 +6164b0032546206820b0044323612064b140408a545821212121b100211c5950582121b1000425204668b007254561b00051 +58211bb0054338591b6164595358232f23f91b2f23e959b0012b2d2cb00162b0002342b101032542204620686164b0032546 +206861645358232f23f91b2f23e959b0012b2d2cb00162b0002342b1010525423fe9b0012b2d2cb00162b0002342b1010325 +423ff9b0012b2d2c111217392d2cc12d2cb2000100432020b004438a45b003436169604460422d2c4520b0032342b2010205 +43764323438a23616960b004234218b00b2a2d2cb0002342184569b0406120b000515821b0411bb04061b0005158b0461bb0 +485959b00523424520b001234269b0022342b00c2a182d2c204568442d2cba00110005ffc0422b2d2cb2110500422b2d2c20 +20b102038a4223b0016142466820b0405458b0406059b00423422d2cb1020343114312173931002d2c2e2d2cc52d2c3fb014 +2a2d00010000000100003b6b731b5f0f3cf500030800000000007c259dc0000000007c259dc0ffa6fe000812060000000006 +00020000000000000000000000000000006900a00110016401af01ea020e02b302f80336035903a70412046a04d105100550 +05ea0632066e06bf075907b80824085d0904096e09db0a2f0a9a0b210b9a0be50c1e0c5f0cad0cf60d180d710dbb0df60e72 +0eba0f230f450fab1007103b107e10fb1130117d11d2124e124e126612a112ee133613a013f31443146b14da1527158315e8 +169716c61728176617de18071865190a19961a3f1abb1b031b7d1bd11be91c011c421cc21ce81d301d851da41e061e731eba +1f1a1f8a1fe1202b401e072703220b1f080f04275d0e0d076a0c5d07550551234a055d5d2b0d35008db8033c851d2b2b0002 +000000000000ff7b0014000000000000000000000000000000000000000000630000000100020102000f001c0030003d00b6 +00dc004a005700040011001e0025003200b5004c005900060013002000270034004e005b00df000800150022002900a20036 +0050005d00b70017002b0038003e00450052000c0019002d003a00400047005400d9000e001b002f003c0049005600030010 +001d0024003100b4004b0058001200260033004d005a00070014002800a30035004f005c000900160023002a003700440051 +00b300b2000b0018002c0039004600d80053000d001a002e003b004800550258690000000007005a000300010409000000be +00000003000104090001000a00be0003000104090002000e00c80003000104090003002000d60003000104090004000a00be +0003000104090005001a00f60003000104090006000a01100043006f00700079007200690067006800740020002800430029 +00200031003900390034002c00200042006100730069006c0020004b002e0020004d0061006c00790073006800650076002e +00200041006c006c0020005200690067006800740073002000520065007300650072007600650064002e0030003100320042 +0061004b006f004d006100200046006f006e0074007300200043006f006c006c0065006300740069006f006e002c0020004c +006500760065006c002d0042002e0063006d0072003100300052006500670075006c006100720046006f006e0074004d006f +006e006700650072003a0063006d0072003100300031002e0031002f00310032002d004e006f0076002d003900340043006d +00720031003000030056000004fe0577000f001b0029004a404227220225140b011a080209201c0225161e0c040816120218 +100216141a14060e020208160009100703041f0e0218016a171412051a011c01026a12110c06022b0f032b31002b2a303303 +3316171e01332132363736373303011133152135331123352115031321132326272623212207060766103b05180888640210 +65870818053b10fc2f3b02a43b3bfd5cf8110472113c05150fe3fdfce30f1505015e9d1b080606081b9dfea2022301686868 +fe986868020c0148feb88c180c0c188c000100acfe7301a400e100180020401806010e0f08090107010416070b00026b1106 +0405011a0f032b31003f2b301334373e013d01062322263534363332161514060706232226cb084a5221312f42422f493e5b +5308050a14fe8f090847ba671921422f3040875272cd5204120000020056ffd303a805540028003a003b403300010b020932 +25130c00080801220126290b140602231c090007030408014e182f010500014e251f010650370f0006033c0f032b31002b2b +303716333236373e01350e0123222e0135343e0133321e02151402062322263534363332161514062301323e013d02342623 +220e0215141616e7388b4e87252b1823835577bb6470c67e7ca7582374e7a379a8392a29393a280108546b305a7f5165300d +166256426a4d57c592536a81d3777bd57d87d5ee749efeb7dc72732a39392a283a019c71a85327089af24776864f74a47d00 +000100480000070c057700250037402b211b0b03131e0917090f0c080c000916100701041225180b0a041b220956131b0005 +692204000602270f032b2b2b3f3f3f3f3f2a3033353235113423352132170901363321152215111433152135323511010623 +2227011114331548d3d30183190801be01be08190183d3d3fdacd3fe0809191a09fe0ed34879042d414817fb790487174841 +fb9b4148484104a8fae61717050afba07948000100730000047b0577001e0033402b1d0c02071509130107220e0c01080501 +15200009010702041c1413110d0c0b060509351d03010501200f032b31002b2b3033223d0134370121220e0115231321321d +01140701213236373e02373303891604031cfee090bb5f3c1703b41702fce3012d6dae3b292e0e073c23171c080404f04cae +9301d517180904fb13283a297c7462fdd5000001008f031f018d058d001b001f401714010f160001060104090c190c026b12 +031005011d0f032b31003f2b3001222635343e0137363332161514070e0215141736333216151406011d4a442c533608040a +1509314a26021d39313f40031f87524e91832f04130908092b758642150a2741302e4200000100ac0479018d055a000c0018 +40110a0f030c000801044807000005010e0f032b31002b3013343633321e01151406232226ac442d1c361e422e2d4404e92d +441e361d2c44440000030039fe5a03e103a0003b004b0057005b4052210802514c0914011a11021f0151260f0a15082a0126 +4c28100603012f194809010740263807000704042a014e3c0917011f1d02633444110521110259244e0106542c0803040a06 +02633c00110603590f032b2b31002b2b30173436372e013534372635343e023332173e013332161514062322263534372207 +1e0115140e01232227061514163b01321e0115140e012322262637141e0133323e013534262b012206060132353426232206 +15141616396a49292b3d5e3762783f7a612a733e2c3826191a26196a4c252d609d53705d1d4736a87ac48598d35a5bd49873 +709e46459e6fc48ca82f4f30011bae4a64644a1c4ca0496d171f5e35604a5c774070522b472d313f2c19262619260f49256b +35578b4d3d283236512c84795a773535775a455d2d2d5d456b3f2d5101d8f86b8b8b6b446e4600010027ffe902a804ec001b +0037402e1401010f090a0103010122080a03080f24190900070204070a016a151201050c01030106015b07000d06021d0f03 +2b31002e2b2b303711233532363533112115211114163332363d013315140e01232226d1aa867e3b0121fedf394a463e3b2f +5c427b8ff6023535fa92fe8748fdcf5580874e797d407d509300000200ac0000018d05ba000c001d002040181b01030f0a09 +100701041216010f014807000a05011f0f032b31002e2b3037343633321e0115140623222613033534363332161d01031406 +2b012226ac442d1c361e422e2d445454452c2d43520c0619050b712e421e361c2d4444015403b00c2b3b3b2b0cfc50070c0d +000100ac0000018d00e1000c00184011030f0a09000701044807000005010e0f032b31002b3037343633321e011514062322 +26ac442d1c361e422e2d44712e421e361c2d4444000200acfe73019303730018002600294021240f1d0a000805010d0f0709 +010702041607190a000321016b1005050501280f032b31003f2b30133437363d010623222635343633321615140e01070623 +222603343e0133321e01151406232226cb048f1e232f42422f472f23473106090a141f1e351e1d351e412f2e43fe8f09049b +d10a12422f30408356488c86350612047d1d342020341d2e434300030046000005350577001700250036003a403432010622 +080c02080f0126272501061c010122000908070304070102124c132000050f014d0c2c04062601551804080603380f032b2b +2b30333532351134233521321e0115140607321e0115140e01232514163321323e0135342e0123213521323e0235342e0223 +21220e011546d3d302f168d48bcd8959bc7885d46bfe5a32360104508950427b4ffe7701333e6d572f23445c35fefc25291a +484104654148519f6c7da91a62a45c70ac5d892c15548d504e9560362d556b3c3562502d061d1e0000020073ffd305c505a4 +0011002a0023401c2024090c000815240009000702044a0e1b00054a27040006022c0f032b31002b30052224023534123e01 +33321e011215140204011e0133323e0137361110272e01232206070e0215141616031dc3fec9b065b5fe9291ffb662b0fec8 +fdec3cb068438169257a7a3cb36364b43c30351615392dcf0157bd8c0112d47c7dd6fef691bdfea9cf010a5e6f365e39b801 +420127ac566868564397a2575db1aa0000020044031f02cf058d001a00350034402a2a012401220702090f0f0c0b08010433 +1827221d1b042013096b2e2000050c00026b1305040602370f032b2b31002e2e2b301334373e013534270623222635343633 +321e0115140607062322262534373e013534270623222635343633321e01151406070623222662084c56021d392e42422e33 +401b615408040b14018e084e54021e382f42422f32401b615308050a14033b090842c066150a27422f304044662f71d64a04 +120a090844bd67150a27422f304044662f71d54b04120002003f000001fe055a000f001c0025401c1a0f130c000801040a0a +0009170f02100901035c0a051405011e0f032b31003f3f2b303335323635113426233525111416331501343633321e011514 +062322263f465a3d5a01274e41fe98442c1d361e442d2c4448162b022f4f244816fd002b164804e92d441e361d2c44440001 +0027ffe904100373001d002140170c1c09140a060a130f0c0b0907063715050105011f0f032b31003f3f3f2e3021012e0123 +352115221514171b01363534262335211522060701062b012201f4fed5134f4001ae7302e6cf063b2801543f5f1afeec0819 +0f1902f026154848310804fdbe020a1011272d48483a39fd481700020073fe730635058d0047004b004f4044271e024b2e02 +1f130c0a064930024237021f0a030a06020445073b07220c180c340107014b4a49484241403e38302f27261e1d1c140c0b03 +0200162e2b100b05014d0f032b31003f3f3f3f2b30013437132122263534363321132122263534363321133e013332161d01 +0321133e013332161d0103213216151406232103213216151406232103062322263534371321030623222601211321015602 +a6fe9c11161611017d51fe321116161101e7ac03150f1118a80181ac03150f1118a8016610151510fe815201d110151510fe +17aa0b1e111802a6fe7faa0b1e11180114018251fe7ffe9c0404026c1a0f1118013c18110f1a027f0d11181108fd94027f0d +11181108fd941a0f1118fec418110f1afd811e18110404026cfd811e1802d7013c0000020050ffd303ae0554000f00200023 +401c1827070c00081027000900070204550c140005551c03000602220f032b31002b300522021134123633321e0215140206 +27323612353402262322060215141e01160200fbb541c1ae87ac5a2141beaf72701a1a6f7374701a0b306b2d019d011db201 +3adb84d1ef83b0fecdd735ea011ca09a0104d3d4fefd9a72cad7930000020073011005c502f0000d001b002040191f150e00 +061f07000006020418011101320a030a05011d0f032b31002b30132226353436332132161514062301222635343633213216 +151406239a1116161105060f16160ffafa1116161105060f16160f01101a0f111818110f1a018e18110f1a1a0f1118000002 +0044000005a8057700120028002a402424010622080c020817010122000908070204070102124c0d1d00055313040006022a +0f032b2b2b30333532351134233521321e011215140e02232514163b013236373e01353426272e012b01220e011544d3d302 +f38ce8a4595aa9e787fe983236cb69bd3e412c2c413dbc6bcb25291a48410465414879caff008682f5c572892c155b5156d5 +8f95e058575d061d1e0000030073fe7305d105a400260031004e004f404539012c12094424090c000827362c00064d1b0227 +0112250009180715102007000704042509392925033e2f091d0112014c0e3e14054d016b2f3201064c4a04000603500f032b +2b31003f2b2b30052224023534123e0133321e011215140206071e01333236353436333215140623222e0227062732372e01 +23220615141627343e01333216173e03353426272e01232206070e011514121726031dc3fec9b065b5fe9291ffb66263bd7e +2752494d6e0f07177383485e311c0b5a665c56134f50314441762c4f2f596828445c331739443bb66567b53c463794ad162d +cf0157bd8c0112d47c7dd6fef6918dfef4d43b5b656a4c070c1b93f6476d903b1f3b2f586949303443772d50317362338b99 +ab568efa64596d6b5b66fa8cdafe9b4a2a000001003500000417058d002a004140392524230c0412010915011222140a0208 +291f1c031e01012200090a0702040b0c211f1d1815130f07122a010a010226015d0b051505012c0f032b2b3f2b2b30333532 +363511342e0123352511253635342623352115220f01011e0133152135323534270307151416331535465a213e41012f0113 +29221801818b8b920105364954fe684625c57d5b4548162b043337310b4816fc34f1272118194848797ffe8e4d2c48482b1f +2f01186ce42b16480001001900000421037300330044403c2c2b1211040901091a011b180c0309220b0a0a08322623032501 +012200090a070204332f2c2b29261f1b191512110f0c0a0510352401010501350f032b31002b2b30333532363f01032e0123 +352115220615141f0137363534262335211522060f01011e0133152135323635342f0107061514163315194e8a30baf22455 +4d01aa1b2f06a47b19211b01794f8b30a2010627544efe56192e06b893172217483d3ced013b2d1548481817080bd59e1e1e +192448483d3ccffea62d14484818170909f4bc1a22192448000201020419035c059a000a001500204016150a0e0c030c1514 +0b0a0907063e1200010501170f032b31003f3f2e2e3001133633321e011514070325133633321e011514070301026f123113 +281710bf01216f123113281710bf0433013334142612151afefa1a013334142612151afefa0000040073ff8d063506000029 +003600470054005c405324070303311a02014f4802091601271f310806271a0500063f0e021b2a4f01062701264837020604 +0424014c5202012d34020922016a434c040503015f523b100607016a092d010600015f3412100604560f032b2a31002b2a30 +05343701062322271615140e0223222e0135343e01333217163332363736333216151407010623222613323635342e012322 +0615141601222e0135343e0133321e0115140e010627323e01353426232206151416011d0603a07b94a093291f406544608a +45458a604c3fa5e279d2430a16121706fbdf091511188565682b5c4666424403ef608a45458a605978371f3f6545455e2a68 +656642444a0c09056645536279428d7d5180c05d5bc1803da26b641016130b09f9d70d1a0355f17743ab79e38681e5fc9180 +c15d5bc18087be57428c7f51367baa4376f0e38582e40001006600000398055400320044403b2e2918030d2b091210020d22 +200c0408310103012b140009110702042901091509310132302e034f240909051b012a100303481501030602340f032b2b31 +002b2b3033353437013e0335342e01232206073633321615140623222635343e0233321e0115140e020f0133323637363733 +036604013e485a58333e7b57598e1d080e2e41412e30413a6d894d75ca764e7abe1ee8c591c30618193c3a37050601604e6a +8a8f5054995c6b55023e312f41432d4d87693863b57959a083a61cdf05051aa3fe93000200730000035205a4000c003a0035 +402d221b021803091827290c00083801030f0a0910070204552d1400050e0148070001061b014e1f250106033c0f032b3100 +2b2b3025343633321e0115140623222613353412373e0135342e012322060733321615140623222635343e0133321e011514 +06070e011d0114062b0122260156442d1c361e422e2d4454625a1e1c265e554f89260c29393a28283a639e5361b575383375 +8d0c0619050b712e421e361c2d44440154688601025d20593154602c403f392a283a3a28547f44337e663c6f2452ec846407 +0c0d0001003f000004e105770026003b40321b0106160910010622080c02080a01221622100602041d000909070103122601 +1d016a1c1a06052301511504080602280f032b2b3f2e2b2b3033353235113423352113232e032b01220e01151133323e0135 +331123342e012b01111421153fd3d30469393b0e2f5a9784bb25291a916962253b3b256269910106484104654148fe2b8195 +5522061d1efdf1256269fdd9696225fdf141480000020073fe5c03520400002c00390036402c1c013719090f303700061927 +2a07000702040c23091c014e26200105100148342d01065515000006033b0f032b31003f2e2b2b30173436373e023d013436 +3b0132161d011406070615141e013332363723222635343633321615140e0123222613343633321e01151406232226732e2e +456135090719070b4b4a2b1247495ca92e0a293a3a29283a7ab85991c3e3442d1c361e422e2d448d3b6f25388ea45864060d +0c076882fe653a704b5d383c433a29283a3a285e7f3a8a04a92d441e361d2c44440000010073ffd303fe05a400490045403c +3627131204052f0a091a011c012f23200c0c08440148010a2240091207020444361c1312050e070924015e3c0e0105330116 +016a07011206024b0f032b2b31002b2b301711343b01321615141633323e0135342e0127252e0135343e0133321737363b01 +32161511142b012235342e01272623220e0115141617051e0315140e0123222e012707062b0122731219060af9ca47784332 +5e3bfef684a76cb76acd795406080e060b111813233e2463a34676466d58010a42754e2c66b96e448e78315606090c121d01 +de100a06c9dd5285473e75560e4123d9876aba6c8b85060908fe251212347c722464477a43588f174110536e87486fc5781e +3e3187060001003d000006850389003f0047403c0f0b02110c0237012627140a0e0801042f091e090a0a00091d011f015b18 +2314052e01300111015b2a3415063f010a01020b015b3b05150603410f032b31003f3f3f3f2b30333532363511342e012335 +25153e013332173e0133321e0115111416331521353236351134262322061511141633152135323635113426232206151114 +1633153d465a213e41012929a15fec29299e5e5d7f405b45fe2b465a3a5a769a5a46fe2b465a3a5a77995a4548162b022f37 +310b4816c85870c0566a3c7b5dfe142b164848162b01e6677ebe79fe6c2b164848162b01e66481be79fe6c2b164800010039 +000003350373001f0033402b1e0d02071709150107260f0a0108050117250009010702041d1615130e0d0c060509391e0301 +0501210f032b31002b2b3033223d0134370123220e021523132132161d01140701333236373e01373303501706023cb85771 +49213b1702ae090d04fdc3c459772a271d083b23171008060308163d6b5e01520d0a0c0608fcf9162a278461fe79000100ac +031f01aa058d001a001f40170701090f0f0c01080104180c00026b13050405011c0f032b31002e2b301334373e0135342706 +23222635343633321e011514060706232226cb084e54021e382f42422f32401b615308050a14033b090844bd67150a27422f +304044662f71d54b041200020039000003c5055400160019002f4026180102160122080a0a0601041905110917010212100a +02070112011901570c161d05011b0f032b2b3f2e2e2b30133501363b01321511331523151416331521353236353525211139 +0279070e1e17c9c9784ffdcc4f78fe2701e501524803b00a17fc5d48c92a174848172ac94802d5000001003f000005be0577 +0023003a402f220d1f000601041a09120c080c0009191307010412231b1109040e0c091e0151160e02052001510c04080602 +250f032b2b2b3f3f3f3f2b303335323511342335211522151121113423352115221511143315213532351121111433153fd3 +d30265d3025cd30264d2d2fd9cd3fda4d34841046541484841fe0e01f241484841fb9b41484841022bfdd54148000001003f +ffd305be05770022002d40240d221f0900070104160c050c170402121506021209096819120005510900000602240f032b2b +2b3f3f2b3001113423352115221511141e0133323e0135113423352115221511140e02232226260112d30265d33f957875b2 +60d301edd2437fac6188f39001cf031f41484841fce972cb7f7bcc7502df79484879fd195fb6935488ea000100f2fe00020a +06000007002040191f02030006001f050700080204070302670501100501090f032b31002b301311211523113315f20118c6 +c6fe00080052f8a4520000020035ffe9042d058d0019002a0033402a080127270c0a040818011d26150910070204070c0009 +55112100050601191807035b1a000506022c0f032b31003f3f2b303311342e01233525113e0233321e0215140e0123222627 +07371e0133323e013534272e0223220607d5213e41012f225b68365c9d744179d17d4e9230465a22804e6a83342d1445552f +528c2904bc37310b4816fd7f26391e4a82a95a7cd67f50427bc94b5f7aba67ad5a284427574900020039ffe903c503960010 +00240023401c1b26090a00081124000900070204540d140005542104000602260f032b31002b3005222e0135343e0233321e +0115140606273236353426272e02232206070e011514161602007bd27a437da6617ecf787ad17aa46e162517474f2a407326 +26152879177dd27c5eae894d85df7e7bd37d3ceeb867873722331b3a363a8b6073b77c0000010073fe0002540600001b0016 +400f0e000a0202126014060005011d0f032b2b2e2e30132235343700111001263534363b013217161a0115140a0106070623 +851204015efea6080b0713060493c45b316ba4720406fe001209040156028b028b0152050c070b0474feb4fe88c491fee7fe +efe75a0400020056ffd303a80554002c00420040403817011a24091a25090c0008100121013626240a14082d230009000703 +04170150293201054e0d1300063c013f0121014e3a05190603440f032b31002b2b3005222e023534123633321e0115140623 +22263534363b012e0123220e04153e0133321e021514060627323e0235342e0123220e0115141714060714161602007faa5d +247ef5a8467945392a283a3a280b1a5f333e6954381f082484535b966c396bc27b4f602d0b166265536b3102010124662d87 +d7ec79a20146d63567492a393a29283a2523365c6f8e7c5e546b4a83a85678d980414877795874a47d70ab4f1b0e03040358 +b47c0001004cffd303b8057700230028401f03271509000701041c0c0c0d0152100810050b010001472019110602250f032b +31003f2e2b30371e0133323e013511342135211522061511140e0123222e0135343633321e0115140623ba257643425b2dfe +ee0268455871b35f539861443321361f44327334375b873f03c5414848162bfc33629854437f5433441f3820324400010025 +ffd30812057700300039402f211202221f131007050422060c0a0801042b190b03132f092809201c19181513110c0b0a070b +2c2205010501320f032b31003f3f2a2b3005012e0123352115221d010901272e012335211522151416150901363534262335 +2115220701062b0122270901062b0122027dfe5e105c4a0225ac014701172f105c4a0224ae040146013302723e01b6a226fe +7007170f1707feaefeac07180e181705052b164848370afc10035e922b16484837030c02fc1703b8060d3630484879fb3316 +160413fbed160001002dfe00014606000007002040191f06030006001f010700080204050102670703040501090f032b3100 +2b3013353311233521112dc7c70119fe0052075c52f8000000020044ffe9043b058d001c002e003d403317012820090b0128 +27080a04081a01000120261909120702041809120c1801110124010c015b12191705552c04000602300f032b31003f3f2b2b +3005222e0135343e013332161711342e0123352511141e01331505350606251e0133323637112e0223220e0115141601f479 +c86f79d07d4b8631213d41012f213d41fecb3592fee4237545558e2318495b336b8234111783d6787cd57e3f3801aa37310b +4816fb2d36310b4817813d44c94350624e01eb2d472679bc67527a0000020044fe73043b0389001b002b0034402c11011401 +2325160a0c0805011c2608091007020400071b010101201514035b1605150655280d0006022d10032b31003f2b3001353236 +35110e0123222e0235343e01333216173733111416331501323637112e0123220e02151416160266465a3092505c9e754178 +d17957942c49365a45fdc55b8f2215845c466e49243a78fe7347182a017b404e4c82ab587ad77e6252b4fb732a184701ac79 +5c016862904a7c904052c186000100aa04930354055800130025401e030111010b1c090c0a08010113011b070d0a0602043c +0a00000501150f032b31002b3013373633321e0133323717070623222e01232207aa3b5050275d5d27524c293b514f275d5d +27524c04b6465c2e2e5c23455d2f2e5d00010073ff5605c504aa001f0026401e06011f011f0f160a0601041b0b1303021217 +011e01670e070a0501210f032b2b2e2e2b301322263534363321113436333216151121321615140623211114062322263511 +9a11161611025a18110f1a025a0f16160ffda61a0f111801d71a0f0f1a025c10151510fda41a0f0f1afda410151510025c00 +00030056ffd303a80554001e002e003c0043403a302f2b2a13030637230937250b0c000823231b09000702042b2a02333b09 +64172600051301660f3304060301663b071006641f000006043e0f032b2b31002b2b3013343637272e0135343e0133321e01 +15140e0107171e0115140e012322262637141e0133323635342e0127250e010613173e0135342e0123220e01151456a27f4c +465867ab615ba96d41714075516377c46d6ac57b6f59925077c21f3722feed406b3e8df8566e4d7c473e7e5201377bbd3f31 +2e9954629e5a4a8a5f45765e214b35ac5f6fb66456a36b51864c8b73274d3f14b22268820229a0328e59457340305f406000 +0001003f000004a8057700160024401c1001012200090807010415080c150907010412510c04000501180f032b2b3f2e2b30 +3335323511342335211520151114163b01323e013733033fd3d30298fefa32369e99a645123b394841046541484841fb9b2c +1570c1a0fde700010017000005e705770021002f4027180119160b0308220a0c0a080104100009211917130d090107120b01 +1001531e04050501230f032b2b3f2e2b302135323511012e0123352115221514170901363534262335211522060701111433 +1501cfd3fe521e6c5302418f04017201520d472c01bc508927fe73d3484101a602c3291448482f040cfd9d022b1316292548 +48393cfd75fe5a41480000010042000002e305a40028003d403419011b070914011b270e0c0408200122010522070a0a0802 +040009282219035211170106070102050120015c24080706022a10032b31003f2b2b3033353236351123353335343e023332 +161514062322263534372623220e011d01331523111416331542455a9d9d375d7941456f3527273737211a3c552aece5784e +48162b02a248f54273563152442735352740160b557a3cf148fd5e2a174800010044ffe902e103960040004c4042302f240f +0e0604072908091701190129291d0a0c083b013f0108263909120702041a0a3b302f190f0e060b2c09211a0265340b010501 +010601652c13030602420f032b2b31003f2b2b301711343b013217123332363534262f012e0235343e0133321737343b0132 +161511142b01223534262322061514161f011e0215140e0223222707142b01224412190c0439db61836646894571485f9858 +694e3b0a0f060a101912776b5c8761418b467947335b7c44805b4b0b0c1206014e1010fed7585c425d111b0f3e67445a7333 +3833050b06fef413136b8244533949101a104c74494a6d482256510500010017017b023501fa000300174010190200000601 +04400301000501050f032b31002b301335211517021e017b7f7f000200ac0000018d0373000c001a0022401b180f110a0008 +030f0a090007020415010d014807000a05011c0f032b31002b3037343633321e0115140623222611343e0133321e01151406 +232226ac442d1c361e422e2d441e351e1d351e412f2e43712e421e361c2d444402be1d342020341d2e434300000200420000 +05bc05ba001c001f00314028221e1500061b100d030f01012200090a0702041f071f1e1d1c181514131009310e0101050121 +0f032b31002e2e2b303335323701363b013217011e0133152135323d0103210306151416331503210342b72a01ae061b1a1b +0601c1136b50fdc5aa6ffe0f5c0261382301c1e1487904e31616fae52b164848370a0140fef8070e3331480210028e000001 +003f000005be0577001f003240261b0b1809100c080c000911070103121f0f0a030c1c091a0169130c0105691c0400060221 +0f032b2b2b3f3f3f3f2e2e3033353235112623352132170111342335211522151114062b01222701111433153fd343900188 +0a0402d5d301e7d21005190a04fca4d3487904620c4808fbd5037279484879fb5c060c0804f2fbc779480002012f031f03ba +058d001b00370035402b32011c013013020f15000b060104250c080c19130d0b041f11093528026b2e1f10056b1103000602 +390f032b2b31003f3f2b3001222635343637363332161514070e021514173633321e0115140621222635343e013736333216 +1514070e021514173633321615140601bc49445f5508050b1308314c25021e3820331e41015e4a442c533608040a1509314a +26021d39313f40031f875271d44c04130909082b788342150a271e32212f4187524e91832f0413090a072b758642150a2741 +302e42000001003d0000044c058d0028003340290c0120270f0a0408010418090b0c0009170119015b121d140528010a0102 +0b015b24051506022a0f032b31003f3f3f2b30333532363511342e01233525113e0133321615111416331521353236351134 +262322061511141633153d465a213e4101302b9a5d8e8f5a46fe2b465a3a5a77995a4548162b043337310b4816fd40556788 +8cfe142b164848162b01e66481be79fe6c2b16480001003dffe9044c03890023003240281e0121010c261d09120701041c09 +160a070a1c0115011d015b1610150506015b0700040602250f032b31003f3f3f2b303711342e0123352511141e0133323635 +11342e0123352511141e01331505350e01232226dd213e410136174b526e82223d410135213e41fed126885293adf401c437 +310b4816fd6b50592cb875016c37310b4816fd3136310b4817ad4d607d0000010073fe00038b0600000f0013400b0d06380a +00000501110f032b31002e2e30133437013e013332161d010106232226730202c804140d1217fd380c1b1118fe29060207b6 +0c0d161308f84a19180000010073ffd3055205a4003a0038402f302a0b033203091c011f013222230c0c0803221209000702 +04301f0208380927016a0d0808054a38170006023c0f032b2b31002b2b30251e0133323e0235343b013215140e0223222426 +0235341236243332161737363b0132161511142b012235342e012726232206070e0115141601d346d2715fa2794112191052 +96c36a93fef9c36d6dc301079369c048770c020f060a1025132f4930739571cf474b3a3adb59674b86aa5e10146bc7975477 +cf011093930110d0755b53a8060a07fdd712123b92833270665a60f58b8bf60000020044000004fe0577001500230032402a +1f010622080c02082417100006020400090701021215011b12094a0c1b00051601511204010602250f032b2b2b3f2b303335 +32351134233521321e0115140e012321111433150321323e013534262b01220e011544d3d302d970e09192de71feb8d3d901 +16718c4193abae25291a4841046541485cb07572ac59fe0a414802bc418970a792061d1e0002ffa6fe5c01b2055a001c0029 +003d40310a0002270209270f200c00080227110700070204170c0a0b0a1d0b02061a0924015d0d06080500014e1a14010602 +2b0f032b2b31003f3f2e2b2b3013163332363511342e0123352511140e0123222635343633321615140613343633321e0115 +1406232226292f3b513f284543013f4e864f59903a28283a238a442d1c361e422e2d44fea817a560032237310b4816fc044f +8d555850283a3a281f3406382d441e361d2c444400010025ffe905a003730032003140252d1a0d031331092a09220a140a06 +0a211d1a191715130e0d0c09070c312305010501340f032b31003f3f3f3f3f2a3021012e012335211522151e01151b01272e +0123352115221514171b01363534262335211522060703062b0122270b01062b012201c5fef712444101a6790101c5aa1b10 +464001947902cdba04492d015c3e5915f40718101807cfcf0a160f1902e92d15484833030606fdd801e1472d154848330807 +fdbf020a100d2b3148484138fd4e17170248fdb8170000030073ff8d038b0600004f0058005f005c4053595841321e191411 +0f080a4018092a013c39025a0140212b0c1608500100014e01182201090e070204280c4801543c3b034e2f350905504e4103 +5a190003682a280a0622015e1e1411044e0b05030603610f032b31003f2b2b300535222e013534363332161514062b012227 +2e01231e0233112e0327263d023436373e0133353315321e011514062322263534363b013217332e0223111e01171e011d01 +1406070e012315353e0235342e012727110e02151401db6da3583a28283a3a28020e070203010e577c423c3337331d723c36 +2b8e3d4866a65c3a28283a3a28020e06051258793e546a2f3c3f3b3732893b4573403e6f4b483f7544735f6cb76a283a3a28 +283a020101416c3b025611101a221c74a102024b8f3a2b485e5c61a969283a3a28283a023d5a32fde11331303ca15704529c +3b32485fa6095580434f754e13d5020c07497143c700000100b20000035e055400110024401d0a0104012207061106010400 +09110701031209015a0d04010501130f032b2b3f2b30333520351106233532373332161511142115be01006aa2fb801d070d +0100484104333348830b07fb474148000001003f000005370577002d0044403d1a0106151c01210102090f010622080c0208 +0a012c012215211406260101220009080703042c09070104121c016a1b19020522015114040806022f0f032b2b2b2a303335 +3235113423352113232e022b01220e01151133323e0135331123342e012b011114163b01323e023733033fd3d30486393b16 +4faeaec925291a976867273c3c276768973236d98eaf6135173b56484104654148fe2bb1a03c061d1efe0c236369fdda6864 +23fdd72c152d69a794fde700000200acfe46018d04000010001d001f40160f141b000601040e0618011101480b010a05011f +0f032b31002e2e2b3013351334363b013216151315140623222611343633321e01151406232226ac54090719070b52432d2c +45442d1c361e422e2d44feac0c03b0060d0c07fc500c2b3b3b050e2d441e361d2c44440000020044ffd305db05770030003d +004940402f1e01032c170939010622080c0208100127322c010617272409000703040009070102123001352d096a201a0005 +10014a0c3504063101532d040806033f0f032b2b2b3f2b2b30333532351134233521321e0115140e01071e011f011e013332 +363534363b013215140e012322263d0134262b0111143315033332363534262b01220e011544d3d3028373fdaa67a1545c8a +0e1d142c4b403f0d0713142c533994d58e67f6d3d3dbaab2b0ac7325291a48410465414853aa7656875b14208c5ab67b7977 +46070b1b386b46938eb66692fde7414802d789a4a388061d1e000001003f0000020e058d0010001a40120b0c000910010a01 +025c0b05140501120f032b31003f3f30333532363511342e0123352511141633153f465a213e4101305a4548162b04333731 +0b4816fafc2b164800010027fe5c04100373002f002e4024022723070007010429111a0a0b0a1b19140c0505120e010a0100 +01542d26130501310f032b2b3f3f2e2e2b30131633323f01012e0123352115221514171b013635342e012335211522060701 +0e0223222635343633321e011514068d272f815246fecd134e4101ae7102e4cd061b2b1b01543f5f1afe9e1c4b6c404b7134 +261a29172cfeb01fc3ac02f026154848310804fdd001f8101319251448483a39fc9c426f476349263417281b213400030056 +ffd305d105ba003a00460053005940513d2f15100302061e2a090a274e0c00074001210147011e22200a070835013b013701 +2a2233091a0703042f211f1b041210014a520927013d016a0d4a11054003025f520601060201554300010603550f032b2b2b +2b2b3013343f012e0135343e01333216151406071e03173e013f013635342623352115220f010e01071e0133323e01353314 +0e012322270623222626053237260227070615141616133e0135342e0123220e0115145652f4272b4783565e578b711d4255 +65333d5b53330b583801c9b9474244714443874a3b673d3c4c824dc6a9abc95aac6b0183a88d65bc3b52582c5f7b5d781437 +2e344520010a7352fe66d76951975fac685bc27b477e868a41488d8b5a0c1730264848797076af504d653c653c4c88519c9c +4c8f937d69011483545c9e458b5f032b67ac4f31624a476731b100010056ffd303a80554004b00504047250119124400020b +0302091f1c0219252c0c0408350124120b010603233d09000703041211021622094a3906000535014e311604061c0f00034b +222801064748410006044d0f032b2b31002b2a30371e0133323635342e022b01223d01343337323e01353426232206073e01 +33321615140623222635343e0133321e0215140e01071e0215140e0123222e0135343633321e0115140623c330a25d776415 +32573f88121271485f2c5c5e4e8e2a0406042e3e3e2e2d406aa7543e8a70474d865059a0617cca7060c17b443321371f4631 +9e4644cb813a74643c131210096c9b46627e3b3c0101402d2c40402c58824325456c4556926a1a11649c5a71b76749926633 +441f3820324400020073ffe905c505a4004800590056404d2301512b474202183d020933270a0c0008272051000615014901 +1201272b181a063d2600090007040441012e2709450147016a0f2e09052315025b274d01065b561c00066a38050006045b0f +032b2b31002b2a3005222e01023534123e0133321e0112151402232226270e0123222e0135343e0133321617333216151114 +1633323635342e0223220e0215141e0233323e01373332161514070401323637112e0223220e0215141616031d92f9ba6565 +baf99291fab9644c8948780f30894b76ba6a6aba76579a2f67060a1e245e355ca3e98484e9a55d5ba8ea8461c3bc595c060b +0dfec0fea5528a26194d673542643e2039741777d101098d8d0109d17676d1fef78dbcfef948413e4b7fd27270d37f624e0b +07fde72a4bf29a80fbbf7071bdf88282f3c3701f3a2c0c070e049601506b5201a23455334f788b3b54ba800000010073ffd3 +05e105a4003f0044403a36300c0b0905380309220125013822290c0c08160103221909100702041309360a02073e090c012d +01251602510f0719054a3e1d000602410f032b2b31003f2b2b30251e013332363d013421352115220615111406232226270e +012322240235341236243332161737363b0132161511142b012235342e012726232206070e01151001d74ad57472baff0002 +4b414c0b0713591a32d675c7feb9be6ec50106936abd4a770c020f060a1025132f4930739572d0474b3adb5a66746bac4148 +48162bfe6c070b5a235654cd0157c5910112d0755b53a8060a07fdd712123b92833270665a60f58bfec60001004a0000057b +057700220026401d19010922110c02080104130f000922120f010412511e05000501240f032b2b3f2e2e2b30213532363511 +34262b0122070e010723132113232e0127262b01220e01151114163315015a67c2313756a94725200b3b2704e3273c0c1d26 +48a85625291ac26648172a04652c154a25a37b01d5fe2b849b244a061d1efb9b2a17480000020052ffe903f203960032003e +0047403e0b010904090926170a00081101240127043b14062c1f0236262f091007030429096a252200053b012c015b1b0503 +060e0133011401570b00190603400f032b31003f2b2b30373436243335342e01232207321615140623222635343633321e01 +151114163332363d013315140e01232226270e012322262637141633323e013d0122060652c0010d7934623b884727333a28 +293ac47653a86b222422213c30512f3c57052694544e9765a66a48426c405dc380c97a993f543b6f473d3b27293a3a296c69 +478458fe332843442783832e53315d404d5b2e634f486242723fd53d82000001003d0000044c038900280035402b0b010c01 +20270f0a0c08010418090a0a0009170119015b121d140528010a01020b015b24051506022a0f032b31003f3f3f2b30333532 +363511342e01233525153e0133321615111416331521353236351134262322061511141633153d465a213e41012929a15f8e +8f5a46fe2b465a3a5a77995a4548162b022f37310b4816c85870888cfe142b164848162b01e66481be79fe6c2b1648000001 +0000020603fe023b00030017401027020000060104360301000501050f032b31002b301135211503fe020635350000010000 +020607fe023b000300174010270200000601042b0301000501050f032b31002b301135211507fe0206353500000100c7fe00 +02a8060000200016400f1f0d1b100212601705000501220f032b2b2e2e30012e010a0135341a013637343b0132161514070e +0202151001161514062b0122027b72a56934346ba66f0a13060a0464855124015c060b05130afe045ae90108012091930120 +010ae857040b07090462e0fdfef193fd75feae060b050d0000010066ffd30398055400460043403b443e00030d0409201b02 +2b01121d2911062c13020d272f0a04080423370900070304262402513307010541013b00021b1202682c17150602480f032b +31002b2b30371e02333236353426272e0123220e0207232226351134363b01163332373332161d0114230e01232227113e01 +33321e0115140e0123222e01353436333216151406232226b2155777409470050b135f4545633e300617050f0d07068a9b98 +8d06070c0446d3715256446b5371b36079d07a65a9613c2d2d3d3d2d0712e93c6237e6a447612d486c2a383e020d0602ac05 +0b42420a06130a5d6817fe7d372f82d16d7bd27c68b0632e3a3b2d2c3d0300010035000002ae0577000f001a4013080c0009 +0f0907010412510c04000501110f032b2b3f3f303335323511342335211522151114331535dddd0279dddd48410465414848 +41fb9b41480000010027ffd305d70577001e002a40211501161307030422060c0a0801040d1d0914100d0c0907062f160501 +0501200f032b31003f2e2b3005012e012335211522151e01150901363534262335211522060701062b012202d1fe1d12664f +0233aa010101890173045c3901ba4d7519fe31061b1a1b1705052b16484835030504fbeb03dd0811322e48483940fb331600 +00010044ffe903520396002a003040282824140e04161e091624080a00081e24000900070204280114014e0b111105551a04 +0006022c0f032b31002b2b3005222e0135343e01333216151406232226353436372623220e0115141e0133323637343b0132 +161d01060601fe7cca7473cb7c78c5392929392e2247806278303b83636188191019060a1fb81781d77979de855e6b283b3b +282435072d82c05e63b97977600c0b0706798e00000100ec044e0312058d0005001840100504000313020c3f030100050107 +0f032b31003f2a300127090107270114280114011229e9044e2b0114feec2bcd00020035fe73042d0389001d002d003e4034 +08012a21090a010b012a250e0a0c0818012126160910070204090a000755122600051d011e010901020a015b19051d06022f +0f032b31003f3f2b2b301335323635113426233525153e0133321e0115140e012322271114163315031e0133323e0235342e +012322060735465a5050012f38955479c26d79d17d95675b45a024804c476d4a233b7956538b29fe7347182a03db381c4816 +7f3e4183d5777cd67f79fe9a2a18470254495f4c808d4252bf83554900010085028d0379060000370030402935302b281c19 +140f0c000a13250103013534332d2b2a22211918110f0e06050f3a1f090b0501390f032b31002a301322263534372d012635 +343633321705032734363332161d010325363332161514070d011615140623222725131514062322263537130506c117251d +0127fed91d2418100e01041e02251816252001040e1019231dfed901271d2319110dfefc2025161825021efefc0c034c2718 +220f8c890f221a260bbe014c0417201e1904feb4be0b261a220f898c0f2218270abefeb5041820211704014bbe0a00010073 +ffd303e1056800200024401d0e010c011701141207150601041e09140c0603124e1b00000501220f032b2b3f2b3025343e02 +371323200706072313331514163321151406070106021114062322260164284d6d41bbeafe940b1b183b433cfa78017d0101 +fee668343a28293a3572dad5cd5a01040a219c01ae06251635020202fe749afe88fee7283a3a0001003f000005e30577002a +0041403a2625240c040601091401151209030622080c0a08291f1c031e01012200090a0702042a221f1d1815130f0907010b +122701510c040805012c0f032b2b2b2b3033353235113423352115221511013635342623352115220709011e013315213532 +363534270107111433153fd3d30265d302791a372301bda87ffe9501c1375363fde831431afe91e5d34841046541484841fd +6802601c1c2021484879fea4fd6751284848142519270220ddfe854148000001002f000005cf057700310046403e2a291110 +0408010919011a170b0308220a0c0a08302421032301012200090a070204312d2a2927241d1c1a181411100e0b0904031230 +2201010501330f032b31002b2b303335323709012e0123352115220615141701133635342623352115220709011e01331521 +35323635342709010615141633152fd0530154fe87206d54023d256002010cec08532e01f4d053fee701b1236b53fdc22165 +03febdfed906522d487401fa023e261548481d1a0404fe680160120b2a30484875fe5dfd6c271448481d1a060201eefe490c +102a304800020039ffe903520396002000280033402b1e1c020f14092626080a000828220f000614240009000703040d015c +1e22080521015411041006022a0f032b31002b2b3005222e0135343e0133321e021514232115141633323e01373e013b0132 +1506060121342e0123220601fe7dd1776dc3785e8b5a2e15fdaf899b3f6b4f0e020b0712151dc0fe7901d32b6451747f1783 +db7a78d8853f70985b1b16aaf4386439070b1a749502234d9e69d90000010035000002e903890023002e40260b01150c021b +270f0a0c0801040a0a00091a1812031223010a01020b015d2005150501250f032b2b3f3f2b30333532363511342e01233525 +153e0133321615140623222635343723220e01151114331535465a213e410125217a573d6035272636270c53692cc748162b +022f37310b4816c8596f483b25373626371778b251feb0414800000006000100000000000000000005540056023700ac0400 +00560754004804e300730237008f023700ac04000039031b0027023700ac023700ac023700ac05aa00460637007304000044 +0237003f0437002706aa00730400005006370073061b00440637007304370035043700190400010206aa00730400006603c7 +00730537003f03c700730471007306aa003d038d0039023700ac040000390600003f0600003f023700f20471003504000039 +031b007304000056041b004c083700250237002d0471004404370044040000aa06370073040000560500003f060000170271 +00420327004402aa000002aa0017023700ac060000420600003f0400012f0471003d0471003d0400007305c7007305710044 +0271ffa605c7002504000073040000b20571003f023700ac05e300440237003f043700270637005604000056063700730646 +007305c7004a040000520471003d0400000008000000031b00c70400006602e3003506000027038d0044040000ec04710035 +04000085040000730637003f0600002f038d00390321003500010000006300600004000000000002000c00060016000000c4 +0062000400010001000005a4fe4600000837ffa6ff8e0812000100000000000000000000000000000063000003e701900005 +0000019a01710000fe5a019a0171000004a2006602120000020b050000000000000000000000000000000000000000000000 +0000000000400020007e05a4fe5a000006000200000000>]def + FontName currentdict end definefont pop + + %%!PS-TrueTypeFont-1.0-2.3499908 + 10 dict begin + /FontType 42 def + /FontMatrix [1 0 0 1 0 0] def + /FontName /DejaVuSans def + /FontInfo 7 dict dup begin + /FullName (DejaVu Sans) def + /FamilyName (DejaVu Sans) def + /Version (Version 2.35) def + /ItalicAngle 0.0 def + /isFixedPitch false def + /UnderlinePosition -130 def + /UnderlineThickness 90 def + end readonly def + /Encoding StandardEncoding def + /FontBBox [-2090 -948 3673 2524] def + /PaintType 0 def + /CIDMap 0 def + /CharStrings 485 dict dup begin +/.notdef 0 def +/.null 1 def +/nonmarkingreturn 2 def +/space 3 def +/exclam 4 def +/A 5 def +/C 6 def +/D 7 def +/E 8 def +/G 9 def +/H 10 def +/I 11 def +/J 12 def +/K 13 def +/L 14 def +/N 15 def +/O 16 def +/R 17 def +/S 18 def +/T 19 def +/U 20 def +/W 21 def +/Y 22 def +/Z 23 def +/grave 24 def +/a 25 def +/c 26 def +/d 27 def +/e 28 def +/g 29 def +/h 30 def +/i 31 def +/j 32 def +/k 33 def +/l 34 def +/n 35 def +/o 36 def +/r 37 def +/s 38 def +/t 39 def +/u 40 def +/w 41 def +/y 42 def +/z 43 def +/dieresis 44 def +/macron 45 def +/acute 46 def +/periodcentered 47 def +/cedilla 48 def +/Aring 49 def +/AE 50 def +/Ccedilla 51 def +/Egrave 52 def +/Eacute 53 def +/Ecircumflex 54 def +/Edieresis 55 def +/Igrave 56 def +/Iacute 57 def +/Icircumflex 58 def +/Idieresis 59 def +/Eth 60 def +/Ntilde 61 def +/Ograve 62 def +/Oacute 63 def +/Ocircumflex 64 def +/Otilde 65 def +/Odieresis 66 def +/multiply 67 def +/Oslash 68 def +/Ugrave 69 def +/Uacute 70 def +/Ucircumflex 71 def +/Udieresis 72 def +/Yacute 73 def +/Thorn 74 def +/germandbls 75 def +/agrave 76 def +/aacute 77 def +/acircumflex 78 def +/atilde 79 def +/adieresis 80 def +/aring 81 def +/ae 82 def +/ccedilla 83 def +/egrave 84 def +/eacute 85 def +/ecircumflex 86 def +/edieresis 87 def +/igrave 88 def +/iacute 89 def +/icircumflex 90 def +/idieresis 91 def +/eth 92 def +/ntilde 93 def +/ograve 94 def +/oacute 95 def +/ocircumflex 96 def +/otilde 97 def +/odieresis 98 def +/divide 99 def +/oslash 100 def +/ugrave 101 def +/uacute 102 def +/ucircumflex 103 def +/udieresis 104 def +/yacute 105 def +/thorn 106 def +/ydieresis 107 def +/Amacron 108 def +/amacron 109 def +/Abreve 110 def +/abreve 111 def +/Aogonek 112 def +/aogonek 113 def +/Cacute 114 def +/cacute 115 def +/Ccircumflex 116 def +/ccircumflex 117 def +/Cdotaccent 118 def +/cdotaccent 119 def +/Ccaron 120 def +/ccaron 121 def +/Dcaron 122 def +/dcaron 123 def +/Dcroat 124 def +/dcroat 125 def +/Emacron 126 def +/emacron 127 def +/Ebreve 128 def +/ebreve 129 def +/Edotaccent 130 def +/edotaccent 131 def +/Eogonek 132 def +/eogonek 133 def +/Ecaron 134 def +/ecaron 135 def +/Gcircumflex 136 def +/gcircumflex 137 def +/Gbreve 138 def +/gbreve 139 def +/Gdotaccent 140 def +/gdotaccent 141 def +/Gcommaaccent 142 def +/gcommaaccent 143 def +/Hcircumflex 144 def +/hcircumflex 145 def +/Hbar 146 def +/hbar 147 def +/Itilde 148 def +/itilde 149 def +/Imacron 150 def +/imacron 151 def +/Ibreve 152 def +/ibreve 153 def +/Iogonek 154 def +/iogonek 155 def +/Idotaccent 156 def +/dotlessi 157 def +/IJ 158 def +/ij 159 def +/Jcircumflex 160 def +/jcircumflex 161 def +/Kcommaaccent 162 def +/kcommaaccent 163 def +/kgreenlandic 164 def +/Lacute 165 def +/lacute 166 def +/Lcommaaccent 167 def +/lcommaaccent 168 def +/Lcaron 169 def +/lcaron 170 def +/Ldot 171 def +/ldot 172 def +/Lslash 173 def +/lslash 174 def +/Nacute 175 def +/nacute 176 def +/Ncommaaccent 177 def +/ncommaaccent 178 def +/Ncaron 179 def +/ncaron 180 def +/napostrophe 181 def +/Eng 182 def +/eng 183 def +/Omacron 184 def +/omacron 185 def +/Obreve 186 def +/obreve 187 def +/Ohungarumlaut 188 def +/ohungarumlaut 189 def +/OE 190 def +/oe 191 def +/Racute 192 def +/racute 193 def +/Rcommaaccent 194 def +/rcommaaccent 195 def +/Rcaron 196 def +/rcaron 197 def +/Sacute 198 def +/sacute 199 def +/Scircumflex 200 def +/scircumflex 201 def +/Scedilla 202 def +/scedilla 203 def +/Scaron 204 def +/scaron 205 def +/Tcommaaccent 206 def +/tcommaaccent 207 def +/Tcaron 208 def +/tcaron 209 def +/Tbar 210 def +/tbar 211 def +/Utilde 212 def +/utilde 213 def +/Umacron 214 def +/umacron 215 def +/Ubreve 216 def +/ubreve 217 def +/Uring 218 def +/uring 219 def +/Uhungarumlaut 220 def +/uhungarumlaut 221 def +/Uogonek 222 def +/uogonek 223 def +/Wcircumflex 224 def +/wcircumflex 225 def +/Ycircumflex 226 def +/ycircumflex 227 def +/Ydieresis 228 def +/Zacute 229 def +/zacute 230 def +/Zdotaccent 231 def +/zdotaccent 232 def +/Zcaron 233 def +/zcaron 234 def +/longs 235 def +/uni0180 236 def +/uni0181 237 def +/uni0182 238 def +/uni0183 239 def +/uni0184 240 def +/uni0185 241 def +/uni0186 242 def +/uni0187 243 def +/uni0188 244 def +/uni0189 245 def +/uni018A 246 def +/uni018B 247 def +/uni018C 248 def +/uni018D 249 def +/uni018E 250 def +/uni018F 251 def +/uni0190 252 def +/uni0191 253 def +/florin 254 def +/uni0193 255 def +/uni0194 256 def +/uni0195 257 def +/uni0196 258 def +/uni0197 259 def +/uni0198 260 def +/uni0199 261 def +/uni019A 262 def +/uni019B 263 def +/uni019C 264 def +/uni019D 265 def +/uni019E 266 def +/uni019F 267 def +/Ohorn 268 def +/ohorn 269 def +/uni01A2 270 def +/uni01A3 271 def +/uni01A4 272 def +/uni01A5 273 def +/uni01A6 274 def +/uni01A7 275 def +/uni01A8 276 def +/uni01A9 277 def +/uni01AA 278 def +/uni01AB 279 def +/uni01AC 280 def +/uni01AD 281 def +/uni01AE 282 def +/Uhorn 283 def +/uhorn 284 def +/uni01B1 285 def +/uni01B2 286 def +/uni01B3 287 def +/uni01B4 288 def +/uni01B5 289 def +/uni01B6 290 def +/uni01B7 291 def +/uni01B8 292 def +/uni01B9 293 def +/uni01BA 294 def +/uni01BB 295 def +/uni01BC 296 def +/uni01BD 297 def +/uni01BE 298 def +/uni01BF 299 def +/uni01C0 300 def +/uni01C1 301 def +/uni01C2 302 def +/uni01C3 303 def +/uni01C4 304 def +/uni01C5 305 def +/uni01C6 306 def +/uni01C7 307 def +/uni01C8 308 def +/uni01C9 309 def +/uni01CA 310 def +/uni01CB 311 def +/uni01CC 312 def +/uni01CD 313 def +/uni01CE 314 def +/uni01CF 315 def +/uni01D0 316 def +/uni01D1 317 def +/uni01D2 318 def +/uni01D3 319 def +/uni01D4 320 def +/uni01D5 321 def +/uni01D6 322 def +/uni01D7 323 def +/uni01D8 324 def +/uni01D9 325 def +/uni01DA 326 def +/uni01DB 327 def +/uni01DC 328 def +/uni01DD 329 def +/uni01DE 330 def +/uni01DF 331 def +/uni01E0 332 def +/uni01E1 333 def +/uni01E2 334 def +/uni01E3 335 def +/uni01E4 336 def +/uni01E5 337 def +/Gcaron 338 def +/gcaron 339 def +/uni01E8 340 def +/uni01E9 341 def +/uni01EA 342 def +/uni01EB 343 def +/uni01EC 344 def +/uni01ED 345 def +/uni01EE 346 def +/uni01EF 347 def +/uni01F0 348 def +/uni01F1 349 def +/uni01F2 350 def +/uni01F3 351 def +/uni01F4 352 def +/uni01F5 353 def +/uni01F6 354 def +/uni01F7 355 def +/uni01F8 356 def +/uni01F9 357 def +/Aringacute 358 def +/aringacute 359 def +/AEacute 360 def +/aeacute 361 def +/Oslashacute 362 def +/oslashacute 363 def +/uni0200 364 def +/uni0201 365 def +/uni0202 366 def +/uni0203 367 def +/uni0204 368 def +/uni0205 369 def +/uni0206 370 def +/uni0207 371 def +/uni0208 372 def +/uni0209 373 def +/uni020A 374 def +/uni020B 375 def +/uni020C 376 def +/uni020D 377 def +/uni020E 378 def +/uni020F 379 def +/uni0210 380 def +/uni0211 381 def +/uni0212 382 def +/uni0213 383 def +/uni0214 384 def +/uni0215 385 def +/uni0216 386 def +/uni0217 387 def +/Scommaaccent 388 def +/scommaaccent 389 def +/uni021A 390 def +/uni021B 391 def +/uni021C 392 def +/uni021D 393 def +/uni021E 394 def +/uni021F 395 def +/uni0220 396 def +/uni0221 397 def +/uni0222 398 def +/uni0223 399 def +/uni0224 400 def +/uni0225 401 def +/uni0226 402 def +/uni0227 403 def +/uni0228 404 def +/uni0229 405 def +/uni022A 406 def +/uni022B 407 def +/uni022C 408 def +/uni022D 409 def +/uni022E 410 def +/uni022F 411 def +/uni0230 412 def +/uni0231 413 def +/uni0232 414 def +/uni0233 415 def +/uni0234 416 def +/uni0235 417 def +/uni0236 418 def +/dotlessj 419 def +/uni0238 420 def +/uni0239 421 def +/uni023A 422 def +/uni023B 423 def +/uni023C 424 def +/uni023D 425 def +/uni023E 426 def +/uni023F 427 def +/uni0240 428 def +/uni0241 429 def +/uni0242 430 def +/uni0243 431 def +/uni0244 432 def +/uni0245 433 def +/uni0246 434 def +/uni0247 435 def +/uni0248 436 def +/uni0249 437 def +/uni024A 438 def +/uni024B 439 def +/uni024C 440 def +/uni024D 441 def +/uni024E 442 def +/uni024F 443 def +/uni0259 444 def +/uni0292 445 def +/uni02BC 446 def +/circumflex 447 def +/caron 448 def +/breve 449 def +/ring 450 def +/ogonek 451 def +/tilde 452 def +/hungarumlaut 453 def +/uni0307 454 def +/uni030C 455 def +/uni030F 456 def +/uni0311 457 def +/uni0312 458 def +/uni031B 459 def +/uni0326 460 def +/Lambda 461 def +/Sigma 462 def +/eta 463 def +/uni0411 464 def +/quoteright 465 def +/dlLtcaron 466 def +/Dieresis 467 def +/Acute 468 def +/Tilde 469 def +/Grave 470 def +/Circumflex 471 def +/Caron 472 def +/uni0311.case 473 def +/Breve 474 def +/Dotaccent 475 def +/Hungarumlaut 476 def +/Doublegrave 477 def +/Eng.alt 478 def +/uni03080304 479 def +/uni03070304 480 def +/uni03080301 481 def +/uni03080300 482 def +/uni03030304 483 def +/uni0308030C 484 def +end readonly def + /sfnts[<0001000000120100000400204744454603ad02160000012c0000002247504f537feb94760000015000000ec44753 +5542720d76a300001014000000e84d415448093f3384000010fc000000f64f532f326aab715a000011f400000056636d6170 +0048065b0000124c000000586376742000691d39000012a4000001fe6670676d7134766a000014a4000000ab676173700007 +0007000015500000000c676c7966aa1f812c0000155c0000a37c68656164085dc2860000b8d800000036686865610d9f094d +0000b91000000024686d747800d59d920000b9340000078a6c6f6361df7708600000c0c0000003cc6d617870065206710000 +c48c000000206e616d6527ed3dbc0000c4ac000001d4706f7374ee52dc100000c68000000f56707265703b07f1000000d5d8 +0000056800010000000c00000000000000020003000300030001003101bb000101de01de0001000000010000000a002e003c +000244464c54000e6c61746e0018000400000000ffff0000000400000000ffff0001000000016b65726e0008000000010000 +00010004000200000001000800020ace000400000b380c0e0019003700000000000000000000000000000000ff9000000000 +00000000000000000000000000000000000000000000000000000000000000000000ffdc0000000000000000000000000000 +00000000000000000000000000000000000000000000ff9000000000000000000000ff900000000000000000000000000000 +ffb70000ff9a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000ffb70000fee6ff9afef0000000000000ffdc00000000ffdc000000000000ffdcff44000000000000ffdc0000 +ffdcffdc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000ff90000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ff9a0000000000000000ff6b0000ff7d0000ffd30000ffa400000000ffa4 +000000000000ffa4ff900000ff9affd3ffa40000ffa4ffa40000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000ffdc000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +ff9000000000ff9000000000000000000000fee60000fef000000000fef0000000000000ff1500000000ff90fee6fef00000 +fef0ff1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ffd3ffd3ffdcffdcffd3ffdc0000000000000000000000000000ffd30000 +ffd3000000000000ffd300000000000000480000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffdc00000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ffdc00000000ffdc0000ff610000ff6100000000ffdcffdc00000000ffdc +00000000ffdc0000ff75000000000000ffdc0000ffdc0000003900000000ffdc0000ffdcffdcffdcffdc000000000000ffdc +ffdcff6100000000ff90ffadff61ff75000000000000ffdc000000000000ffdc00000000ffdc0000ff610000ff6100000000 +ffdcffdc00000000ffdc00000000ffdc00000000000000000000ffdc0000ffdc0000003900000000ffdc0000ffdcffdcffdc +ffdc000000000000ffdc0000ff6100000000ff90ffadff610000000000000000ffdc00000000000000000000000000000000 +00000000ff900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000ffd3ffd3ffdcffdcffd3ffdc0000000000000000 +000000000000ffd30000ffd3000000000000ffd3000000000000ffdc00000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000ff880000000000000000ffdc000000000000feadfea4fea400000000fea4 +fed3fead0000fec9fec10000ff88feadfea40000fea4fec900000000fea40000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001003300320033003c003e003f0040004100420045 +0046004700480049004a004b0054005500560057005c005d005e005f0060006100620069006b006c006e007000720078007a +007c0087008a00a500a900ac00b400c000c100c400c500ca00cc00d000da00e400e90002002300320032000e00330033000f +003e00420003004500480006004900490007004a004a0010004b004b0011005400570009005c005c0012005d005d000a005e +0062000b00690069000d006b006b000d006c006c0013006e006e001300700070001400720072000f00780078000f007c007c +0015008700870009008a008a000100a500a5000200a900a9000200ac00ac001600b400b4000a00c000c0000400c100c1000c +00c400c4000400c500c5001700ca00ca000500cc00cc000500d000d0001800da00da000600e400e4000700e900e900080002 +0067003200320015003300330016003e00420004004500480007004900490008004a004b0003004c004c0017004d004d000a +004e0051001700530053000b00540054001800550055000c005600570018005c005c0019005d005d000e005e005e001a005f +005f000f00600062001a00650065001b00660066001300670068001b006900690014006b006b0014006c006c001c006d006d +001d006e006e001c006f006f001d00700070001c00710071001d00720072000100730073001e00740074001f007500750020 +00760076001f00770077002100780078000100790079001e007a007a0002007b007b0022007d007d0023007f007f00240081 +0081002400830083002400850085002400870087000c00880088001f008a008a0025008b008b000d008c008c001f008e008e +0026009b009b0027009f009f002700a500a5000300a900a9000300b400b4000e00b800b8001f00b900b9002800ba00ba001f +00bb00bb002800bc00bc002900bd00bd002800c000c0000300c100c1001000c300c3002700c400c4000300c500c5001000c6 +00c6000500c800c8000500ca00ca000500cb00cb001100cc00cc000500cd00cd001100ce00ce002a00cf00cf002100d000d0 +000600d100d1001200d200d2002b00d500d5002c00d700d7002c00d900d9002c00da00da000700db00db001300dd00dd002c +00df00df002c00e000e0002d00e100e1002e00e200e2002f00e300e3003000e400e4000800e900e900090132013200200156 +01560031015701570032015801580031015901590032018401840005018601860033018701870020019a019a001f019b019b +0034019d019d0020019e019e0035019f019f003600010000000a00c200d0001444464c54007a6172616200b461726d6e00b4 +6272616900b463616e7300b46368657200b46379726c00b467656f7200b46772656b00b468616e6900b46865627200b46b61 +6e6100b46c616f2000b46c61746e00846d61746800b46e6b6f2000b46f67616d00b472756e7200b474666e6700b474686169 +00b4000400000000ffff00000000000649534d2000284b534d2000284c534d2000284e534d200028534b5320002853534d20 +00280000ffff000100000000000000016c6f636c000800000001000000010004000100000001000800010006012800010001 +00b600010000000a00e000e80050003c0c0007dd00000000028200000460000005d500000000000004600000000000000000 +0000000000000460000000000000016800000460000000550000000000000000000000000000000000000000000000000000 +0000000000000000010e0000027600000000000000000000000000000000000000000000000000000000000000000000005a +0000010e0000005a0000005a0000010e00000000000000000000010e0000005a0000005a0000010e0000005a0000005a0000 +005a000001720000005a0000005a000002380000fb8f0000003c00000000000000000028000a000a00000000000100000000 +0001040e019000050000053305990000011e05330599000003d7006602120000020b06030308040202040000000e00000000 +000000000000000050664564004000c5024f0614fe14019a076d01e30000000100000000000000000003000000030000001c +0000000a0000003c000300010000001c0004002000000004000400010000024fffff000000c5ffffff6c000100000000000c +00000000001c0000000000000001000000c50000024f00000031013500b800cb00cb00c100aa009c01a600b8006600000071 +00cb00a002b20085007500b800c301cb0189022d00cb00a600f000d300aa008700cb03aa0400014a003300cb000000d90502 +00f4015400b4009c01390114013907060400044e04b4045204b804e704cd0037047304cd04600473013303a2055605a60556 +053903c5021200c9001f00b801df007300ba03e9033303bc0444040e00df03cd03aa00e503aa0404000000cb008f00a4007b +00b80014016f007f027b0252008f00c705cd009a009a006f00cb00cd019e01d300f000ba018300d5009803040248009e01d5 +00c100cb00f600830354027f00000333026600d300c700a400cd008f009a0073040005d5010a00fe022b00a400b4009c0000 +0062009c0000001d032d05d505d505d505f0007f007b005400a406b80614072301d300b800cb00a601c301ec069300a000d3 +035c037103db0185042304a80448008f0139011401390360008f05d5019a0614072306660179046004600460047b009c0000 +0277046001aa00e904600762007b00c5007f027b000000b4025205cd006600bc00660077061000cd013b01850389008f007b +0000001d00cd074a042f009c009c0000077d006f0000006f0335006a006f007b00ae00b2002d0396008f027b00f600830354 +063705f6008f009c04e10266008f018d02f600cd03440029006604ee00730000140000960000b707060504030201002c2010 +b002254964b040515820c859212d2cb002254964b040515820c859212d2c20100720b00050b00d7920b8ffff5058041b0559 +b0051cb0032508b0042523e120b00050b00d7920b8ffff5058041b0559b0051cb0032508e12d2c4b505820b0fd454459212d +2cb002254560442d2c4b5358b00225b0022545445921212d2c45442d2cb00225b0022549b00525b005254960b0206368208a +108a233a8a10653a2d000000000200080002ffff0003000201350000020005d5000300090035400f07008304810208070501 +030400000a10fc4bb00b5458b90000ffc038593cec32393931002fe4fccc3001b6000b200b500b035d253315231133110323 +030135cbcbcb14a215fefe05d5fd71fe9b016500000200100000056805d50002000a00c24041001101000405040211050504 +01110a030a0011020003030a0711050406110505040911030a08110a030a4200030795010381090509080706040302010009 +050a0b10d4c4173931002f3ce4d4ec1239304b5358071005ed0705ed071005ed0705ed071008ed071005ed071005ed071008 +ed5922b2200c01015d40420f010f020f070f080f005800760070008c000907010802060309041601190256015802500c6701 +6802780176027c0372047707780887018802800c980299039604175d005d090121013301230321032302bcfeee0225fe7be5 +0239d288fd5f88d5050efd1903aefa2b017ffe8100010073ffe3052705f000190036401a0da10eae0a951101a100ae049517 +91118c1a07190d003014101a10fcec32ec310010e4f4ecf4ec10eef6ee30b40f1b1f1b02015d01152e012320001110002132 +3637150e01232000111000213216052766e782ff00fef00110010082e7666aed84feadfe7a0186015386ed0562d55f5efec7 +fed8fed9fec75e5fd34848019f01670168019f47000200c9000005b005d500080011002e4015009509810195100802100a00 +05190d32001c09041210fcecf4ec113939393931002fecf4ec30b2601301015d011133200011100021252120001110002901 +0193f40135011ffee1fecbfe42019f01b20196fe68fe50fe61052ffb770118012e012c0117a6fe97fe80fe7efe96000100c9 +0000048b05d5000b002e401506950402950081089504ad0a05010907031c00040c10fcec32d4c4c431002fececf4ec10ee30 +b21f0d01015d132115211121152111211521c903b0fd1a02c7fd3902f8fc3e05d5aafe46aafde3aa00010073ffe3058b05f0 +001d0039402000051b0195031b950812a111ae15950e91088c1e02001c1134043318190b101e10fcecfce4fcc4310010e4f4 +ecf4ec10fed4ee11393930251121352111060423200011100021320417152e0123200011100021323604c3feb6021275fee6 +a0fea2fe75018b015e9201076f70fc8bfeeefeed011301126ba8d50191a6fd7f53550199016d016e01994846d75f60fecefe +d1fed2fece25000100c90000053b05d5000b002c4014089502ad0400810a0607031c053809011c00040c10fcec32fcec3231 +002f3ce432fcec30b2500d01015d133311211133112311211123c9ca02decacafd22ca05d5fd9c0264fa2b02c7fd39000001 +00c90000019305d50003002eb700af02011c00040410fc4bb0105458b9000000403859ec31002fec3001400d300540055005 +60058f059f05065d13331123c9caca05d5fa2b000001ff96fe66019305d5000b004240130b0200079505b000810c05080639 +011c00040c10fc4bb0105458b9000000403859ece43939310010e4fcec1139393001400d300d400d500d600d8f0d9f0d065d +13331110062b013533323635c9cacde34d3f866e05d5fa93fef2f4aa96c2000100c90000056a05d5000a00ef402808110506 +0507110606050311040504021105050442080502030300af09060501040608011c00040b10fcec32d4c4113931002f3cec32 +1739304b5358071004ed071005ed071005ed071004ed5922b2080301015d4092140201040209081602280528083702360534 +084702460543085502670276027705830288058f0894029b08e702150603090509061b031907050a030a07180328052b062a +073604360536063507300c41034004450540064007400c62036004680567077705700c8b038b058e068f078f0c9a039d069d +07b603b507c503c507d703d607e803e904e805ea06f703f805f9062c5d71005d711333110121090121011123c9ca029e0104 +fd1b031afef6fd33ca05d5fd890277fd48fce302cffd3100000100c90000046a05d500050025400c0295008104011c033a00 +040610fcecec31002fe4ec304009300750078003800404015d133311211521c9ca02d7fc5f05d5fad5aa000100c900000533 +05d500090079401e071101020102110607064207020300af0805060107021c0436071c00040a10fcecfcec11393931002f3c +ec323939304b5358071004ed071004ed5922b21f0b01015d40303602380748024707690266078002070601090615011a0646 +0149065701580665016906790685018a0695019a069f0b105d005d13210111331121011123c901100296c4fef0fd6ac405d5 +fb1f04e1fa2b04e1fb1f00020073ffe305d905f0000b00170023401306951200950c91128c1809190f33031915101810fcec +fcec310010e4f4ec10ee300122001110003332001110002720001110002120001110000327dcfefd0103dcdc0101feffdc01 +3a0178fe88fec6fec5fe870179054cfeb8fee5fee6feb80148011a011b0148a4fe5bfe9efe9ffe5b01a40162016201a50002 +00c90000055405d50013001c00b14035090807030a061103040305110404034206040015030415950914950d810b04050603 +1109001c160e050a191904113f140a1c0c041d10fcec32fcc4ec1117391139393931002f3cf4ecd4ec123912391239304b53 +58071005ed071005ed1117395922b2401e01015d40427a130105000501050206030704150015011402160317042500250125 +0226032706260726082609201e3601360246014602680575047505771388068807980698071f5d005d011e01171323032e01 +2b01112311212016151406011133323635342623038d417b3ecdd9bf4a8b78dcca01c80100fc83fd89fe9295959202bc1690 +7efe68017f9662fd8905d5d6d88dba024ffdee878383850000010087ffe304a205f00027007e403c0d0c020e0b021e1f1e08 +0902070a021f1f1e420a0b1e1f0415010015a11494189511049500942591118c281e0a0b1f1b0700221b190e2d0719142228 +10dcc4ecfcece4111239393939310010e4f4e4ec10eef6ee10c6111739304b535807100eed11173907100eed1117395922b2 +0f2901015db61f292f294f29035d01152e012322061514161f011e0115140421222627351e013332363534262f012e013534 +24333216044873cc5fa5b377a67ae2d7feddfee76aef807bec72adbc879a7be2ca0117f569da05a4c53736807663651f192b +d9b6d9e0302fd04546887e6e7c1f182dc0abc6e426000001fffa000004e905d50007004a400e0602950081040140031c0040 +050810d4e4fce431002ff4ec3230014bb00a5458bd00080040000100080008ffc03811373859401300091f00100110021f07 +1009400970099f09095d03211521112311210604effdeecbfdee05d5aafad5052b00000100b2ffe3052905d5001100404016 +0802110b0005950e8c09008112081c0a38011c00411210fc4bb0105458b90000ffc03859ecfcec310010e432f4ec11393939 +393001b61f138f139f13035d133311141633323635113311100021200011b2cbaec3c2aecbfedffee6fee5fedf05d5fc75f0 +d3d3f0038bfc5cfedcfed6012a01240000010044000007a605d5000c017b4049051a0605090a09041a0a09031a0a0b0a021a +01020b0b0a061107080705110405080807021103020c000c011100000c420a050203060300af0b080c0b0a09080605040302 +010b07000d10d4cc173931002f3cec32321739304b5358071005ed071008ed071008ed071005ed071008ed071005ed0705ed +071008ed5922b2000e01015d40f206020605020a000a000a120a2805240a200a3e023e05340a300a4c024d05420a400a5902 +6a026b05670a600a7b027f027c057f05800a960295051d070009020803000406050005000601070408000807090009040a0a +0c000e1a0315041508190c100e200421052006200720082309240a250b200e200e3c023a033504330530083609390b3f0c30 +0e460046014a0240044505400542064207420840084009440a4d0c400e400e58025608590c500e6602670361046205600660 +0760086409640a640b770076017b027803770474057906790777087008780c7f0c7f0e860287038804890585098a0b8f0e97 +049f0eaf0e5b5d005d1333090133090133012309012344cc013a0139e3013a0139cdfe89fefec5fec2fe05d5fb1204eefb12 +04eefa2b0510faf00001fffc000004e705d50008009440280311040504021101020505040211030208000801110000084202 +0300af0602070440051c0040070910d4e4fce4123931002fec3239304b5358071005ed071008ed071008ed071005ed5922b2 +000a01015d403c05021402350230023005300846024002400540085102510551086502840293021016011a031f0a26012903 +37013803400a670168037803700a9f0a0d5d005d03330901330111231104d9019e019bd9fdf0cb05d5fd9a0266fcf2fd3902 +c7000001005c0000051f05d500090090401b03110708070811020302420895008103950508030001420400060a10dc4bb009 +544bb00a545b58b90006ffc03859c4d4e411393931002fecf4ec304b5358071005ed071005ed592201404005020a07180729 +02260738074802470748080905030b08000b16031a08100b2f0b350339083f0b47034a084f0b55035908660369086f0b7703 +78087f0b9f0b165d005d13211501211521350121730495fc5003c7fb3d03b0fc6705d59afb6faa9a0491000100aa04f00289 +066600030031400901b400b3040344010410dcec310010f4ec30004bb009544bb00e545b58bd0004ffc00001000400040040 +381137385909012301016f011a99feba0666fe8a01760002007bffe3042d047b000a002500bc4027191f0b17090e00a91706 +b90e1120861fba1cb923b8118c170c001703180d09080b1f030814452610fcecccd4ec323211393931002fc4e4f4fcf4ec10 +c6ee10ee11391139123930406e301d301e301f3020302130223f27401d401e401f402040214022501d501e501f5020502150 +2250277027851d871e871f8720872185229027a027f0271e301e301f30203021401e401f40204021501e501f50205021601e +601f60206021701e701f70207021801e801f80208021185d015d0122061514163332363d01371123350e0123222635343633 +2135342623220607353e0133321602bedfac816f99b9b8b83fbc88accbfdfb0102a79760b65465be5af3f00233667b6273d9 +b4294cfd81aa6661c1a2bdc0127f8b2e2eaa2727fc0000010071ffe303e7047b0019003f401b00860188040e860d880ab911 +04b917b8118c1a07120d004814451a10fce432ec310010e4f4ec10fef4ee10f5ee30400b0f1b101b801b901ba01b05015d01 +152e0123220615141633323637150e0123220011100021321603e74e9d50b3c6c6b3509d4e4da55dfdfed6012d010655a204 +35ac2b2be3cdcde32b2baa2424013e010e0112013a2300020071ffe3045a06140010001c003840191ab9000e14b905088c0e +b801970317040008024711120b451d10fcecf4ec323231002fece4f4c4ec10c4ee30b6601e801ea01e03015d011133112335 +0e0123220211100033321601141633323635342623220603a2b8b83ab17ccbff00ffcb7cb1fdc7a79292a8a89292a703b602 +5ef9eca86461014401080108014461fe15cbe7e7cbcbe7e700020071ffe3047f047b0014001b007040240015010986088805 +15a90105b90c01bb18b912b80c8c1c1b1502081508004b02120f451c10fcecf4ecc4111239310010e4f4ece410ee10ee10f4 +ee1112393040293f1d701da01dd01df01d053f003f013f023f153f1b052c072f082f092c0a6f006f016f026f156f1b095d71 +015d0115211e0133323637150e01232000111000333200072e0123220607047ffcb20ccdb76ac76263d06bfef4fec70129fc +e20107b802a5889ab90e025e5abec73434ae2a2c0138010a01130143feddc497b4ae9e0000020071fe56045a047b000b0028 +004a4023190c1d0912861316b90f03b92623b827bc09b90fbd1a1d261900080c4706121220452910fcc4ecf4ec323231002f +c4e4ece4f4c4ec10fed5ee1112393930b6602a802aa02a03015d01342623220615141633323617100221222627351e013332 +363d010e0123220211101233321617353303a2a59594a5a59495a5b8fefefa61ac51519e52b5b439b27ccefcfcce7cb239b8 +023dc8dcdcc8c7dcdcebfee2fee91d1eb32c2abdbf5b6362013a01030104013a6263aa00000100ba00000464061400130034 +4019030900030e0106870e11b80c970a010208004e0d09080b461410fcec32f4ec31002f3cecf4c4ec1112173930b2601501 +015d0111231134262322061511231133113e013332160464b87c7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870614 +fd9e6564ef00000200c100000179061400030007002b400e06be04b100bc020501080400460810fc3cec3231002fe4fcec30 +400b1009400950096009700905015d1333112311331523c1b8b8b8b80460fba00614e9000002ffdbfe5601790614000b000f +0044401c0b0207000ebe0c078705bd00bc0cb110081005064f0d01080c00461010fc3cec32e4391239310010ece4f4ec10ee +1112393930400b1011401150116011701105015d13331114062b01353332363511331523c1b8a3b54631694cb8b80460fb8c +d6c09c61990628e9000100ba0000049c0614000a00bc40290811050605071106060503110405040211050504420805020303 +bc009709060501040608010800460b10fcec32d4c4113931002f3cece41739304b5358071004ed071005ed071005ed071004 +ed5922b2100c01015d405f04020a081602270229052b0856026602670873027705820289058e08930296059708a302120905 +0906020b030a072803270428052b062b07400c6803600c8903850489058d068f079a039707aa03a705b607c507d607f703f0 +03f704f0041a5d71005d1333110133090123011123bab90225ebfdae026bf0fdc7b90614fc6901e3fdf4fdac0223fddd0001 +00c100000179061400030022b7009702010800460410fcec31002fec30400d10054005500560057005f00506015d13331123 +c1b8b80614f9ec00000100ba00000464047b001300364019030900030e0106870e11b80cbc0a010208004e0d09080b461410 +fcec32f4ec31002f3ce4f4c4ec1112173930b46015cf1502015d0111231134262322061511231133153e013332160464b87c +7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870460ae6564ef00020071ffe30475047b000b0017004a401306b91200 +b90cb8128c1809120f51031215451810fcecf4ec310010e4f4ec10ee3040233f197b007b067f077f087f097f0a7f0b7b0c7f +0d7f0e7f0f7f107f117b12a019f01911015d012206151416333236353426273200111000232200111000027394acab9593ac +ac93f00112feeef0f1feef011103dfe7c9c9e7e8c8c7e99cfec8feecfeedfec70139011301140138000100ba0000034a047b +001100304014060b0700110b03870eb809bc070a06080008461210fcc4ec3231002fe4f4ecc4d4cc11123930b450139f1302 +015d012e012322061511231133153e0133321617034a1f492c9ca7b9b93aba85132e1c03b41211cbbefdb20460ae66630505 +0001006fffe303c7047b002700e7403c0d0c020e0b531f1e080902070a531f1f1e420a0b1e1f041500860189041486158918 +b91104b925b8118c281e0a0b1f1b0700521b080e07081422452810fcc4ecd4ece4111239393939310010e4f4ec10fef5ee10 +f5ee121739304b535807100eed111739070eed1117395922b2002701015d406d1c0a1c0b1c0c2e092c0a2c0b2c0c3b093b0a +3b0b3b0c0b200020012402280a280b2a132f142f152a16281e281f292029212427860a860b860c860d12000000010202060a +060b030c030d030e030f03100319031a031b031c041d09272f293f295f297f2980299029a029f029185d005d7101152e0123 +22061514161f011e0115140623222627351e013332363534262f012e01353436333216038b4ea85a898962943fc4a5f7d85a +c36c66c661828c65ab40ab98e0ce66b4043fae282854544049210e2a99899cb62323be353559514b50250f2495829eac1e00 +00010037000002f2059e0013003840190e05080f03a9001101bc08870a0b08090204000810120e461410fc3cc4fc3cc43239 +3931002fecf43cc4ec3211393930b2af1501015d01112115211114163b01152322263511233533110177017bfe854b73bdbd +d5a28787059efec28ffda0894e9a9fd202608f013e00000200aeffe30458047b00130014003b401c030900030e0106870e11 +8c0a01bc14b80c0d0908140b4e020800461510fcecf439ec3231002fe4e432f4c4ec1112173930b46f15c01502015d131133 +1114163332363511331123350e0123222601aeb87c7c95adb8b843b175c1c801cf01ba02a6fd619f9fbea4027bfba0ac6663 +f003a80000010056000006350460000c01eb404905550605090a0904550a0903550a0b0a025501020b0b0a06110708070511 +0405080807021103020c000c011100000c420a050203060300bf0b080c0b0a09080605040302010b07000d10d44bb00a544b +b011545b4bb012545b4bb013545b4bb00b545b58b9000000403859014bb00c544bb00d545b4bb010545b58b90000ffc03859 +cc173931002f3cec32321739304b5358071005ed071008ed071008ed071005ed071008ed071005ed0705ed071008ed592201 +40ff050216021605220a350a49024905460a400a5b025b05550a500a6e026e05660a79027f0279057f05870299029805940a +bc02bc05ce02c703cf051d0502090306040b050a080b09040b050c1502190316041a051b081b09140b150c25002501230227 +03210425052206220725082709240a210b230c390336043608390c300e460248034604400442054006400740084409440a44 +0b400e400e560056015602500451055206520750085309540a550b6300640165026a0365046a056a066a076e09610b670c6f +0e7500750179027d0378047d057a067f067a077f07780879097f097b0a760b7d0c870288058f0e97009701940293039c049b +05980698079908402f960c9f0ea600a601a402a403ab04ab05a906a907ab08a40caf0eb502b103bd04bb05b809bf0ec402c3 +03cc04ca05795d005d13331b01331b013301230b012356b8e6e5d9e6e5b8fedbd9f1f2d90460fc96036afc96036afba00396 +fc6a0001003dfe56047f0460000f018b40430708020911000f0a110b0a00000f0e110f000f0d110c0d00000f0d110e0d0a0b +0a0c110b0b0a420d0b0910000b058703bd0e0bbc100e0d0c0a09060300080f040f0b1010d44bb00a544bb008545b58b9000b +004038594bb0145458b9000bffc03859c4c4111739310010e432f4ec113911391239304b5358071005ed071008ed071008ed +071005ed071008ed0705ed173259220140f0060005080609030d160a170d100d230d350d490a4f0a4e0d5a095a0a6a0a870d +800d930d120a000a09060b050c0b0e0b0f1701150210041005170a140b140c1a0e1a0f270024012402200420052908280925 +0a240b240c270d2a0e2a0f201137003501350230043005380a360b360c380d390e390f301141004001400240034004400540 +06400740084209450a470d490e490f40115400510151025503500450055606550756085709570a550b550c590e590f501166 +016602680a690e690f60117b08780e780f89008a09850b850c890d890e890f9909950b950c9a0e9a0fa40ba40cab0eab0fb0 +11cf11df11ff11655d005d050e012b01353332363f01013309013302934e947c936c4c543321fe3bc3015e015ec368c87a9a +488654044efc94036c0000010058000003db04600009009d401a081102030203110708074208a900bc03a905080301000401 +060a10dc4bb00b544bb00c545b58b90006ffc038594bb0135458b9000600403859c432c411393931002fecf4ec304b535807 +1005ed071005ed592201404205021602260247024907050b080f0b18031b082b08200b36033908300b400140024503400440 +054308570359085f0b6001600266036004600562087f0b800baf0b1b5d005d1321150121152135012171036afd4c02b4fc7d +02b4fd650460a8fcdb93a8032500000200d7054603290610000300070092400e0602ce0400cd080164000564040810dcfcd4 +ec310010fc3cec3230004bb00a544bb00d545b58bd00080040000100080008ffc03811373859014bb00c544bb00d545b4bb0 +0e545b4bb017545b58bd0008ffc000010008000800403811373859014bb00f544bb019545b58bd00080040000100080008ff +c03811373859401160016002600560067001700270057006085d0133152325331523025ecbcbfe79cbcb0610cacaca000001 +00d50562032b05f60003002fb702ef00ee0401000410d4cc310010fcec30004bb009544bb00e545b58bd0004ffc000010004 +00040040381137385913211521d50256fdaa05f694000001017304ee0352066600030031400902b400b3040344010410d4ec +310010f4ec30004bb009544bb00e545b58bd0004ffc00001000400040040381137385901330123028bc7feba990666fe8800 +000100db024801ae034600030012b7028300040119000410d4ec310010d4ec3013331523dbd3d30346fe00010123fe7502c1 +00000013001f400e09060a0df306001300102703091410dcd4ecd4cc31002fd4fcc4123930211e0115140623222627351e01 +333236353426270254373678762e572b224a2f3b3c2b2d3e6930595b0c0c83110f302e1e573d0003001000000568076d000b +000e002100cb40540c110d0c1b1c1b0e111c1b1e111c1b1d111c1c1b0d11210f210c110e0c0f0f2120110f211f11210f2142 +0c1b0f0d0903c115091e950d098e201c1e1d1c18201f210d12060e180c061b0056181c0f0656121c212210d4c4d4ec3210d4 +ee32113911391112391139391112393931002f3ce6d6ee10d4ee1112393939304b5358071005ed0705ed071008ed071005ed +071005ed0705ed0705ed071008ed5922b2202301015d40201a0c730c9b0c03070f081b5023660d690e750d7b0e791c791d76 +20762180230c5d005d013426232206151416333236030121012e01353436333216151406070123032103230354593f405758 +3f3f5998fef00221fe583d3e9f7372a13f3c0214d288fd5f88d5065a3f5957413f5858fef3fd19034e29734973a0a1724676 +29fa8b017ffe8100000200080000074805d5000f00130087403911110e0f0e10110f0f0e0d110f0e0c110e0f0e420595030b +951101951095008111079503ad0d0911100f0d0c050e0a00040806021c120a0e1410d4d43cec32d4c4c41112173931002f3c +ececc4f4ecec10ee10ee304b5358071005ed0705ed071005ed071005ed5922b2801501015d4013671177107711860c851096 +119015a015bf15095d01152111211521112115211121032301170121110735fd1b02c7fd3902f8fc3dfdf0a0cd02718bfeb6 +01cb05d5aafe46aafde3aa017ffe8105d59efcf00310ffff0073fe75052705f012260006000010070030012d0000ffff00c9 +0000048b076b122600080000100701d6049e0175ffff00c90000048b076b122600080000100701d4049e0175ffff00c90000 +048b076d122600080000110701d7049e017500074003400c015d3100ffff00c90000048b074e122600080000110701d3049e +017500094005400c4010025d3100ffff003b000001ba076b1226000b0000100701d6032f0175ffff00a20000021f076b1226 +000b0000100701d4032f0175fffffffe00000260076d1226000b0000110701d7032f01750008b401060a00072b31ffff0006 +00000258074e1226000b0000110701d3032f01750008b4000a0701072b310002000a000005ba05d5000c0019006740201009 +a90b0d95008112950e0b0707011913040f0d161904320a110d1c0800791a10f43cec32c4f4ec10c4173931002fc632eef6ee +10ee32304028201b7f1bb01b039f099f0a9f0b9f0c9f0e9f0f9f109f11bf09bf0abf0bbf0cbf0ebf0fbf10bf11105d015d13 +21200011100029011123353313112115211133200011100021d301a001b10196fe69fe50fe60c9c9cb0150feb0f30135011f +fee1fecb05d5fe97fe80fe7efe9602bc9001e3fe1d90fdea0118012e012c0117ffff00c900000533075e1226000f00001107 +01d504fe01750014b400132204072b400930133f2210131f22045d31ffff0073ffe305d9076b122600100000100701d60527 +0175ffff0073ffe305d9076b122600100000100701d405270175ffff0073ffe305d9076d122600100000110701d705270175 +0010b40f1a1e15072b40051f1a101e025d31ffff0073ffe305d9075e122600100000110701d5052701750018b40321300907 +2b400d30213f3020212f3010211f30065d31ffff0073ffe305d9074e122600100000110701d3052701750014b4031f1a0907 +2b4009401f4f1a101f1f1a045d3100010119003f059c04c5000b0085404d0a9c0b0a070807099c080807049c030407070605 +9c060706049c0504010201039c0202010b9c0001000a9c090a010100420a080706040201000805030b090c0b0a0907050403 +0108020008060c10d43ccc321739310010d43ccc321739304b5358071008ed071005ed071005ed071008ed071005ed071008 +ed071005ed071008ed59220902070901270901370901059cfe3701c977fe35fe357601c8fe387601cb01cb044cfe35fe3779 +01cbfe357901c901cb79fe3501cb00030066ffba05e5061700090013002b009e403c1d1f1a0d2b2c130a0100040d29262014 +0d042a261e1a0495260d951a91268c2c2b2c2a141710201e23130a0100041d2910071f07192333101917102c10fcecfcecc0 +111239391739123939111239391139310010e4f4ec10ee10c010c011123939123912173912391112393930402a57005a1557 +1955216a1565217b15761c7521094613590056136a006413641c6a287c007313761c7a280b5d015d09011e01333200113426 +272e012322001114161707260235100021321617371707161215100021222627072704b6fd333ea15fdc010127793da15fdc +fefd2727864e4f0179013b82dd57a266aa4e50fe88fec680dd5ba2670458fcb240430148011a70b8b84043feb8fee570bc44 +9e660108a0016201a54d4bbf59c667fef69efe9ffe5b4b4bbf58ffff00b2ffe30529076b122600140000100701d604ee0175 +ffff00b2ffe30529076b122600140000100701d404ee0175ffff00b2ffe30529076d122600140000110701d704ee01750014 +b40a141800072b40092f1420181f141018045d31ffff00b2ffe30529074e122600140000110701d304ee0175001cb4011914 +09072b401150195f1440194f1420192f1410191f14085d31fffffffc000004e7076b122600160000100701d4047301750002 +00c90000048d05d5000c0015003d401b0e95090d9502f600810b150f090304011219063f0d0a011c00041610fcec3232fcec +11173931002ff4fcecd4ec3040090f171f173f175f1704015d1333113332041514042b011123131133323635342623c9cafe +fb0101fefffbfecacafe8d9a998e05d5fef8e1dcdce2feae0427fdd192868691000100baffe304ac0614002f009a40302d27 +210c04060d2000042a1686171ab9132ab90397138c2e0c090d1d2021270908242708061d082410162d081000463010fcc4fc +cc10c6eed4ee10ee1139391239123931002fe4feee10fed5ee12173917393040400f050f060f070f270f288a0c8a0d070a06 +0a070a0b0a0c0a0d0a1f0d200a210c220426190d191f19203a203a214d1f4d20492149226a1f6a20a506a507a620185d015d +133436333216170e011514161f011e0115140623222627351e013332363534262f012e01353436372e01232206151123baef +dad0db0397a83a4139a660e1d3408849508c4174783b655c6057a7970883718288bb0471c8dbe8e00873602f512a256a8e64 +acb71918a41e1d5f5b3f543e373b875b7fac1d67708b83fb9300ffff007bffe3042d0666122600190000110600185200000b +40073f262f261f26035d3100ffff007bffe3042d06661226001900001106002e5200000b40073f262f261f26035d3100ffff +007bffe3042d0666122600190000110601bf52000008b40b282c14072b31ffff007bffe3042d0637122600190000110601c4 +52000014b4142e3c0b072b4009202e2f3c102e1f3c045d31ffff007bffe3042d06101226001900001106002c52000020b414 +2d280b072b40157f286f28502d5f28402d4f28302d3f28002d0f280a5d31ffff007bffe3042d0706122600190000110601c2 +52000025400e262c142c260b0732381438320b072b10c42b10c4310040093f353f2f0f350f2f045d30000003007bffe3076f +047b00060033003e01034043272d253d0e0d0034a925168615881200a90e3a12b91c192e862dba2a03b90ebb07310ab81f19 +8c253f343726060f0025371c07260f1500080d3d26080f2d370822453f10fcecccd4fc3cd4ecc41112393911391112391112 +39310010c4e432f43cc4e4fc3cf4ec10c4ee3210ee10f4ee10ee11391139111239304081302b302c302d302e302f3030402b +402c402d402e402f4030502b502c502d502e502f5030852b853080409040a040b040c040d040e040e040f0401d3f003f063f +0d3f0e3f0f05302c302d302e302f402c402d402e402f502c502d502e502f6f006f066f0d6f0e6f0f602c602d602e602f702c +702d702e702f802c802d802e802f1d5d71015d012e0123220607033e013332001d01211e0133323637150e01232226270e01 +232226353436332135342623220607353e013332160322061514163332363d0106b601a58999b90e444ad484e20108fcb20c +ccb768c86464d06aa7f84d49d88fbdd2fdfb0102a79760b65465be5a8ed5efdfac816f99b9029497b4ae9e01305a5efeddfa +5abfc83535ae2a2c79777878bba8bdc0127f8b2e2eaa272760fe18667b6273d9b429ffff0071fe7503e7047b1226001a0000 +10070030008f0000ffff0071ffe3047f06661226001c000010070018008b0000ffff0071ffe3047f06661226001c00001007 +002e008b0000ffff0071ffe3047f06661226001c0000110701bf008b00000008b4151e221b072b31ffff0071ffe3047f0610 +1226001c00001107002c008b0000000740034020015d3100ffffffc7000001a6066610270018ff1d00001206009d0000ffff +00900000026f06661027002eff1d00001206009d0000ffffffde0000025c06661226009d0000110701bfff1d00000008b401 +070b00072b31fffffff40000024606101226009d00001107002cff1d00000008b4000b0801072b3100020071ffe304750614 +000e00280127405e257b26251e231e247b23231e0f7b231e287b27281e231e262728272524252828272223221f201f212020 +1f42282726252221201f08231e030f2303b91b09b9158c1b23b1292627120c212018282523221f051e0f060c121251061218 +452910fcecf4ec113939173912393911123939310010ecc4f4ec10ee12391239121739304b535807100ec9071008c9071008 +c907100ec9071008ed070eed071005ed071008ed5922b23f2a01015d407616252b1f28222f232f2429252d262d272a283625 +462558205821602060216622752075217522132523252426262627272836243625462445255a205a21622062217f007f017f +027a037b097f0a7f0b7f0c7f0d7f0e7f0f7f107f117f127f137f147b157a1b7a1c7f1d7f1e762076217822a02af02a275d00 +5d012e0123220615141633323635342613161215140023220011340033321617270527252733172517050346325829a7b9ae +9291ae36097e72fee4e6e7fee50114dd12342a9ffec1210119b5e47f014d21fed903931110d8c3bcdedebc7abc01268ffee0 +adfffec9013700fffa01370505b46b635ccc916f6162ffff00ba000004640637122600230000100701c400980000ffff0071 +ffe304750666122600240000100600187300ffff0071ffe3047506661226002400001006002e7300ffff0071ffe304750666 +122600240000110601bf73000008b40f1a1e15072b31ffff0071ffe304750637122600240000110601c473000014b415202e +0f072b400920202f2e10201f2e045d31ffff0071ffe3047506101226002400001106002c73000014b4031f1a09072b400940 +1f4f1a301f3f1a045d31000300d9009605db046f00030007000b0029401400ea0206ea0402089c040a0c090501720400080c +10dcd43cfc3cc4310010d4c4fcc410ee10ee3001331523113315230121152102dff6f6f6f6fdfa0502fafe046ff6fe12f502 +41aa00030048ffa2049c04bc00090013002b00e4403c2b2c261f1d1a130a0100040d292620140d042a261e1a04b9260db91a +b8268c2c2b2c2a141710201e23130a01000410071f1d0712235129101217452c10fcec32f4ec32c011121739123939111239 +391139310010e4f4ec10ee10c010c011123939123912173911393911123930407028013f2d5914561c551d56206a1566217f +007b047f057f067f077f087f097f0a7f0b7f0c7b0d7a157b1a7f1b7f1c7f1d7f1e7f1f7f207b217f227f237f247f257b269b +199525a819a02df02d2659005613551d5a2869006613651c6a287a007413761c7a28891e95189a24a218ad24115d015d0901 +1e01333236353426272e0123220615141617072e01351000333216173717071e011510002322262707270389fe1929674193 +ac145c2a673e97a913147d36360111f15d9f438b5f923536feeef060a13f8b600321fdb02a28e8c84f759a2929ebd3486e2e +974dc577011401383334a84fb34dc678feedfec73433a84effff00aeffe304580666122600280000100600187b00ffff00ae +ffe3045806661226002800001006002e7b00ffff00aeffe304580666122600280000110601bf7b000008b40b171b01072b31 +ffff00aeffe3045806101226002800001106002c7b000018b4021b180a072b400d401b4f18301b3f18001b0f18065d31ffff +003dfe56047f06661226002a00001006002e5e00000200bafe5604a406140010001c003e401b14b905081ab9000e8c08b801 +bd03971d11120b471704000802461d10fcec3232f4ec310010ece4e4f4c4ec10c6ee304009601e801ea01ee01e04015d2511 +231133113e013332001110022322260134262322061514163332360173b9b93ab17bcc00ffffcc7bb10238a79292a7a79292 +a7a8fdae07befda26461febcfef8fef8febc6101ebcbe7e7cbcbe7e7ffff003dfe56047f06101226002a00001106002c5e00 +0016b418171219072b400b30173f1220172f121f12055d31ffff00100000056807311027002d00bc013b1306000500000010 +b40e030209072b400540034f02025d31ffff007bffe3042d05f61026002d4a001306001900000010b41803020f072b40056f +027f03025d31ffff0010000005680792102701c100ce014a1306000500000012b418000813072b310040056f006f08025d30 +ffff007bffe3042d061f102601c14fd71306001900000008b422000819072b31ffff0010fe7505a505d51226000500001007 +01c302e40000ffff007bfe750480047b122600190000100701c301bf0000ffff0073ffe30527076b122600060000100701d4 +052d0175ffff0071ffe303e706661226001a00001007002e00890000ffff0073ffe30527076d102701d7054c017513060006 +00000009b204041e103c3d2f3100ffff0071ffe303e706661226001a0000100701bf00a40000ffff0073ffe3052707501027 +01db054c0175120600060000ffff0071ffe303e70614102701c604a400001206001a0000ffff0073ffe30527076d12260006 +0000110701d8052d0175000740031f1d015d3100ffff0071ffe303e706661226001a0000100701c000890000ffff00c90000 +05b0076d102701d804ec0175120600070000ffff0071ffe305db06141226001b0000110701d205140000000b40075f1d3f1d +1f1d035d3100ffff000a000005ba05d51006003c000000020071ffe304f4061400180024004a40240703d30901f922b90016 +1cb90d108c16b805970b021f0c04030008080a0647191213452510fcecf43cc4fc173cc431002fece4f4c4ec10c4eefd3cee +3230b660268026a02603015d01112135213533153315231123350e0123220211100033321601141633323635342623220603 +a2feba0146b89a9ab83ab17ccbff00ffcb7cb1fdc7a79292a8a89292a703b6014e7d93937dfafca864610144010801080144 +61fe15cbe7e7cbcbe7e7ffff00c90000048b07331226000800001007002d00a1013dffff0071ffe3047f05f61027002d0096 +00001306001c0000000740037000015d3100ffff00c90000048b076d102701da04a10175130600080000000740034000015d +3100ffff0071ffe3047f0648102701c1009600001306001c0000000740037000015d3100ffff00c90000048b0750102701db +049e0175120600080000ffff0071ffe3047f0614102701c6049600001206001c0000ffff00c9fe75048d05d5122600080000 +100701c301cc0000ffff0071fe75047f047b1226001c0000100701c301780000ffff00c90000048b07671226000800001107 +01d804a6016f00074003400c015d3100ffff0071ffe3047f06611226001c0000110701c00094fffb0010b400211d0f072b40 +050f21001d025d31ffff0073ffe3058b076d102701d7055c01751306000900000009b2040415103c3d2f3100ffff0071fe56 +045a0666102601bf68001306001d00000009b204040a103c3d2f3100ffff0073ffe3058b076d122600090000100701da051b +0175ffff0071fe56045a06481226001d0000100701c1008b0000ffff0073ffe3058b0750102701db055c0175130600090000 +00080040033f00015d30ffff0071fe56045a0614102701c6046a00001206001d0000ffff0073fe01058b05f0102701cc055e +ffed120600090000ffff0071fe56045a0634102701ca03e0010c1206001d0000ffff00c90000053b076d102701d705020175 +1306000a00000014b40c020607072b40092f0220061f021006045d31ffffffe500000464076d102701d7031601751306001e +0000002ab414020613072b31004bb00e5158bb0014ffc00013ffc0383859400d901490138014801340144013065d000200c9 +0000068b05d500130017003a401e060212950914110c9515ad0400810e0a070c17041c0538120d14011c001810dcec3232cc +fcec3232cc31002f3ce432fcecdc3232ec3232300133152135331533152311231121112311233533171521350171ca02deca +a8a8cafd22caa8a8ca02de05d5e0e0e0a4fbaf02c7fd390451a4a4e0e000000100780000049f0614001b003e402103090003 +16010e12870d1506871619b810970a010208004e130e11150908100b1c10dc32ec3232ccccf4ec31002f3cecf4c4ecdc32ec +32111217393001112311342623220615112311233533353315211521113e01333216049fb87c7c95acb97d7db90160fea042 +b375c1c602a4fd5c029e9f9ebea4fd8704f6a47a7aa4febc6564ef00ffffffe400000278075e102701d5032e01751306000b +00000008b41e09181f072b31ffffffd3000002670637102701c4ff1d00001306009d00000008b41c08161d072b31ffff0003 +0000025907311027002dff2e013b1306000b00000008b404030205072b31fffffff20000024805f51027002dff1dffff1306 +009d00000008b404030205072b31fffffff500000267076d102701da032e01751306000b00000008b40e00080f072b31ffff +ffe4000002560648102701c1ff1d00001306009d00000008b40e00080f072b31ffff00b0fe75022505d5102701c3ff640000 +1206000b0000ffff0096fe75020b0614102701c3ff4a00001206001f0000ffff00c90000019507501226000b0000110701db +032f01750013b306010700103c103c3100b43f073f06025d3000000200c100000179047b00030004002c400b04b800bf0204 +010800460510fcec3931002fece43040110404340444041006400650066006700608015d1333112313c1b8b85c0460fba004 +7b00ffff00c9fe6603ef05d51027000c025c00001106000b00000008400311040110ec31ffff00c1fe5603b1061410270020 +023800001106001f00000008400319460110ec31ffffff96fe66025f076d102701d7032e01751306000c00000008b4080206 +07072b31ffffffdbfe56025c0666102701bfff1d0000130601a300000008b408020607072b31ffff00c9fe1e056a05d51027 +01cc051b000a1206000d0000ffff00bafe1e049c0614102701cc04ac000a120600210000000100ba0000049c0460000a00bb +4028081105060507110606050311040504021105050442080502030300bc09060501040608010800460b10fcec32d4c41139 +31002f3cec321739304b5358071004ed071005ed071005ed071004ed5922b2100c01015d405f04020a081602270229052b08 +56026602670873027705820289058e08930296059708a3021209050906020b030a072803270428052b062b07400c6803600c +8903850489058d068f079a039707aa03a705b607c507d607f703f003f704f0041a5d71005d1333110133090123011123bab9 +0225ebfdae026bf0fdc7b90460fe1b01e5fdf2fdae0221fddf00ffff00c90000046a076c102701d4036e01761206000e0000 +ffff00c10000024a076c102701d4035a0176130600220000001eb10304103c31004bb00e5158b900000040385940079f008f +004f00035d30ffff00c9fe1e046a05d5102701cc049b000a1206000e0000ffff0088fe1e01ad0614102701cc031e000a1306 +00220000000740034000015d3100ffff00c90000046a05d5102701d2029fffc31206000e0000ffff00c10000030006141027 +01d202390002110600220000000940058f001f00025d3100ffff00c90000046a05d51027002f023100771206000e0000ffff +00c10000028406141027002f00d6007311060022000000174bb00d514bb011534bb018515a5b58b900000040385931000001 +fff20000047505d5000d003f401e0c0b0a040302060006950081080304010b0e000405011c0c073a0900790e10f43cecc4fc +3cc411123911123931002fe4ec11173930b4300f500f02015d1333112517011121152111072737d3cb013950fe7702d7fc5e +944de105d5fd98db6ffeeefde3aa023b6a6e9e0000010002000002480614000b005e401a0a09080403020600970603040109 +0a00047a0501080a7a07000c10d43ce4fc3ce411123911123931002fec173930014bb0105458bd000c00400001000c000cff +c038113738594013100d400d500d600d73047a0a700de00df00d095d133311371707112311072737c7b87d4cc9b87b4ac506 +14fda65a6a8dfce3029a586a8d00ffff00c900000533076c102701d404c501761306000f0000000740034f00015d3100ffff +00ba00000464066d1026002e4207130600230000000940053f004f00025d3100ffff00c9fe1e053305d5102701cc0500000a +1206000f0000ffff00bafe1e0464047b102701cc0490000a120600230000ffff00c900000533075f1226000f0000110701d8 +04f501670014b4040f0b00072b40092f0f200b1f0f100b045d31ffff00ba000004640666122600230000110701c0008d0000 +0010b40019150c072b40050f190015025d31ffff00cd000005b905d51027002301550000100601be1b00000100c9fe560519 +05f0001c003b400d191612181c1c120a051c07411d10fc4bb0105458b90007ffc03859ec32d4fccc113100400c199516b007 +02950e910881072fe4f4ec10f4ec30011021220615112311331536373633321219011407062b0135333236350450fecdb3d7 +caca4e696a99e3e95152b55731664f037f01acffdefcb205d5f1864343fec1feccfc6fd561609c5aa000000100bafe560464 +047b001f003b401c0d13000318150787061087181cb816bc15070d08004e13170816462010fcec32f4ecc431002fe4f4c4ec +d4ec1112173930b46021cf2102015d01111407062b0135333237363511342623220615112311331536373633321716046452 +51b5fee96926267c7c95acb9b942595a75c1636302a4fd48d660609c30319902b29f9ebea4fd870460ae653232777800ffff +0073ffe305d907311027002d0127013b1306001000000010b40d020307072b40051f021003025d31ffff0071ffe3047505f5 +1026002d73ff1306002400000008b413020319072b31ffff0073ffe305d9076d102701da052701751306001000000010b411 +000817072b400510001f08025d31ffff0071ffe304750648102601c173001306002400000008b41d080023072b31ffff0073 +ffe305d9076b102701dc05270175120600100000ffff0071ffe304750666102701c500a00000120600240000000200730000 +080c05d500100019003b401f059503110195008118079503ad091812100a1506021c1100040815190d101a10fcecd4c4c4d4 +ec32123939393931002fecec32f4ec3210ee30011521112115211121152120001110002117232000111000213307fafd1a02 +c7fd3902f8fbd7fe4ffe4101bf01b16781febffec0014001418105d5aafe46aafde3aa017c0170016d017caafee1fee0fedf +fedf00030071ffe307c3047b0006002700330084403107080010860f880c00a9082e0cb916132803b908bb22251fb819138c +340600162231090f0008074b311209512b121c453410fcecf4fcf4ecc4111239391239310010e432f43cc4e4ec3210c4ee32 +10ee10f4ee1112393040253f355f3570359f35cf35d035f035073f003f063f073f083f09056f006f066f076f086f09055d71 +015d012e01232206070515211e0133323637150e01232226270e01232200111000333216173e013332002522061514163332 +36353426070a02a48999b90e0348fcb20cccb76ac86264d06aa0f25147d18cf1feef0111f18cd3424ee88fe20108fab094ac +ab9593acac029498b3ae9e355abec73434ae2a2c6e6d6e6d01390113011401386f6c6b70fedd87e7c9c9e7e8c8c7e900ffff +00c900000554076c102701d404950176120600110000ffff00ba00000394066d1026002e4207120600250000ffff00c9fe1e +055405d5102701cc0510000a120600110000ffff0082fe1e034a047b102701cc0318000a120600250000ffff00c900000554 +075f122600110000110701d8047d016700080040035f1d015d30ffff00ba0000035a0666122600250000110601c01b000010 +b411171309072b40050f170013025d31ffff0087ffe304a2076c102701d404950176120600120000ffff006fffe303c7066d +1026002e4207120600260000ffff0087ffe304a2076d102701d704930175130600120000000bb404201529291049633a3100 +ffff006fffe303c70666102601bf2500130600260000000bb404201529291049633a3100ffff0087fe7504a205f012260012 +000010070030008b0000ffff006ffe7503c7047b122600260000100600301700ffff0087ffe304a2076d1226001200001107 +01d8048b0175000bb42b200e22221049633a3100ffff006fffe303c70666122600260000110701c704270000000bb42b200e +22221049633a3100fffffffafe7504e905d5102600305000120600130000ffff0037fe7502f2059e10260030e10012060027 +0000fffffffa000004e9075f122600130000110701d8047301670010b4010d0900072b310040035f08015d30ffff00370000 +02fe0682122600270000110701d202370070000740038f14015d31000001fffa000004e905d5000f00464018070b95040c09 +030f9500810905014007031c0c00400a0e1010d43ce4ccfc3ce4cc31002ff4ec3210d43cec323001401300111f0010011002 +1f0f1011401170119f11095d032115211121152111231121352111210604effdee0109fef7cbfef70109fdee05d5aafdc0aa +fdbf0241aa02400000010037000002f2059e001d0043401f0816a90517041aa900011bbc0d8710100d0e020608040008171b +15191d461e10fc3c3cc432fc3c3cc4c432393931002fecf43cc4fc3cdc3cec3230b2af1f01015d0111211521152115211514 +17163b0115232227263d0123353335233533110177017bfe85017bfe85252673bdbdd5515187878787059efec28fe98ee989 +27279a504fd2e98ee98f013effff00b2ffe30529075e102701d504ee01751306001400000010b41f091827072b400510091f +18025d31ffff00aeffe304580637102701c4008300001306002800000008b41e081626072b31ffff00b2ffe3052907311027 +002d00ee013b1306001400000014b40503020d072b40092f0220031f021003045d31ffff00aeffe3045805f51027002d0083 +ffff1306002800000008b40603020e072b31ffff00b2ffe30529076d102701da04ee01751306001400000010b40f00081707 +2b400510001f08025d31ffff00aeffe304580648102701c1008300001306002800000008b410000818072b31ffff00b2ffe3 +0529076f122600140000100701c200f00069ffff00aeffe3045806ca122600280000110601c27cc40009400540154021025d +3100ffff00b2ffe30529076b102701dc04ee0175120600140000ffff00aeffe3045e0666102701c500b00000120600280000 +ffff00b2fe75052905d5122600140000100701c300fa0000ffff00aefe7504e8047b122600280000100701c302270000ffff +0044000007a60774102701d705f5017c1306001500000008b415020614072b31ffff005600000635066d102701bf01450007 +1306002900000008b415020614072b31fffffffc000004e70774102701d70472017c1306001600000008b40b020607072b31 +ffff003dfe56047f066d102601bf5e071306002a00000008b418020617072b31fffffffc000004e7074e1226001600001107 +01d3047301750008b400100b04072b31ffff005c0000051f076c102701d404950176120600170000ffff0058000003db066d +1026002e42071206002b0000ffff005c0000051f0750102701db04be0175120600170000ffff0058000003db0614102701c6 +041700001306002b0000000e0140094f0a5f0aaf0adf0a045d31ffff005c0000051f076d122600170000100701d804be0175 +ffff0058000003db06661226002b0000110601c01b000010b4010f0b00072b40050f0f000b025d310001002f000002f80614 +0010002340120b870a970102a905bc010a10080406024c1110fc3cccfccc31002ff4ec10f4ec302123112335333534363b01 +1523220706150198b9b0b0aebdaeb063272603d18f4ebbab9928296700020020ffe304a40614000f002c0044402504b91014 +0cb9201c8c14b8222925a92c242797222e45001218472a20062c2808252327462d10fc3cccec323232ccf4ecec31002ff4dc +3cec3210e4f4c4ec10c6ee300134272623220706151417163332373601363736333217161110070623222726271523112335 +3335331521152103e5535492925453535492925453fd8e3a59587bcc7f80807fcc7b58593ab99a9ab90145febb022fcb7473 +7374cbcb747373740252643031a2a2fef8fef8a2a2313064a805047d93937d000003ff970000055005d50008001100290043 +40231900950a0995128101950aad1f110b080213191f05000e1c1605191c2e09001c12042a10fcec32fcecd4ec1117393939 +31002fececf4ec10ee3930b20f2201015d01112132363534262301112132363534262325213216151406071e011514042321 +1122061d012335343601f70144a39d9da3febc012b94919194fe0b0204e7fa807c95a5fef0fbfde884769cc002c9fddd878b +8c850266fe3e6f727170a6c0b189a21420cb98c8da05305f693146b5a300ffff00c9000004ec05d5120601d00000000200ba +ffe304a40614001600260038401f1bb9000423b9100c8c04b81216a913971228451417120847101f160813462710fcec3232 +f4ecc4ec31002ff4ec10e4f4c4ec10c6ee300136373633321716111007062322272627152311211525013427262322070615 +1417163332373601733a59587bcc7f80807fcc7b58593ab9034efd6b027253549292545353549292545303b6643031a2a2fe +f8fef8a2a2313064a80614a601fcc0cb74737374cbcb7473737400020000000004ec05d5000a00170033400c170b19001910 +2e050b1c15162fdcec32fcecc410cc31400905950cad0b81069514002fece4f4ecb315150b141112392f3001342726232111 +213237360111213204151404232111230104174f4ea3febc0144a34e4ffd7c014efb0110fef0fbfde8c9013801b78b4443fd +dd444304a8fd9adadeddda044401910000020000ffe304a406150012001e003e400d111220131206470d1912080f102fdcec +3232f4ecc410cc31400e0016b903b80e0c1cb9098c11970e002fe4f4ecc410f4ecc4b30f0f110e1112392f30013e01333200 +1110022322262715231123013301342623220615141633323601733ab17bcc00ffffcc7bb13ab9ba0122510272a79292a7a7 +9292a703b66461febcfef8fef8febc6164a8044401d1fc1acbe7e7cbcbe7e70000010073ffe3052705f000190030401b1986 +0088169503911a0d860c881095098c1a1b10131906300d001a10dc3cf4ecec310010f4ecf4ec10f4ecf4ec30133e01332000 +11100021222627351e01332000111000212206077368ed8601530186fe7afead84ed6a66e78201000110fef0ff0082e76605 +624747fe61fe98fe99fe614848d35f5e01390127012801395e5f00010073ffe3065a0764002400444022219520250da10eae +0a951101a100ae04951791118c25200719141b110d003014102510fcfc32ec10ecc4310010e4f4ecf4ec10eef6ee10dcec30 +b40f261f2602015d01152e0123200011100021323637150e0123200011100021321716173637363b0115232206052766e782 +ff00fef00110010082e7666aed84feadfe7a01860153609c0d0c105366e34d3f866e0562d55f5efec7fed8fed9fec75e5fd3 +4848019f01670168019f240304c3627aaa9600010071ffe304cc06140022004e402400860188040e860d880ab91104b917b8 +118c2301871e972307121419081e0d004814452310fcf432ccec10ec310010f4ec10e4f4ec10fef4ee10f5ee30400b0f2410 +2480249024a02405015d01152e0123220615141633323637150e012322001110002132173534363b011523220603e74e9d50 +b3c6c6b3509d4e4da55dfdfed6012d01064746a1b54530694c047ef52b2be3cdcde32b2baa2424013e010e0112013a0c0fd6 +c09c6100ffff000a000005ba05d51006003c00000002ff970000061405d50008001a002e4015009509810195100802100a00 +05190d32001c09041b10fcecf4ec113939393931002fecf4ec30b2601301015d011133200011100021252120001110002901 +1122061d012335343601f7f40135011ffee1fecbfe42019f01b20196fe68fe50fe6184769cc0052ffb770118012e012c0117 +a6fe97fe80fe7efe9605305f693146b5a300000200c9000004ec05d500070014002e400c160804131c0a2e00190e101510fc +ecf4ec32c4c431400c139509810a049512ad03950a002fecf4ec10f4ec300110290111212206112111212224353424332111 +21019e01400144febca39d034efde8fbfef00110fb014efd7c01b7feef0223870393fa2bdadeddda01c000020071ffe3045a +06140012001e003f401d1cb9110e16b905088c0eb80312870197031904110802470013120b451f10fcecc4f4ec323231002f +fcec10e4f4c4ec10c4ee30b660208020a02003015d0135211123350e01232202111000333216171101141633323635342623 +2206010d034db83ab17ccbff00ffcb7cb13afd8da79292a8a89292a7056ea6f9eca864610144010801080144616401b9fcc0 +cbe7e7cbcbe7e70000020071fe560474046300190027005440140d0c0b202945170b12021a12175106201211452810fcecc4 +f4b27f17015decd4ec10ec1112393900400e0d0c1d09060709b9041db914b62810f4ecd4fcd4cc1112393940060025530c0d +0c070e10ec39313025161510212227351633323534252627261110003332000314020336262322061514161716173e01036b +9dfe47dd7866f6f6fef8d0758e0112eff00113019b2701ab9494acbc7e4033636e424f8dfef0469946755c30257087010f01 +0f0139fec7feed9cfefc01a0cbe5e8c3c2c70b060e2adc00000100830000044505d5000b002b40090d05091c000b07020c10 +dcc4c4d4ec32c431400c0a950b8102069507ad039502002fecf4ec10f4ec300111213521112135211121350445fc3e02f8fd +3902c7fd1a05d5fa2baa021daa01baaa00020075ffe305d905f00013001a0044402601140008a107ae040095141795110095 +14ad04950b91118c1b01141a1a190f3314190700101b10fcc4ecf4ec111239310010e4f4ecf4e410ee10ee10f4ee11123930 +13211000212206073536243320001110002120003716003332003775048ffeedfeee8bfc706f010792015e018bfe88fec6fe +b7fe97dc0d00ffcaca00ff0d030c010c0132605fd74648fe67fe92fe9ffe5b01b7ccc3fee4011cc3000100a4ffe3047b05f0 +0028004040240a8609880d9506912900169513ad291f8620881c95238c292a14091f101903191926102910fcecd4ecd4c4c4 +cc310010f4ecf4ec10f4ec3910f4ecf4ec30012e0135342433321617152e012322061514163b011523220615141633323637 +150e0123202435343601d8838e010ce659c97372be5398a39e95b6aea5b9c7be6dc8546ac75efee8fed0a3032521ab7cb2d1 +2020b426247b737077a695848f963231c32525f2dd90c4000001ff96fe66042305d500110041401f1108120d950cb0120695 +040295008104ad12110800070c050107031c00041210fcec32d4c4c411123939310010ecf4ec10ee10f4ec10393930b20f0b +01015d13211521112115211110062b013533323635c9035afd700250fdb0cde34d3f866e05d5aafe48aafd9ffef2f4aa96c2 +0001ff7ffe5602f80614001b00654023130a0f870dbd1d0518011408a906018700971606bc1c021b0700070905081517134c +1c10fc4bb00a5458b90013004038594bb0165458b90013ffc038593cc4fc3cc4c4123939310010e432fcec10ee3212393910 +f4ec39393001b6401d501da01d035d01152322061d012115211114062b013533323635112335333534363302f8b0634d012f +fed1aebdaeb0634db0b0aebd0614995068638ffbebbbab995068042a8f4ebbab00010073ffe3069707640026004940101502 +001c04111c1a34043321190b462710fcecfcf4ec10fcc4c4314018169515270005240195032495081ba11aae1e950e91088c +270010e4f4ecf4ec10fed4ee11393910dcec3025112135211106042320001110002132161734363b01152322061d012e0123 +200011100021323604c3feb6021275fee6a0fea2fe75018b015e5ba344c9e34d3f866e70fc8bfeeefeed011301126ba8d501 +91a6fd7f53550199016d016e01991919bceaaa96c2d75f60fecefed1fed2fece250000020008fe52057605d5000f00250095 +400d27501201120419170c191f242610d4d4ecd4ecd45dc4b510080003040c1112173931400a00951bbd1125122481260010 +e4323232f4ecb31f17081b1112393930400c131111121208232511242408070510ec3c0710ec3cb613110812082408070810 +ecb623110824081208070810ecb410251311230f40101615140317132408222120031f231208040711121739071112173901 +3237363534272627060706151417161301330116171615140706232227263534373637013302bf362c1c1f332c2c331f1c2c +3601d9defdba68432e4b649b9b644b2e4368fdbadefefd2014423949795c5c794939421420037a035efbcfc8ae77428b4157 +57418b4277aec8043100000100ba000007470614002a004f40112c0d120408112a1508264e1f1b081d462b10fcec32f4ecc4 +c4ccd4ec3931004019088709271426008711151b260320111887200923b81e97111c2f3cecf43cc4ec1112173910ec123939 +10ec30253237363534272627351617161114002b012226351134262322061511231133113e013332161511141633054c9554 +574a3e79e06d6ffee0dd46bb9d7c7c95acb9b942b375c1c64c699c62659bde705f21941d8f91feecf5fee6c8ce01089f9ebe +a4fd870614fd9e6564efe8fef2936700000100c9000002c605d5000b002e40100b02000695008107050806011c00040c10fc +4bb0105458b9000000403859ecc4393931002fe4ec113939300113331114163b011523222611c9ca6e863f4de3cd05d5fc2d +c296aaf4010e0001000a0000025205d5000b00454011020b95050800af060305011c0a0800040c10fc3cc44bb0105458bb00 +08004000000040383859ec32c431002fecdc3cf4323001400d300d400d500d600d8f0d9f0d065d1333113315231123112335 +33c9cabfbfcabfbf05d5fd16aafdbf0241aa000100c9000005f705f000170066400e001c0107080f07090b0f1c0e041810fc +ec32d4c4113910d4ec00310040250b110809080a1109090811110708071011080807420b0810030e0c1702059513910eaf0c +092f3cecf4ec393911121739304b5358071004ed071005ed071005ed071004ed592201233534262322070901210111231133 +110136333217161505f7aa49264625fddd031afef6fd33caca026c5571885555044879365023fdf9fce302cffd3105d5fd89 +02434f5c5b6e000100b90000049c0614001200cb400b040d090c0e10090800461310fcec32d4c41139c43100400f42100d0a +030b11069503970bbc110e2f3ce4fce411121739304b5358401410110d0e0d0f110e0e0d0b110c0d0c0a110d0d0c071004ed +071005ed071005ed071004ed59b2101401015d40350b0b0a0f280b270c280d2b0e2b0f4014680b6014890b850c890d8d0e8f +0f9a0b970faa0ba70db60fc50fd60ff70bf00bf70cf00c1a5db4090d090e0271004025040a0a10160a270a290d2b10560a66 +0a6710730a770d820a890d8e10930a960d9710a30a125d1334363b011523220615110133090123011123b9a3b5bfa8694c02 +25ebfdae026bf0fdc7b9047ed6c09c6199fdff01e3fdf4fdac0223fddd000001000a0000022a0614000b0032400705010808 +00460c10fc3cec3231004008020ba905080097062fecd43cec3230400d100d400d500d600d700df00d06015d133311331523 +112311233533c1b8b1b1b8b7b70614fd3890fd4402bc90000001003d0000047f0614000f00a0401308020b05010e070d080c +06090406110c06001010d4c4b28006015dd4c410c4cc11121739b410094009025d3100400f08020b05010e06060004090697 +0d002f3cf4c4c4111217393040320a03a902a90ba90508040c0709040f11000e11010d060100051102110e110f0e01110001 +0d110c070c0b11081107110d060d070510ececec071005ec08ec08ec05ecec070810ec0510ec0708103c3cecec0efc3c3301 +27052725273317251705012309013d01eb47fed42101294bc834013a21fec901edc3fec6fe7e0432bc656363c58a686168fa +d7033cfcc400000100b2ffe3072705d50027004a4012001214201d1c291f50121c14500a1c08042810fcecfcfcfcccfc3c11 +12393100401607140a1c11000621080e18952103248c28121d0881202ff43c3c10f43cc4ec32111217393039250e01232227 +263511331114171633323635113311141716333237363511331123350e012322272603a645c082af5f5fcb2739758fa6cb39 +39777b5353cbcb3fb0797a5655d57c767b7ae2041bfbefba354ebea403ecfbefa24e4d5f60a303ecfa29ae67623e3e000001 +ff96fe66053305d50011008c402907110102010211060706420811000d950cb01207020300af05060107021c04360b0e0c39 +071c00041210fcece43939fcec11393931002fec32393910fcec113939304b5358071004ed071004ed5922b21f0b01015d40 +303602380748024707690266078002070601090615011a06460149065701580665016906790685018a0695019a069f13105d +005d13210111331121011110062b013533323635c901100296c4fef0fd6acde3473f866e05d5fb1f04e1fa2b04e1fb87fef2 +f4aa96c2ffff00bafe560464047b100601cf000000030073ffe305d905f0000b001200190031400b19101906330f13190010 +1a10fcec32f4ec323100400f16950913950fad1a0c950391098c1a10e4f4ec10f4ec10ec3013100021200011100021200001 +220007212602011a0133321213730179013a013b0178fe88fec5fec6fe8702b5caff000c03ac0efefd5608fbdcdcf80802e9 +016201a5fe5bfe9ffe9efe5b01a403c5fee4c3c3011cfd7afefffec2013d0102ffff0067ffe3061d061410260010f4001007 +01cb05a20134ffff0076ffe304d304eb102701cb0458000b10060024050000020073ffe306cf05f00014001f0033401c0495 +10af0015950d91001b95078c0021131c001e1c100418190a102010fcecd43cecdcecc431002ff4ec10f4ec10f4ec30211134 +262311062120001110002132172132161901012200111000333237112606056e7abcfec5fec6fe870179013b70610127e3cd +fc58dcfefd0103dcaf808a03d3c296fb8bd301a40162016201a51bf4fef2fc2d054cfeb8fee6fee5feb86704184600020071 +fe560559047b00160021003a4020058711bc2217b90eb8221db9088c16bd22110105231508011f08051a120b452210fcecd4 +ecdcecc4111239310010e4f4ec10f4ec10f4ec30011134272623110623220011100033321733321716151101220615141633 +3237112604a126266989f0f1feef0111f16452d8b55251fd1a94acab95814054fe560474993130fcbc9d0139011301140138 +1b6060d6fb8c0589e7c9c9e73a02f0360002ff97000004f105d50008001c003a40180195100095098112100a080204000519 +0d3f11001c09041d10fcec32fcec11173931002ff4ecd4ec30400b0f151f153f155f15af1505015d01113332363534262325 +2132041514042b0111231122061d012335343601f7fe8d9a9a8dfe3801c8fb0101fefffbfeca84769cc0052ffdcf92878692 +a6e3dbdde2fda805305f693146b5a300000200b9fe5604a4061400180024004f402423b900171db90e11b8178c01bd25030c +09a90697251a12144706090307200c000802462510fcec3232cc113939f4ec310010f4ec393910e4e4f4c4ec10c4ee304009 +60268026a026e02604015d2511231134363b01152322061d013e013332001110022322260134262322061514163332360173 +baa3b5fee7694c3ab17bcc00ffffcc7bb10238a79292a7a79292a7a8fdae0628d6c09c6199c86461febcfef8fef8febc6101 +ebcbe7e7cbcbe7e7000200c9fef8055405d50015001d005640170506031300091d1810050a1a1904133f0e160a120c041e10 +fcec3232fcc4ec1117391139393931004010001706030417950916950f81040d810b2fecdcf4ecd4ec123939123930014009 +201f401f75047c05025d011e01171323032e012b0111231133113320161514060111333236102623038d417b3ecdd9bf4a8b +78dccacafe0100fc83fd89fe8d9a998e01b416907efe68017f9662fe9105d5fef8d6d88dba024ffdd192010c910000010072 +ffe3048d05f0002100644011071819061d0a0f1d19042d00220a19152210dcece4fcecc41112393939393100401942191807 +06040e21000ea10f940c9511209500940291118c2210e4f4e4ec10eef6ee10ce111739304b5358400a180207060719020606 +0707100eed07100eed591336200410060f010e0114163332371504232027263534363f013637363427262007cce401c60117 +cae27b9a87bcade1f8fefdd6fee79291d7e27aa63c3b595afea1e405a44ce4fe8fc02d181f7cec888bd05f7070d9b6d92b19 +1f3233d940406d0000010064ffe303bc047b002700cf40110a1e1d090d21142108060d0800521a452810fce4ecd4ecc41112 +393939393140191e1d0a09041300862789241486138910b91724b903b8178c280010e4f4ec10fef5ee10f5ee121739304012 +1b1c021a1d53090a201f02211e530a0a09424b535807100eed111739070eed1117395922b2000101015d40112f293f295f29 +7f2980299029a029f029085d4025200020272426281e281d2a152f142f132a12280a2809290829072401861e861d861c861b +12005d40171c1e1c1d1c1c2e1f2c1e2c1d2c1c3b1f3b1e3b1d3b1c0b71133e013332161514060f010e011514163332363715 +0e012322263534363f013e0135342623220607a04cb466cee098ab40ab658c8261c6666cc35ad8f7a5c43f946289895aa84e +043f1e1eac9e8295240f25504b51593535be2323b69c89992a0e2149405454282800ffff00c90000048b05d5100601ce0000 +0002fef2fe5602d706140016001f0036400c1d0e0a1506140108170a4f2010fc32fc32cccc10d4cc3100400f141f87000b1b +87109720048706bd2010fcec10f4ecd43cec3230011114163b01152322263511232035342132171617331525262726232207 +063301774d63b0aebdaebefef2012fb5523512bffe860811216e7c030377046afb3d685099abbb04aed2d860406f9b9a2c18 +3041330000010037fe5602f2059e001d003f400e0e14080802090400081a1c18461e10fc3cc4fc3cdc3239fccc3100401218 +05081903a9001b01bc08871510870ebd152ffcec10ecf43cccec321139393001112115211114163b011514062b0135333237 +363d0122263511233533110177017bfe854b73bda4b446306a2626d5a78787059efec28ffda0894eaed6c09c303199149fd2 +02608f013e0000010018000004e905d5000f005840150d0a0c06029500810400070140031c050b1c0d051010d4d4ec10fce4 +393931002ff4ec32c4393930014bb00a5458bd00100040000100100010ffc03811373859401300111f00100110021f071011 +401170119f11095d012115211123112322061d012335343601ae033bfdeecb5e84769cc005d5aafad5052b5a693146b5a300 +00010037000002f20614001b0049401019160b080417090204000810130e461c10fc3cc4fc3cc43232173931004013130019 +8716970a0e05080f03a91101bc08870a2fecf43cec3211393910f4ec393930b2af1501015d01152115211114163b01152322 +2635112335333534363b01152322060177017bfe854b73bdbdd5a28787aebdaeb0634d04c3638ffda0894e9a9fd202608f4e +bbab99510001fffafe6604e905d5000f0054401407950abd100e0295008110080140031c00400d1010d4e4fce4c4310010f4 +ec3210f4ec30014bb00a5458bd00100040000100100010ffc03811373859401300111f00100110021f0f1011401170119f11 +095d032115211114163b01152322261901210604effdee6e863f4ee3cdfdee05d5aafb3dc296aaf4010e04c3ffff00adfff7 +065f061410260014fb14100701cb05e40134ffff00b0ffe3056904eb102701cb04ee000b1006002802000001004effe305cf +05ca001f003a40101d1a1921100004330a1114190d0a102010fcc4fcc410f4c4ecfcc43100400e0d11011d951e1081201795 +078c2010f4ec10fc3cec32323230012116121510002120001134123721352115060215140033320035340227352105cffec0 +a18efe7ffed1fecffe81919efec10258b2c70109d8d80108c6b1025805188dfed8c2fecbfe77018a013eb8012a8bb2b261fe +b4caeffedd0122f0ca014c61b200000100c9ffe1057605d5001b002d400d10150c070803190c181c15041c10fcecd4ec2f3c +111239310040090816811c0095108c1c10f4ec10ecc430253200353427262735171612151007062127262726190133111416 +3302c6d8010863416eb3a18ec0bffecf4de86167ca6e868d0122f0caa66d5744018dfed8c2fecbc5c40206747a010e03f0fc +10c296000001fffc000005f005f000170064400f131c140c040b070040051c0940071810d4e4fce41239c4392fec3100400b +12151400950e910b09af062fec39f4eccc39393040190c110405040b110a0b0505040b110c0b0809080a11090908424b5358 +071005ed071008ed071008ed071005ed59220122070607011123110133090136333217161d012335342604d739152511fe84 +cbfdf0d9019e014e5aa3885555aa4905470e1819fdbffd3902c7030efd9a01f9885c5b6e837936500001003dfe5605d8047b +001f016a4017120e151b1f1808151f0e0d0c0a09060300081f041f0b2010d44bb00a544bb008545b58b9000b004038594bb0 +145458b9000bffc03859c4c411173910d4ec11391112393100403a0708020911001f0a110b0a00001f0e111d001f0d110c0d +00001f0d110e0d0a0b0a0c110b0b0a420d0b0920000b058703bd201bb912b80bbc172010c4e4f4ec10f4ec11391139123930 +4b5358071005ed071008ed071008ed071005ed071008ed0705ed1732592201408d0a000a09060b050c170115021004100517 +0a140b140c2700240124022004200529082809250a240b240c270d37003501350230043005380a360b360c380d4100400140 +024003400440054006400740084209450a470d5400510151025503500450055606550756085709570a550b550c6601660268 +0a7b0889008a09850b850c890d9909950b950ca40ba40c465d004025060005080609030d160a170d100d230d350d490a4f0a +4e0d5a095a0a6a0a870d800d930d125d050e012b01353332363f01013309013637363332161d012335342623220706070293 +4e947c936c4c543321fe3bc3015e011a1530588783b9b251393929140a68c87a9a488654044efc9402c0343360bf8672723a +542a14190001005c0000051f05d5001100c0403506030207020c0f100b1007110b100b101102070242050d95040e12109500 +810795090c06030f040e04080e00100700014208000a1210dc4bb009544bb00a545b58b9000affc03859c4d4e411393910c4 +10c411173931002fecf4ec10d43cec32304b5358071005ed071005ed0710053c3c0710053c3c592201404005020a0b180b29 +02260b380b4802470b48100905070b10001316071a1010132f13350739103f1347074a104f1355075911660769106f137707 +78107f139f13165d005d132115012115210121152135012135210121730495fe700119fe73fe5403c7fb3d01b9fed5019f01 +83fc6705d59afe1190fdeeaa9a02229001df00010058000003db0460001100c540310c0f100b100603020702101102070207 +110b100b4210a900bc09050da9040e07a90910070f03060c0601000e0408010a1210dc4bb00b544bb00c545b58b9000affc0 +38594bb0135458b9000a00403859c432c4c4c411173931002fecd43cec3210f4ec304b5358071005ed071005ed0710053c3c +0710053c3c59220140420502160226024702490b050b100f1318071b102b1020133607391030134001400245074008400943 +10570759105f136001600266076008600962107f138013af131b5d005d13211503331521012115213501233521012171036a +fbc2fec2fec302b4fc7d012bd40150010dfd650460a8fedc90fe8f93a8015c900139000100a0ffc104f805d500220070400e +0b0e0d080a04190e10160a0d1e2310dcc4c4d439c4ec1239b43f0e4f0e025d111239310040130a0995100f0b950d81231fa1 +1eae00951a8c2310f4ecf4ec10f4ec39d4ec3930400a10110a0b0a0b110f100f071005ec071005ec400e090a370f0205100b +0b15103b0b04015d005d25323736353427262b013501213521150132171617161514070621222726273516171602a8c06364 +5c5da5ae0181fcfc0400fe656a806256519898fee8777d7e866a7f7e6b4b4b8f86494a9801eaaa9afe16382a6d688adc7a79 +131225c3311919000001005cffc104b405d50022005e400f1816151b1f130d1916051f19150d2310dcc4b430154015025dec +d4c4c41139113911123931004013191b951314189516812304a105ae0095098c2310f4ecf4ec10f4ec39d4ec3930400a1311 +1918191811141314071005ec071005ec25323736371506070623202726353437363736330135211521011523220706151417 +1602ac897e7f6a867e7d77fee89898515662806afe650400fcfc0181aea55d5c64636b191931c3251213797adc8a686d2a38 +01ea9aaafe16984a49868f4b4b0000010068fe4c043f0460002000a3400b0006020c121b130306022110dcccc4c4d4ec1112 +393100401a0c1b0018064200a90707032104a9031386149310b918bd03bc2110e4fcecf4ec10ec1112392fecec1112393930 +40080611000511010702070510ec0410ec401b03050500140516002305250037003405460043055b0054057e000d015d401b +040604011406140125062401350137064501460654015c067f060d005d4009061507161a151a12045d090135211521011523 +220706151417163332363715060706232024353437363736025bfe65036afd6501aeaea55d5c6463be6dc8546a64635efee8 +fed05156628001dc01dca893fe0da64a4b848f4b4b3231c3251312f2dd8a686d2a3800010071fe5603e80460002000000132 +37363715060706232011342524353423302101213521150120151005061514027f544d4f5157505661fe200196011cebfede +01e5fd65036afe9e016ffe30e2feee15152cb3200d0e0119ee3525627c023893a8fe64e5feec3118618b000100960000044a +05f00024000025211521350137213521363736353427262322070607353e01333204151407060733152307018902c1fc4c01 +3a73fea701e25f25275354865f696a787ad458e80114221f4a68ec30aaaaaa014075906d484c49774b4b212143cc3132e8c2 +5c52496090310001005dffc104f905d500190035400e1b0308110a0b080700081907461a10fcd4ec10ecd4d4eccc3100400d +169501001a06950d0b9509811a10f4ecd4ec10ccd4ec300110201134262321112115211125241716100f0106070620243501 +26030ab9a5fdf703a1fd2901730100a2513b1c142d98fdc4fed00190fedb01258693032caafe250101d068fee056291d2479 +f2dd00010068fe4c043f0460001a0033400b1c0408120a0c081a08461b10fcc4ecd4d4eccc3100400f0287001a18bd1b0787 +0e0c870abc1b10f4ecd4ec10fccc32ec30171633201134262321112115211133321e01100f0106070621222768aace0196b9 +a5fe9f0319fd9fdd69e4a63b1c142d98fee8bbd4a76301258693032caafe2663d4fee056291d24794a0000010058ffe303a5 +059e0024000001071617161514070621222726273516171633323736373427262b01132335331133113315022102aa706c6e +89feed5551514c49544e50b36339013a56c03e02e5e5cae703e67d1e7773aaba7d9d121123ac281816724185624c72010fa4 +0114feeca400000200bafe5604a4047b000e00170040400b1911080d0417000802461810fcec3232d4eccc3100400c421587 +05098c03bc0001bd1810ecc4f4f4ccec304b5358b617050f8700000e070410ed0010cc590511231133153637363332171615 +100100353427262322070173b9b9348751d2b84d4efccf0272393878dcad7afed0060aaa425231707199fe57fee40190f985 +4241ef00000100c9fe56019305d500030026400a009702bd04010800460410fcec310010ecec30400d100540055005600570 +05f00506015d13331123c9caca05d5f88100ffff00c9fe56032705d51027012c019400001006012c000000010014fe56039c +05d50013003a401d0c09a90f061302a91005050a00970abd14070309050108120d0c10001410d43c3ccc32fc3c3ccc323100 +10ecec11392f3cec32dc3cec323001331121152115211521112311213521352135210173ca015ffea1015ffea1cafea1015f +fea1015f05d5fd97a8f0aafd2c02d4aaf0a8ffff00c90000019405d5100600049400ffff00c900000ad0076d102700e905b1 +0000100600070000ffff00c9000009b00666102700ea05d50000100600070000ffff0071ffe308910666102700ea04b60000 +1006001b0000ffff00c9fe66062405d51027000c049100001006000e0000ffff00c9fe5605de061410270020046500001006 +000e0000ffff00c1fe5602ef06141027002001760000100600220000ffff00c9fe6606f205d51027000c055f00001006000f +0000ffff00c9fe5606b7061410270020053e00001006000f0000ffff00bafe5605de06141027002004650000100600230000 +ffff001000000568076d122600050000110701d804be01750006b10e00103c31ffff007bffe3042d06661226001900001106 +01c05a000008b40b2b2714072b31fffffffe00000260076d1226000b0000110701d8032f0175000bb407200100001049633a +3100ffffffe00000025e06661226009d0000110701c0ff1f0000000bb408200100001049633a3100ffff0073ffe305d9076d +122600100000100701d805270175ffff0071ffe304750666122600240000110601c076000006b11b0c103c31ffff00b2ffe3 +0529076d122600140000110701d804f601750006b11505103c31ffff00aeffe304580666122600280000110601c07600000b +b418200b01011049633a3100ffff00b2ffe305290833102601df3000120600140000ffff00aeffe3045807311027002d007b +013b120600680000ffff00b2ffe30529085a122600140000100601e13600ffff00aeffe304580722122600280000100701e1 +ffbefec8ffff00b2ffe30529085a122600140000100601e43000ffff00aeffe304580722122600280000100701e4ffc4fec8 +ffff00b2ffe305290860122600140000100601e23006ffff00aeffe304580722122600280000100701e2ffbefec8ffff0071 +ffe3047f047b120601bc0000ffff0010000005680833122600050000100601df0000ffff007bffe3042d0731122600500000 +1007002d0052013bffff0010000005680833122600050000100601e00000ffff007bffe3042d06f4122600190000100701e0 +ff93fec1ffff00080000074807341027002d02d7013e120600320000ffff007bffe3076f05f21027002d01e8fffc12060052 +000000010073ffe3060405f00025005440102124221e1c11340200043318190b102610fcecfc3ccce4fcc4c431004018041f +012200051b2395251b950812a111ae15950e91088c2610e4f4ecf4ec10fed4ee113939dcb00b4b5458b1224038593ccc3230 +011133152315060423200011100021320417152e012320001110002132363735233533352135058b797975fee6a0fea2fe75 +018b015e9201076f70fc8bfeeefeed011301126ba843fdfdfeb6030cfed658ff53550199016d016e01994846d75f60fecefe +d1fed2fece2527b55884a60000020071fe5604fa047b000b00340058400e0f22322500080c470612182c453510fcc4ecf4ec +3232c4c43100401b20110e23250c29091886191cb91503b9322fb833bc09b915bd26292fc4e4ece4f4c4ec10fed5ee111239 +39d43ccc3230b660368036a03603015d01342623220615141633323617140733152306070621222627351e01333237363721 +3521363d010e0123220211101233321617353303a2a59594a5a59495a5b813b3c61f3a7ffefa61ac51519e52b55a1511fd84 +029a1639b27ccefcfcce7cb239b8023dc8dcdcc8c7dcdceb6e58465d408c1d1eb32c2a5f171c45475e5b6362013a01030104 +013a6263aa00ffff0073ffe3058b076d122600090000110701d8054a01750010b1210e103c4007942154212421035d31ffff +0071fe56045a0663102601c04afd1206001d0000ffff00c90000056a076d102701d804a201751206000d0000ffffffe90000 +049c076d122600210000110701d8031a0175002ab401100c00072b31004bb00e5158bb0001ffc00000ffc0383859400d9001 +90008001800040014000065dffff0073fe7505d905f0102701c301340000120600100000ffff0071fe750475047b102701c3 +00800000120600240000ffff0073fe7505d907311027002d0127013b120601560000ffff0071fe75047505f51026002d73ff +120601570000ffff00a0ffc104f8076d102701d804be0175120601230000ffff0058fe4c042f0666102601c01b00100601bd +0000ffffffdbfe5602640666102701c0ff250000110601a30000000bb403200807071049633a3100ffff00c900000ad005d5 +1027001705b10000100600070000ffff00c9000009b005d51027002b05d50000100600070000ffff0071ffe3089106141027 +002b04b600001006001b0000ffff0073ffe3058b076c102701d4051b0176120600090000ffff0071fe56045a06631226001d +00001006002e1bfd000100c9ffe3082d05d5001d0035400e0e1c1119031c06381b011c00041e10fcec32fcec32d4ec310040 +0e0f1a9502ad0400811c0a95158c1c2fe4ec10e432fcecc43013331121113311141716173237363511331114070621202726 +3511211123c9ca02deca3e3d9994423eca6460fee6feed6764fd22ca05d5fd9c0264fbec9f504e014f4ba4029ffd5adf8078 +7876e9010dfd3900000200c9fe56050205f0000e00170040400b19111c0d0417001c02041810fcec3232d4eccc3100400c42 +159505098c03810001bd1810ecc4f4f4ccec304b5358b617050f8700000e070410ed0010cc59251123113315363736333217 +1615100100113427262322030193caca389157e2c65354fc9102a13d3c81edba9cfdba077fb9485735787aa4fe37fece01ae +010c8f4746feff00ffff00c900000533076b102701d6051e01751206000f0000ffff00ba0000046406641226002300001007 +00180118fffeffff0010000005680773122600310000100701d4065c017dffff007bffe304dc0773122600510000100701d4 +05ec017dffff000800000748076c102701d4065c0176120600320000ffff007bffe3076f06631226005200001007002e0165 +fffdffff0066ffba05e5076c102701d404fe0176120600440000ffff0048ffa2049c06631226006400001006002e1cfdffff +0010000005680770122600050000100701dd04e5017affff007bffe3042d0664102701c80498fffe120600190000ffff0010 +000005680736122600050000100701d904bc013effff007bffe3042d0648102701c904650000120600190000ffff00c90000 +048b0770122600080000100701dd04a5017affff0071ffe3047f0663102701c804bafffd1206001c0000ffff00c90000048b +0736122600080000100701d904a6013effff0071ffe3047f0648102701c904a900001206001c0000ffffffa7000002730770 +1226000b0000100701dd0359017affffffc3000002810663102701c80366fffd1206009d0000ffff00050000027707361226 +000b0000100701d9033e013effffffe3000002550648102701c9032400001206009d0000ffff0073ffe305d9077012260010 +0000100701dd0541017affff0071ffe304750664102701c8049ffffe120600240000ffff0073ffe305d90736122600100000 +100701d9051c013effff0071ffe304750648102701c904980000120600240000ffff00c70000055407701226001100001007 +01dd0479017affff00820000034a0663102701c80425fffd120600250000ffff00c9000005540736122600110000100701d9 +0480013effff00ba0000035e0648102701c9042d0000120600250000ffff00b2ffe305290770122600140000100701dd0515 +017affff00aeffe304580664102701c804d4fffe120600280000ffff00b2ffe305290736122600140000100701d904ec013e +ffff00aeffe304580648102701c904ab0000120600280000ffff0087fe1404a205f0102701cc04760000120600120000ffff +006ffe1403c7047b102701cc042c0000120600260000fffffffafe1404e905d5102701cc04530000120600130000ffff0037 +fe1402f2059e102701cc040000001206002700000001009cfe52047305f0002e0000010411140e010c01073536243e013534 +2623220f0135373e0335342e03232207353633321e0115140e02033f01346fb9ff00feea99c80131b95c7d705f73a3f83c66 +683d23374b4826b8f3efce83cb7c173a6e02a243fedb70cea0886022a0378c999d4f65843348ab6a1a41638b52375633220c +b8bea456b6803c66717400010047fe4f03bc047b00340000011e0315140e0507353e0435342623220f0135373e0435342e03 +23220607352433321e0115140602a746703e21426c989db3954aa2f59e6328765d3b3fd8df2241573f2d1f3143412345a893 +010a8670b8746701cd08445a58254b8a6c61463d270f822e605b625b33587019568b550d203c4566392c462a1b0a3b5a9a85 +4792616e9900ffff00c90000053b076d102701d8050401751206000a0000fffffff000000464076d102701d8032101751306 +001e0000002ab414050113072b31004bb00e5158bb0014ffc00013ffc0383859400d901490138014801340144013065d0001 +00c9fe56051905f00013002e401203950e91098112b008131c120b061c08411410fc4bb0105458b90008ffc03859ec32d4fc +31002fece4f4ec300134262322061511231133153e0117321219012304509a99b3d7caca51cc9de3e9c9037fd7d5ffdefcb2 +05d5f1878601fec1feccfad900030071ff700644061400070028003400002516333235342722073633321510212227060723 +36372635060706232227261037363332171617113300101716203736102726200704b61125a03434ca6e88f4feaa49352218 +c41d43303a58597ccb807f7f80cb7c59583ab8fcd55354012454545454fedc548205af2d0120b8cefebf0f483a45933c2464 +3031a2a20210a2a2313064025efce6fe6a74737374019674737300020071ffe3052505f0000c003b0057401c240014330418 +103d450a1c28421d181c21383b101c3742041c2f453c10fcecf4ecccb2203b015df4ecccf4ecec1112173931004012243300 +9514ad3c0d3b1c1d913c07082c8c3c10f4ec10f4ccd4cc10f4ec39393001220706101716203736353426030e011514171633 +32373635342726273532171615140607161716151407062027263534373637262726353437362102cbb86a6b6b6a01706b6b +d4f482aa5f3bcca85f604c6d82e4968baa98ac5f609c9bfdba9b9c6061abab43558274010102c54d4dfef24d4d4d4e86879a +0227037c4f45482d4141889e2b4d08646861ba80b2202263638fd974747474d98f6363221f46595882534a0000020071ffe3 +0471050f000d00340043401636450a0818420e3432081028292b08264204081f453510fcecf4eccc32d4eccc32f4ecec3100 +400e3429142200b92ead3507b91c8c3510f4ec10f4ec3939cc32300122070610171620373635342726131615140706071617 +16151407062027263534363726272635343733061417163332373635342702719053525253012053535352fe3a3448829252 +518584fe128485a492903b343fa12b49488382494a2c02c54d4dfef24d4d4d4e86874d4d024a4062994059202263638fd974 +747474d98fc62223564b8e594941e84141414174773e0001005cfe56051f05d50015009f400c0f141112420b081506110d16 +10dc4bb009544bb00a545b58b9000dffc03859c4c4d4ece41139393100400c420795050c0f95118114950c2fecf4ec10dcec +304b5358400a14110e0f0e0f11131413071005ed071005ed5901404005130a0e180e2913260e380e4813470e480f0905140b +0f001716141a0f10172f173514390f3f1747144a0f4f175514590f6614690f6f177714780f7f179f17165d005d051007062b +0135333237363d01213501213521150121051f9e4872fee9692626fbf503b0fc670495fc5003c714fedf50259c303199149a +0491aa9afb6f00010058fe5603db0460001500ac400c0b08150d0f14121112060d1610dc4bb00b544bb00c545b58b9000dff +c038594bb0135458b9000d00403859c4c4b440126012025dc411393910d4b440156015025dec3100400c4207a9050c0fa911 +bc14a90c2fecf4ec10dcec304b5358400a0f1113141314110e0f0e071005ed071005ed590140320513161326134713490e05 +0b0f0f1718141b0f2b0f20173614390f30174514490f5714590f5f176614680f7f178017af17135d005d051007062b013533 +3237363d0121350121352115012103db9e4872fee9692626fd3502b4fd65036afd4c02b414fedf50259c30319914a8032593 +a8fcdb00ffff0010000005680750102701db04bc0175120600050000ffff007bffe3042d0614102701c6044a000012060019 +0000ffff00c9fe75048b05d51226000800001007003000a20000ffff0071fe75047f047b1226001c0000100600307b00ffff +0073ffe305d90833122600100000100601df6200ffff0071ffe3047507311226006200001007002d0073013bffff0073ffe3 +05d90833122600100000100601e36900ffff0071ffe3047506e9122600240000100701e3ffb5feb6ffff0073ffe305d90750 +102701db05270175120600100000ffff0071ffe304750614102701c604730000120600240000ffff0073ffe305d908331226 +00100000100601e06a00ffff0071ffe3047507311226019b00001007002d0073013bfffffffc000004e707311027002d0072 +013b120600160000ffff003dfe56047f05f51026002d5eff1206002a00000002008aff70035c060e00070019000025163332 +3534272207363332151021222706072336372637113301ce1125a03434ca6e88f4feaa49352218c41d433101b88205af2d01 +20b8cefebf0f483a45933c5a0530000200baff70064e047b0007002b00002516333235342722073633321510212227060723 +36372637113426232206151123113315363736333217161504c01125a03434ca6e88f4feaa49352218c41d4331017c7c95ac +b9b942595a75c163638205af2d0120b8cefebf0f483a45933c5a01c09f9ebea4fd870460ae6532327778e80000020037ff70 +0361059e0007002100002516333235342722073633321510212227060723363726351123353311331121152101d31125a034 +34ca6e88f4feaa49362118c41d43318787b9017bfe858205af2d0120b8cefebf0f483a45933c5a02f38f013efec28f000001 +ffdbfe5601790460000b003840150b020700078705bd00bc0c080c05064f010800460c10fcece4391239310010e4f4ec1112 +393930400b100d400d500d600d700d05015d13331114062b013533323635c1b8a3b54631694c0460fb8cd6c09c6199000003 +0071ffe3078c061400090023002f00414013314525121447051b0d082b180e47011221453010fcecf43c3cfc3c3cf4ecec31 +0040102808b90a2e04b9161d8c110ab80d97192fece432f432ec3210ec323000101716203610262007133217113311363332 +0010022322271523350623222726103736001027262007061017162037012f53540124a8a8fedc54b9f572b972f4cc00ffff +ccf472b972f5cb807f7f80055d5354fedc5453535401245402fafe6a7473e70196e773010dc5025efda2c5febcfdf0febcc5 +a8a8c5a2a20210a2a2fce9019674737374fe6a74737300030071fe56078c047b000b0025002f004440133145011224472b11 +1d12070e1e47271217453010fcecf43c3cfc3c3cf4ecec310040120a2ab913042eb9211ab80c138c0fbd1dbc3010e4e4e432 +f43cec3210ec3230001027262007061017162037032227112311062322272610373633321735331536333200100200101716 +20361026200706cd5354fedc54535354012454b9f472b972f5cb807f7f80cbf572b972f4cc00fffffaa253540124a8a8fedc +540164019674737374fe6a747373fef3c5fdae0252c5a2a20210a2a2c5aaaac5febcfdf0febc0317fe6a7473e70196e77300 +0003fffdffba057c06170012001600190000013313011709012303210f012307272337273709013301032103024ae5860161 +66fe70017cd288fdd6cd32463b520201142f0290feee16016fbd015d6a05d5fea101a159fe27fc1b017ff18e464601113804 +c4fd1901b1fe4f011f000002000cffba058a06170022002c0000172713261110373621321716173717071526270116171621 +3237363715060706232027130123262320070611147266dc75c3c3015386763d3a6566632e31fcf4090b880100827473666a +777684feb4c23902d8017482ff00888846580105bb01170168cfd024121b785976bb2b21fc660d0c9d2f2f5fd3482424c701 +15035c2f9c9dfed8ad0000020009ffa2045d04bc0022002b0000172737263510373621321716173717071526270116171633 +32373637150607062322271301262322070615146960bd559796010655512e2d595f761918fdd3070663b3504e4f4e4d5253 +5df0933701ee4747b363635e4ee68dcc01129d9d110a106c4f8f550e0bfd5e08087115162baa2412129001050256117172cd +67000001000a0000046a05d5000d003b40160c050a95020c06950081080305011c073a0c0a00040e10fc3cccecfc3ccc3100 +2fe4ecd43cec3230400d300f500f800780087f047f0306015d1333113315231121152111233533c9cabfbf02d7fc5fbfbf05 +d5fd7790fdeeaa02bc900002ffb2ffba05310617000f001200000115230111231101270111213521371709012104e934fe22 +cbfe0d67025afdee04993866fda6012cfed405693efdccfd090207fdb35802c70252aa4259fe0b0162000001006ffe100419 +047b003d0000013427262f0126272635343633321617152e0123220706151417161f0116171615140706071f011633152322 +27262f012627262726273516171633323736030a3233ab40ab4c4ce0ce66b44c4ea85a8944453131943fc650537b57849f93 +2a4c2754724759ed1e241011616c6663636182464601274b2828250f244a4b829eac1e1eae28282a2a54402524210e2c4b4c +899c5b40139f7e249a3d265bf31e1003021223be351a1b2d2c0000010058fe1004330460001800001321150116170117163b +0115232227262f01262b013d01012171036afd4e5c310108932a4c6c9354724759ed3d5a5e02b4fd650460a8fcdd1031fef8 +7e249a3d265bf33f9c0c0325000100500000048d05d500180036401112130c0b040f000501081916011c040f1910d4d4ecd4 +ec1139391117393100400b0095050f95100b951281022ff4ecd4ecd4ec3001231123113332363534262b0122060735363b01 +3204151404029127caf18d9a9a8dfe45af4f98abfef40108fef7025afda603009187888f2a2cb646dce1d7e7000100500000 +038f047b0018003740100a08060f040c01000412131608000c1910d4d4ecd4ec12391217393100400d16b901170c860d8808 +b90fb8172ff4ecf4ee10d4ec3001333236353427262322070607353633321716151406231123012f648d9a4c55864956564e +98abfb7d84d4c2ca01a691878d414815152bb6466e74dbd5e5fefc000003000a000004ec05d5000c00150028005c401a150f +0c06171d230500121c1a0919202e02040d001c262516042910fc3cccec3232ccfcecd4ec1117393939310040152801952504 +0400051d00950e0d95168105950ead232fececf4ec10ee391112392f3cec3230b20f2a01015d011521152115213236353426 +2301112132363534262325213216151406071e011514042321112335330193015bfea50144a39d9da3febc012b94919194fe +0b0204e7fa807c95a5fef0fbfde8bfbf02c9c990ca878b8c850266fe3e6f727170a6c0b189a21420cb98c8da017090000002 +000cffe305ce05d50014001d005f400f15031c0709053816011c131100411e10fc4bb0105458b90000ffc038593cccec32fc +3cccec32310040161d17100a000714039511091616001a950d8c0400811e10e432f4ec11392f3c3cec323211393939393001 +b61f1f8f1f9f1f035d133311211133113315231510002120001135233533052115141633323635b2cb02e1cba5a5fedffee6 +fee5fedfa6a603acfd1faec3c2ae05d5fd96026afd96a496fedcfed6012a012496a4a47df0d3d3f0ffff00100000056805d5 +100601cd0000000300c9ff42048b069300130017001b00000133073315230321152103211521072337231121011323110113 +211103b8aa41589297010afebcb9022efd9841aa41b002aefe3cb9d9011397fe560693beaafe46aafde3aabebe05d5fad502 +1dfde302c701bafe460000040071ff42047f051e00050026002d00310000012627262703051521031633323637150e012322 +27072313262726111000333217373307161716051326232206071b01231603c702530e106f019afe2b944a616ac76263d06b +7b6350aa6d211c9d0129fc383147aa5c392f83fdbc8714169ab90e5a6fcf0b0294975a100dfef2365afe971c3434ae2a2c21 +c20109171d9c010a0113014309ace0223292c5014a02ae9efe63010eac000001ff96fe66025205d500130059401f0b02070c +010c95120f14079505b010811400110d0508063901111c0c10041410fc4bb0105458b90010004038593cec32e43939c410c4 +310010e4fcec10d43cec32111239393001400d30154015501560158f159f15065d01231110062b0135333236351123353311 +3311330252bfcde34d3f866ebfbfcabf0277fdf1fef2f4aa96c2020fa602b8fd48000002ffdbfe56021c0614001300170053 +402417be14b1180f060b000b8709bd180213a9051000bc180c18090a4f15050108141000461810fc3c3cec3232e439123931 +0010e4dc3ce43210f4ec1112393910f4ec30400b1019401950196019701905015d1333113315231114062b01353332363511 +23353311331523c1b8a3a3a3b54631694cb5b5b8b80460fe08a4fe28d6c09c619901d8a403ace90000020073fe6606b005f1 +0018002400434024030c0d069509b025229500161c950d108c169101af25090608021f0d001c02191913102510fcecd4ec32 +3210cc3939310010ece4f4c4ec10c4ee10e4ec113939300135331114163b011523222611350e012320001110002132160110 +1233321211100223220204b3c46e86454de3cd4deca5fef2feac0154010ea5ecfcdfeacccdebebcdccea04ede8fa93c296aa +f4010e7f848001ab015c015c01ab80fd78fee3febb0145011d011d0145febb0000020071fe560540047b0018002400484022 +188700bd2522b9110e1cb905088c0eb812bc25011718131f041108134719120b452510fcecf4ec323210cc3939310010ece4 +f4c4ec10c4ee10f4ec30b660268026a02603015d012322263d010e012322021110003332161735331114163b010114163332 +36353426232206054046b5a33ab17ccbff00ffcb7cb13ab84c6931fbefa79292a8a89292a7fe56c0d6bc6461014401080108 +01446164aafb8c9961033dcbe7e7cbcbe7e70002000a0000055405d50017002000bb4018050603150900201a12050a1d1904 +153f180a1c0e110c042110fc3cccec32fcc4ec1117391139393931004021090807030a061103040305110404034206040019 +03041019950d09189511810b042f3cf4ecd432ec32123912391239304b5358071005ed071005ed1117395922b2402201015d +40427a1701050005010502060307041500150114021603170425002501250226032706260726082609202236013602460146 +02680575047505771788068807980698071f5d005d011e01171323032e012b01112311233533112120161514060111333236 +35342623038d417b3ecdd9bf4a8b78dccabfbf01c80100fc83fd89fe9295959202bc16907efe68017f9662fd890277a602b8 +d6d88dba024ffdee878383850001000e0000034a047b0018003d400a0a18030806120804461910fc3cc4c4fc3c3c31004010 +12110b15870eb8030818a9050209bc032fe4d43cec3210f4ecc4d4cc30b4501a9f1a02015d0115231123112335331133153e +013332161f012e0123220615021eabb9acacb93aba85132e1c011f492c9ca70268a4fe3c01c4a401f8ae66630505bd1211ce +a1000002fff6000004ec05d500110014000003331721373307331521011123110121353305211704d997020c96d9979cfef5 +fef6cbfef6fef49d0277fed19805d5e0e0e0a4fe76fd3902c7018aa4a4e20002000bfe5604b504600018001b0000050e012b +01353332363f0103213533033313211333033315212b011302934e947c936c4c543321cdfed6f0bec3b8014cb8c3b9effed7 +c1da6d68c87a9a48865401f28f01cdfe3301cdfe338ffef000020071ffe3047f047b0014001b004140240015010986088805 +01a91518b91215bb05b90cb8128c1c02151b1b080f4b15120801451c10fcc4ecf4ec111239310010e4f4ece410ee10ee10f4 +ee111239301335212e0123220607353e01332000111000232200371e013332363771034e0ccdb76ac76263d06b010c0139fe +d7fce2fef9b802a5889ab90e02005abec73434ae2a2cfec8fef6feedfebd0123c497b4ae9e0000010058fe4c042f04600020 +00a9400a1b1f151222061e1f0e2110dcd4c4d4c4ec10ccb2001f1b1112393140161b4200a91a1a1e211da91e0e860d9311b9 +09bd1ebc210010e4fcecf4ec10ec1112392fececb315060009111239393040081b11001c11201a1f070510ec0410ec401b0c +1c0a001b1c19002a1c2a0038003b1c49004c1c54005b1c71000d015d401b041b0420141b1420251b24203520371b4520461b +54205c1b7f1b0d005d4009070b060c1a0c1a0f045d0132171617161514042122272627351e0133323736353427262b013501 +21352115023c6a80625651fed0fee85e63646a54c86dbe63645c5da5ae01aefd65036a01dc382a6d688addf2121325c33132 +4b4b8f844b4aa601f393a800ffff00b203fe01d705d5100601d10000000100c104ee033f066600060037400c040502b400b3 +07040275060710dcec39310010f4ec323930004bb009544bb00e545b58bd0007ffc000010007000700403811373859013313 +2327072301b694f58bb4b48b0666fe88f5f5000100c104ee033f066600060037400c0300b40401b307030575010710dcec39 +310010f43cec3930004bb009544bb00e545b58bd0007ffc0000100070007004038113738590103331737330301b6f58bb4b4 +8bf504ee0178f5f5fe88000100c7052903390648000d0057400e0bf0040700b30e0756080156000e10dcecd4ec310010f43c +d4ec30004bb0095458bd000effc00001000e000e00403811373859004bb00f544bb010545b4bb011545b58bd000e00400001 +000e000effc0381137385913331e0133323637330e01232226c7760b615756600d760a9e91919e06484b4b4a4c8f90900002 +00ee04e103120706000b00170020401103c115f209c10ff11800560c780656121810d4ecf4ec310010f4ecf4ec3001342623 +2206151416333236371406232226353436333216029858404157574140587a9f73739f9f73739f05f43f5857404157584073 +a0a073739f9f0001014cfe7502c1000000130020400f0b0e0a07f30ef40001000a0427111410d4ecc4d4cc31002ffcfcc412 +393021330e0115141633323637150e0123222635343601b8772d2b3736203e1f26441e7a73353d581f2e2e0f0f850a0a575d +3069000100b6051d034a0637001b006340240012070e0b040112070f0b0412c3190704c3150bed1c0f010e00071556167707 +5608761c10f4ecfcec1139393939310010fc3cfcd43cec11123911123911123911123930004bb009544bb00c545b58bd001c +ffc00001001c001c0040381137385901272e0123220607233e013332161f011e0133323637330e0123222601fc3916210d26 +24027d02665b2640253916210d2624027d02665b2640055a371413495287931c21371413495287931c00000200f004ee03ae +066600030007004240110602b40400b3080407030005010305070810d4dcd4cc1139111239310010f43cec3230004bb00954 +4bb00e545b58bd0008ffc000010008000800403811373859013303230333032302fcb2f88781aadf890666fe880178fe8800 +0002fda2047bfe5a0614000300040025400c02be00b104b805040108000510d4ec39310010e4fcec30000140070404340444 +04035d0133152317fda2b8b85e0614e9b0000002fcc5047bff43066600060007003c400f0300b40401b307b8080703057501 +0810dcec3939310010e4f43cec3930004bb009544bb00e545b58bd0007ffc000010007000700403811373859010333173733 +0307fdbaf58bb4b48bf54e04ee0178f5f5fe88730002fc5d04eeff1b066600030007004240110602b40400b3080405010007 +030107050810d4dcd4cc1139111239310010f43cec3230004bb009544bb00e545b58bd0008ffc00001000800080040381137 +38590113230321132303fd0fcd87f80200be89df0666fe880178fe8801780001fcbf0529ff310648000c0018b50756080156 +002fecd4ec3100b40af00400072f3cdcec3003232e0123220607233e012016cf760b615756600d760a9e01229e05294b4b4a +4c8f90900001fe1f03e9ff4405280003000a40030201040010d4cc3001231333fef2d3a48103e9013f000001fef0036b007b +04e000130031400607560e0411002f4bb00c544bb00d545b4bb00e545b58b9000000403859dc32dcec310040050a04c10011 +2fc4fccc3001351e0133323635342627331e01151406232226fef03d581f2e2e0f0f850a0a575d306903d7772d2b3736203e +1f26441e7a7335000001fd6afe14fe8fff540003000a40030300040010d4cc3005330323fdbcd3a481acfec0000100100000 +056805d50006003c400b420695028105010804010710d4c4c431002f3cf4ec304b5358401206110302010511040403061102 +0011010102050710ec10ec0710ec0810ec5933230133012301e5d5023ae50239d2fe2605d5fa2b050e00000100c90000048b +05d5000b00464011420a06950781000495030d01080407040c10fc3cd43ccc31002fec32f4ec32304b535840120b11050504 +0a110606050b11050011040504050710ec10ec0710ec0810ec5925211521350901352115210101b102dafc3e01dffe2103b0 +fd3801dfaaaaaa02700211aaaafdf300000100bafe560464047b00150031401606870e12b80cbc02bd0b17460308004e090d +080c461610fcec32f4ecec31002fece4f4c4ec304005a017801702015d011123113426232206151123113315363736333217 +160464b87c7c95acb9b942595a75c1636302a4fbb204489f9ebea4fd870460ae653232777800000200c9000004ec05d50008 +0015002e400c17090019102e040b1c15041610fcec32f4ecc4cc3100400c0b9515811404950cad0595142fecf4ec10f4ec30 +0134262321112132361315211121320415140429011104179da3febc0144a39d6cfd10014efb0110fef9fefcfde801b78b87 +fddd8704a8a6fe40dadeddda05d5000100b203fe01d705d500050018400b039e00810603040119000610dcecd4cc310010f4 +ec300133150323130104d3a4815205d598fec1013f000001ffb9049a00c706120003000a40030003040010d4cc3011330323 +c775990612fe88000002fcd7050eff2905d90003000700a5400d0400ce0602080164000564040810d4fcdcec310010d43cec +3230004bb00e544bb011545b58bd00080040000100080008ffc03811373859014bb00e544bb00d545b4bb017545b58bd0008 +ffc000010008000800403811373859014bb011544bb019545b58bd00080040000100080008ffc03811373859004bb0185458 +bd0008ffc00001000800080040381137385940116001600260056006700170027005700608015d0133152325331523fe5ecb +cbfe79cbcb05d9cbcbcb0001fd7304eefef005f60003007f40110203000301000003420002fa040103030410c410c0310010 +f4cc304b5358071005c9071005c95922004bb00c5458bd0004ffc000010004000400403811373859004bb00e5458bd000400 +40000100040004ffc03811373859402006021502250125023602460256026a016702090f000f011f001f012f002f01065d01 +5d01330323fe37b9e49905f6fef80001fcb6050eff4a05e9001d0075402116100f03130c0701000308170cc30413c31b08fa +1e10010f00071656180756091e10d4ecd4ec1139393939310010f43cecd4ec321217391112173930004bb00c5458bd001eff +c00001001e001e00403811373859004bb00e5458bd001e00400001001e001effc03811373859b4100b1f1a025d01272e0123 +22061d012334363332161f011e013332363d01330e01232226fdfc39191f0c24287d6756243d303917220f20287d02675422 +3b0539210e0b322d066576101b1e0d0c3329066477100001fd0c04eefe8b05f60003008940110102030200030302420001fa +040103030410c410c0310010f4cc304b5358071005c9071005c95922004bb00c5458bd0004ffc00001000400040040381137 +3859004bb00e5458bd00040040000100040004ffc03811373859402a06000601160012012400240135014301550055019f00 +9f01af00af010e0f000f031f001f032f002f03065d015d01132303fdc7c499e605f6fef801080001fccf04eeff3105f80006 +0077400a04000502fa070402060710d4c439310010f43cc43930004bb00c5458bd0007ffc000010007000700403811373859 +004bb00e5458bd00070040000100070007ffc03811373859014bb00e5458bd0007ffc0000100070007004038113738594013 +0f000f010c041f001f011d042f002f012d0409005d01331323270723fda2bcd38ba6a68b05f8fef6b2b20001fccf04eeff31 +05f800060086400a03040100fa070305010710d4c439310010f4c4323930004bb00c544bb009545b4bb00a545b4bb00b545b +58bd0007ffc000010007000700403811373859004bb00e5458bd00070040000100070007ffc03811373859014bb00e5458bd +0007ffc000010007000700403811373859401300000303000610001203100620002203200609005d01033317373303fda2d3 +8ba6a68bd304ee010ab2b2fef6000001fcc70506ff3905f8000d000003232e0123220607233e01333216c7760d6353526110 +760aa08f909f050636393738777b7a000001fcc70506ff3905f8000d006a400e070004c30bfa0e0756080156000e10d4ecd4 +ec310010f4fccc3230004bb00c5458bd000effc00001000e000e00403811373859004bb00e5458bd000e00400001000e000e +ffc03811373859014bb00e544bb00f545b58bd000effc00001000e000e0040381137385901331e0133323637330e01232226 +fcc7760d6353526110760aa08f909f05f836393738777b7a0001fd9a050efe6605db00030047b700ce02040164000410d4ec +310010d4ec30004bb00e544bb011545b58bd00040040000100040004ffc03811373859004bb0185458bd0004ffc000010004 +00040040381137385901331523fd9acccc05dbcd0002fce604eeffb205f600030007001340070004030708000410cc310010 +d43ccc32300133032303330323fef9b9e4998bb9e49905f6fef80108fef80002fc4e04eeff1a05f600030007000001132303 +21132303fd07c499e40208c499e405f6fef80108fef80108000100d5fe56052705d50013004a402111110102010211101110 +420b950a11020300af10130b100111021c0436111c001410dcecfcec113939cc31002f3cec323939dcec304b5358071004ed +071004ed5922b21f1501015d1333011133111407062b01353332373635011123d5b802e2b85251b5fee9692626fd1eb805d5 +fb83047dfa17d660609c3031ad047dfb8300ffff0192066303e808331027002d00bd023d100701d304bc0155ffff0192065e +03e80833102701db04bc01501007002d00bd023dffff0193066303e5085a102701d404f00264100701d304bc0155ffff0193 +066303e5085a102701d6048c0264100701d304bc0155ffff0176066a040a0833102701d504c0015c1007002d00bd023dffff +018b066303ed085a102701d804bc0262100701d304bc0155000100000002599939a3946a5f0f3cf5001f080000000000d17e +0ee400000000d17e0ee4f7d6fc4c0e5909dc00000008000000000000000000010000076dfe1d00000efef7d6fa510e590001 +000000000000000000000000000001e004cd00660000000002aa0000028b0000033501350579001005960073062900c9050e +00c906330073060400c9025c00c9025cff96053f00c9047500c905fc00c9064c0073058f00c90514008704e3fffa05db00b2 +07e9004404e3fffc057b005c040000aa04e7007b046600710514007104ec007105140071051200ba023900c10239ffdb04a2 +00ba023900c1051200ba04e50071034a00ba042b006f03230037051200ae068b005604bc003d04330058040000d7040000d5 +04000173028b00db040001230579001007cb000805960073050e00c9050e00c9050e00c9050e00c9025c003b025c00a2025c +fffe025c00060633000a05fc00c9064c0073064c0073064c0073064c0073064c007306b40119064c006605db00b205db00b2 +05db00b205db00b204e3fffc04d700c9050a00ba04e7007b04e7007b04e7007b04e7007b04e7007b04e7007b07db007b0466 +007104ec007104ec007104ec007104ec00710239ffc7023900900239ffde0239fff404e50071051200ba04e5007104e50071 +04e5007104e5007104e5007106b400d904e50048051200ae051200ae051200ae051200ae04bc003d051400ba04bc003d0579 +001004e7007b0579001004e7007b0579001004e7007b05960073046600710596007304660071059600730466007105960073 +04660071062900c9051400710633000a05140071050e00c904ec0071050e00c904ec0071050e00c904ec0071050e00c904ec +0071050e00c904ec00710633007305140071063300730514007106330073051400710633007305140071060400c90512ffe5 +075400c9058f0078025cffe40239ffd3025c00030239fff2025cfff50239ffe4025c00b002390096025c00c9023900c104b8 +00c9047200c1025cff960239ffdb053f00c904a200ba04a200ba047500c9023900c1047500c902390088047500c9030000c1 +047500c902bc00c1047ffff20246000205fc00c9051200ba05fc00c9051200ba05fc00c9051200ba068200cd05fc00c90512 +00ba064c007304e50071064c007304e50071064c007304e50071088f0073082f0071058f00c9034a00ba058f00c9034a0082 +058f00c9034a00ba05140087042b006f05140087042b006f05140087042b006f05140087042b006f04e3fffa0323003704e3 +fffa0323003704e3fffa0323003705db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2 +051200ae05db00b2051200ae07e90044068b005604e3fffc04bc003d04e3fffc057b005c04330058057b005c04330058057b +005c0433005802d1002f0514002005e1ff97057d00c9051400ba057d00000514000005a0007305960073046600710633000a +068dff97057d00c90514007104e50071050e0083064c007504ea00a4049aff9602d1ff7f06330073057e000807df00ba02d4 +00c9025c000a05f700c904a200b90239000a04bc003d07cb00b205fcff96051200ba064c0073074e006704e5007607970073 +061300710537ff97051400b9058f00c905140072042b0064050e00c902b0fef20323003704e300180323003704e3fffa06dd +00ad051200b0061d004e05c400c905f3fffc05d8003d057b005c04330058055400a00554005c049f00680433007105170096 +0554005d049f006804150058051400ba025c00c903f000c903ac0014025d00c90b6000c90a6400c9093c007106af00c9064b +00c903a700c1077300c9076400c9066100ba0579001004e7007b025cfffe0239ffe0064c007304e5007105db00b2051200ae +05db00b2051200ae05db00b2051200ae05db00b2051200ae05db00b2051200ae04ec00710579001004e7007b0579001004e7 +007b07cb000807db007b06330073051400710633007305140071053f00c904a2ffe9064c007304e50071064c007304e50071 +055400a0049f00580239ffdb0b6000c90a6400c9093c0071063300730514007108e700c9057500c905fc00c9051200ba0579 +001004e7007b07cb000807db007b064c006604e500480579001004e7007b0579001004e7007b050e00c904ec0071050e00c9 +04ec0071025cffa70239ffc3025c00050239ffe3064c007304e50071064c007304e50071058f00c7034a0082058f00c9034a +00ba05db00b2051200ae05db00b2051200ae05140087042b006f04e3fffa032300370504009c042c0047060400c90512fff0 +05e200c906b400710596007104e20071057b005c043300580579001004e7007b050e00c904ec0071064c007304e50071064c +007304e50071064c007304e50071064c007304e5007104e3fffc04bc003d03cc008a06be00ba03d100370239ffdb07fc0071 +07fc00710579fffd0596000c046600090475000a04e3ffb2042b006f0433005804d3005003d50050057d000a05db000c0579 +0010050e00c904ec0071025cff960239ffdb0640007305140071058f000a034a000e04e3fff604bc000b04ec0071049f0058 +028b00b2040000c1040000c1040000c7040000ee0400014c040000b6040000f00000fda20000fcc50000fc5d0000fcbf0000 +fe1f0000fef00000fd6a05790010050e00c9051200ba057d00c9028b00b20000ffb90000fcd70000fd730000fcb60000fd0c +0000fccf0000fccf0000fcc70000fcc70000fd9a0000fce60000fc4e05fc00d5057801920192019301930176018b00000000 +0000000000000000003100ae00f90138016701ba01e8020c024302d602f8034c0391041b049704cf051105ee064f06ae06d6 +076c07b70803086d08d1090d0935097209ea0a080a440a950acc0b7b0bb80bfa0d0c0df10e570eb30ed80eff0f140f440fe4 +104f105b106710731084109610a210ae10bf10d01135114c115811641179119211a9120d12a912b512c112d812f312ff1342 +13d513e713f91409141f143b145a15371543154f155b156c157d1589159515a615b7168f169b16a616b116c116d716ed171b +17d517e017eb17fb1813181e186d1884189918ad18c318d318df18eb18f7190319151921192d1939194a195619621975197d +19db19e719f81a091a1a1a261a321a3e1a4a1a5b1a701a821a931a9f1aab1abc1ac81ad41ae01af71b191b5c1ba61bb71bc8 +1bd91bea1bfb1c0c1c181c241c3b1c611c721c831c941ca51cb11cbd1d351d411d5d1d691d7a1d861d981da41dbd1dfa1e42 +1e531e641e701e7c1e931ea81eb41eff1f4d1f621f721f871f971fa31faf1ffe2092209e20a920b520c120d220e620f220fd +21102122212e2139214c215f216a2175218a219b21dc2229223e224f22662277228c229d22a922ba22c622d222de22ea22fb +230c231d232d233e234a2355236123752381239523c12427248a249224ec2532258525cd262d268a269226dc271a276d27d9 +2807285e28ba28f9295429b92a432aaa2ad72b0f2b6d2bf62c252c992cf92d602d682db92dc52dd12e242e792ec42f242f82 +2fec308f309730e43130317831c5320b32173223327932bf331c34043488350d357d35e4366b36a136da3723376937a237ec +380c38183857385f386b38773883388f389b38a738b338bf38cb38db38eb38fe3911391d392c393c394e395939653970397c +39873993399e39aa39b239bd39c939d439e039ec39f83a613adb3af03afb3b073b293b353b413b4d3b583b643b6f3b823b8e +3b9a3ba63bb23bbd3c083c533c5f3c6b3c773c833c8f3c9b3ca73cb23cbe3cca3cd63ce23cee3cfa3d063d123d1e3d2a3d36 +3d423d4e3d5a3d663d723d7e3d8a3d963da23dae3dba3dc63dd23dde3dea3df63e023e483e913e9d3ebf3ef83f4a3fd04042 +40b74133413f414b41574162416d417941844190419c41a841b341bf41cb41d642004241427542a74317438943c0440b4452 +448844b1450d4538457a45bd462b468b469346c7471c476947b7481748744907494d497449a349f54a7e4a864ab34ae14b26 +4b5c4b8c4beb4c214c434c764cad4cd24ce54d1f4d314d624da04ddd4e1c4e394e4b4eb04efd4f654fb85005505b507550c4 +50f4511251285170517d518a519751a451b151be0001000001e50354002b0068000c00020010009900080000041502160008 +000400000007005a000300010409000001300000000300010409000100160130000300010409000200080146000300010409 +00030016013000030001040900040016013000030001040900050018014e0003000104090006001401660043006f00700079 +0072006900670068007400200028006300290020003200300030003300200062007900200042006900740073007400720065 +0061006d002c00200049006e0063002e00200041006c006c0020005200690067006800740073002000520065007300650072 +007600650064002e000a0043006f007000790072006900670068007400200028006300290020003200300030003600200062 +00790020005400610076006d006a006f006e00670020004200610068002e00200041006c006c002000520069006700680074 +0073002000520065007300650072007600650064002e000a00440065006a0061005600750020006300680061006e00670065 +0073002000610072006500200069006e0020007000750062006c0069006300200064006f006d00610069006e000a00440065 +006a006100560075002000530061006e00730042006f006f006b00560065007200730069006f006e00200032002e00330035 +00440065006a00610056007500530061006e00730002000000000000ff7e005a000000000000000000000000000000000000 +000001e5000000010002000300040024002600270028002a002b002c002d002e002f003100320035003600370038003a003c +003d00430044004600470048004a004b004c004d004e004f005100520055005600570058005a005c005d008e00da008d00c3 +00de00630090006400cb006500c800ca00cf00cc00cd00ce00e9006600d300d000d100af006700f0009100d600d400d50068 +00eb00ed0089006a0069006b006d006c006e00a0006f0071007000720073007500740076007700ea0078007a0079007b007d +007c00b800a1007f007e0080008100ec00ee00ba01020103010401050106010700fd00fe01080109010a010b00ff0100010c +010d010e0101010f0110011101120113011401150116011701180119011a00f800f9011b011c011d011e011f012001210122 +0123012401250126012701280129012a00fa00d7012b012c012d012e012f0130013101320133013401350136013701380139 +00e200e3013a013b013c013d013e013f01400141014201430144014501460147014800b000b10149014a014b014c014d014e +014f01500151015200fb00fc00e400e50153015401550156015701580159015a015b015c015d015e015f0160016101620163 +0164016501660167016800bb0169016a016b016c00e600e7016d016e016f0170017101720173017401750176017701780179 +017a017b017c017d017e017f00a60180018101820183018401850186018701880189018a018b018c018d018e018f01900191 +01920193019401950196019701980199019a019b019c019d019e019f01a001a101a201a301a401a501a601a701a801a901aa +01ab01ac01ad01ae01af01b001b101b201b301b401b501b601b701b801b901ba01bb01bc01bd01be01bf01c001c101c201c3 +01c401c501c601c701c801c901ca01cb01cc01cd01ce01cf01d001d101d201d301d401d501d601d701d801d901da01db01dc +01dd01de01df01e001e101e201e301e401e501e601e701e801e901ea01eb01ec01ed01ee01ef01f001f101f201f301f401f5 +01f601f701f801f901fa01fb01fc01fd01fe01ff0200020102020203020402050206020702080209020a020b020c020d020e +020f0210021102120213021402150216021702180219021a021b021c021d021e021f02200221022202230224022502260227 +02280229022a022b022c022d022e022f0230023102320233023402350236023702380239023a023b023c023d023e023f00d8 +00e100db00dd00e000d900df0240024102420243024402450246024702480249024a00b7024b024c024d024e024f02500251 +02520253025402550256025702580259025a025b025c025d07416d6163726f6e07616d6163726f6e06416272657665066162 +7265766507416f676f6e656b07616f676f6e656b0b4363697263756d666c65780b6363697263756d666c65780a43646f7461 +6363656e740a63646f74616363656e7406446361726f6e06646361726f6e064463726f617407456d6163726f6e07656d6163 +726f6e06456272657665066562726576650a45646f74616363656e740a65646f74616363656e7407456f676f6e656b07656f +676f6e656b06456361726f6e06656361726f6e0b4763697263756d666c65780b6763697263756d666c65780a47646f746163 +63656e740a67646f74616363656e740c47636f6d6d61616363656e740c67636f6d6d61616363656e740b4863697263756d66 +6c65780b6863697263756d666c657804486261720468626172064974696c6465066974696c646507496d6163726f6e07696d +6163726f6e064962726576650669627265766507496f676f6e656b07696f676f6e656b02494a02696a0b4a63697263756d66 +6c65780b6a63697263756d666c65780c4b636f6d6d61616363656e740c6b636f6d6d61616363656e740c6b677265656e6c61 +6e646963064c6163757465066c61637574650c4c636f6d6d61616363656e740c6c636f6d6d61616363656e74064c6361726f +6e066c6361726f6e044c646f74046c646f74064e6163757465066e61637574650c4e636f6d6d61616363656e740c6e636f6d +6d61616363656e74064e6361726f6e066e6361726f6e0b6e61706f7374726f70686503456e6703656e67074f6d6163726f6e +076f6d6163726f6e064f6272657665066f62726576650d4f68756e676172756d6c6175740d6f68756e676172756d6c617574 +06526163757465067261637574650c52636f6d6d61616363656e740c72636f6d6d61616363656e7406526361726f6e067263 +61726f6e06536163757465067361637574650b5363697263756d666c65780b7363697263756d666c65780c54636f6d6d6161 +6363656e740c74636f6d6d61616363656e7406546361726f6e06746361726f6e04546261720474626172065574696c646506 +7574696c646507556d6163726f6e07756d6163726f6e0655627265766506756272657665055572696e67057572696e670d55 +68756e676172756d6c6175740d7568756e676172756d6c61757407556f676f6e656b07756f676f6e656b0b5763697263756d +666c65780b7763697263756d666c65780b5963697263756d666c65780b7963697263756d666c6578065a6163757465067a61 +637574650a5a646f74616363656e740a7a646f74616363656e74056c6f6e677307756e693031383007756e69303138310775 +6e693031383207756e693031383307756e693031383407756e693031383507756e693031383607756e693031383707756e69 +3031383807756e693031383907756e693031384107756e693031384207756e693031384307756e693031384407756e693031 +384507756e693031384607756e693031393007756e693031393107756e693031393307756e693031393407756e6930313935 +07756e693031393607756e693031393707756e693031393807756e693031393907756e693031394107756e69303139420775 +6e693031394307756e693031394407756e693031394507756e6930313946054f686f726e056f686f726e07756e6930314132 +07756e693031413307756e693031413407756e693031413507756e693031413607756e693031413707756e69303141380775 +6e693031413907756e693031414107756e693031414207756e693031414307756e693031414407756e69303141450555686f +726e0575686f726e07756e693031423107756e693031423207756e693031423307756e693031423407756e69303142350775 +6e693031423607756e693031423707756e693031423807756e693031423907756e693031424107756e693031424207756e69 +3031424307756e693031424407756e693031424507756e693031424607756e693031433007756e693031433107756e693031 +433207756e693031433307756e693031433407756e693031433507756e693031433607756e693031433707756e6930314338 +07756e693031433907756e693031434107756e693031434207756e693031434307756e693031434407756e69303143450775 +6e693031434607756e693031443007756e693031443107756e693031443207756e693031443307756e693031443407756e69 +3031443507756e693031443607756e693031443707756e693031443807756e693031443907756e693031444107756e693031 +444207756e693031444307756e693031444407756e693031444507756e693031444607756e693031453007756e6930314531 +07756e693031453207756e693031453307756e693031453407756e693031453506476361726f6e06676361726f6e07756e69 +3031453807756e693031453907756e693031454107756e693031454207756e693031454307756e693031454407756e693031 +454507756e693031454607756e693031463007756e693031463107756e693031463207756e693031463307756e6930314634 +07756e693031463507756e693031463607756e693031463707756e693031463807756e69303146390a4172696e6761637574 +650a6172696e676163757465074145616375746507616561637574650b4f736c61736861637574650b6f736c617368616375 +746507756e693032303007756e693032303107756e693032303207756e693032303307756e693032303407756e6930323035 +07756e693032303607756e693032303707756e693032303807756e693032303907756e693032304107756e69303230420775 +6e693032304307756e693032304407756e693032304507756e693032304607756e693032313007756e693032313107756e69 +3032313207756e693032313307756e693032313407756e693032313507756e693032313607756e69303231370c53636f6d6d +61616363656e740c73636f6d6d61616363656e7407756e693032314107756e693032314207756e693032314307756e693032 +314407756e693032314507756e693032314607756e693032323007756e693032323107756e693032323207756e6930323233 +07756e693032323407756e693032323507756e693032323607756e693032323707756e693032323807756e69303232390775 +6e693032324107756e693032324207756e693032324307756e693032324407756e693032324507756e693032324607756e69 +3032333007756e693032333107756e693032333207756e693032333307756e693032333407756e693032333507756e693032 +333608646f746c6573736a07756e693032333807756e693032333907756e693032334107756e693032334207756e69303233 +4307756e693032334407756e693032334507756e693032334607756e693032343007756e693032343107756e693032343207 +756e693032343307756e693032343407756e693032343507756e693032343607756e693032343707756e693032343807756e +693032343907756e693032344107756e693032344207756e693032344307756e693032344407756e693032344507756e6930 +32344607756e693032353907756e693032393207756e693032424307756e693033303707756e693033304307756e69303330 +4607756e693033313107756e693033313207756e693033314207756e6930333236064c616d626461055369676d6103657461 +07756e693034313109646c4c746361726f6e0844696572657369730541637574650554696c64650547726176650a43697263 +756d666c6578054361726f6e0c756e69303331312e6361736505427265766509446f74616363656e740c48756e676172756d +6c6175740b446f75626c65677261766507456e672e616c740b756e6930333038303330340b756e6930333037303330340b75 +6e6930333038303330310b756e6930333038303330300b756e6930333033303330340b756e6930333038303330430000b802 +8040fffbfe03fa1403f92503f83203f79603f60e03f5fe03f4fe03f32503f20e03f19603f02503ef8a4105effe03ee9603ed +9603ecfa03ebfa03eafe03e93a03e84203e7fe03e63203e5e45305e59603e48a4105e45303e3e22f05e3fa03e22f03e1fe03 +e0fe03df3203de1403dd9603dcfe03db1203da7d03d9bb03d8fe03d68a4105d67d03d5d44705d57d03d44703d3d21b05d3fe +03d21b03d1fe03d0fe03cffe03cefe03cd9603cccb1e05ccfe03cb1e03ca3203c9fe03c6851105c61c03c51603c4fe03c3fe +03c2fe03c1fe03c0fe03bffe03befe03bdfe03bcfe03bbfe03ba1103b9862505b9fe03b8b7bb05b8fe03b7b65d05b7bb03b7 +8004b6b52505b65d40ff03b64004b52503b4fe03b39603b2fe03b1fe03b0fe03affe03ae6403ad0e03acab2505ac6403abaa +1205ab2503aa1203a98a4105a9fa03a8fe03a7fe03a6fe03a51203a4fe03a3a20e05a33203a20e03a16403a08a4105a09603 +9ffe039e9d0c059efe039d0c039c9b19059c64039b9a10059b19039a1003990a0398fe0397960d0597fe03960d03958a4105 +95960394930e05942803930e0392fa039190bb0591fe03908f5d0590bb039080048f8e25058f5d038f40048e25038dfe038c +8b2e058cfe038b2e038a8625058a410389880b05891403880b03878625058764038685110586250385110384fe0383821105 +83fe0382110381fe0380fe037ffe0340ff7e7d7d057efe037d7d037c64037b5415057b25037afe0379fe03780e03770c0376 +0a0375fe0374fa0373fa0372fa0371fa0370fe036ffe036efe036c21036bfe036a1142056a530369fe03687d036711420566 +fe0365fe0364fe0363fe0362fe03613a0360fa035e0c035dfe035bfe035afe0359580a0559fa03580a035716190557320356 +fe035554150555420354150353011005531803521403514a130551fe03500b034ffe034e4d10054efe034d10034cfe034b4a +13054bfe034a4910054a1303491d0d05491003480d0347fe0346960345960344fe0343022d0543fa0342bb03414b0340fe03 +3ffe033e3d12053e14033d3c0f053d12033c3b0d053c40ff0f033b0d033afe0339fe033837140538fa033736100537140336 +350b05361003350b03341e03330d0332310b0532fe03310b03302f0b05300d032f0b032e2d09052e10032d09032c32032b2a +25052b64032a2912052a25032912032827250528410327250326250b05260f03250b0324fe0323fe03220f03210110052112 +032064031ffa031e1d0d051e64031d0d031c1142051cfe031bfa031a42031911420519fe031864031716190517fe03160110 +0516190315fe0314fe0313fe031211420512fe0311022d05114203107d030f64030efe030d0c16050dfe030c0110050c1603 +0bfe030a100309fe0308022d0508fe030714030664030401100504fe03401503022d0503fe0302011005022d0301100300fe +0301b80164858d012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d00>]def + FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +0 0 translate +0 0 576 432 rectclip +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1 setgray +fill +grestore +0 setgray +/Cmr10 16.000 selectfont +gsave + +195.836 385.058 translate +0 rotate +0 0 m /T glyphshow +11.5547 0 m /h glyphshow +20.4375 0 m /e glyphshow +27.5391 0 m /r glyphshow +33.7969 0 m /e glyphshow +40.8984 0 m /space glyphshow +46.2266 0 m /a glyphshow +54.2266 0 m /r glyphshow +60.4844 0 m /e glyphshow +67.5859 0 m /space glyphshow +72.9141 0 m /b glyphshow +81.7969 0 m /a glyphshow +89.7969 0 m /s glyphshow +96.1016 0 m /i glyphshow +100.531 0 m /c glyphshow +107.633 0 m /space glyphshow +112.961 0 m /c glyphshow +120.062 0 m /h glyphshow +128.945 0 m /a glyphshow +136.945 0 m /r glyphshow +143.203 0 m /a glyphshow +151.203 0 m /c glyphshow +158.305 0 m /t glyphshow +164.516 0 m /e glyphshow +171.617 0 m /r glyphshow +177.875 0 m /s glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +34.5859 368.205 translate +0 rotate +0 0 m /A glyphshow +12 0 m /B glyphshow +23.3281 0 m /C glyphshow +34.8828 0 m /D glyphshow +47.0938 0 m /E glyphshow +57.9766 0 m /F glyphshow +68.4062 0 m /G glyphshow +80.9531 0 m /H glyphshow +92.9531 0 m /I glyphshow +98.7266 0 m /J glyphshow +106.938 0 m /K glyphshow +119.367 0 m /L glyphshow +129.367 0 m /M glyphshow +144.023 0 m /N glyphshow +156.023 0 m /O glyphshow +168.453 0 m /P glyphshow +179.336 0 m /Q glyphshow +191.766 0 m /R glyphshow +203.539 0 m /S glyphshow +212.422 0 m /T glyphshow +223.977 0 m /U glyphshow +235.977 0 m /V glyphshow +247.977 0 m /W glyphshow +264.406 0 m /X glyphshow +276.406 0 m /Y glyphshow +288.406 0 m /Z glyphshow +298.18 0 m /space glyphshow +303.508 0 m /a glyphshow +311.508 0 m /b glyphshow +320.391 0 m /c glyphshow +327.492 0 m /d glyphshow +336.375 0 m /e glyphshow +343.477 0 m /f glyphshow +348.359 0 m /g glyphshow +356.359 0 m /h glyphshow +365.242 0 m /i glyphshow +369.672 0 m /j glyphshow +374.555 0 m /k glyphshow +382.984 0 m /l glyphshow +387.414 0 m /m glyphshow +400.742 0 m /n glyphshow +409.625 0 m /o glyphshow +417.625 0 m /p glyphshow +426.508 0 m /q glyphshow +434.938 0 m /r glyphshow +441.195 0 m /s glyphshow +447.5 0 m /t glyphshow +453.711 0 m /u glyphshow +462.594 0 m /v glyphshow +471.023 0 m /w glyphshow +482.578 0 m /x glyphshow +491.008 0 m /y glyphshow +499.438 0 m /z glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +122.281 350.508 translate +0 rotate +0 0 m /zero glyphshow +8 0 m /one glyphshow +16 0 m /two glyphshow +24 0 m /three glyphshow +32 0 m /four glyphshow +40 0 m /five glyphshow +48 0 m /six glyphshow +56 0 m /seven glyphshow +64 0 m /eight glyphshow +72 0 m /nine glyphshow +80 0 m /space glyphshow +85.3281 0 m /exclam glyphshow +89.7578 0 m /quotedblright glyphshow +97.7578 0 m /numbersign glyphshow +111.086 0 m /dollar glyphshow +119.086 0 m /percent glyphshow +132.414 0 m /ampersand glyphshow +144.844 0 m /quoteright glyphshow +149.273 0 m /parenleft glyphshow +155.484 0 m /parenright glyphshow +161.695 0 m /asterisk glyphshow +169.695 0 m /plus glyphshow +182.125 0 m /comma glyphshow +186.555 0 m /hyphen glyphshow +191.883 0 m /period glyphshow +196.312 0 m /slash glyphshow +204.312 0 m /colon glyphshow +208.742 0 m /semicolon glyphshow +213.172 0 m /exclamdown glyphshow +217.602 0 m /equal glyphshow +230.031 0 m /questiondown glyphshow +237.586 0 m /question glyphshow +245.141 0 m /at glyphshow +257.57 0 m /bracketleft glyphshow +262 0 m /quotedblleft glyphshow +270 0 m /bracketright glyphshow +274.43 0 m /circumflex glyphshow +282.43 0 m /dotaccent glyphshow +286.859 0 m /quoteleft glyphshow +291.289 0 m /emdash glyphshow +299.289 0 m /endash glyphshow +315.289 0 m /hungarumlaut glyphshow +323.289 0 m /tilde glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +203.922 333.177 translate +0 rotate +0 0 m /a glyphshow +8 0 m /n glyphshow +16.8828 0 m /d glyphshow +25.7656 0 m /space glyphshow +31.0938 0 m /a glyphshow +39.0938 0 m /c glyphshow +46.1953 0 m /c glyphshow +53.2969 0 m /e glyphshow +60.3984 0 m /n glyphshow +69.2812 0 m /t glyphshow +75.4922 0 m /e glyphshow +82.5938 0 m /d glyphshow +91.4766 0 m /space glyphshow +96.8047 0 m /c glyphshow +103.906 0 m /h glyphshow +112.789 0 m /a glyphshow +120.789 0 m /r glyphshow +127.047 0 m /a glyphshow +135.047 0 m /c glyphshow +142.148 0 m /t glyphshow +148.359 0 m /e glyphshow +155.461 0 m /r glyphshow +161.719 0 m /s glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +144.602 312.161 translate +0 rotate +0 0 m /Aring glyphshow +10.9453 0 m /AE glyphshow +26.5312 0 m /Ccedilla glyphshow +37.7031 0 m /Egrave glyphshow +47.8125 0 m /Eacute glyphshow +57.9219 0 m /Ecircumflex glyphshow +68.0312 0 m /Edieresis glyphshow +78.1406 0 m /Igrave glyphshow +82.8594 0 m /Iacute glyphshow +87.5781 0 m /Icircumflex glyphshow +92.2969 0 m /Idieresis glyphshow +97.0156 0 m /Eth glyphshow +109.414 0 m /Ntilde glyphshow +121.383 0 m /Ograve glyphshow +133.977 0 m /Oacute glyphshow +146.57 0 m /Ocircumflex glyphshow +159.164 0 m /Otilde glyphshow +171.758 0 m /Odieresis glyphshow +184.352 0 m /multiply glyphshow +197.758 0 m /Oslash glyphshow +210.352 0 m /Ugrave glyphshow +222.062 0 m /Uacute glyphshow +233.773 0 m /Ucircumflex glyphshow +245.484 0 m /Udieresis glyphshow +257.195 0 m /Yacute glyphshow +266.969 0 m /Thorn glyphshow +276.648 0 m /germandbls glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +136.82 292.195 translate +0 rotate +0 0 m /agrave glyphshow +9.80469 0 m /aacute glyphshow +19.6094 0 m /acircumflex glyphshow +29.4141 0 m /atilde glyphshow +39.2188 0 m /adieresis glyphshow +49.0234 0 m /aring glyphshow +58.8281 0 m /ae glyphshow +74.5391 0 m /ccedilla glyphshow +83.3359 0 m /egrave glyphshow +93.1797 0 m /eacute glyphshow +103.023 0 m /ecircumflex glyphshow +112.867 0 m /edieresis glyphshow +122.711 0 m /igrave glyphshow +127.156 0 m /iacute glyphshow +131.602 0 m /icircumflex glyphshow +136.047 0 m /idieresis glyphshow +140.492 0 m /eth glyphshow +150.281 0 m /ntilde glyphshow +160.422 0 m /ograve glyphshow +170.211 0 m /oacute glyphshow +180 0 m /ocircumflex glyphshow +189.789 0 m /otilde glyphshow +199.578 0 m /odieresis glyphshow +209.367 0 m /divide glyphshow +222.773 0 m /oslash glyphshow +232.562 0 m /ugrave glyphshow +242.703 0 m /uacute glyphshow +252.844 0 m /ucircumflex glyphshow +262.984 0 m /udieresis glyphshow +273.125 0 m /yacute glyphshow +282.594 0 m /thorn glyphshow +292.75 0 m /ydieresis glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +121.945 270.192 translate +0 rotate +0 0 m /Amacron glyphshow +10.9453 0 m /amacron glyphshow +20.75 0 m /Abreve glyphshow +31.6953 0 m /abreve glyphshow +41.5 0 m /Aogonek glyphshow +52.4453 0 m /aogonek glyphshow +62.25 0 m /Cacute glyphshow +73.4219 0 m /cacute glyphshow +82.2188 0 m /Ccircumflex glyphshow +93.3906 0 m /ccircumflex glyphshow +102.188 0 m /Cdotaccent glyphshow +113.359 0 m /cdotaccent glyphshow +122.156 0 m /Ccaron glyphshow +133.328 0 m /ccaron glyphshow +142.125 0 m /Dcaron glyphshow +154.445 0 m /dcaron glyphshow +164.602 0 m /Dcroat glyphshow +177 0 m /dcroat glyphshow +187.156 0 m /Emacron glyphshow +197.266 0 m /emacron glyphshow +207.109 0 m /Ebreve glyphshow +217.219 0 m /ebreve glyphshow +227.062 0 m /Edotaccent glyphshow +237.172 0 m /edotaccent glyphshow +247.016 0 m /Eogonek glyphshow +257.125 0 m /eogonek glyphshow +266.969 0 m /Ecaron glyphshow +277.078 0 m /ecaron glyphshow +286.922 0 m /Gcircumflex glyphshow +299.32 0 m /gcircumflex glyphshow +309.477 0 m /Gbreve glyphshow +321.875 0 m /gbreve glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +164.969 248.939 translate +0 rotate +0 0 m /Gdotaccent glyphshow +12.3984 0 m /gdotaccent glyphshow +22.5547 0 m /Gcommaaccent glyphshow +34.9531 0 m /gcommaaccent glyphshow +45.1094 0 m /Hcircumflex glyphshow +57.1406 0 m /hcircumflex glyphshow +67.2812 0 m /Hbar glyphshow +81.9375 0 m /hbar glyphshow +93.0547 0 m /Itilde glyphshow +97.7734 0 m /itilde glyphshow +102.219 0 m /Imacron glyphshow +106.938 0 m /imacron glyphshow +111.383 0 m /Ibreve glyphshow +116.102 0 m /ibreve glyphshow +120.547 0 m /Iogonek glyphshow +125.266 0 m /iogonek glyphshow +129.711 0 m /Idotaccent glyphshow +134.43 0 m /dotlessi glyphshow +138.875 0 m /IJ glyphshow +148.312 0 m /ij glyphshow +157.203 0 m /Jcircumflex glyphshow +161.922 0 m /jcircumflex glyphshow +166.367 0 m /Kcommaaccent glyphshow +176.859 0 m /kcommaaccent glyphshow +186.125 0 m /kgreenlandic glyphshow +195.391 0 m /Lacute glyphshow +204.305 0 m /lacute glyphshow +208.75 0 m /Lcommaaccent glyphshow +217.664 0 m /lcommaaccent glyphshow +222.109 0 m /Lcaron glyphshow +231.023 0 m /lcaron glyphshow +237.023 0 m /Ldot glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +123.125 227.17 translate +0 rotate +0 0 m /ldot glyphshow +5.46875 0 m /Lslash glyphshow +14.4609 0 m /lslash glyphshow +19.0078 0 m /Nacute glyphshow +30.9766 0 m /nacute glyphshow +41.1172 0 m /Ncommaaccent glyphshow +53.0859 0 m /ncommaaccent glyphshow +63.2266 0 m /Ncaron glyphshow +75.1953 0 m /ncaron glyphshow +85.3359 0 m /napostrophe glyphshow +98.3516 0 m /Eng glyphshow +110.32 0 m /eng glyphshow +120.461 0 m /Omacron glyphshow +133.055 0 m /omacron glyphshow +142.844 0 m /Obreve glyphshow +155.438 0 m /obreve glyphshow +165.227 0 m /Ohungarumlaut glyphshow +177.82 0 m /ohungarumlaut glyphshow +187.609 0 m /OE glyphshow +204.727 0 m /oe glyphshow +221.094 0 m /Racute glyphshow +232.211 0 m /racute glyphshow +238.789 0 m /Rcommaaccent glyphshow +249.906 0 m /rcommaaccent glyphshow +256.484 0 m /Rcaron glyphshow +267.602 0 m /rcaron glyphshow +274.18 0 m /Sacute glyphshow +284.336 0 m /sacute glyphshow +292.672 0 m /Scircumflex glyphshow +302.828 0 m /scircumflex glyphshow +311.164 0 m /Scedilla glyphshow +321.32 0 m /scedilla glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +128.219 205.27 translate +0 rotate +0 0 m /Scaron glyphshow +10.1562 0 m /scaron glyphshow +18.4922 0 m /Tcommaaccent glyphshow +28.2656 0 m /tcommaaccent glyphshow +34.5391 0 m /Tcaron glyphshow +44.3125 0 m /tcaron glyphshow +50.5859 0 m /Tbar glyphshow +60.3594 0 m /tbar glyphshow +66.6328 0 m /Utilde glyphshow +78.3438 0 m /utilde glyphshow +88.4844 0 m /Umacron glyphshow +100.195 0 m /umacron glyphshow +110.336 0 m /Ubreve glyphshow +122.047 0 m /ubreve glyphshow +132.188 0 m /Uring glyphshow +143.898 0 m /uring glyphshow +154.039 0 m /Uhungarumlaut glyphshow +165.75 0 m /uhungarumlaut glyphshow +175.891 0 m /Uogonek glyphshow +187.602 0 m /uogonek glyphshow +197.742 0 m /Wcircumflex glyphshow +213.562 0 m /wcircumflex glyphshow +226.648 0 m /Ycircumflex glyphshow +236.422 0 m /ycircumflex glyphshow +245.891 0 m /Ydieresis glyphshow +255.664 0 m /Zacute glyphshow +266.625 0 m /zacute glyphshow +275.023 0 m /Zdotaccent glyphshow +285.984 0 m /zdotaccent glyphshow +294.383 0 m /Zcaron glyphshow +305.344 0 m /zcaron glyphshow +313.742 0 m /longs glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +120.906 184.205 translate +0 rotate +0 0 m /uni0180 glyphshow +10.1562 0 m /uni0181 glyphshow +21.9141 0 m /uni0182 glyphshow +32.8906 0 m /uni0183 glyphshow +43.0469 0 m /uni0184 glyphshow +54.0234 0 m /uni0185 glyphshow +64.1797 0 m /uni0186 glyphshow +75.4297 0 m /uni0187 glyphshow +86.6016 0 m /uni0188 glyphshow +95.3984 0 m /uni0189 glyphshow +107.797 0 m /uni018A glyphshow +120.898 0 m /uni018B glyphshow +131.875 0 m /uni018C glyphshow +142.031 0 m /uni018D glyphshow +151.82 0 m /uni018E glyphshow +161.93 0 m /uni018F glyphshow +174.523 0 m /uni0190 glyphshow +184.352 0 m /uni0191 glyphshow +193.555 0 m /florin glyphshow +199.188 0 m /uni0193 glyphshow +211.586 0 m /uni0194 glyphshow +222.57 0 m /uni0195 glyphshow +238.312 0 m /uni0196 glyphshow +243.969 0 m /uni0197 glyphshow +248.688 0 m /uni0198 glyphshow +260.617 0 m /uni0199 glyphshow +269.883 0 m /uni019A glyphshow +274.328 0 m /uni019B glyphshow +283.797 0 m /uni019C glyphshow +299.383 0 m /uni019D glyphshow +311.352 0 m /uni019E glyphshow +321.492 0 m /uni019F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +124.211 166.258 translate +0 rotate +0 0 m /Ohorn glyphshow +14.6094 0 m /ohorn glyphshow +24.3984 0 m /uni01A2 glyphshow +39.5781 0 m /uni01A3 glyphshow +51.7266 0 m /uni01A4 glyphshow +62.1562 0 m /uni01A5 glyphshow +72.3125 0 m /uni01A6 glyphshow +83.4297 0 m /uni01A7 glyphshow +93.5859 0 m /uni01A8 glyphshow +101.922 0 m /uni01A9 glyphshow +112.031 0 m /uni01AA glyphshow +117.406 0 m /uni01AB glyphshow +123.68 0 m /uni01AC glyphshow +133.453 0 m /uni01AD glyphshow +139.727 0 m /uni01AE glyphshow +149.5 0 m /Uhorn glyphshow +163.227 0 m /uhorn glyphshow +173.367 0 m /uni01B1 glyphshow +185.594 0 m /uni01B2 glyphshow +197.125 0 m /uni01B3 glyphshow +209.023 0 m /uni01B4 glyphshow +220.711 0 m /uni01B5 glyphshow +231.672 0 m /uni01B6 glyphshow +240.07 0 m /uni01B7 glyphshow +250.727 0 m /uni01B8 glyphshow +261.383 0 m /uni01B9 glyphshow +270.625 0 m /uni01BA glyphshow +279.023 0 m /uni01BB glyphshow +289.203 0 m /uni01BC glyphshow +299.859 0 m /uni01BD glyphshow +309.102 0 m /uni01BE glyphshow +317.266 0 m /uni01BF glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +110.68 142.527 translate +0 rotate +0 0 m /uni01C0 glyphshow +4.71875 0 m /uni01C1 glyphshow +12.5938 0 m /uni01C2 glyphshow +19.9375 0 m /uni01C3 glyphshow +24.6641 0 m /uni01C4 glyphshow +47.4141 0 m /uni01C5 glyphshow +68.1953 0 m /uni01C6 glyphshow +86.6641 0 m /uni01C7 glyphshow +100.031 0 m /uni01C8 glyphshow +112.617 0 m /uni01C9 glyphshow +119.922 0 m /uni01CA glyphshow +134.82 0 m /uni01CB glyphshow +149.602 0 m /uni01CC glyphshow +162.359 0 m /uni01CD glyphshow +173.305 0 m /uni01CE glyphshow +183.109 0 m /uni01CF glyphshow +187.828 0 m /uni01D0 glyphshow +192.273 0 m /uni01D1 glyphshow +204.867 0 m /uni01D2 glyphshow +214.656 0 m /uni01D3 glyphshow +226.367 0 m /uni01D4 glyphshow +236.508 0 m /uni01D5 glyphshow +248.219 0 m /uni01D6 glyphshow +258.359 0 m /uni01D7 glyphshow +270.07 0 m /uni01D8 glyphshow +280.211 0 m /uni01D9 glyphshow +291.922 0 m /uni01DA glyphshow +302.062 0 m /uni01DB glyphshow +313.773 0 m /uni01DC glyphshow +323.914 0 m /uni01DD glyphshow +333.758 0 m /uni01DE glyphshow +344.703 0 m /uni01DF glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +90.0078 120.092 translate +0 rotate +0 0 m /uni01E0 glyphshow +10.9453 0 m /uni01E1 glyphshow +20.75 0 m /uni01E2 glyphshow +36.3359 0 m /uni01E3 glyphshow +52.0469 0 m /uni01E4 glyphshow +64.4453 0 m /uni01E5 glyphshow +74.6016 0 m /Gcaron glyphshow +87 0 m /gcaron glyphshow +97.1562 0 m /uni01E8 glyphshow +107.648 0 m /uni01E9 glyphshow +116.914 0 m /uni01EA glyphshow +129.508 0 m /uni01EB glyphshow +139.297 0 m /uni01EC glyphshow +151.891 0 m /uni01ED glyphshow +161.68 0 m /uni01EE glyphshow +172.336 0 m /uni01EF glyphshow +181.578 0 m /uni01F0 glyphshow +186.023 0 m /uni01F1 glyphshow +208.773 0 m /uni01F2 glyphshow +229.555 0 m /uni01F3 glyphshow +248.023 0 m /uni01F4 glyphshow +260.422 0 m /uni01F5 glyphshow +270.578 0 m /uni01F6 glyphshow +288.383 0 m /uni01F7 glyphshow +299.297 0 m /uni01F8 glyphshow +311.266 0 m /uni01F9 glyphshow +321.406 0 m /Aringacute glyphshow +332.352 0 m /aringacute glyphshow +342.156 0 m /AEacute glyphshow +357.742 0 m /aeacute glyphshow +373.453 0 m /Oslashacute glyphshow +386.047 0 m /oslashacute glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +138.602 98.7609 translate +0 rotate +0 0 m /uni0200 glyphshow +10.9453 0 m /uni0201 glyphshow +20.75 0 m /uni0202 glyphshow +31.6953 0 m /uni0203 glyphshow +41.5 0 m /uni0204 glyphshow +51.6094 0 m /uni0205 glyphshow +61.4531 0 m /uni0206 glyphshow +71.5625 0 m /uni0207 glyphshow +81.4062 0 m /uni0208 glyphshow +86.125 0 m /uni0209 glyphshow +90.5703 0 m /uni020A glyphshow +95.2891 0 m /uni020B glyphshow +99.7344 0 m /uni020C glyphshow +112.328 0 m /uni020D glyphshow +122.117 0 m /uni020E glyphshow +134.711 0 m /uni020F glyphshow +144.5 0 m /uni0210 glyphshow +155.617 0 m /uni0211 glyphshow +162.195 0 m /uni0212 glyphshow +173.312 0 m /uni0213 glyphshow +179.891 0 m /uni0214 glyphshow +191.602 0 m /uni0215 glyphshow +201.742 0 m /uni0216 glyphshow +213.453 0 m /uni0217 glyphshow +223.594 0 m /Scommaaccent glyphshow +233.75 0 m /scommaaccent glyphshow +242.086 0 m /uni021A glyphshow +251.859 0 m /uni021B glyphshow +258.133 0 m /uni021C glyphshow +268.164 0 m /uni021D glyphshow +276.508 0 m /uni021E glyphshow +288.539 0 m /uni021F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +118.953 75.8109 translate +0 rotate +0 0 m /uni0220 glyphshow +11.7656 0 m /uni0221 glyphshow +25.1719 0 m /uni0222 glyphshow +36.3438 0 m /uni0223 glyphshow +46.1094 0 m /uni0224 glyphshow +57.0703 0 m /uni0225 glyphshow +65.4688 0 m /uni0226 glyphshow +76.4141 0 m /uni0227 glyphshow +86.2188 0 m /uni0228 glyphshow +96.3281 0 m /uni0229 glyphshow +106.172 0 m /uni022A glyphshow +118.766 0 m /uni022B glyphshow +128.555 0 m /uni022C glyphshow +141.148 0 m /uni022D glyphshow +150.938 0 m /uni022E glyphshow +163.531 0 m /uni022F glyphshow +173.32 0 m /uni0230 glyphshow +185.914 0 m /uni0231 glyphshow +195.703 0 m /uni0232 glyphshow +205.477 0 m /uni0233 glyphshow +214.945 0 m /uni0234 glyphshow +222.539 0 m /uni0235 glyphshow +236.023 0 m /uni0236 glyphshow +243.656 0 m /dotlessj glyphshow +248.102 0 m /uni0238 glyphshow +264.07 0 m /uni0239 glyphshow +280.039 0 m /uni023A glyphshow +290.984 0 m /uni023B glyphshow +302.156 0 m /uni023C glyphshow +310.953 0 m /uni023D glyphshow +319.867 0 m /uni023E glyphshow +329.641 0 m /uni023F glyphshow +grestore +/DejaVuSans 16.000 selectfont +gsave + +213.938 56.1484 translate +0 rotate +0 0 m /uni0240 glyphshow +8.39844 0 m /uni0241 glyphshow +18.0469 0 m /uni0242 glyphshow +25.7109 0 m /uni0243 glyphshow +36.6875 0 m /uni0244 glyphshow +48.3984 0 m /uni0245 glyphshow +59.3438 0 m /uni0246 glyphshow +69.4531 0 m /uni0247 glyphshow +79.2969 0 m /uni0248 glyphshow +84.0156 0 m /uni0249 glyphshow +88.4609 0 m /uni024A glyphshow +100.961 0 m /uni024B glyphshow +111.117 0 m /uni024C glyphshow +122.234 0 m /uni024D glyphshow +128.812 0 m /uni024E glyphshow +138.586 0 m /uni024F glyphshow +grestore +/Cmr10 16.000 selectfont +gsave + +248.008 38.9422 translate +0 rotate +0 0 m /i glyphshow +4.42969 0 m /n glyphshow +13.3125 0 m /space glyphshow +18.6406 0 m /b glyphshow +27.5234 0 m /e glyphshow +34.625 0 m /t glyphshow +40.8359 0 m /w glyphshow +52.3906 0 m /e glyphshow +59.4922 0 m /e glyphshow +66.5938 0 m /n glyphshow +75.4766 0 m /exclam glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/scatter.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/scatter.eps new file mode 100644 index 000000000000..b21ff4234af4 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/scatter.eps @@ -0,0 +1,306 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: scatter.eps +%%Creator: Matplotlib v3.6.0.dev2701+g27bf604984.d20220719, https://matplotlib.org/ +%%CreationDate: Tue Jul 19 12:36:23 2022 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 10 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +/p0_0 { +newpath +translate +72 141.529351 m +17.327389 80.435325 l +126.672611 80.435325 l +cl + +} bind def +/p0_1 { +newpath +translate +72 158.4 m +-17.28 100.8 l +72 43.2 l +161.28 100.8 l +cl + +} bind def +/p0_2 { +newpath +translate +72 141.529351 m +11.959333 113.386062 l +34.892827 67.849263 l +109.107173 67.849263 l +132.040667 113.386062 l +cl + +} bind def +/p0_3 { +newpath +translate +72 158.4 m +-5.318748 129.6 l +-5.318748 72 l +72 43.2 l +149.318748 72 l +149.318748 129.6 l +cl + +} bind def +1.000 setlinewidth +1 setlinejoin +0 setlinecap +[] 0 setdash +0.000 setgray +gsave +446.4 345.6 72 43.2 clipbox +96.7145 132.649 p0_0 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +166.544 15.5782 p0_1 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +149.874 179.799 p0_2 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +34.7409 104.813 p0_3 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +145.839 37.968 p0_0 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +147.462 82.9425 p0_1 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +147.29 120.393 p0_2 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +151.565 52.8617 p0_3 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +165.375 85.5808 p0_0 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +12.8578 119.079 p0_1 +gsave +1.000 1.000 0.000 setrgbcolor +fill +grestore +stroke +grestore +0.900 0.200 0.100 setrgbcolor +gsave +446.4 345.6 72 43.2 clipbox +326.215567 311.071597 m +334.595085 306.881838 l +334.595085 315.261356 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +184.274432 293.965646 m +190.679432 290.763146 l +190.679432 297.168146 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +276.081223 354.823805 m +283.311607 351.208613 l +283.311607 358.438997 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +411.593191 219.187935 m +420.363106 214.802977 l +420.363106 223.572893 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +141.383198 139.751386 m +149.294063 135.795953 l +149.294063 143.706818 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +154.058079 131.129187 m +160.028366 128.144043 l +160.028366 134.114331 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +247.767539 370.319257 m +255.714503 366.345775 l +255.714503 374.292739 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +410.16817 374.136435 m +419.852735 369.294152 l +419.852735 378.978717 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +450.836918 106.524611 m +457.983473 102.951334 l +457.983473 110.097888 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore +gsave +446.4 345.6 72 43.2 clipbox +397.084416 298.708741 m +402.739273 295.881312 l +402.739273 301.53617 l +cl +gsave +0.000 0.000 1.000 setrgbcolor +fill +grestore +stroke +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/type3.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/type3.eps new file mode 100644 index 000000000000..9c9645b47cf0 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/type3.eps @@ -0,0 +1,112 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Orientation: portrait +%%BoundingBox: 18.0 180.0 594.0 612.0 +%%EndComments +%%BeginProlog +/mpldict 11 dict def +mpldict begin +/d { bind def } bind def +/m { moveto } d +/l { lineto } d +/r { rlineto } d +/c { curveto } d +/cl { closepath } d +/ce { closepath eofill } d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } d +/clipbox { + box + clip + newpath + } d +/sc { setcachedevice } d +%!PS-Adobe-3.0 Resource-Font +%%Creator: Converted from TrueType to Type 3 by Matplotlib. +10 dict begin +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix [0.00048828125 0 0 0.00048828125 0 0] def +/FontBBox [-2090 -948 3673 2524] def +/FontType 3 def +/Encoding [/I /J /slash] def +/CharStrings 4 dict dup begin +/.notdef 0 def +/I{604 0 201 0 403 1493 sc +201 1493 m +403 1493 l +403 0 l +201 0 l +201 1493 l + +ce} d +/J{604 0 -106 -410 403 1493 sc +201 1493 m +403 1493 l +403 104 l +403 -76 369 -207 300 -288 c +232 -369 122 -410 -29 -410 c +-106 -410 l +-106 -240 l +-43 -240 l +46 -240 109 -215 146 -165 c +183 -115 201 -25 201 104 c +201 1493 l + +ce} d +/slash{690 0 0 -190 690 1493 sc +520 1493 m +690 1493 l +170 -190 l +0 -190 l +520 1493 l + +ce} d +end readonly def + +/BuildGlyph { + exch begin + CharStrings exch + 2 copy known not {pop /.notdef} if + true 3 1 roll get exec + end +} d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +} d + +FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +0.000 setgray +/DejaVuSans findfont +12.000 scalefont +setfont +gsave +288.000000 216.000000 translate +0.000000 rotate +0.000000 0 m /I glyphshow +3.539062 0 m /slash glyphshow +7.582031 0 m /J glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/type42_without_prep.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/type42_without_prep.eps new file mode 100644 index 000000000000..435e7b456401 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/type42_without_prep.eps @@ -0,0 +1,21189 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: type42_without_prep.eps +%%Creator: Matplotlib v3.5.0.dev1340+g24f1fb772a.d20210713, https://matplotlib.org/ +%%CreationDate: Tue Jul 13 14:52:53 2021 +%%Orientation: portrait +%%BoundingBox: 75 223 537 569 +%%HiResBoundingBox: 75.600000 223.200000 536.400000 568.800000 +%%EndComments +%%BeginProlog +/mpldict 12 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +%!PS-TrueTypeFont-1.0-2.22937 +%%Title: DejaVu Sans +%%Copyright: Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. DejaVu changes are in public domain +%%Creator: Converted from TrueType to type 42 by PPR +15 dict begin +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix[1 0 0 1 0 0]def +/FontBBox[-1021 -463 1793 1232]def +/FontType 42 def +/Encoding StandardEncoding def +/FontInfo 10 dict dup begin +/FamilyName (DejaVu Sans) def +/FullName (DejaVu Sans) def +/Notice (Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. DejaVu changes are in public domain ) def +/Weight (Book) def +/Version (Version 2.35) def +/ItalicAngle 0.0 def +/isFixedPitch false def +/UnderlinePosition -130 def +/UnderlineThickness 90 def +end readonly def +/sfnts[<0001000000090080000300106376742000691D390000009C000001FE6670676D +7134766A0000029C000000AB676C796668846831000003480008795C68656164085DC287 +00087CA400000036686865610D9F1FBF00087CDC00000024686D7478C535D8FD00087D00 +000061666C6F63616096C68C0008DE68000061886D6178701CCE067100093FF000000020 +707265703B07F1000009401000000568013500B800CB00CB00C100AA009C01A600B80066 +0000007100CB00A002B20085007500B800C301CB0189022D00CB00A600F000D300AA0087 +00CB03AA0400014A003300CB000000D9050200F4015400B4009C01390114013907060400 +044E04B4045204B804E704CD0037047304CD04600473013303A2055605A60556053903C5 +021200C9001F00B801DF007300BA03E9033303BC0444040E00DF03CD03AA00E503AA0404 +000000CB008F00A4007B00B80014016F007F027B0252008F00C705CD009A009A006F00CB +00CD019E01D300F000BA018300D5009803040248009E01D500C100CB00F600830354027F +00000333026600D300C700A400CD008F009A0073040005D5010A00FE022B00A400B4009C +00000062009C0000001D032D05D505D505D505F0007F007B005400A406B80614072301D3 +00B800CB00A601C301EC069300A000D3035C037103DB0185042304A80448008F01390114 +01390360008F05D5019A0614072306660179046004600460047B009C00000277046001AA +00E904600762007B00C5007F027B000000B4025205CD006600BC00660077061000CD013B +01850389008F007B0000001D00CD074A042F009C009C0000077D006F0000006F0335006A +006F007B00AE00B2002D0396008F027B00F600830354063705F6008F009C04E10266008F +018D02F600CD03440029006604EE00730000140000960000B707060504030201002C2010 +B002254964B040515820C859212D2CB002254964B040515820C859212D2C20100720B000 +50B00D7920B8FFFF5058041B0559B0051CB0032508B0042523E120B00050B00D7920B8FF +FF5058041B0559B0051CB0032508E12D2C4B505820B0FD454459212D2CB002254560442D +2C4B5358B00225B0022545445921212D2C45442D2CB00225B0022549B00525B005254960 +B0206368208A108A233A8A10653A2D0000020066FE96046605A400030007001A400C04FB +0006FB0108057F0204002FC4D4EC310010D4ECD4EC301311211125211121660400FC7303 +1BFCE5FE96070EF8F2720629000201350000020005D5000300090035400F070083048102 +08070501030400000A10FC4BB00B5458B90000FFC038593CEC32393931002FE4FCCC3001 +B6000B200B500B035D253315231133110323030135CBCBCB14A215FEFE05D5FD71FE9B01 +65000000000200C503AA02E905D5000300070042400F0501840400810804050600050204 +0810FC4BB012544BB013545B58B90002FFC03859FCDCEC310010F43CEC323001400F3009 +4009500960097009A009BF09075D0111231121112311016FAA0224AA05D5FDD5022BFDD5 +022B00000002009E0000061705BE0003001F006040311B0B008707041D0905190D028717 +130F15111F1E1C1B1A17161514131211100E0D0C090807060504030201001A0A18062010 +FCCC173931002F3CD43C3CFC3C3CD43C3CC432EC32323040110B010B020B0C0B0D14041A +111A12141F08015D012103210B0121133303211521032115210323132103231321352113 +213521130417FEDD5401254468012469A0670138FEA152013EFE9B68A067FEDB67A168FE +C5016054FEBE0169660385FEB20387FE61019FFE619AFEB299FE62019EFE62019E99014E +9A019F00000300AAFED3046D061400210028002F00BD405522020A0B0A27012628020B0B +0A1D011E1C022F292F1B0229292F42131110220A1B29041706092A210502178616068605 +11231A8A168910002A8A0589022D08160A1E07291A1203000922100903010726080D0506 +3010FC4BB0095458B90005FFC038594BB00C544BB010545B4BB00F545B58B90005004038 +593CECF4173CFC173CF4E4EC31002FE4ECC4D4E4EC32C410EE10EE111239113911121739 +111239304B5358071004ED07100EED11173907100EED111739071004ED59220123032E01 +27351E0117112E01353436373533151E0117152E0127111E011514060703110E01151416 +17113E0135342602B4640169D26A66D16FDDC9DACC645DAE5353AF5CE3D6E3D664747A71 +E17F817BFED3012D022D2DB440410101C824AC96A3BC0EEBE8041F1BAF2A2E04FE5523B4 +9CA9C30F0300019A0D6A585660D5FE4F116E5A586800000000050071FFE3072905F0000B +001700230027003300894036240F252625260F2724274200920C1E922E8D18922406920C +8D26128C2824913427211B2509030D150E090D0F210D2B0E1B0D0F310B3410FC4BB00954 +4BB00B545B4BB00C545B4BB014545B4BB00E545B4BB00D545B58B90031FFC03859C4ECF4 +EC10EEF6EE1139111239310010E432F43CE4EC10EEF6EE10EE304B5358071005ED071005 +ED5922012206151416333236353426273216151406232226353436012206151416333236 +3534262533012313321615140623222635343605D157636357556363559EBABB9DA0BABB +FC97566362575763640331A0FC5AA01F9EBCBB9F9FB9BA029194848295958283957FDCBB +BBDBDBBBBCDB026195828494948481967FF9F3060DDBBBBDDADBBCBADC00000000020081 +FFE305FE05F00009003001CD40960D010E0C861112110B860A0B12121109860009151615 +070106088616161502010301861D1E1D008609001E1E1D201F02211E110A130A17161503 +181411130A07080206091113130A0201020300110A130A171602181511130A141113130A +42120B090306000A1E0328150E0628270695182B9527942491188C0E130A2E0B0E09002E +1215270E1E032E1227210E110F132103121B103110FCECC4D4D4EC10C6EE113911123939 +1139391139113931002FC6E4F6E6EE10EE10C6111239111739111739304B5358071005ED +0705ED111739071005ED111739071005ED1117390705ED111739071005ED111739071008 +ED07100EED11173907100EED111739071008ED071008ED07100EED1117395922B20F3201 +015D40B2070B052209291C001C011F02170B2A002A0126123A003412440B5E0059015A0A +55125A1A5A1F5930671E7B009B009A0199029708950B931595169522992D1F090B090C08 +110C270C2818021B09190B190C19111C141C15161D1F3227002701290923122A132A1428 +152F323B09341239133F324A094C144B1546194F3256015A09590C551259135C1F5F326A +0C691160327501790C7A1193009301970295059C079C089F089A099B0B9A0C9032A032B0 +32395D005D010E011514163332363709013E0137330602070123270E0123220035343637 +2E0135343633321617152E0123220615141601F25B55D4A05FA649FE7B01FC3B4206BA0C +685D0117FC8F68E483F1FECE86863032DEB853A555579E4469833B032351A15892C23F40 +028FFDF859CB7284FEFE7EFEE39359570113D780E1633F7D3CA2C52424B62F316F583367 +000100C503AA016F05D500030037400A0184008104000502040410FC4BB012544BB01354 +5B58B90002FFC03859EC310010F4EC3001400D40055005600570059005A005065D011123 +11016FAA05D5FDD5022B0000000100B0FEF2027B0612000D0037400F069800970E0D0700 +03120600130A0E10DC4BB0135458B9000AFFC038594BB00F5458B9000A00403859E432EC +113939310010FCEC300106021514121723260235341237027B86828385A0969594970612 +E6FE3EE7E7FE3BE5EB01C6E0DF01C4EC000100A4FEF2026F0612000D001F400F07980097 +0E0701000B12041308000E10DC3CF4EC113939310010FCEC301333161215140207233612 +353402A4A096959596A08583830612ECFE3CDFE0FE3AEBE501C5E7E701C200000001003D +024A03C305F00011004E402C100D0B00040C090704020408039905110C990A010E911208 +0C0A030906110301030200140F040B09140D061210D43CE432DC3CE43217391112173931 +0010F4D43CEC32C4EC32173912173930010D01072511231105272D0137051133112503C3 +FE9901673AFEB072FEB03A0167FE993A015072015004DFC2C362CBFE870179CB62C3C263 +CB0179FE87CB0000000100D9000005DB0504000B002340110009019C0703050215040017 +0A0615080C10DCFC3CFC3CEC31002FD43CFC3CC43001112115211123112135211103AE02 +2DFDD3A8FDD3022D0504FDD3AAFDD3022DAA022D0001009EFF1201C300FE00050019400C +039E0083060304011900180610FCECD4CC310010FCEC30373315032313F0D3A48152FEAC +FEC001400001006401DF027F028300030011B6009C020401000410DCCC310010D4EC3013 +21152164021BFDE50283A400000100DB000001AE00FE00030011B7008302011900180410 +FCEC31002FEC3037331523DBD3D3FEFE00010000FF4202B205D50003002D4014001A0102 +01021A03000342029F008104020001032FC43939310010F4EC304B5358071005ED071005 +ED5922013301230208AAFDF8AA05D5F96D00000000020087FFE3048F05F0000B00170023 +401306A01200A00C91128C18091C0F1E031C151B1810FCECF4EC310010E4F4EC10EE3001 +2202111012333212111002273200111000232200111000028B9C9D9D9C9D9D9D9DFB0109 +FEF7FBFBFEF701090550FECDFECCFECDFECD0133013301340133A0FE73FE86FE87FE7301 +8D0179017A018D00000100E10000045A05D5000A004040154203A00402A005810700A009 +081F061C03001F010B10D44BB00F5458B9000100403859ECC4FCEC31002FEC32F4ECD4EC +304B5358592201B40F030F04025D3721110535253311211521FE014AFE990165CA014AFC +A4AA047348B848FAD5AA0000000100960000044A05F0001C009E4027191A1B03181C1105 +0400110505044210A111940DA014910400A00200100A02010A1C171003061D10FC4BB015 +544BB016545B4BB014545B58B90003FFC03859C4D4ECC0C011123931002FEC32F4ECF4EC +304B5358071005ED0705ED01B01C1011173959220140325504560556077A047A05761B87 +190704000419041A041B051C74007606751A731B741C82008619821A821B821CA800A81B +115D005D25211521353600373E0135342623220607353E01333204151406070600018902 +C1FC4C73018D33614DA7865FD3787AD458E80114455B19FEF4AAAAAA7701913A6D974977 +964243CC3132E8C25CA5701DFEEB00000001009CFFE3047305F000280070402E0015130A +86091F862013A0150DA00993061CA020932391068C15A329161C13000314191C2620101C +03141F09062910FC4BB016544BB014545B58B90009FFC03859C4C4D4ECF4EC1117393931 +0010ECE4F4E4EC10E6EE10EE10EE10EE11123930014009641E611F6120642104005D011E +0115140421222627351E013332363534262B013533323635342623220607353E01333204 +151406033F91A3FED0FEE85EC76A54C86DBEC7B9A5AEB6959EA39853BE7273C959E6010C +8E03251FC490DDF22525C33132968F8495A67770737B2426B42020D1B27CAB0000020064 +000004A405D50002000D0081401D010D030D0003030D4200030B07A00501038109010C0A +001C0608040C0E10DC4BB00B544BB00D545B58B9000CFFC03859D43CC4EC32113931002F +E4D43CEC321239304B5358071004C9071005C9592201402A0B002A004800590069007700 +8A000716012B0026012B0336014E014F0C4F0D5601660175017A0385010D5D005D090121 +03331133152311231121350306FE0201FE35FED5D5C9FD5E0525FCE303CDFC33A8FEA001 +60C300000001009EFFE3046405D5001D005E4023041A071186101D1AA00714A010890D02 +A000810D8C07A41E171C010A031C000A10061E10FC014BB016544BB014545B58B90010FF +C038594BB00F5458B9001000403859C4D4EC10C4EE310010E4E4F4EC10E6EE10FEC410EE +1112393013211521113E0133320015140021222627351E0133323635342623220607DD03 +19FDA02C582CFA0124FED4FEEF5EC3685AC06BADCACAAD51A15405D5AAFE920F0FFEEEEA +F1FEF52020CB3130B69C9CB6242600000002008FFFE3049605F0000B0024005840241306 +000D860C00A01606A01C16A510A00C8922911C8C250C22091C191E131C03211F1B2510FC +ECECF4ECE4310010E4F4E4FCE410EE10EE10EE111239304014CB00CB01CD02CD03CD04CB +05CB0607A41EB21E025D015D01220615141633323635342601152E01232202033E013332 +0015140023200011100021321602A4889F9F88889F9F01094C9B4CC8D30F3BB26BE10105 +FEF0E2FEFDFEEE0150011B4C9B033BBAA2A1BBBBA1A2BA0279B82426FEF2FEEF575DFEEF +EBE6FEEA018D0179016201A51E000000000100A80000046805D500060063401805110203 +0203110405044205A0008103050301040100060710FCCCC411393931002FF4EC304B5358 +071005ED071005ED5922014BB0165458BD00070040000100070007FFC038113738594012 +58020106031A05390548056703B000B006075D005D13211501230121A803C0FDE2D301FE +FD3305D556FA81052B0000000003008BFFE3048B05F0000B0023002F00434025180C00A0 +2706A01E2DA012911E8C27A330180C242A1C15241C0F091C151B1E031C0F211B3010FCC4 +ECF4C4EC10EE10EE113939310010ECE4F4EC10EE10EE3939300122061514163332363534 +26252E01353424333216151406071E011514042322243534361314163332363534262322 +06028B90A5A59090A6A5FEA5829100FFDEDFFE918192A3FEF7F7F7FEF7A4489183829393 +82839102C59A87879A9B86879A5620B280B3D0D0B380B22022C68FD9E8E8D98FC6016174 +828274748282000000020081FFE3048705F00018002400584023071F1901860019A00AA5 +04A00089161FA01091168C25071C1C21131E0022221C0D1B2510FCECE4F4ECEC310010E4 +F4EC10E6FEF5EE10EE111239304016C419C21AC01BC01CC01DC21EC41F07AA12BC12E912 +035D015D37351E01333212130E0123220035340033200011100021222601323635342623 +2206151416E14C9C4BC8D30F3AB26CE0FEFB0110E201030111FEB1FEE54C9C013E889F9F +88889F9F1FB82426010D0112565C010FEBE60116FE73FE86FE9FFE5B1E0297BAA2A1BBBB +A1A2BA00000200F0000001C3042300030007001C400E068304A600830205010304001808 +10FC3CEC3231002FECF4EC303733152311331523F0D3D3D3D3FEFE0423FE00000002009E +FF1201C304230003000900254013028300079E048300A60A07080501190400180A10FC3C +EC32D4CC310010E4FCEC10EE3013331523113315032313F0D3D3D3A481520423FEFDD9AC +FEC00140000100D9005E05DB04A60006004D402A029C030403019C0001040403019C0201 +050605009C06054205040201000503A806A7070102002404230710FCEC3239310010F4EC +1739304B53580704ED071008ED071008ED071004ED592209021501350105DBFBF80408FA +FE050203F0FE91FE93B601D1A601D100000200D9016005DB03A200030007001C400D009C +02069C040805010400230810FC3CC432310010D4ECD4EC301321152115211521D90502FA +FE0502FAFE03A2A8F0AA0000000100D9005E05DB04A60006004F402B069C000603040305 +9C040403009C010201069C05060202014206050302000504A801A7070602240400230710 +FC3CEC39310010F4EC1739304B5358071008ED071004ED071004ED071008ED5922133501 +15013501D90502FAFE040603F0B6FE2FA6FE2FB6016D000000020093000003B005F00003 +00240065402B241E0906040A1D13040014861388109517910083021D1A0D0905040A1E01 +0D1C1A041C05010300261A132510DC4BB00C5458B90013FFC03859C4FCECD4EC10EE1139 +3911123911123931002FEEF6FEF4EE10CD11393917393001B679097A0A7A20035D253315 +2313233534363F013E0135342623220607353E013332161514060F010E01070E01150187 +CBCBC5BF385A5A3933836C4FB3615EC167B8DF485A582F27080606FEFE01919A65825659 +355E31596E4643BC3938C29F4C8956562F3519153C34000000020087FE9C077105A2000B +004C00954032180C0309A919151B03A94C0F34330FAC30A93715AC24A937434D33341E1A +00281206180C281A2B1E2849122B2A28492C3D4D10DCECFCEC10FEFDFE3CC610EE111239 +39310010D4C4FCEC10FEEDD4C610C5EE3210C4EE11393930004BB009544BB00C545B4BB0 +10545B4BB013545B4BB014545B58BD004DFFC00001004D004D0040381137385940090F4E +1F4E2F4E3F4E04015D011416333236353426232206010E01232226353436333216173533 +113E01353426272624232206070602151412171604333236371706042322242726023534 +12373624333204171E011510000502FA8E7C7B8D907A798F02213C9B67ACD7D8AB679C3B +8F92A53F4068FED5B07BE2609DB1736D6901149D81F9685A7DFED998B9FEB8808086887E +810152BDD4016B7B4B4FFEC2FEE802198FA3A48E8CA5A4FE484D49F9C8C8FA4B4C83FD20 +16DFB16BBC50838B414066FEB5C19FFEEA6A686D57516F6167837D7D0149BDB6014A7D7F +87AEA062E67BFEF9FED00600000200100000056805D50002000A00C24041001101000405 +04021105050401110A030A0011020003030A0711050406110505040911030A08110A030A +4200030795010381090509080706040302010009050A0B10D4C4173931002F3CE4D4EC12 +39304B5358071005ED0705ED071005ED0705ED071008ED071005ED071005ED071008ED59 +22B2200C01015D40420F010F020F070F080F005800760070008C00090701080206030904 +1601190256015802500C67016802780176027C0372047707780887018802800C98029903 +9604175D005D090121013301230321032302BCFEEE0225FE7BE50239D288FD5F88D5050E +FD1903AEFA2B017FFE810000000300C9000004EC05D5000800110020004340231900950A +0995128101950AAD1F110B080213191F05000E1C1605191C2E09001C12042110FCEC32FC +ECD4EC111739393931002FECECF4EC10EE3930B20F2201015D0111213236353426230111 +2132363534262325213216151406071E01151404232101930144A39D9DA3FEBC012B9491 +9194FE0B0204E7FA807C95A5FEF0FBFDE802C9FDDD878B8C850266FE3E6F727170A6C0B1 +89A21420CB98C8DA00010073FFE3052705F000190036401A0DA10EAE0A951101A100AE04 +951791118C1A07190D003014101A10FCEC32EC310010E4F4ECF4EC10EEF6EE30B40F1B1F +1B02015D01152E0123200011100021323637150E01232000111000213216052766E782FF +00FEF00110010082E7666AED84FEADFE7A0186015386ED0562D55F5EFEC7FED8FED9FEC7 +5E5FD34848019F01670168019F470000000200C9000005B005D500080011002E40150095 +09810195100802100A0005190D32001C09041210FCECF4EC113939393931002FECF4EC30 +B2601301015D0111332000111000212521200011100029010193F40135011FFEE1FECBFE +42019F01B20196FE68FE50FE61052FFB770118012E012C0117A6FE97FE80FE7EFE960000 +000100C90000048B05D5000B002E401506950402950081089504AD0A05010907031C0004 +0C10FCEC32D4C4C431002FECECF4EC10EE30B21F0D01015D132115211121152111211521 +C903B0FD1A02C7FD3902F8FC3E05D5AAFE46AAFDE3AA0000000100C90000042305D50009 +002940120695040295008104AD08050107031C00040A10FCEC32D4C431002FECF4EC10EE +30B20F0B01015D13211521112115211123C9035AFD700250FDB0CA05D5AAFE48AAFD3700 +00010073FFE3058B05F0001D0039402000051B0195031B950812A111AE15950E91088C1E +02001C1134043318190B101E10FCECFCE4FCC4310010E4F4ECF4EC10FED4EE1139393025 +1121352111060423200011100021320417152E0123200011100021323604C3FEB6021275 +FEE6A0FEA2FE75018B015E9201076F70FC8BFEEEFEED011301126BA8D50191A6FD7F5355 +0199016D016E01994846D75F60FECEFED1FED2FECE250000000100C90000053B05D5000B +002C4014089502AD0400810A0607031C053809011C00040C10FCEC32FCEC3231002F3CE4 +32FCEC30B2500D01015D133311211133112311211123C9CA02DECACAFD22CA05D5FD9C02 +64FA2B02C7FD3900000100C90000019305D50003002EB700AF02011C00040410FC4BB010 +5458B9000000403859EC31002FEC3001400D30054005500560058F059F05065D13331123 +C9CACA05D5FA2B000001FF96FE66019305D5000B004240130B0200079505B000810C0508 +0639011C00040C10FC4BB0105458B9000000403859ECE43939310010E4FCEC1139393001 +400D300D400D500D600D8F0D9F0D065D13331110062B013533323635C9CACDE34D3F866E +05D5FA93FEF2F4AA96C20000000100C90000056A05D5000A00EF40280811050605071106 +06050311040504021105050442080502030300AF09060501040608011C00040B10FCEC32 +D4C4113931002F3CEC321739304B5358071004ED071005ED071005ED071004ED5922B208 +0301015D4092140201040209081602280528083702360534084702460543085502670276 +027705830288058F0894029B08E702150603090509061B031907050A030A07180328052B +062A073604360536063507300C41034004450540064007400C6203600468056707770570 +0C8B038B058E068F078F0C9A039D069D07B603B507C503C507D703D607E803E904E805EA +06F703F805F9062C5D71005D711333110121090121011123C9CA029E0104FD1B031AFEF6 +FD33CA05D5FD890277FD48FCE302CFFD31000000000100C90000046A05D500050025400C +0295008104011C033A00040610FCECEC31002FE4EC304009300750078003800404015D13 +3311211521C9CA02D7FC5F05D5FAD5AA000100C90000061F05D5000C00BF403403110708 +070211010208080702110302090A0901110A0A09420A070203080300AF080B0509080302 +01050A061C043E0A1C00040D10FCECFCEC11173931002F3CC4EC32111739304B53580710 +05ED071008ED071008ED071005ED5922B2700E01015D405603070F080F09020A15021407 +130A260226072007260A200A3407350A69027C027B07790A80028207820A90021604010B +0313011B0323012C032708280934013C035608590965086A097608790981018D0395019B +03145D005D13210901211123110123011123C9012D017D017F012DC5FE7FCBFE7FC405D5 +FC0803F8FA2B051FFC000400FAE10000000100C90000053305D500090079401E07110102 +0102110607064207020300AF0805060107021C0436071C00040A10FCECFCEC1139393100 +2F3CEC323939304B5358071004ED071004ED5922B21F0B01015D40303602380748024707 +690266078002070601090615011A06460149065701580665016906790685018A0695019A +069F0B105D005D13210111331121011123C901100296C4FEF0FD6AC405D5FB1F04E1FA2B +04E1FB1F00020073FFE305D905F0000B00170023401306951200950C91128C1809190F33 +031915101810FCECFCEC310010E4F4EC10EE300122001110003332001110002720001110 +002120001110000327DCFEFD0103DCDC0101FEFFDC013A0178FE88FEC6FEC5FE87017905 +4CFEB8FEE5FEE6FEB80148011A011B0148A4FE5BFE9EFE9FFE5B01A40162016201A50000 +000200C90000048D05D500080013003A40180195100095098112100A0802040005190D3F +11001C09041410FCEC32FCEC11173931002FF4ECD4EC30400B0F151F153F155F15AF1505 +015D011133323635342623252132041514042B0111230193FE8D9A9A8DFE3801C8FB0101 +FEFFFBFECA052FFDCF92878692A6E3DBDDE2FDA800020073FEF805D905F0000B001D0052 +402A1110020F010C0D0C0E010D0D0C420F1E0C06951200951891128C0D1E0D1B0F0C0309 +191B33031915101E10FCECFCEC1139391139310010C4E4F4EC10EE391239304B53580710 +05ED071005ED17395922012200111000333200111000130123270E012320001110002120 +001110020327DCFEFD0103DCDC0101FEFF3F010AF4DD212310FEC5FE870179013B013A01 +78D1054CFEB8FEE5FEE6FEB80148011A011B0148FACFFEDDEF020201A50161016201A5FE +5BFE9EFEFCFE8E00000200C90000055405D50013001C00B14035090807030A0611030403 +05110404034206040015030415950914950D810B040506031109001C160E050A19190411 +3F140A1C0C041D10FCEC32FCC4EC1117391139393931002F3CF4ECD4EC12391239123930 +4B5358071005ED071005ED1117395922B2401E01015D40427A1301050005010502060307 +041500150114021603170425002501250226032706260726082609201E36013602460146 +02680575047505771388068807980698071F5D005D011E01171323032E012B0111231121 +2016151406011133323635342623038D417B3ECDD9BF4A8B78DCCA01C80100FC83FD89FE +9295959202BC16907EFE68017F9662FD8905D5D6D88DBA024FFDEE878383850000010087 +FFE304A205F00027007E403C0D0C020E0B021E1F1E080902070A021F1F1E420A0B1E1F04 +15010015A11494189511049500942591118C281E0A0B1F1B0700221B190E2D0719142228 +10DCC4ECFCECE4111239393939310010E4F4E4EC10EEF6EE10C6111739304B535807100E +ED11173907100EED1117395922B20F2901015DB61F292F294F29035D01152E0123220615 +14161F011E0115140421222627351E013332363534262F012E01353424333216044873CC +5FA5B377A67AE2D7FEDDFEE76AEF807BEC72ADBC879A7BE2CA0117F569DA05A4C5373680 +7663651F192BD9B6D9E0302FD04546887E6E7C1F182DC0ABC6E426000001FFFA000004E9 +05D50007004A400E0602950081040140031C0040050810D4E4FCE431002FF4EC3230014B +B00A5458BD00080040000100080008FFC03811373859401300091F00100110021F071009 +400970099F09095D03211521112311210604EFFDEECBFDEE05D5AAFAD5052B00000100B2 +FFE3052905D50011004040160802110B0005950E8C09008112081C0A38011C00411210FC +4BB0105458B90000FFC03859ECFCEC310010E432F4EC11393939393001B61F138F139F13 +035D133311141633323635113311100021200011B2CBAEC3C2AECBFEDFFEE6FEE5FEDF05 +D5FC75F0D3D3F0038BFC5CFEDCFED6012A012400000100100000056805D5000600B74027 +04110506050311020306060503110403000100021101010042030401AF00060403020005 +05010710D4C4173931002FEC3239304B5358071005ED071008ED071008ED071005ED5922 +B2500801015D406200032A03470447055A037D038303070600070208040906150114021A +041A052A002601260229042905250620083800330133023C043C05370648004501450249 +0449054706590056066602690469057A0076017602790479057506800898009706295D00 +5D21013309013301024AFDC6D301D901DAD2FDC705D5FB1704E9FA2B00010044000007A6 +05D5000C017B4049051A0605090A09041A0A09031A0A0B0A021A01020B0B0A0611070807 +05110405080807021103020C000C011100000C420A050203060300AF0B080C0B0A090806 +05040302010B07000D10D4CC173931002F3CEC32321739304B5358071005ED071008ED07 +1008ED071005ED071008ED071005ED0705ED071008ED5922B2000E01015D40F206020605 +020A000A000A120A2805240A200A3E023E05340A300A4C024D05420A400A59026A026B05 +670A600A7B027F027C057F05800A960295051D0700090208030004060500050006010704 +08000807090009040A0A0C000E1A0315041508190C100E20042105200620072008230924 +0A250B200E200E3C023A033504330530083609390B3F0C300E460046014A024004450540 +0542064207420840084009440A4D0C400E400E58025608590C500E660267036104620560 +06600760086409640A640B770076017B027803770474057906790777087008780C7F0C7F +0E860287038804890585098A0B8F0E97049F0EAF0E5B5D005D1333090133090133012309 +012344CC013A0139E3013A0139CDFE89FEFEC5FEC2FE05D5FB1204EEFB1204EEFA2B0510 +FAF000000001003D0000053B05D5000B006640060D0406000A0C10D4C4DCC4C431B48000 +7F0A025D0040050300AF09062F3CEC32304BB04250584014071106060509110A0B0A0311 +0405040111000B00050710EC0710EC0710EC0710EC40140B0A0307000809040700050904 +0601020A0306010F0F0F0F5913330901330901230901230181D901730175D9FE200200D9 +FE5CFE59DA021505D5FDD5022BFD33FCF8027BFD85031D000001FFFC000004E705D50008 +0094402803110405040211010205050402110302080008011100000842020300AF060207 +0440051C0040070910D4E4FCE4123931002FEC3239304B5358071005ED071008ED071008 +ED071005ED5922B2000A01015D403C050214023502300230053008460240024005400851 +02510551086502840293021016011A031F0A2601290337013803400A670168037803700A +9F0A0D5D005D03330901330111231104D9019E019BD9FDF0CB05D5FD9A0266FCF2FD3902 +C70000000001005C0000051F05D500090090401B03110708070811020302420895008103 +950508030001420400060A10DC4BB009544BB00A545B58B90006FFC03859C4D4E4113939 +31002FECF4EC304B5358071005ED071005ED592201404005020A07180729022607380748 +02470748080905030B08000B16031A08100B2F0B350339083F0B47034A084F0B55035908 +660369086F0B770378087F0B9F0B165D005D13211501211521350121730495FC5003C7FB +3D03B0FC6705D59AFB6FAA9A04910000000100B0FEF2025806140007003B400F04A906B2 +02A900B10805010343000810DC4BB00C5458B90000004038594BB012544BB013545B58B9 +0000FFC03859FCCC32310010FCECF4EC301321152311331521B001A8F0F0FE5806148FF9 +FC8F000000010000FF4202B205D50003002D4014021A010100001A03030242019F008104 +020001032FC43939310010F4EC304B5358071005ED071005ED592213012301AA0208AAFD +F805D5F96D069300000100C7FEF2026F061400070030401003A901B205A900B108004304 +0602040810FC4BB00F544BB010545B58B90002004038593CDCEC310010FCECF4EC300111 +213533112335026FFE58EFEF0614F8DE8F06048F000100D903A805DB05D500060018400A +0304010081070301050710DCCC39310010F4CC3239300901230901230103BC021FC9FE48 +FE48C9021F05D5FDD3018BFE75022D000001FFECFE1D0414FEAC0003000FB500A9010002 +0410C4C43100D4EC30011521350414FBD8FEAC8F8F000000000100AA04F0028906660003 +0031400901B400B3040344010410DCEC310010F4EC30004BB009544BB00E545B58BD0004 +FFC00001000400040040381137385909012301016F011A99FEBA0666FE8A01760002007B +FFE3042D047B000A002500BC4027191F0B17090E00A91706B90E1120861FBA1CB923B811 +8C170C001703180D09080B1F030814452610FCECCCD4EC323211393931002FC4E4F4FCF4 +EC10C6EE10EE11391139123930406E301D301E301F3020302130223F27401D401E401F40 +2040214022501D501E501F50205021502250277027851D871E871F8720872185229027A0 +27F0271E301E301F30203021401E401F40204021501E501F50205021601E601F60206021 +701E701F70207021801E801F80208021185D015D0122061514163332363D01371123350E +01232226353436332135342623220607353E0133321602BEDFAC816F99B9B8B83FBC88AC +CBFDFB0102A79760B65465BE5AF3F00233667B6273D9B4294CFD81AA6661C1A2BDC0127F +8B2E2EAA2727FC00000200BAFFE304A40614000B001C0038401903B90C0F09B918158C0F +B81B971900121247180C06081A461D10FCEC3232F4EC31002FECE4F4C4EC10C6EE30B660 +1E801EA01E03015D013426232206151416333236013E0133320011100223222627152311 +3303E5A79292A7A79292A7FD8E3AB17BCC00FFFFCC7BB13AB9B9022FCBE7E7CBCBE7E702 +526461FEBCFEF8FEF8FEBC6164A8061400010071FFE303E7047B0019003F401B00860188 +040E860D880AB91104B917B8118C1A07120D004814451A10FCE432EC310010E4F4EC10FE +F4EE10F5EE30400B0F1B101B801B901BA01B05015D01152E012322061514163332363715 +0E0123220011100021321603E74E9D50B3C6C6B3509D4E4DA55DFDFED6012D010655A204 +35AC2B2BE3CDCDE32B2BAA2424013E010E0112013A23000000020071FFE3045A06140010 +001C003840191AB9000E14B905088C0EB801970317040008024711120B451D10FCECF4EC +323231002FECE4F4C4EC10C4EE30B6601E801EA01E03015D0111331123350E0123220211 +100033321601141633323635342623220603A2B8B83AB17CCBFF00FFCB7CB1FDC7A79292 +A8A89292A703B6025EF9ECA86461014401080108014461FE15CBE7E7CBCBE7E700020071 +FFE3047F047B0014001B00704024001501098608880515A90105B90C01BB18B912B80C8C +1C1B1502081508004B02120F451C10FCECF4ECC4111239310010E4F4ECE410EE10EE10F4 +EE1112393040293F1D701DA01DD01DF01D053F003F013F023F153F1B052C072F082F092C +0A6F006F016F026F156F1B095D71015D0115211E0133323637150E012320001110003332 +00072E0123220607047FFCB20CCDB76AC76263D06BFEF4FEC70129FCE20107B802A5889A +B90E025E5ABEC73434AE2A2C0138010A01130143FEDDC497B4AE9E000001002F000002F8 +061400130059401C0510010C08A906018700970E06BC0A02130700070905080D0F0B4C14 +10FC4BB00A5458B9000B004038594BB00E5458B9000BFFC038593CC4FC3CC4C412393931 +002FE432FCEC10EE321239393001B640155015A015035D01152322061D01211521112311 +2335333534363302F8B0634D012FFED1B9B0B0AEBD0614995068638FFC2F03D18F4EBBAB +00020071FE56045A047B000B0028004A4023190C1D0912861316B90F03B92623B827BC09 +B90FBD1A1D261900080C4706121220452910FCC4ECF4EC323231002FC4E4ECE4F4C4EC10 +FED5EE1112393930B6602A802AA02A03015D013426232206151416333236171002212226 +27351E013332363D010E0123220211101233321617353303A2A59594A5A59495A5B8FEFE +FA61AC51519E52B5B439B27CCEFCFCCE7CB239B8023DC8DCDCC8C7DCDCEBFEE2FEE91D1E +B32C2ABDBF5B6362013A01030104013A6263AA00000100BA000004640614001300344019 +030900030E0106870E11B80C970A010208004E0D09080B461410FCEC32F4EC31002F3CEC +F4C4EC1112173930B2601501015D0111231134262322061511231133113E013332160464 +B87C7C95ACB9B942B375C1C602A4FD5C029E9F9EBEA4FD870614FD9E6564EF00000200C1 +00000179061400030007002B400E06BE04B100BC020501080400460810FC3CEC3231002F +E4FCEC30400B1009400950096009700905015D1333112311331523C1B8B8B8B80460FBA0 +0614E9000002FFDBFE5601790614000B000F0044401C0B0207000EBE0C078705BD00BC0C +B110081005064F0D01080C00461010FC3CEC32E4391239310010ECE4F4EC10EE11123939 +30400B1011401150116011701105015D13331114062B01353332363511331523C1B8A3B5 +4631694CB8B80460FB8CD6C09C61990628E90000000100BA0000049C0614000A00BC4029 +0811050605071106060503110405040211050504420805020303BC009709060501040608 +010800460B10FCEC32D4C4113931002F3CECE41739304B5358071004ED071005ED071005 +ED071004ED5922B2100C01015D405F04020A081602270229052B08560266026708730277 +05820289058E08930296059708A3021209050906020B030A072803270428052B062B0740 +0C6803600C8903850489058D068F079A039707AA03A705B607C507D607F703F003F704F0 +041A5D71005D1333110133090123011123BAB90225EBFDAE026BF0FDC7B90614FC6901E3 +FDF4FDAC0223FDDD000100C100000179061400030022B7009702010800460410FCEC3100 +2FEC30400D10054005500560057005F00506015D13331123C1B8B80614F9EC00000100BA +0000071D047B0022005A4026061209180F00061D07150C871D2003B81BBC19100700110F +0808065011080F501C18081A462310FCEC32FCFCFCEC11123931002F3C3CE4F43CC4EC32 +111217393040133024502470249024A024A024BF24DF24FF2409015D013E013332161511 +231134262322061511231134262322061511231133153E01333216042945C082AFBEB972 +758FA6B972778DA6B9B93FB0797AAB03897C76F5E2FD5C029EA19CBEA4FD87029EA29BBF +A3FD870460AE67627C000000000100BA00000464047B001300364019030900030E010687 +0E11B80CBC0A010208004E0D09080B461410FCEC32F4EC31002F3CE4F4C4EC1112173930 +B46015CF1502015D0111231134262322061511231133153E013332160464B87C7C95ACB9 +B942B375C1C602A4FD5C029E9F9EBEA4FD870460AE6564EF00020071FFE30475047B000B +0017004A401306B91200B90CB8128C1809120F51031215451810FCECF4EC310010E4F4EC +10EE3040233F197B007B067F077F087F097F0A7F0B7B0C7F0D7F0E7F0F7F107F117B12A0 +19F01911015D012206151416333236353426273200111000232200111000027394ACAB95 +93ACAC93F00112FEEEF0F1FEEF011103DFE7C9C9E7E8C8C7E99CFEC8FEECFEEDFEC70139 +0113011401380000000200BAFE5604A4047B0010001C003E401B1AB9000E14B90508B80E +8C01BD03BC1D11120B471704000802461D10FCEC3232F4EC310010E4E4E4F4C4EC10C4EE +304009601E801EA01EE01E04015D2511231133153E013332001110022322260134262322 +061514163332360173B9B93AB17BCC00FFFFCC7BB10238A79292A7A79292A7A8FDAE060A +AA6461FEBCFEF8FEF8FEBC6101EBCBE7E7CBCBE7E700000000020071FE56045A047B000B +001C003E401B03B90C0F09B91815B80F8C1BBD19BC1D180C06081A47001212451D10FCEC +F4EC3232310010E4E4E4F4C4EC10C6EE304009601E801EA01EE01E04015D011416333236 +353426232206010E012322021110003332161735331123012FA79292A8A89292A702733A +B17CCBFF00FFCB7CB13AB8B8022FCBE7E7CBCBE7E7FDAE646101440108010801446164AA +F9F60000000100BA0000034A047B001100304014060B0700110B03870EB809BC070A0608 +0008461210FCC4EC3231002FE4F4ECC4D4CC11123930B450139F1302015D012E01232206 +1511231133153E0133321617034A1F492C9CA7B9B93ABA85132E1C03B41211CBBEFDB204 +60AE6663050500000001006FFFE303C7047B002700E7403C0D0C020E0B531F1E08090207 +0A531F1F1E420A0B1E1F041500860189041486158918B91104B925B8118C281E0A0B1F1B +0700521B080E07081422452810FCC4ECD4ECE4111239393939310010E4F4EC10FEF5EE10 +F5EE121739304B535807100EED111739070EED1117395922B2002701015D406D1C0A1C0B +1C0C2E092C0A2C0B2C0C3B093B0A3B0B3B0C0B200020012402280A280B2A132F142F152A +16281E281F292029212427860A860B860C860D12000000010202060A060B030C030D030E +030F03100319031A031B031C041D09272F293F295F297F2980299029A029F029185D005D +7101152E012322061514161F011E0115140623222627351E013332363534262F012E0135 +3436333216038B4EA85A898962943FC4A5F7D85AC36C66C661828C65AB40AB98E0CE66B4 +043FAE282854544049210E2A99899CB62323BE353559514B50250F2495829EAC1E000000 +00010037000002F2059E0013003840190E05080F03A9001101BC08870A0B080902040008 +10120E461410FC3CC4FC3CC432393931002FECF43CC4EC3211393930B2AF1501015D0111 +2115211114163B01152322263511233533110177017BFE854B73BDBDD5A28787059EFEC2 +8FFDA0894E9A9FD202608F013E000000000200AEFFE30458047B00130014003B401C0309 +00030E0106870E118C0A01BC14B80C0D0908140B4E020800461510FCECF439EC3231002F +E4E432F4C4EC1112173930B46F15C01502015D1311331114163332363511331123350E01 +23222601AEB87C7C95ADB8B843B175C1C801CF01BA02A6FD619F9FBEA4027BFBA0AC6663 +F003A8000001003D0000047F0460000600FB402703110405040211010205050402110302 +060006011100000642020300BF0506050302010504000710D44BB00A5458B90000004038 +594BB014544BB015545B58B90000FFC03859C4173931002FEC3239304B5358071005ED07 +1008ED071008ED071005ED592201408E48026A027B027F02860280029102A40208060006 +0109030904150015011A031A0426002601290329042008350035013A033A043008460046 +014903490446054806400856005601590359045008660066016903690467056806600875 +0074017B037B0475057A068500850189038904890586069600960197029A039804980597 +06A805A706B008C008DF08FF083E5D005D133309013301233DC3015E015EC3FE5CFA0460 +FC5403ACFBA0000000010056000006350460000C01EB404905550605090A0904550A0903 +550A0B0A025501020B0B0A061107080705110405080807021103020C000C011100000C42 +0A050203060300BF0B080C0B0A09080605040302010B07000D10D44BB00A544BB011545B +4BB012545B4BB013545B4BB00B545B58B9000000403859014BB00C544BB00D545B4BB010 +545B58B90000FFC03859CC173931002F3CEC32321739304B5358071005ED071008ED0710 +08ED071005ED071008ED071005ED0705ED071008ED59220140FF050216021605220A350A +49024905460A400A5B025B05550A500A6E026E05660A79027F0279057F05870299029805 +940ABC02BC05CE02C703CF051D0502090306040B050A080B09040B050C1502190316041A +051B081B09140B150C2500250123022703210425052206220725082709240A210B230C39 +0336043608390C300E460248034604400442054006400740084409440A440B400E400E56 +0056015602500451055206520750085309540A550B6300640165026A0365046A056A066A +076E09610B670C6F0E7500750179027D0378047D057A067F067A077F07780879097F097B +0A760B7D0C870288058F0E97009701940293039C049B05980698079908402F960C9F0EA6 +00A601A402A403AB04AB05A906A907AB08A40CAF0EB502B103BD04BB05B809BF0EC402C3 +03CC04CA05795D005D13331B01331B013301230B012356B8E6E5D9E6E5B8FEDBD9F1F2D9 +0460FC96036AFC96036AFBA00396FC6A0001003B000004790460000B0143404605110607 +06041103040707060411050401020103110202010B110001000A11090A0101000A110B0A +0708070911080807420A070401040800BF05020A0704010408000208060C10D44BB00A54 +4BB00F545B4BB010545B4BB011545B58B90006004038594BB0145458B90006FFC03859C4 +D4C411173931002F3CEC321739304B5358071005ED071008ED071008ED071005ED071005 +ED071008ED071008ED071005ED59220140980A04040A1A04150A260A3D04310A55045707 +580A660A76017A047607740A8D04820A99049F049707920A900AA601A904AF04A507A30A +A00A1C0A03040505090A0B1A03150515091A0B2903260525092A0B200D3A013903370534 +073609390B300D4903460545094A0B400D59005601590259035705560659075608560959 +0B500D6F0D78017F0D9B019407AB01A407B00DCF0DDF0DFF0D2F5D005D09022309012309 +013309010464FE6B01AAD9FEBAFEBAD901B3FE72D9012901290460FDDFFDC101B8FE4802 +4A0216FE71018F000001003DFE56047F0460000F018B40430708020911000F0A110B0A00 +000F0E110F000F0D110C0D00000F0D110E0D0A0B0A0C110B0B0A420D0B0910000B058703 +BD0E0BBC100E0D0C0A09060300080F040F0B1010D44BB00A544BB008545B58B9000B0040 +38594BB0145458B9000BFFC03859C4C4111739310010E432F4EC113911391239304B5358 +071005ED071008ED071008ED071005ED071008ED0705ED173259220140F0060005080609 +030D160A170D100D230D350D490A4F0A4E0D5A095A0A6A0A870D800D930D120A000A0906 +0B050C0B0E0B0F1701150210041005170A140B140C1A0E1A0F2700240124022004200529 +082809250A240B240C270D2A0E2A0F201137003501350230043005380A360B360C380D39 +0E390F30114100400140024003400440054006400740084209450A470D490E490F401154 +00510151025503500450055606550756085709570A550B550C590E590F50116601660268 +0A690E690F60117B08780E780F89008A09850B850C890D890E890F9909950B950C9A0E9A +0FA40BA40CAB0EAB0FB011CF11DF11FF11655D005D050E012B01353332363F0101330901 +3302934E947C936C4C543321FE3BC3015E015EC368C87A9A488654044EFC94036C000000 +00010058000003DB04600009009D401A081102030203110708074208A900BC03A9050803 +01000401060A10DC4BB00B544BB00C545B58B90006FFC038594BB0135458B90006004038 +59C432C411393931002FECF4EC304B5358071005ED071005ED5922014042050216022602 +47024907050B080F0B18031B082B08200B36033908300B40014002450340044005430857 +0359085F0B6001600266036004600562087F0B800BAF0B1B5D005D132115012115213501 +2171036AFD4C02B4FC7D02B4FD650460A8FCDB93A803250000010100FEB2041706140024 +00774034190F150B0625091A10151D0B05202103000BA90900A901C00915A913B1250C09 +0A05241619001D0A05130214002019430A0F052510D44BB00C5458B90005004038593CC4 +FC3CC43239391112391112393911123939310010FCECC4F4EC10EE121739123911393911 +1239111239393001B20026015D05152322263D0134262B01353332363D0134363B011523 +22061D011406071E011D0114163304173EF9A96C8E3D3D8F6BA9F93E448D565B6E6F5A56 +8DBE9094DDEF97748F7395F0DD938F588DF89D8E191B8E9CF88D580000010104FE1D01AE +061D00030012B70100B1040005020410D4EC310010FCCC300111231101AEAA061DF80008 +0000000000010100FEB2041706140024008740361F251B160C0F081B0B15190F04052003 +0019A91B00A923C01B0FA911B1251C191A150F010400081A15231204001A1F154310000B +042510D44BB00A5458B90004FFC038594BB00E5458B90004004038593CC432FC3CC41112 +39391112391112393911123939310010FCECC4F4EC10EE12173911123939113911393911 +12393001B20026015D053332363D013436372E013D0134262B01353332161D0114163B01 +152322061D0114062B010100468C555A6F6F5A558C463FF9A76C8E3E3E8E6CA7F93FBE56 +8FF89C8E1B198E9DF88E578F93DDF095738F7497EFDD9400000100D901D305DB0331001D +0023401001101B0C0013049C1B139C0C1E000F1E10D4C4310010D4FCD4EC10C011123939 +3001150E01232227262726272623220607353E01333217161716171633323605DB69B361 +6E920B05070F9B5E58AC6269B3616E930A05080E9B5E56A90331B24F443B040203053E4D +53B24F453C040203053E4C0000020135FE8B020004600003000900654011070083048102 +BC0A08070400030501000A10FC3CEC323939310010F4E4FCCC30014BB00B5458BD000A00 +400001000A000AFFC03811373859014BB00F544BB010545B4BB013545B58BD000AFFC000 +01000A000A00403811373859B6000B200B500B035D012335331123111333130200CBCBCB +15A2140362FEFA2B028F0165FE9B0000000200ACFEC704230598000600210051402B1316 +14000F0C010B078608880B10860F880CB914160BB91D1F1CB8168C221C1500091E130B0F +070412192210DCECD43CD43C3CEC3232310010E4F43CC4EC10C4FEF4EE10F5EE12391112 +391112393025110E0115141601152E0127033E0137150E01071123112600111000371133 +131E0102A693A4A402104A88440146894841894D66F1FEF70109F16601498983035812E2 +B8B9E203A1AC292A03FCA0052A27AA1E2307FEE4012014013301010102013216011FFEE1 +04210000000100810000046205F0001B00604021071608018600120AA914080C04A00094 +1991100CA00E000D090B071C130F15111C10DC3CCCCCFC3CC4D4C431002FEC32F4E4EC10 +D43CEE3210EE11393930014BB00C5458BD001CFFC00001001C001C00403811373859B436 +01360202005D01152E012322061D0121152111211521353311233533351036333216044E +4C883D94740187FE79022DFC1FECC7C7D6E83D9705B4B629299BD4D78FFE2FAAAA01D18F +EE0105F31F0000000002005E005204BC04B20023002F0083404903091B15042D1E00271C +02211D0C122D140B0A03130F011D2DB913EB0FEC27B91DEB21301E0C0012042A2414301C +151B2A1D131C180903240B0A0103022428027306742A281C73183010DCE4ECF4E4EC1217 +391239391112393912393911123911121739310010D4E4ECF4E4EC10C011121739123939 +1112393911393912173930013717071E01151406071707270E01232226270727372E0135 +3436372737173E01333216133426232206151416333236037BCF72CE25242628D172CF3B +743D3A783DCF71CF25252626CF73CF3774403C755C9B72709E9D71719C03E1D173CE3B77 +3E3F7339CF71CF28262525CF73CE3E763A407438CE73CF272524FE7C709A9A70729C9D00 +00010052000004C305D5001800C6404610021116110F020E0F1616110F02100F080D080E +020D0D08420F0B090400D31706120BD31409100D81020C090E0305160F03151210030011 +66130065011C0D660A056507031910D43CEC32ECFCEC32EC12173912393911173931002F +E432D43CEC32D43CEC32111239304B5358071005ED071008ED071008ED071005ED592201 +4BB00C5458BD0019FFC0000100190019004038113738594028860F900FA60FA00FB50F05 +270C270D270E291028112812370E3910870C8812A60DA50EAA10A9110E5D005D01211123 +112135213527213521013309013301211521071521048DFE63C9FE6001A054FEB40108FE +C3BE017B0179BFFEC20108FEB554019F01C7FE3901C77B339B7B024AFD4402BCFDB67B9B +3300000000020104FEA201AE059800030007001C400D01F50004F5050804000506020810 +DC3CEC32310010D4ECD4EC30011123111311231101AEAAAAAA0198FD0A02F60400FD0A02 +F60000000002005CFF3D03A205F0000B003E0091403C2F302A0600171D3036040D278A26 +0D8A0C2AC626C52310C60CC53C91233F2F0600173004131D2D0936031357392D57200957 +0C221A3926220357333F10DCECE4C4D4E4ECD4EC10EE113911123911173939310010C4F4 +E4EC10E6EE10EE10EE111739393911123930014BB00A544BB00B545B4BB00C545B4BB00E +545B58BD003F00400001003F003FFFC03811373859010E01151416173E0135342613152E +0123220615141716171E01151406071E0115140623222627351E0133323635342F012E01 +353436372E01353436333216017B3F3E8BFA3F3E8FCC538F38616CCE1A0ED3835C5D3E39 +CCAD499A5857943A6671DD19D6805D5B3B3BC8A6499903A82E5A2E4C85872D5B2E4B8802 +93A4272750475A730F08779A655A8C35346D408EA81D1DA42727544C667B0E7899665B8F +312C7045829F1D00000200D7054603290610000300070092400E0602CE0400CD08016400 +0564040810DCFCD4EC310010FC3CEC3230004BB00A544BB00D545B58BD00080040000100 +080008FFC03811373859014BB00C544BB00D545B4BB00E545B4BB017545B58BD0008FFC0 +00010008000800403811373859014BB00F544BB019545B58BD00080040000100080008FF +C03811373859401160016002600560067001700270057006085D0133152325331523025E +CBCBFE79CBCB0610CACACA000003011B000006E505CD0017002F0049004340263DCB3E3A +CC41CA2431CB3034CC47CA18C900C824C90C3761443D305E2A0906445E1E0906124A10DC +CCFCEC10FEED3210EE31002FEEF6FEFDEED6EE10FDEED6EE300132041716121514020706 +04232224272602353412373624172206070E01151416171E01333236373E01353426272E +0117152E0123220615141633323637150E0123222635343633321604009801076D6D6C6C +6D6DFEF99898FEF96D6D6C6C6D6D01079883E25E5E60605E5EE28384E35E5D5D5E5C5EE3 +A742824295A7AB9B407A42438946D8FBFBD8498805CD6E6D6DFEFA9A98FEFB6D6D6E6E6D +6D0105989A01066D6D6E675E5E5EE58281E35E5E5F5F5E5DE28385E35D5E5EF5812120AF +9D9FAE1F227F1D1CF4D0D1F21C0000000003007301D5033B05F00003001E0029005F4033 +280725041F12181002E3001FDD1000E125DD050A19DF18DE15DD0AE01C912A00180D1F10 +220602012811066B046C18226B0D2A10DCECCCFCEC3232C0C011123939111239310010F4 +E4FCF4EC10C4EEEDD6EE10EE11123912391139393013211521011123350E012322263534 +363B0135342623220607353E013332160522061514163332363D018B02B0FD5002AE952C +905D8098BFBCB675753E8844499145B7B3FEECA17E6252688202507B02B8FE40703F4487 +71878A045B5B22227F1C1CB0F0434F404D90721D0002009E008D042504230006000D0086 +404903E804050402E8010205050402E8030206000601E80000060AE80B0C0B09E808090C +0C0B09E80A090D070D08E807070D4209020B04E70700A60E090C05020703006F050A076F +0C6E0E10FCFC3CD4EC321139111239310010F43CEC323939304B5358071004ED071008ED +071008ED071004ED071004ED071008ED071008ED071004ED592201150901150135131509 +011501350425FED3012DFE2B23FED3012DFE2B0423BFFEF4FEF4BF01A25201A2BFFEF4FE +F4BF01A252000000000100D9011F05DB035E00050017400A049C020006031701000610DC +D4EC310010D4C4EC30132111231121D90502A8FBA6035EFDC10195000001006401DF027F +028300030011B6009C020401000410DCCC310010D4EC301321152164021BFDE50283A400 +0004011B000006E505CD0017002F0038004C006040364542433F32C94830C9394A43CA0C +39CA00C918C80CC924484533300431423C3F39364931604B3660433C5E12091E4B5E0609 +1E5F2A4D10DCE4FCEC10FEFDC4EE10EE32113939123912173931002FEEF6FEED10ED3210 +EED6EE3912393930012206070E01151416171E01333236373E01353426272E0127320417 +161215140207060423222427260235341237362413231133323635342627321615140607 +1E011F0123272E012B01112311040083E25E5E60605E5EE28384E35E5D5D5E5C5EE38498 +01076D6D6C6C6D6DFEF99898FEF96D6D6C6C6D6D01077D7B7B6E575866B0AE696018432E +89AC813B4936429B05665E5E5EE58281E35E5E5F5F5E5DE28385E35D5E5E676E6D6DFEFA +9A98FEFB6D6D6E6E6D6D0105989A01066D6D6EFE62FEEC3E4B4C3F677779567011084D49 +DFD16033FE9C0344000100D50562032B05F60003002FB702EF00EE0401000410D4CC3100 +10FCEC30004BB009544BB00E545B58BD0004FFC000010004000400403811373859132115 +21D50256FDAA05F694000000000200C30375033D05F0000B001A0020401106C315C400C3 +0C911B095A125B035A181B10DCECFCEC310010F4ECFCEC30012206151416333236353426 +273216171E011514062322263534360200506E6E50506E6F4F40762B2E2EB98687B4B805 +6F6F504F6D6D4F4F7081312E2D724284B7B48786BA000000000200D9000005DB0504000B +000F002E401805D007039C00D009010C9C0E0D02150400170C08150A061010D43CEC32FC +3CEC3231002FECD43CECFC3CEC300111211521112311213521110121152103AE022DFDD3 +A8FDD3022DFDD30502FAFE0504FE7DAAFE7D0183AA0183FBA6AA00000001005E029C02B4 +05F00018004A4024007D060400177D060604420402000EDD0F00DD02F70BDD0F12911900 +0E087E01150E031910DCC4D4C4EC1139310010F4C4ECFCEC10EE111239304B5358071005 +ED17320705ED5922012115213536370035342623220607353E0133321615140106010C01 +A8FDAA223F01586855347A484D853991AEFEB538030E726E1F3801315E425123237B1C1C +846C8BFEE430000000010062028D02CD05F00028004840270015130ADD091FDD2013DD15 +0DDD09F806F71CDD20F823912916130014197E26107E03141F092910DCC4C4D4ECD4EC11 +393939310010F4E4ECFCE4ECD4EC10EE10EE11123930011E0115140623222627351E0133 +32363534262B013533323635342623220607353E01333216151406020C5C65BEB1397D46 +3477436D786F6C565E5E61645F28665149803790A95A0460126D527C861514791B1A4F46 +4A4C6C3F3C3A3D1217731112766345600001017304EE0352066600030031400902B400B3 +040344010410D4EC310010F4EC30004BB009544BB00E545B58BD0004FFC0000100040004 +0040381137385901330123028BC7FEBA990666FE88000000000100AEFE5604E504600020 +004D402513191F03160603090C0301120F06871C168C0A01BC00BD2119091209080B4E1F +020800462110FCEC32F4ECC41239310010E4E432F43CECDCC41117391112173930B61F22 +6022CF2203015D13113311141633323635113311141633323637150E01232226270E0123 +22262711AEB88A879495B8232509201C29492345520F329162668F2AFE56060AFD489194 +A8A8028DFCA23C390B0C9417164E504F4F4E4EFDD70000000001009EFF3B043905D5000D +00254012080204C1008106020E00075D05035D010B0E10D4D4FCDCEC39310010C432F4EC +1139300121112311231123112E01353424027901C08DBE8ED7EB010405D5F966061FF9E1 +034E11DDB8BEE800000100DB024801AE034600030012B7028300040119000410D4EC3100 +10D4EC3013331523DBD3D30346FE000000010123FE7502C100000013001F400E09060A0D +F306001300102703091410DCD4ECD4CC31002FD4FCC4123930211E011514062322262735 +1E01333236353426270254373678762E572B224A2F3B3C2B2D3E6930595B0C0C83110F30 +2E1E573D00010089029C02C505DF000A002C40180700DD0903DD0402DD09F705910B087C +065D037C017C000B10DCF4E4FCE4310010F4ECECD4EC10EE323013331107353733113315 +219CCCDFE689CDFDD7030A0263297427FD2B6E000003006001D5036405F00003000F001B +002E401902E300E116DD0AE010DD04911C00130D01196B076C136B0D1C10DCECFCEC3911 +1239310010F4ECF4ECFCEC30132115210132161514062322263534361722061514163332 +363534268B02B0FD500158B3CECEB3B3D0D0B3697E7F68697D7C02507B041BDDBFBFDBDC +BEBFDD73A18885A0A08589A0000200C1008D044804230006000D008640490CE80D0C090A +090BE80A0A090DE80708070CE80B0C08080705E8060502030204E803030206E800010005 +E80405010100420C050A03E70700A60E0C08010500086F0A07016F0300700E10FC3CFCD4 +3CEC1239111239310010F43CEC323939304B5358071008ED071004ED071004ED071008ED +071008ED071004ED071004ED071008ED59221301150135090125011501350901C101D5FE +2B012DFED301B201D5FE2B012DFED30423FE5E52FE5EBF010C010CBFFE5E52FE5EBF010C +010C0000FFFF0089FFE3077F05F01026007B000010270B4F048BFD6410070B2603350000 +FFFF0089FFE3073F05F01026007B000010270074048BFD6410070B2603350000FFFF0062 +FFE3077F05F010260075000010270B4F048BFD6410070B26033500000002008FFE6E03AC +0460002000240086402F201A05020406190010860F880C002183230C9513BD23BC250622 +1916090501001A2209001C01221C21260F091C162510DCECD4FCECD4EC11123911123911 +12391239310010E4F4EC10FECD10F4EE123939173930014BB010544BB012545B4BB01354 +5B58BD0025FFC000010025002500403811373859400B7404740574067407761C055D0133 +1514060F010E0115141633323637150E012322263534363F013E01373E01351323353301 +F4BE375A5A3A33836D4EB4605EC067B8E04959583026080706C4CACA02CF9C6582575835 +5E31596E4643BC3938C29F4C8956562F3519153C36010EFEFFFF001000000568076B1226 +002400001007171904BC0175FFFF001000000568076B1226002400001007171704BC0175 +FFFF001000000568076D1226002400001107171A04BC01750010B4050D110A072B40050F +0D0011025D310000FFFF001000000568075E1226002400001107171804BC01750014B40A +142305072B400940144F2320142F23045D310000FFFF001000000568074E122600240000 +1107171604BC01750014B40A120D05072B400930123F0D00120F0D045D31000000030010 +00000568076D000B000E002100CB40540C110D0C1B1C1B0E111C1B1E111C1B1D111C1C1B +0D11210F210C110E0C0F0F2120110F211F11210F21420C1B0F0D0903C115091E950D098E +201C1E1D1C18201F210D12060E180C061B0056181C0F0656121C212210D4C4D4EC3210D4 +EE32113911391112391139391112393931002F3CE6D6EE10D4EE1112393939304B535807 +1005ED0705ED071008ED071005ED071005ED0705ED0705ED071008ED5922B2202301015D +40201A0C730C9B0C03070F081B5023660D690E750D7B0E791C791D7620762180230C5D00 +5D013426232206151416333236030121012E013534363332161514060701230321032303 +54593F4057583F3F5998FEF00221FE583D3E9F7372A13F3C0214D288FD5F88D5065A3F59 +57413F5858FEF3FD19034E29734973A0A172467629FA8B017FFE81000002000800000748 +05D5000F00130087403911110E0F0E10110F0F0E0D110F0E0C110E0F0E420595030B9511 +01951095008111079503AD0D0911100F0D0C050E0A00040806021C120A0E1410D4D43CEC +32D4C4C41112173931002F3CECECC4F4ECEC10EE10EE304B5358071005ED0705ED071005 +ED071005ED5922B2801501015D4013671177107711860C851096119015A015BF15095D01 +152111211521112115211121032301170121110735FD1B02C7FD3902F8FC3DFDF0A0CD02 +718BFEB601CB05D5AAFE46AAFDE3AA017FFE8105D59EFCF003100000FFFF0073FE750527 +05F01226002600001007007A012D0000FFFF00C90000048B076B12260028000010071719 +049E0175FFFF00C90000048B076B12260028000010071717049E0175FFFF00C90000048B +076D1226002800001107171A049E017500074003400C015D31000000FFFF00C90000048B +074E12260028000011071716049E017500094005400C4010025D3100FFFF003B000001BA +076B1226002C000010071719032F0175FFFF00A20000021F076B1226002C000010071717 +032F0175FFFFFFFE00000260076D1226002C00001107171A032F01750008B401060A0007 +2B310000FFFF000600000258074E1226002C000011071716032F01750008B4000A070107 +2B3100000002000A000005BA05D5000C0019006740201009A90B0D95008112950E0B0707 +011913040F0D161904320A110D1C0800791A10F43CEC32C4F4EC10C4173931002FC632EE +F6EE10EE32304028201B7F1BB01B039F099F0A9F0B9F0C9F0E9F0F9F109F11BF09BF0ABF +0BBF0CBF0EBF0FBF10BF11105D015D132120001110002901112335331311211521113320 +0011100021D301A001B10196FE69FE50FE60C9C9CB0150FEB0F30135011FFEE1FECB05D5 +FE97FE80FE7EFE9602BC9001E3FE1D90FDEA0118012E012C01170000FFFF00C900000533 +075E1226003100001107171804FE01750014B400132204072B400930133F2210131F2204 +5D310000FFFF0073FFE305D9076B1226003200001007171905270175FFFF0073FFE305D9 +076B1226003200001007171705270175FFFF0073FFE305D9076D1226003200001107171A +052701750010B40F1A1E15072B40051F1A101E025D310000FFFF0073FFE305D9075E1226 +0032000011071718052701750018B403213009072B400D30213F3020212F3010211F3006 +5D310000FFFF0073FFE305D9074E12260032000011071716052701750014B4031F1A0907 +2B4009401F4F1A101F1F1A045D31000000010119003F059C04C5000B0085404D0A9C0B0A +070807099C080807049C0304070706059C060706049C0504010201039C0202010B9C0001 +000A9C090A010100420A080706040201000805030B090C0B0A0907050403010802000806 +0C10D43CCC321739310010D43CCC321739304B5358071008ED071005ED071005ED071008 +ED071005ED071008ED071005ED071008ED59220902070901270901370901059CFE3701C9 +77FE35FE357601C8FE387601CB01CB044CFE35FE377901CBFE357901C901CB79FE3501CB +00030066FFBA05E5061700090013002B009E403C1D1F1A0D2B2C130A0100040D29262014 +0D042A261E1A0495260D951A91268C2C2B2C2A141710201E23130A0100041D2910071F07 +192333101917102C10FCECFCECC0111239391739123939111239391139310010E4F4EC10 +EE10C010C011123939123912173912391112393930402A57005A15571955216A1565217B +15761C7521094613590056136A006413641C6A287C007313761C7A280B5D015D09011E01 +333200113426272E01232200111416170726023510002132161737170716121510002122 +2627072704B6FD333EA15FDC010127793DA15FDCFEFD2727864E4F0179013B82DD57A266 +AA4E50FE88FEC680DD5BA2670458FCB240430148011A70B8B84043FEB8FEE570BC449E66 +0108A0016201A54D4BBF59C667FEF69EFE9FFE5B4B4BBF58FFFF00B2FFE30529076B1226 +003800001007171904EE0175FFFF00B2FFE30529076B1226003800001007171704EE0175 +FFFF00B2FFE30529076D1226003800001107171A04EE01750014B40A141800072B40092F +1420181F141018045D310000FFFF00B2FFE30529074E1226003800001107171604EE0175 +001CB401191409072B401150195F1440194F1420192F1410191F14085D310000FFFFFFFC +000004E7076B1226003C00001007171704730175000200C90000048D05D5000C0015003D +401B0E95090D9502F600810B150F090304011219063F0D0A011C00041610FCEC3232FCEC +11173931002FF4FCECD4EC3040090F171F173F175F1704015D1333113332041514042B01 +1123131133323635342623C9CAFEFB0101FEFFFBFECACAFE8D9A998E05D5FEF8E1DCDCE2 +FEAE0427FDD1928686910000000100BAFFE304AC0614002F009A40302D27210C04060D20 +00042A1686171AB9132AB90397138C2E0C090D1D2021270908242708061D082410162D08 +1000463010FCC4FCCC10C6EED4EE10EE1139391239123931002FE4FEEE10FED5EE121739 +17393040400F050F060F070F270F288A0C8A0D070A060A070A0B0A0C0A0D0A1F0D200A21 +0C220426190D191F19203A203A214D1F4D20492149226A1F6A20A506A507A620185D015D +133436333216170E011514161F011E0115140623222627351E013332363534262F012E01 +353436372E01232206151123BAEFDAD0DB0397A83A4139A660E1D3408849508C4174783B +655C6057A7970883718288BB0471C8DBE8E00873602F512A256A8E64ACB71918A41E1D5F +5B3F543E373B875B7FAC1D67708B83FB93000000FFFF007BFFE3042D0666122600440000 +110600435200000B40073F262F261F26035D3100FFFF007BFFE3042D0666122600440000 +110600765200000B40073F262F261F26035D3100FFFF007BFFE3042D0666122600440000 +1106028852000008B40B282C14072B31FFFF007BFFE3042D06371226004400001106029E +52000014B4142E3C0B072B4009202E2F3C102E1F3C045D31FFFF007BFFE3042D06101226 +004400001106006A52000020B4142D280B072B40157F286F28502D5F28402D4F28302D3F +28002D0F280A5D31FFFF007BFFE3042D07061226004400001106029C52000025400E262C +142C260B0732381438320B072B10C42B10C4310040093F353F2F0F350F2F045D30000000 +0003007BFFE3076F047B00060033003E01034043272D253D0E0D0034A925168615881200 +A90E3A12B91C192E862DBA2A03B90EBB07310AB81F198C253F343726060F0025371C0726 +0F1500080D3D26080F2D370822453F10FCECCCD4FC3CD4ECC41112393911391112391112 +39310010C4E432F43CC4E4FC3CF4EC10C4EE3210EE10F4EE10EE11391139111239304081 +302B302C302D302E302F3030402B402C402D402E402F4030502B502C502D502E502F5030 +852B853080409040A040B040C040D040E040E040F0401D3F003F063F0D3F0E3F0F05302C +302D302E302F402C402D402E402F502C502D502E502F6F006F066F0D6F0E6F0F602C602D +602E602F702C702D702E702F802C802D802E802F1D5D71015D012E0123220607033E0133 +32001D01211E0133323637150E01232226270E0123222635343633213534262322060735 +3E013332160322061514163332363D0106B601A58999B90E444AD484E20108FCB20CCCB7 +68C86464D06AA7F84D49D88FBDD2FDFB0102A79760B65465BE5A8ED5EFDFAC816F99B902 +9497B4AE9E01305A5EFEDDFA5ABFC83535AE2A2C79777878BBA8BDC0127F8B2E2EAA2727 +60FE18667B6273D9B4290000FFFF0071FE7503E7047B1226004600001007007A008F0000 +FFFF0071FFE3047F066612260048000010070043008B0000FFFF0071FFE3047F06661226 +0048000010070076008B0000FFFF0071FFE3047F066612260048000011070288008B0000 +0008B4151E221B072B310000FFFF0071FFE3047F06101226004800001107006A008B0000 +000740034020015D31000000FFFFFFC7000001A6066610270043FF1D0000120600F30000 +FFFF00900000026F066610270076FF1D0000120600F30000FFFFFFDE0000025C06661226 +00F3000011070288FF1D00000008B401070B00072B310000FFFFFFF40000024606101226 +00F300001107006AFF1D00000008B4000B0801072B31000000020071FFE304750614000E +00280127405E257B26251E231E247B23231E0F7B231E287B27281E231E26272827252425 +2828272223221F201F2120201F42282726252221201F08231E030F2303B91B09B9158C1B +23B1292627120C212018282523221F051E0F060C121251061218452910FCECF4EC113939 +173912393911123939310010ECC4F4EC10EE12391239121739304B535807100EC9071008 +C9071008C907100EC9071008ED070EED071005ED071008ED5922B23F2A01015D40761625 +2B1F28222F232F2429252D262D272A283625462558205821602060216622752075217522 +132523252426262627272836243625462445255A205A21622062217F007F017F027A037B +097F0A7F0B7F0C7F0D7F0E7F0F7F107F117F127F137F147B157A1B7A1C7F1D7F1E762076 +217822A02AF02A275D005D012E0123220615141633323635342613161215140023220011 +340033321617270527252733172517050346325829A7B9AE9291AE36097E72FEE4E6E7FE +E50114DD12342A9FFEC1210119B5E47F014D21FED903931110D8C3BCDEDEBC7ABC01268F +FEE0ADFFFEC9013700FFFA01370505B46B635CCC916F6162FFFF00BA0000046406371226 +005100001007029E00980000FFFF0071FFE3047506661226005200001006004373000000 +FFFF0071FFE3047506661226005200001006007673000000FFFF0071FFE3047506661226 +005200001106028873000008B40F1A1E15072B31FFFF0071FFE304750637122600520000 +1106029E73000014B415202E0F072B400920202F2E10201F2E045D31FFFF0071FFE30475 +06101226005200001106006A73000014B4031F1A09072B4009401F4F1A301F3F1A045D31 +000300D9009605DB046F00030007000B0029401400EA0206EA0402089C040A0C09050172 +0400080C10DCD43CFC3CC4310010D4C4FCC410EE10EE3001331523113315230121152102 +DFF6F6F6F6FDFA0502FAFE046FF6FE12F50241AA00030048FFA2049C04BC00090013002B +00E4403C2B2C261F1D1A130A0100040D292620140D042A261E1A04B9260DB91AB8268C2C +2B2C2A141710201E23130A01000410071F1D0712235129101217452C10FCEC32F4EC32C0 +11121739123939111239391139310010E4F4EC10EE10C010C01112393912391217391139 +3911123930407028013F2D5914561C551D56206A1566217F007B047F057F067F077F087F +097F0A7F0B7F0C7B0D7A157B1A7F1B7F1C7F1D7F1E7F1F7F207B217F227F237F247F257B +269B199525A819A02DF02D2659005613551D5A2869006613651C6A287A007413761C7A28 +891E95189A24A218AD24115D015D09011E01333236353426272E0123220615141617072E +01351000333216173717071E011510002322262707270389FE1929674193AC145C2A673E +97A913147D36360111F15D9F438B5F923536FEEEF060A13F8B600321FDB02A28E8C84F75 +9A2929EBD3486E2E974DC577011401383334A84FB34DC678FEEDFEC73433A84EFFFF00AE +FFE304580666122600580000100600437B000000FFFF00AEFFE304580666122600580000 +100600767B000000FFFF00AEFFE304580666122600580000110602887B000008B40B171B +01072B31FFFF00AEFFE3045806101226005800001106006A7B000018B4021B180A072B40 +0D401B4F18301B3F18001B0F18065D31FFFF003DFE56047F06661226005C000010060076 +5E000000000200BAFE5604A406140010001C003E401B14B905081AB9000E8C08B801BD03 +971D11120B471704000802461D10FCEC3232F4EC310010ECE4E4F4C4EC10C6EE30400960 +1E801EA01EE01E04015D2511231133113E01333200111002232226013426232206151416 +3332360173B9B93AB17BCC00FFFFCC7BB10238A79292A7A79292A7A8FDAE07BEFDA26461 +FEBCFEF8FEF8FEBC6101EBCBE7E7CBCBE7E70000FFFF003DFE56047F06101226005C0000 +1106006A5E000016B418171219072B400B30173F1220172F121F12055D310000FFFF0010 +0000056807311027007100BC013B1306002400000010B40E030209072B400540034F0202 +5D310000FFFF007BFFE3042D05F6102600714A001306004400000010B41803020F072B40 +056F027F03025D31FFFF00100000056807921027029A00CE014A1306002400000012B418 +000813072B310040056F006F08025D30FFFF007BFFE3042D061F1026029A4FD713060044 +00000008B422000819072B31FFFF0010FE7505A505D51226002400001007029D02E40000 +FFFF007BFE750480047B1226004400001007029D01BF0000FFFF0073FFE30527076B1226 +0026000010071717052D0175FFFF0071FFE303E706661226004600001007007600890000 +FFFF0073FFE30527076D1027171A054C01751306002600000009B204041E103C3D2F3100 +FFFF0071FFE303E706661226004600001007028800A40000FFFF0073FFE3052707501027 +171E054C0175120600260000FFFF0071FFE303E70614102702B804A40000120600460000 +FFFF0073FFE30527076D1226002600001107171B052D0175000740031F1D015D31000000 +FFFF0071FFE303E706661226004600001007028900890000FFFF00C9000005B0076D1027 +171B04EC0175120600270000FFFF0071FFE305DB06141226004700001107171505140000 +000B40075F1D3F1D1F1D035D31000000FFFF000A000005BA05D510060092000000020071 +FFE304F4061400180024004A40240703D30901F922B900161CB90D108C16B805970B021F +0C04030008080A0647191213452510FCECF43CC4FC173CC431002FECE4F4C4EC10C4EEFD +3CEE3230B660268026A02603015D01112135213533153315231123350E01232202111000 +33321601141633323635342623220603A2FEBA0146B89A9AB83AB17CCBFF00FFCB7CB1FD +C7A79292A8A89292A703B6014E7D93937DFAFCA86461014401080108014461FE15CBE7E7 +CBCBE7E7FFFF00C90000048B07331226002800001007007100A1013DFFFF0071FFE3047F +05F61027007100960000130600480000000740037000015D31000000FFFF00C90000048B +076D1027171D04A10175130600280000000740034000015D31000000FFFF0071FFE3047F +06481027029A00960000130600480000000740037000015D31000000FFFF00C90000048B +07501027171E049E0175120600280000FFFF0071FFE3047F0614102702B8049600001206 +00480000FFFF00C9FE75048D05D51226002800001007029D01CC0000FFFF0071FE75047F +047B1226004800001007029D01780000FFFF00C90000048B07671226002800001107171B +04A6016F00074003400C015D31000000FFFF0071FFE3047F066112260048000011070289 +0094FFFB0010B400211D0F072B40050F21001D025D310000FFFF0073FFE3058B076D1027 +171A055C01751306002A00000009B2040415103C3D2F3100FFFF0071FE56045A06661026 +028868001306004A00000009B204040A103C3D2F31000000FFFF0073FFE3058B076D1226 +002A00001007171D051B0175FFFF0071FE56045A06481226004A00001007029A008B0000 +FFFF0073FFE3058B07501027171E055C01751306002A000000080040033F00015D300000 +FFFF0071FE56045A0614102702B8046A00001206004A0000FFFF0073FE01058B05F01027 +02D7055EFFED1206002A0000FFFF0071FE56045A0634102702C303E0010C1206004A0000 +FFFF00C90000053B076D1027171A050201751306002B00000014B40C020607072B40092F +0220061F021006045D310000FFFFFFE500000464076D1027171A031601751306004B0000 +002AB414020613072B31004BB00E5158BB0014FFC00013FFC0383859400D901490138014 +801340144013065D000200C90000068B05D500130017003A401E060212950914110C9515 +AD0400810E0A070C17041C0538120D14011C001810DCEC3232CCFCEC3232CC31002F3CE4 +32FCECDC3232EC3232300133152135331533152311231121112311233533171521350171 +CA02DECAA8A8CAFD22CAA8A8CA02DE05D5E0E0E0A4FBAF02C7FD390451A4A4E0E0000000 +000100780000049F0614001B003E40210309000316010E12870D1506871619B810970A01 +0208004E130E11150908100B1C10DC32EC3232CCCCF4EC31002F3CECF4C4ECDC32EC3211 +1217393001112311342623220615112311233533353315211521113E01333216049FB87C +7C95ACB97D7DB90160FEA042B375C1C602A4FD5C029E9F9EBEA4FD8704F6A47A7AA4FEBC +6564EF00FFFFFFE400000278075E10271718032E01751306002C00000008B41E09181F07 +2B310000FFFFFFD30000026706371027029EFF1D0000130600F300000008B41C08161D07 +2B310000FFFF000300000259073110270071FF2E013B1306002C00000008B40403020507 +2B310000FFFFFFF20000024805F510270071FF1DFFFF130600F300000008B40403020507 +2B310000FFFFFFF500000267076D1027171D032E01751306002C00000008B40E00080F07 +2B310000FFFFFFE40000025606481027029AFF1D0000130600F300000008B40E00080F07 +2B310000FFFF00B0FE75022505D51027029DFF6400001206002C0000FFFF0096FE75020B +06141027029DFF4A00001206004C0000FFFF00C90000019507501226002C00001107171E +032F01750013B306010700103C103C3100B43F073F06025D30000000000200C100000179 +047B00030004002C400B04B800BF0204010800460510FCEC3931002FECE4304011040434 +0444041006400650066006700608015D1333112313C1B8B85C0460FBA0047B00FFFF00C9 +FE6603EF05D51027002D025C00001106002C00000008400311040110EC310000FFFF00C1 +FE5603B106141027004D023800001106004C00000008400319460110EC310000FFFFFF96 +FE66025F076D1027171A032E01751306002D00000008B408020607072B310000FFFFFFDB +FE56025C066610270288FF1D0000130601F900000008B408020607072B310000FFFF00C9 +FE1E056A05D5102702D7051B000A1206002E0000FFFF00BAFE1E049C0614102702D704AC +000A1206004E0000000100BA0000049C0460000A00BB4028081105060507110606050311 +040504021105050442080502030300BC09060501040608010800460B10FCEC32D4C41139 +31002F3CEC321739304B5358071004ED071005ED071005ED071004ED5922B2100C01015D +405F04020A081602270229052B0856026602670873027705820289058E08930296059708 +A3021209050906020B030A072803270428052B062B07400C6803600C8903850489058D06 +8F079A039707AA03A705B607C507D607F703F003F704F0041A5D71005D13331101330901 +23011123BAB90225EBFDAE026BF0FDC7B90460FE1B01E5FDF2FDAE0221FDDF00FFFF00C9 +0000046A076C10271717036E01761206002F0000FFFF00C10000024A076C10271717035A +01761306004F0000001EB10304103C31004BB00E5158B900000040385940079F008F004F +00035D30FFFF00C9FE1E046A05D5102702D7049B000A1206002F0000FFFF0088FE1E01AD +0614102702D7031E000A1306004F0000000740034000015D31000000FFFF00C90000046A +05D510271715029FFFC31206002F0000FFFF00C100000300061410271715023900021106 +004F0000000940058F001F00025D3100FFFF00C90000046A05D510270079023100771206 +002F0000FFFF00C10000028406141027007900D600731106004F000000174BB00D514BB0 +11534BB018515A5B58B9000000403859310000000001FFF20000047505D5000D003F401E +0C0B0A040302060006950081080304010B0E000405011C0C073A0900790E10F43CECC4FC +3CC411123911123931002FE4EC11173930B4300F500F02015D1333112517011121152111 +072737D3CB013950FE7702D7FC5E944DE105D5FD98DB6FFEEEFDE3AA023B6A6E9E000000 +00010002000002480614000B005E401A0A090804030206009706030401090A00047A0501 +080A7A07000C10D43CE4FC3CE411123911123931002FEC173930014BB0105458BD000C00 +400001000C000CFFC038113738594013100D400D500D600D73047A0A700DE00DF00D095D +133311371707112311072737C7B87D4CC9B87B4AC50614FDA65A6A8DFCE3029A586A8D00 +FFFF00C900000533076C1027171704C50176130600310000000740034F00015D31000000 +FFFF00BA00000464066D102600764207130600510000000940053F004F00025D31000000 +FFFF00C9FE1E053305D5102702D70500000A120600310000FFFF00BAFE1E0464047B1027 +02D70490000A120600510000FFFF00C900000533075F1226003100001107171B04F50167 +0014B4040F0B00072B40092F0F200B1F0F100B045D310000FFFF00BA0000046406661226 +0051000011070289008D00000010B40019150C072B40050F190015025D310000FFFF00CD +000005B905D510270051015500001006027E1B00000100C9FE56051905F0001C003B400D +191612181C1C120A051C07411D10FC4BB0105458B90007FFC03859EC32D4FCCC11310040 +0C199516B00702950E910881072FE4F4EC10F4EC30011021220615112311331536373633 +321219011407062B0135333236350450FECDB3D7CACA4E696A99E3E95152B55731664F03 +7F01ACFFDEFCB205D5F1864343FEC1FECCFC6FD561609C5AA0000000000100BAFE560464 +047B001F003B401C0D13000318150787061087181CB816BC15070D08004E131708164620 +10FCEC32F4ECC431002FE4F4C4ECD4EC1112173930B46021CF2102015D01111407062B01 +3533323736351134262322061511231133153637363332171604645251B5FEE96926267C +7C95ACB9B942595A75C1636302A4FD48D660609C30319902B29F9EBEA4FD870460AE6532 +32777800FFFF0073FFE305D90731102700710127013B1306003200000010B40D02030707 +2B40051F021003025D310000FFFF0071FFE3047505F51026007173FF1306005200000008 +B413020319072B31FFFF0073FFE305D9076D1027171D052701751306003200000010B411 +000817072B400510001F08025D310000FFFF0071FFE3047506481026029A730013060052 +00000008B41D080023072B31FFFF0073FFE305D9076B1027171F05270175120600320000 +FFFF0071FFE3047506661027029F00A00000120600520000000200730000080C05D50010 +0019003B401F059503110195008118079503AD091812100A1506021C1100040815190D10 +1A10FCECD4C4C4D4EC32123939393931002FECEC32F4EC3210EE30011521112115211121 +152120001110002117232000111000213307FAFD1A02C7FD3902F8FBD7FE4FFE4101BF01 +B16781FEBFFEC0014001418105D5AAFE46AAFDE3AA017C0170016D017CAAFEE1FEE0FEDF +FEDF000000030071FFE307C3047B0006002700330084403107080010860F880C00A9082E +0CB916132803B908BB22251FB819138C340600162231090F0008074B311209512B121C45 +3410FCECF4FCF4ECC4111239391239310010E432F43CC4E4EC3210C4EE3210EE10F4EE11 +12393040253F355F3570359F35CF35D035F035073F003F063F073F083F09056F006F066F +076F086F09055D71015D012E01232206070515211E0133323637150E01232226270E0123 +2200111000333216173E01333200252206151416333236353426070A02A48999B90E0348 +FCB20CCCB76AC86264D06AA0F25147D18CF1FEEF0111F18CD3424EE88FE20108FAB094AC +AB9593ACAC029498B3AE9E355ABEC73434AE2A2C6E6D6E6D01390113011401386F6C6B70 +FEDD87E7C9C9E7E8C8C7E900FFFF00C900000554076C1027171704950176120600350000 +FFFF00BA00000394066D1026007642071206005500000000FFFF00C9FE1E055405D51027 +02D70510000A120600350000FFFF0082FE1E034A047B102702D70318000A120600550000 +FFFF00C900000554075F1226003500001107171B047D016700080040035F1D015D300000 +FFFF00BA0000035A0666122600550000110602891B000010B411171309072B40050F1700 +13025D31FFFF0087FFE304A2076C1027171704950176120600360000FFFF006FFFE303C7 +066D1026007642071206005600000000FFFF0087FFE304A2076D1027171A049301751306 +00360000000BB404201529291049633A31000000FFFF006FFFE303C70666102602882500 +130600560000000BB404201529291049633A3100FFFF0087FE7504A205F0122600360000 +1007007A008B0000FFFF006FFE7503C7047B1226005600001006007A17000000FFFF0087 +FFE304A2076D1226003600001107171B048B0175000BB42B200E22221049633A31000000 +FFFF006FFFE303C70666122600560000110702BD04270000000BB42B200E22221049633A +31000000FFFFFFFAFE7504E905D51026007A50001206003700000000FFFF0037FE7502F2 +059E1026007AE1001206005700000000FFFFFFFA000004E9075F1226003700001107171B +047301670010B4010D0900072B310040035F08015D300000FFFF0037000002FE06821226 +005700001107171502370070000740038F14015D310000000001FFFA000004E905D5000F +00464018070B95040C09030F9500810905014007031C0C00400A0E1010D43CE4CCFC3CE4 +CC31002FF4EC3210D43CEC323001401300111F00100110021F0F1011401170119F11095D +032115211121152111231121352111210604EFFDEE0109FEF7CBFEF70109FDEE05D5AAFD +C0AAFDBF0241AA024000000000010037000002F2059E001D0043401F0816A90517041AA9 +00011BBC0D8710100D0E020608040008171B15191D461E10FC3C3CC432FC3C3CC4C43239 +3931002FECF43CC4FC3CDC3CEC3230B2AF1F01015D011121152115211521151417163B01 +15232227263D0123353335233533110177017BFE85017BFE85252673BDBDD55151878787 +87059EFEC28FE98EE98927279A504FD2E98EE98F013E0000FFFF00B2FFE30529075E1027 +171804EE01751306003800000010B41F091827072B400510091F18025D310000FFFF00AE +FFE3045806371027029E008300001306005800000008B41E081626072B310000FFFF00B2 +FFE3052907311027007100EE013B1306003800000014B40503020D072B40092F0220031F +021003045D310000FFFF00AEFFE3045805F5102700710083FFFF1306005800000008B406 +03020E072B310000FFFF00B2FFE30529076D1027171D04EE01751306003800000010B40F +000817072B400510001F08025D310000FFFF00AEFFE3045806481027029A008300001306 +005800000008B410000818072B310000FFFF00B2FFE30529076F1226003800001007029C +00F00069FFFF00AEFFE3045806CA1226005800001106029C7CC40009400540154021025D +31000000FFFF00B2FFE30529076B1027171F04EE0175120600380000FFFF00AEFFE3045E +06661027029F00B00000120600580000FFFF00B2FE75052905D51226003800001007029D +00FA0000FFFF00AEFE7504E8047B1226005800001007029D02270000FFFF0044000007A6 +07741027171A05F5017C1306003A00000008B415020614072B310000FFFF005600000635 +066D10270288014500071306005A00000008B415020614072B310000FFFFFFFC000004E7 +07741027171A0472017C1306003C00000008B40B020607072B310000FFFF003DFE56047F +066D102602885E071306005C00000008B418020617072B31FFFFFFFC000004E7074E1226 +003C000011071716047301750008B400100B04072B310000FFFF005C0000051F076C1027 +1717049501761206003D0000FFFF0058000003DB066D1026007642071206005D00000000 +FFFF005C0000051F07501027171E04BE01751206003D0000FFFF0058000003DB06141027 +02B8041700001306005D0000000E0140094F0A5F0AAF0ADF0A045D31FFFF005C0000051F +076D1226003D00001007171B04BE0175FFFF0058000003DB06661226005D000011060289 +1B000010B4010F0B00072B40050F0F000B025D310001002F000002F80614001000234012 +0B870A970102A905BC010A10080406024C1110FC3CCCFCCC31002FF4EC10F4EC30212311 +2335333534363B011523220706150198B9B0B0AEBDAEB063272603D18F4EBBAB99282967 +00020020FFE304A40614000F002C0044402504B910140CB9201C8C14B8222925A92C2427 +97222E45001218472A20062C2808252327462D10FC3CCCEC323232CCF4ECEC31002FF4DC +3CEC3210E4F4C4EC10C6EE30013427262322070615141716333237360136373633321716 +11100706232227262715231123353335331521152103E5535492925453535492925453FD +8E3A59587BCC7F80807FCC7B58593AB99A9AB90145FEBB022FCB74737374CBCB74737374 +0252643031A2A2FEF8FEF8A2A2313064A805047D93937D000003FF970000055005D50008 +00110029004340231900950A0995128101950AAD1F110B080213191F05000E1C1605191C +2E09001C12042A10FCEC32FCECD4EC111739393931002FECECF4EC10EE3930B20F220101 +5D01112132363534262301112132363534262325213216151406071E0115140423211122 +061D012335343601F70144A39D9DA3FEBC012B94919194FE0B0204E7FA807C95A5FEF0FB +FDE884769CC002C9FDDD878B8C850266FE3E6F727170A6C0B189A21420CB98C8DA05305F +693146B5A3000000FFFF00C9000004EC05D5120603A50000000200BAFFE304A406140016 +00260038401F1BB9000423B9100C8C04B81216A913971228451417120847101F16081346 +2710FCEC3232F4ECC4EC31002FF4EC10E4F4C4EC10C6EE30013637363332171611100706 +23222726271523112115250134272623220706151417163332373601733A59587BCC7F80 +807FCC7B58593AB9034EFD6B027253549292545353549292545303B6643031A2A2FEF8FE +F8A2A2313064A80614A601FCC0CB74737374CBCB7473737400020000000004EC05D5000A +00170033400C170B190019102E050B1C15162FDCEC32FCECC410CC31400905950CAD0B81 +069514002FECE4F4ECB315150B141112392F300134272623211121323736011121320415 +1404232111230104174F4EA3FEBC0144A34E4FFD7C014EFB0110FEF0FBFDE8C9013801B7 +8B4443FDDD444304A8FD9ADADEDDDA044401910000020000FFE304A406150012001E003E +400D111220131206470D1912080F102FDCEC3232F4ECC410CC31400E0016B903B80E0C1C +B9098C11970E002FE4F4ECC410F4ECC4B30F0F110E1112392F30013E0133320011100223 +22262715231123013301342623220615141633323601733AB17BCC00FFFFCC7BB13AB9BA +0122510272A79292A7A79292A703B66461FEBCFEF8FEF8FEBC6164A8044401D1FC1ACBE7 +E7CBCBE7E700000000010073FFE3052705F000190030401B19860088169503911A0D860C +881095098C1A1B10131906300D001A10DC3CF4ECEC310010F4ECF4EC10F4ECF4EC30133E +0133200011100021222627351E01332000111000212206077368ED8601530186FE7AFEAD +84ED6A66E78201000110FEF0FF0082E76605624747FE61FE98FE99FE614848D35F5E0139 +0127012801395E5F00010073FFE3065A0764002400444022219520250DA10EAE0A951101 +A100AE04951791118C25200719141B110D003014102510FCFC32EC10ECC4310010E4F4EC +F4EC10EEF6EE10DCEC30B40F261F2602015D01152E0123200011100021323637150E0123 +200011100021321716173637363B0115232206052766E782FF00FEF00110010082E7666A +ED84FEADFE7A01860153609C0D0C105366E34D3F866E0562D55F5EFEC7FED8FED9FEC75E +5FD34848019F01670168019F240304C3627AAA9600010071FFE304CC06140022004E4024 +00860188040E860D880AB91104B917B8118C2301871E972307121419081E0D0048144523 +10FCF432CCEC10EC310010F4EC10E4F4EC10FEF4EE10F5EE30400B0F24102480249024A0 +2405015D01152E0123220615141633323637150E012322001110002132173534363B0115 +23220603E74E9D50B3C6C6B3509D4E4DA55DFDFED6012D01064746A1B54530694C047EF5 +2B2BE3CDCDE32B2BAA2424013E010E0112013A0C0FD6C09C61000000FFFF000A000005BA +05D51006009200000002FF970000061405D50008001A002E401500950981019510080210 +0A0005190D32001C09041B10FCECF4EC113939393931002FECF4EC30B2601301015D0111 +332000111000212521200011100029011122061D012335343601F7F40135011FFEE1FECB +FE42019F01B20196FE68FE50FE6184769CC0052FFB770118012E012C0117A6FE97FE80FE +7EFE9605305F693146B5A300000200C9000004EC05D500070014002E400C160804131C0A +2E00190E101510FCECF4EC32C4C431400C139509810A049512AD03950A002FECF4EC10F4 +EC30011029011121220611211121222435342433211121019E01400144FEBCA39D034EFD +E8FBFEF00110FB014EFD7C01B7FEEF0223870393FA2BDADEDDDA01C000020071FFE3045A +06140012001E003F401D1CB9110E16B905088C0EB8031287019703190411080247001312 +0B451F10FCECC4F4EC323231002FFCEC10E4F4C4EC10C4EE30B660208020A02003015D01 +35211123350E012322021110003332161711011416333236353426232206010D034DB83A +B17CCBFF00FFCB7CB13AFD8DA79292A8A89292A7056EA6F9ECA864610144010801080144 +616401B9FCC0CBE7E7CBCBE7E700000000020071FE560474046300190027005440140D0C +0B202945170B12021A12175106201211452810FCECC4F4B27F17015DECD4EC10EC111239 +3900400E0D0C1D09060709B9041DB914B62810F4ECD4FCD4CC1112393940060025530C0D +0C070E10EC39313025161510212227351633323534252627261110003332000314020336 +262322061514161716173E01036B9DFE47DD7866F6F6FEF8D0758E0112EFF00113019B27 +01AB9494ACBC7E4033636E424F8DFEF0469946755C30257087010F010F0139FEC7FEED9C +FEFC01A0CBE5E8C3C2C70B060E2ADC00000100830000044505D5000B002B40090D05091C +000B07020C10DCC4C4D4EC32C431400C0A950B8102069507AD039502002FECF4EC10F4EC +300111213521112135211121350445FC3E02F8FD3902C7FD1A05D5FA2BAA021DAA01BAAA +00020075FFE305D905F00013001A0044402601140008A107AE04009514179511009514AD +04950B91118C1B01141A1A190F3314190700101B10FCC4ECF4EC111239310010E4F4ECF4 +E410EE10EE10F4EE11123930132110002122060735362433200011100021200037160033 +32003775048FFEEDFEEE8BFC706F010792015E018BFE88FEC6FEB7FE97DC0D00FFCACA00 +FF0D030C010C0132605FD74648FE67FE92FE9FFE5B01B7CCC3FEE4011CC30000000100A4 +FFE3047B05F00028004040240A8609880D9506912900169513AD291F8620881C95238C29 +2A14091F101903191926102910FCECD4ECD4C4C4CC310010F4ECF4EC10F4EC3910F4ECF4 +EC30012E0135342433321617152E012322061514163B011523220615141633323637150E +0123202435343601D8838E010CE659C97372BE5398A39E95B6AEA5B9C7BE6DC8546AC75E +FEE8FED0A3032521AB7CB2D12020B426247B737077A695848F963231C32525F2DD90C400 +0001FF96FE66042305D500110041401F1108120D950CB0120695040295008104AD121108 +00070C050107031C00041210FCEC32D4C4C411123939310010ECF4EC10EE10F4EC103939 +30B20F0B01015D13211521112115211110062B013533323635C9035AFD700250FDB0CDE3 +4D3F866E05D5AAFE48AAFD9FFEF2F4AA96C200000001FF7FFE5602F80614001B00654023 +130A0F870DBD1D0518011408A906018700971606BC1C021B0700070905081517134C1C10 +FC4BB00A5458B90013004038594BB0165458B90013FFC038593CC4FC3CC4C41239393100 +10E432FCEC10EE3212393910F4EC39393001B6401D501DA01D035D01152322061D012115 +211114062B013533323635112335333534363302F8B0634D012FFED1AEBDAEB0634DB0B0 +AEBD0614995068638FFBEBBBAB995068042A8F4EBBAB000000010073FFE3069707640026 +004940101502001C04111C1A34043321190B462710FCECFCF4EC10FCC4C4314018169515 +270005240195032495081BA11AAE1E950E91088C270010E4F4ECF4EC10FED4EE11393910 +DCEC3025112135211106042320001110002132161734363B01152322061D012E01232000 +11100021323604C3FEB6021275FEE6A0FEA2FE75018B015E5BA344C9E34D3F866E70FC8B +FEEEFEED011301126BA8D50191A6FD7F53550199016D016E01991919BCEAAA96C2D75F60 +FECEFED1FED2FECE2500000000020008FE52057605D5000F00250095400D275012011204 +19170C191F242610D4D4ECD4ECD45DC4B510080003040C1112173931400A00951BBD1125 +122481260010E4323232F4ECB31F17081B1112393930400C131111121208232511242408 +070510EC3C0710EC3CB613110812082408070810ECB623110824081208070810ECB41025 +1311230F40101615140317132408222120031F2312080407111217390711121739013237 +363534272627060706151417161301330116171615140706232227263534373637013302 +BF362C1C1F332C2C331F1C2C3601D9DEFDBA68432E4B649B9B644B2E4368FDBADEFEFD20 +14423949795C5C794939421420037A035EFBCFC8AE77428B415757418B4277AEC8043100 +000100BA000007470614002A004F40112C0D120408112A1508264E1F1B081D462B10FCEC +32F4ECC4C4CCD4EC3931004019088709271426008711151B260320111887200923B81E97 +111C2F3CECF43CC4EC1112173910EC12393910EC30253237363534272627351617161114 +002B012226351134262322061511231133113E013332161511141633054C9554574A3E79 +E06D6FFEE0DD46BB9D7C7C95ACB9B942B375C1C64C699C62659BDE705F21941D8F91FEEC +F5FEE6C8CE01089F9EBEA4FD870614FD9E6564EFE8FEF29367000000000100C9000002C6 +05D5000B002E40100B02000695008107050806011C00040C10FC4BB0105458B900000040 +3859ECC4393931002FE4EC113939300113331114163B011523222611C9CA6E863F4DE3CD +05D5FC2DC296AAF4010E00000001000A0000025205D5000B00454011020B95050800AF06 +0305011C0A0800040C10FC3CC44BB0105458BB0008004000000040383859EC32C431002F +ECDC3CF4323001400D300D400D500D600D8F0D9F0D065D133311331523112311233533C9 +CABFBFCABFBF05D5FD16AAFDBF0241AA000100C9000005F705F000170066400E001C0107 +080F07090B0F1C0E041810FCEC32D4C4113910D4EC00310040250B110809080A11090908 +11110708071011080807420B0810030E0C1702059513910EAF0C092F3CECF4EC39391112 +1739304B5358071004ED071005ED071005ED071004ED5922012335342623220709012101 +11231133110136333217161505F7AA49264625FDDD031AFEF6FD33CACA026C5571885555 +044879365023FDF9FCE302CFFD3105D5FD8902434F5C5B6E000100B90000049C06140012 +00CB400B040D090C0E10090800461310FCEC32D4C41139C43100400F42100D0A030B1106 +9503970BBC110E2F3CE4FCE411121739304B5358401410110D0E0D0F110E0E0D0B110C0D +0C0A110D0D0C071004ED071005ED071005ED071004ED59B2101401015D40350B0B0A0F28 +0B270C280D2B0E2B0F4014680B6014890B850C890D8D0E8F0F9A0B970FAA0BA70DB60FC5 +0FD60FF70BF00BF70CF00C1A5DB4090D090E0271004025040A0A10160A270A290D2B1056 +0A660A6710730A770D820A890D8E10930A960D9710A30A125D1334363B01152322061511 +0133090123011123B9A3B5BFA8694C0225EBFDAE026BF0FDC7B9047ED6C09C6199FDFF01 +E3FDF4FDAC0223FDDD0000000001000A0000022A0614000B003240070501080800460C10 +FC3CEC3231004008020BA905080097062FECD43CEC3230400D100D400D500D600D700DF0 +0D06015D133311331523112311233533C1B8B1B1B8B7B70614FD3890FD4402BC90000000 +0001003D0000047F0614000F00A0401308020B05010E070D080C06090406110C06001010 +D4C4B28006015DD4C410C4CC11121739B410094009025D3100400F08020B05010E060600 +040906970D002F3CF4C4C4111217393040320A03A902A90BA90508040C0709040F11000E +11010D060100051102110E110F0E011100010D110C070C0B11081107110D060D070510EC +ECEC071005EC08EC08EC05ECEC070810EC0510EC0708103C3CECEC0EFC3C330127052725 +273317251705012309013D01EB47FED42101294BC834013A21FEC901EDC3FEC6FE7E0432 +BC656363C58A686168FAD7033CFCC400000100B2FFE3072705D50027004A401200121420 +1D1C291F50121C14500A1C08042810FCECFCFCFCCCFC3C1112393100401607140A1C1100 +0621080E18952103248C28121D0881202FF43C3C10F43CC4EC32111217393039250E0123 +2227263511331114171633323635113311141716333237363511331123350E0123222726 +03A645C082AF5F5FCB2739758FA6CB3939777B5353CBCB3FB0797A5655D57C767B7AE204 +1BFBEFBA354EBEA403ECFBEFA24E4D5F60A303ECFA29AE67623E3E000001FF96FE660533 +05D50011008C402907110102010211060706420811000D950CB01207020300AF05060107 +021C04360B0E0C39071C00041210FCECE43939FCEC11393931002FEC32393910FCEC1139 +39304B5358071004ED071004ED5922B21F0B01015D403036023807480247076902660780 +02070601090615011A06460149065701580665016906790685018A0695019A069F13105D +005D13210111331121011110062B013533323635C901100296C4FEF0FD6ACDE3473F866E +05D5FB1F04E1FA2B04E1FB87FEF2F4AA96C20000FFFF00BAFE560464047B1006034B0000 +00030073FFE305D905F0000B001200190031400B19101906330F131900101A10FCEC32F4 +EC323100400F16950913950FAD1A0C950391098C1A10E4F4EC10F4EC10EC301310002120 +0011100021200001220007212602011A0133321213730179013A013B0178FE88FEC5FEC6 +FE8702B5CAFF000C03AC0EFEFD5608FBDCDCF80802E9016201A5FE5BFE9FFE9EFE5B01A4 +03C5FEE4C3C3011CFD7AFEFFFEC2013D01020000FFFF0067FFE3061D061410260032F400 +100702CC05A20134FFFF0076FFE304D304EB102702CC0458000B10060052050000020073 +FFE306CF05F00014001F0033401C049510AF0015950D91001B95078C0021131C001E1C10 +0418190A102010FCECD43CECDCECC431002FF4EC10F4EC10F4EC30211134262311062120 +001110002132172132161901012200111000333237112606056E7ABCFEC5FEC6FE870179 +013B70610127E3CDFC58DCFEFD0103DCAF808A03D3C296FB8BD301A40162016201A51BF4 +FEF2FC2D054CFEB8FEE6FEE5FEB867041846000000020071FE560559047B00160021003A +4020058711BC2217B90EB8221DB9088C16BD22110105231508011F08051A120B452210FC +ECD4ECDCECC4111239310010E4F4EC10F4EC10F4EC300111342726231106232200111000 +333217333217161511012206151416333237112604A126266989F0F1FEEF0111F16452D8 +B55251FD1A94ACAB95814054FE560474993130FCBC9D01390113011401381B6060D6FB8C +0589E7C9C9E73A02F03600000002FF97000004F105D50008001C003A4018019510009509 +8112100A0802040005190D3F11001C09041D10FCEC32FCEC11173931002FF4ECD4EC3040 +0B0F151F153F155F15AF1505015D011133323635342623252132041514042B0111231122 +061D012335343601F7FE8D9A9A8DFE3801C8FB0101FEFFFBFECA84769CC0052FFDCF9287 +8692A6E3DBDDE2FDA805305F693146B5A3000000000200B9FE5604A4061400180024004F +402423B900171DB90E11B8178C01BD25030C09A90697251A12144706090307200C000802 +462510FCEC3232CC113939F4EC310010F4EC393910E4E4F4C4EC10C4EE30400960268026 +A026E02604015D2511231134363B01152322061D013E0133320011100223222601342623 +22061514163332360173BAA3B5FEE7694C3AB17BCC00FFFFCC7BB10238A79292A7A79292 +A7A8FDAE0628D6C09C6199C86461FEBCFEF8FEF8FEBC6101EBCBE7E7CBCBE7E7000200C9 +FEF8055405D50015001D005640170506031300091D1810050A1A1904133F0E160A120C04 +1E10FCEC3232FCC4EC1117391139393931004010001706030417950916950F81040D810B +2FECDCF4ECD4EC123939123930014009201F401F75047C05025D011E01171323032E012B +0111231133113320161514060111333236102623038D417B3ECDD9BF4A8B78DCCACAFE01 +00FC83FD89FE8D9A998E01B416907EFE68017F9662FE9105D5FEF8D6D88DBA024FFDD192 +010C910000010072FFE3048D05F0002100644011071819061D0A0F1D19042D00220A1915 +2210DCECE4FCECC4111239393939310040194219180706040E21000EA10F940C95112095 +00940291118C2210E4F4E4EC10EEF6EE10CE111739304B5358400A180207060719020606 +0707100EED07100EED591336200410060F010E0114163332371504232027263534363F01 +3637363427262007CCE401C60117CAE27B9A87BCADE1F8FEFDD6FEE79291D7E27AA63C3B +595AFEA1E405A44CE4FE8FC02D181F7CEC888BD05F7070D9B6D92B191F3233D940406D00 +00010064FFE303BC047B002700CF40110A1E1D090D21142108060D0800521A452810FCE4 +ECD4ECC41112393939393140191E1D0A09041300862789241486138910B91724B903B817 +8C280010E4F4EC10FEF5EE10F5EE1217393040121B1C021A1D53090A201F02211E530A0A +09424B535807100EED111739070EED1117395922B2000101015D40112F293F295F297F29 +80299029A029F029085D4025200020272426281E281D2A152F142F132A12280A28092908 +29072401861E861D861C861B12005D40171C1E1C1D1C1C2E1F2C1E2C1D2C1C3B1F3B1E3B +1D3B1C0B71133E013332161514060F010E0115141633323637150E012322263534363F01 +3E0135342623220607A04CB466CEE098AB40AB658C8261C6666CC35AD8F7A5C43F946289 +895AA84E043F1E1EAC9E8295240F25504B51593535BE2323B69C89992A0E214940545428 +28000000FFFF00C90000048B05D51006033700000002FEF2FE5602D706140016001F0036 +400C1D0E0A1506140108170A4F2010FC32FC32CCCC10D4CC3100400F141F87000B1B8710 +9720048706BD2010FCEC10F4ECD43CEC3230011114163B01152322263511232035342132 +171617331525262726232207063301774D63B0AEBDAEBEFEF2012FB5523512BFFE860811 +216E7C030377046AFB3D685099ABBB04AED2D860406F9B9A2C1830413300000000010037 +FE5602F2059E001D003F400E0E14080802090400081A1C18461E10FC3CC4FC3CDC3239FC +CC310040121805081903A9001B01BC08871510870EBD152FFCEC10ECF43CCCEC32113939 +3001112115211114163B011514062B0135333237363D0122263511233533110177017BFE +854B73BDA4B446306A2626D5A78787059EFEC28FFDA0894EAED6C09C303199149FD20260 +8F013E0000010018000004E905D5000F005840150D0A0C06029500810400070140031C05 +0B1C0D051010D4D4EC10FCE4393931002FF4EC32C4393930014BB00A5458BD0010004000 +0100100010FFC03811373859401300111F00100110021F071011401170119F11095D0121 +15211123112322061D012335343601AE033BFDEECB5E84769CC005D5AAFAD5052B5A6931 +46B5A30000010037000002F20614001B0049401019160B080417090204000810130E461C +10FC3CC4FC3CC432321739310040131300198716970A0E05080F03A91101BC08870A2FEC +F43CEC3211393910F4EC393930B2AF1501015D01152115211114163B0115232226351123 +35333534363B01152322060177017BFE854B73BDBDD5A28787AEBDAEB0634D04C3638FFD +A0894E9A9FD202608F4EBBAB995100000001FFFAFE6604E905D5000F0054401407950ABD +100E0295008110080140031C00400D1010D4E4FCE4C4310010F4EC3210F4EC30014BB00A +5458BD00100040000100100010FFC03811373859401300111F00100110021F0F10114011 +70119F11095D032115211114163B01152322261901210604EFFDEE6E863F4EE3CDFDEE05 +D5AAFB3DC296AAF4010E04C3FFFF00ADFFF7065F061410260038FB14100702CC05E40134 +FFFF00B0FFE3056904EB102702CC04EE000B1006005802000001004EFFE305CF05CA001F +003A40101D1A1921100004330A1114190D0A102010FCC4FCC410F4C4ECFCC43100400E0D +11011D951E1081201795078C2010F4EC10FC3CEC32323230012116121510002120001134 +123721352115060215140033320035340227352105CFFEC0A18EFE7FFED1FECFFE81919E +FEC10258B2C70109D8D80108C6B1025805188DFED8C2FECBFE77018A013EB8012A8BB2B2 +61FEB4CAEFFEDD0122F0CA014C61B200000100C9FFE1057605D5001B002D400D10150C07 +0803190C181C15041C10FCECD4EC2F3C111239310040090816811C0095108C1C10F4EC10 +ECC4302532003534272627351716121510070621272627261901331114163302C6D80108 +63416EB3A18EC0BFFECF4DE86167CA6E868D0122F0CAA66D5744018DFED8C2FECBC5C402 +06747A010E03F0FC10C296000001FFFC000005F005F000170064400F131C140C040B0700 +40051C0940071810D4E4FCE41239C4392FEC3100400B12151400950E910B09AF062FEC39 +F4ECCC39393040190C110405040B110A0B0505040B110C0B0809080A11090908424B5358 +071005ED071008ED071008ED071005ED5922012207060701112311013309013633321716 +1D012335342604D739152511FE84CBFDF0D9019E014E5AA3885555AA4905470E1819FDBF +FD3902C7030EFD9A01F9885C5B6E8379365000000001003DFE5605D8047B001F016A4017 +120E151B1F1808151F0E0D0C0A09060300081F041F0B2010D44BB00A544BB008545B58B9 +000B004038594BB0145458B9000BFFC03859C4C411173910D4EC11391112393100403A07 +08020911001F0A110B0A00001F0E111D001F0D110C0D00001F0D110E0D0A0B0A0C110B0B +0A420D0B0920000B058703BD201BB912B80BBC172010C4E4F4EC10F4EC11391139123930 +4B5358071005ED071008ED071008ED071005ED071008ED0705ED1732592201408D0A000A +09060B050C1701150210041005170A140B140C2700240124022004200529082809250A24 +0B240C270D37003501350230043005380A360B360C380D41004001400240034004400540 +06400740084209450A470D5400510151025503500450055606550756085709570A550B55 +0C66016602680A7B0889008A09850B850C890D9909950B950CA40BA40C465D0040250600 +05080609030D160A170D100D230D350D490A4F0A4E0D5A095A0A6A0A870D800D930D125D +050E012B01353332363F01013309013637363332161D0123353426232207060702934E94 +7C936C4C543321FE3BC3015E011A1530588783B9B251393929140A68C87A9A488654044E +FC9402C0343360BF8672723A542A14190001005C0000051F05D5001100C0403506030207 +020C0F100B1007110B100B101102070242050D95040E12109500810795090C06030F040E +04080E00100700014208000A1210DC4BB009544BB00A545B58B9000AFFC03859C4D4E411 +393910C410C411173931002FECF4EC10D43CEC32304B5358071005ED071005ED0710053C +3C0710053C3C592201404005020A0B180B2902260B380B4802470B48100905070B100013 +16071A1010132F13350739103F1347074A104F1355075911660769106F13770778107F13 +9F13165D005D132115012115210121152135012135210121730495FE700119FE73FE5403 +C7FB3D01B9FED5019F0183FC6705D59AFE1190FDEEAA9A02229001DF00010058000003DB +0460001100C540310C0F100B100603020702101102070207110B100B4210A900BC09050D +A9040E07A90910070F03060C0601000E0408010A1210DC4BB00B544BB00C545B58B9000A +FFC038594BB0135458B9000A00403859C432C4C4C411173931002FECD43CEC3210F4EC30 +4B5358071005ED071005ED0710053C3C0710053C3C59220140420502160226024702490B +050B100F1318071B102B102013360739103013400140024507400840094310570759105F +136001600266076008600962107F138013AF131B5D005D13211503331521012115213501 +233521012171036AFBC2FEC2FEC302B4FC7D012BD40150010DFD650460A8FEDC90FE8F93 +A8015C9001390000000100A0FFC104F805D500220070400E0B0E0D080A04190E10160A0D +1E2310DCC4C4D439C4EC1239B43F0E4F0E025D111239310040130A0995100F0B950D8123 +1FA11EAE00951A8C2310F4ECF4EC10F4EC39D4EC3930400A10110A0B0A0B110F100F0710 +05EC071005EC400E090A370F0205100B0B15103B0B04015D005D25323736353427262B01 +3501213521150132171617161514070621222726273516171602A8C063645C5DA5AE0181 +FCFC0400FE656A806256519898FEE8777D7E866A7F7E6B4B4B8F86494A9801EAAA9AFE16 +382A6D688ADC7A79131225C3311919000001005CFFC104B405D50022005E400F1816151B +1F130D1916051F19150D2310DCC4B430154015025DECD4C4C41139113911123931004013 +191B951314189516812304A105AE0095098C2310F4ECF4EC10F4EC39D4EC3930400A1311 +1918191811141314071005EC071005EC2532373637150607062320272635343736373633 +01352115210115232207061514171602AC897E7F6A867E7D77FEE89898515662806AFE65 +0400FCFC0181AEA55D5C64636B191931C3251213797ADC8A686D2A3801EA9AAAFE16984A +49868F4B4B00000000010068FE4C043F0460002000A3400B0006020C121B130306022110 +DCCCC4C4D4EC1112393100401A0C1B0018064200A90707032104A9031386149310B918BD +03BC2110E4FCECF4EC10EC1112392FECEC111239393040080611000511010702070510EC +0410EC401B03050500140516002305250037003405460043055B0054057E000D015D401B +040604011406140125062401350137064501460654015C067F060D005D4009061507161A +151A12045D09013521152101152322070615141716333236371506070623202435343736 +3736025BFE65036AFD6501AEAEA55D5C6463BE6DC8546A64635EFEE8FED05156628001DC +01DCA893FE0DA64A4B848F4B4B3231C3251312F2DD8A686D2A38000000010071FE5603E8 +046000200000013237363715060706232011342524353423302101213521150120151005 +061514027F544D4F5157505661FE200196011CEBFEDE01E5FD65036AFE9E016FFE30E2FE +EE15152CB3200D0E0119EE3525627C023893A8FE64E5FEEC3118618B000100960000044A +05F00024000025211521350137213521363736353427262322070607353E013332041514 +07060733152307018902C1FC4C013A73FEA701E25F25275354865F696A787AD458E80114 +221F4A68EC30AAAAAA014075906D484C49774B4B212143CC3132E8C25C52496090310000 +0001005DFFC104F905D500190035400E1B0308110A0B080700081907461A10FCD4EC10EC +D4D4ECCC3100400D169501001A06950D0B9509811A10F4ECD4EC10CCD4EC300110201134 +262321112115211125241716100F010607062024350126030AB9A5FDF703A1FD29017301 +00A2513B1C142D98FDC4FED00190FEDB01258693032CAAFE250101D068FEE056291D2479 +F2DD000000010068FE4C043F0460001A0033400B1C0408120A0C081A08461B10FCC4ECD4 +D4ECCC3100400F0287001A18BD1B07870E0C870ABC1B10F4ECD4EC10FCCC32EC30171633 +201134262321112115211133321E01100F0106070621222768AACE0196B9A5FE9F0319FD +9FDD69E4A63B1C142D98FEE8BBD4A76301258693032CAAFE2663D4FEE056291D24794A00 +00010058FFE303A5059E0024000001071617161514070621222726273516171633323736 +373427262B01132335331133113315022102AA706C6E89FEED5551514C49544E50B36339 +013A56C03E02E5E5CAE703E67D1E7773AABA7D9D121123AC281816724185624C72010FA4 +0114FEECA4000000000200BAFE5604A4047B000E00170040400B1911080D041700080246 +1810FCEC3232D4ECCC3100400C42158705098C03BC0001BD1810ECC4F4F4CCEC304B5358 +B617050F8700000E070410ED0010CC590511231133153637363332171615100100353427 +262322070173B9B9348751D2B84D4EFCCF0272393878DCAD7AFED0060AAA425231707199 +FE57FEE40190F9854241EF00000100C9FE56019305D500030026400A009702BD04010800 +460410FCEC310010ECEC30400D10054005500560057005F00506015D13331123C9CACA05 +D5F88100FFFF00C9FE56032705D5102701820194000010060182000000010014FE56039C +05D50013003A401D0C09A90F061302A91005050A00970ABD14070309050108120D0C1000 +1410D43C3CCC32FC3C3CCC32310010ECEC11392F3CEC32DC3CEC32300133112115211521 +1521112311213521352135210173CA015FFEA1015FFEA1CAFEA1015FFEA1015F05D5FD97 +A8F0AAFD2C02D4AAF0A80000FFFF00C90000019405D5100600049400FFFF00C900000AD0 +076D1027013F05B10000100600270000FFFF00C9000009B006661027014005D500001006 +00270000FFFF0071FFE3089106661027014004B60000100600470000FFFF00C9FE660624 +05D51027002D049100001006002F0000FFFF00C9FE5605DE06141027004D046500001006 +002F0000FFFF00C1FE5602EF06141027004D017600001006004F0000FFFF00C9FE6606F2 +05D51027002D055F0000100600310000FFFF00C9FE5606B706141027004D053E00001006 +00310000FFFF00BAFE5605DE06141027004D04650000100600510000FFFF001000000568 +076D1226002400001107171B04BE01750006B10E00103C31FFFF007BFFE3042D06661226 +00440000110602895A000008B40B2B2714072B31FFFFFFFE00000260076D1226002C0000 +1107171B032F0175000BB407200100001049633A31000000FFFFFFE00000025E06661226 +00F3000011070289FF1F0000000BB408200100001049633A31000000FFFF0073FFE305D9 +076D1226003200001007171B05270175FFFF0071FFE30475066612260052000011060289 +76000006B11B0C103C310000FFFF00B2FFE30529076D1226003800001107171B04F60175 +0006B11505103C31FFFF00AEFFE304580666122600580000110602897600000BB418200B +01011049633A3100FFFF00B2FFE3052908331026174930001206003800000000FFFF00AE +FFE30458073110270071007B013B120600BE0000FFFF00B2FFE30529085A122600380000 +1006174C36000000FFFF00AEFFE3045807221226005800001007174CFFBEFEC8FFFF00B2 +FFE30529085A1226003800001006175130000000FFFF00AEFFE304580722122600580000 +10071751FFC4FEC8FFFF00B2FFE3052908601226003800001006174D30060000FFFF00AE +FFE3045807221226005800001007174DFFBEFEC8FFFF0071FFE3047F047B1206021B0000 +FFFF00100000056808331226002400001006174900000000FFFF007BFFE3042D07311226 +00A60000100700710052013BFFFF00100000056808331226002400001006174B00000000 +FFFF007BFFE3042D06F41226004400001007174BFF93FEC1FFFF00080000074807341027 +007102D7013E120600880000FFFF007BFFE3076F05F21027007101E8FFFC120600A80000 +00010073FFE3060405F00025005440102124221E1C11340200043318190B102610FCECFC +3CCCE4FCC4C431004018041F012200051B2395251B950812A111AE15950E91088C2610E4 +F4ECF4EC10FED4EE113939DCB00B4B5458B1224038593CCC323001113315231506042320 +0011100021320417152E012320001110002132363735233533352135058B797975FEE6A0 +FEA2FE75018B015E9201076F70FC8BFEEEFEED011301126BA843FDFDFEB6030CFED658FF +53550199016D016E01994846D75F60FECEFED1FED2FECE2527B55884A600000000020071 +FE5604FA047B000B00340058400E0F22322500080C470612182C453510FCC4ECF4EC3232 +C4C43100401B20110E23250C29091886191CB91503B9322FB833BC09B915BD26292FC4E4 +ECE4F4C4EC10FED5EE11123939D43CCC3230B660368036A03603015D0134262322061514 +1633323617140733152306070621222627351E013332373637213521363D010E01232202 +11101233321617353303A2A59594A5A59495A5B813B3C61F3A7FFEFA61AC51519E52B55A +1511FD84029A1639B27CCEFCFCCE7CB239B8023DC8DCDCC8C7DCDCEB6E58465D408C1D1E +B32C2A5F171C45475E5B6362013A01030104013A6263AA00FFFF0073FFE3058B076D1226 +002A00001107171B054A01750010B1210E103C4007942154212421035D310000FFFF0071 +FE56045A0663102602894AFD1206004A00000000FFFF00C90000056A076D1027171B04A2 +01751206002E0000FFFFFFE90000049C076D1226004E00001107171B031A0175002AB401 +100C00072B31004BB00E5158BB0001FFC00000FFC0383859400D90019000800180004001 +4000065DFFFF0073FE7505D905F01027029D01340000120600320000FFFF0071FE750475 +047B1027029D00800000120600520000FFFF0073FE7505D90731102700710127013B1206 +01AC0000FFFF0071FE75047505F51026007173FF120601AD00000000FFFF00A0FFC104F8 +076D1027171B04BE0175120601790000FFFF0058FE4C042F0666102602891B0010060254 +00000000FFFFFFDBFE560264066610270289FF250000110601F90000000BB40320080707 +1049633A31000000FFFF00C900000AD005D51027003D05B10000100600270000FFFF00C9 +000009B005D51027005D05D50000100600270000FFFF0071FFE3089106141027005D04B6 +0000100600470000FFFF0073FFE3058B076C10271717051B01761206002A0000FFFF0071 +FE56045A06631226004A0000100600761BFD0000000100C9FFE3082D05D5001D0035400E +0E1C1119031C06381B011C00041E10FCEC32FCEC32D4EC3100400E0F1A9502AD0400811C +0A95158C1C2FE4EC10E432FCECC430133311211133111417161732373635113311140706 +212027263511211123C9CA02DECA3E3D9994423ECA6460FEE6FEED6764FD22CA05D5FD9C +0264FBEC9F504E014F4BA4029FFD5ADF80787876E9010DFD39000000000200C9FE560502 +05F0000E00170040400B19111C0D0417001C02041810FCEC3232D4ECCC3100400C421595 +05098C03810001BD1810ECC4F4F4CCEC304B5358B617050F8700000E070410ED0010CC59 +2511231133153637363332171615100100113427262322030193CACA389157E2C65354FC +9102A13D3C81EDBA9CFDBA077FB9485735787AA4FE37FECE01AE010C8F4746FEFF000000 +FFFF00C900000533076B10271719051E0175120600310000FFFF00BA0000046406641226 +00510000100700430118FFFEFFFF001000000568077312260087000010071717065C017D +FFFF007BFFE304DC0773122600A700001007171705EC017DFFFF000800000748076C1027 +1717065C0176120600880000FFFF007BFFE3076F0663122600A80000100700760165FFFD +FFFF0066FFBA05E5076C1027171704FE01761206009A0000FFFF0048FFA2049C06631226 +00BA0000100600761CFD0000FFFF00100000056807701226002400001007172004E5017A +FFFF007BFFE3042D0664102702C00498FFFE120600440000FFFF00100000056807361226 +002400001007171C04BC013EFFFF007BFFE3042D0648102702C204650000120600440000 +FFFF00C90000048B07701226002800001007172004A5017AFFFF0071FFE3047F06631027 +02C004BAFFFD120600480000FFFF00C90000048B07361226002800001007171C04A6013E +FFFF0071FFE3047F0648102702C204A90000120600480000FFFFFFA70000027307701226 +002C0000100717200359017AFFFFFFC3000002810663102702C00366FFFD120600F30000 +FFFF00050000027707361226002C00001007171C033E013EFFFFFFE30000025506481027 +02C203240000120600F30000FFFF0073FFE305D90770122600320000100717200541017A +FFFF0071FFE304750664102702C0049FFFFE120600520000FFFF0073FFE305D907361226 +003200001007171C051C013EFFFF0071FFE304750648102702C204980000120600520000 +FFFF00C7000005540770122600350000100717200479017AFFFF00820000034A06631027 +02C00425FFFD120600550000FFFF00C90000055407361226003500001007171C0480013E +FFFF00BA0000035E0648102702C2042D0000120600550000FFFF00B2FFE3052907701226 +00380000100717200515017AFFFF00AEFFE304580664102702C004D4FFFE120600580000 +FFFF00B2FFE3052907361226003800001007171C04EC013EFFFF00AEFFE3045806481027 +02C204AB0000120600580000FFFF0087FE1404A205F0102702D704760000120600360000 +FFFF006FFE1403C7047B102702D7042C0000120600560000FFFFFFFAFE1404E905D51027 +02D704530000120600370000FFFF0037FE1402F2059E102702D704000000120600570000 +0001009CFE52047305F0002E0000010411140E010C01073536243E0135342623220F0135 +373E0335342E03232207353633321E0115140E02033F01346FB9FF00FEEA99C80131B95C +7D705F73A3F83C66683D23374B4826B8F3EFCE83CB7C173A6E02A243FEDB70CEA0886022 +A0378C999D4F65843348AB6A1A41638B52375633220CB8BEA456B6803C66717400010047 +FE4F03BC047B00340000011E0315140E0507353E0435342623220F0135373E0435342E03 +23220607352433321E0115140602A746703E21426C989DB3954AA2F59E6328765D3B3FD8 +DF2241573F2D1F3143412345A893010A8670B8746701CD08445A58254B8A6C61463D270F +822E605B625B33587019568B550D203C4566392C462A1B0A3B5A9A854792616E99000000 +FFFF00C90000053B076D1027171B050401751206002B0000FFFFFFF000000464076D1027 +171B032101751306004B0000002AB414050113072B31004BB00E5158BB0014FFC00013FF +C0383859400D901490138014801340144013065D000100C9FE56051905F00013002E4012 +03950E91098112B008131C120B061C08411410FC4BB0105458B90008FFC03859EC32D4FC +31002FECE4F4EC300134262322061511231133153E0117321219012304509A99B3D7CACA +51CC9DE3E9C9037FD7D5FFDEFCB205D5F1878601FEC1FECCFAD9000000030071FF700644 +061400070028003400002516333235342722073633321510212227060723363726350607 +06232227261037363332171617113300101716203736102726200704B61125A03434CA6E +88F4FEAA49352218C41D43303A58597CCB807F7F80CB7C59583AB8FCD553540124545454 +54FEDC548205AF2D0120B8CEFEBF0F483A45933C24643031A2A20210A2A2313064025EFC +E6FE6A74737374019674737300020071FFE3052505F0000C003B0057401C240014330418 +103D450A1C28421D181C21383B101C3742041C2F453C10FCECF4ECCCB2203B015DF4ECCC +F4ECEC11121739310040122433009514AD3C0D3B1C1D913C07082C8C3C10F4EC10F4CCD4 +CC10F4EC39393001220706101716203736353426030E0115141716333237363534272627 +3532171615140607161716151407062027263534373637262726353437362102CBB86A6B +6B6A01706B6BD4F482AA5F3BCCA85F604C6D82E4968BAA98AC5F609C9BFDBA9B9C6061AB +AB43558274010102C54D4DFEF24D4D4D4E86879A0227037C4F45482D4141889E2B4D0864 +6861BA80B2202263638FD974747474D98F6363221F46595882534A0000020071FFE30471 +050F000D00340043401636450A0818420E3432081028292B08264204081F453510FCECF4 +ECCC32D4ECCC32F4ECEC3100400E3429142200B92EAD3507B91C8C3510F4EC10F4EC3939 +CC3230012207061017162037363534272613161514070607161716151407062027263534 +363726272635343733061417163332373635342702719053525253012053535352FE3A34 +48829252518584FE128485A492903B343FA12B49488382494A2C02C54D4DFEF24D4D4D4E +86874D4D024A4062994059202263638FD974747474D98FC62223564B8E594941E8414141 +4174773E0001005CFE56051F05D50015009F400C0F141112420B081506110D1610DC4BB0 +09544BB00A545B58B9000DFFC03859C4C4D4ECE41139393100400C420795050C0F951181 +14950C2FECF4EC10DCEC304B5358400A14110E0F0E0F11131413071005ED071005ED5901 +404005130A0E180E2913260E380E4813470E480F0905140B0F001716141A0F10172F1735 +14390F3F1747144A0F4F175514590F6614690F6F177714780F7F179F17165D005D051007 +062B0135333237363D01213501213521150121051F9E4872FEE9692626FBF503B0FC6704 +95FC5003C714FEDF50259C303199149A0491AA9AFB6F000000010058FE5603DB04600015 +00AC400C0B08150D0F14121112060D1610DC4BB00B544BB00C545B58B9000DFFC038594B +B0135458B9000D00403859C4C4B440126012025DC411393910D4B440156015025DEC3100 +400C4207A9050C0FA911BC14A90C2FECF4EC10DCEC304B5358400A0F1113141314110E0F +0E071005ED071005ED590140320513161326134713490E050B0F0F1718141B0F2B0F2017 +3614390F30174514490F5714590F5F176614680F7F178017AF17135D005D051007062B01 +35333237363D0121350121352115012103DB9E4872FEE9692626FD3502B4FD65036AFD4C +02B414FEDF50259C30319914A8032593A8FCDB00FFFF00100000056807501027171E04BC +0175120600240000FFFF007BFFE3042D0614102702B8044A0000120600440000FFFF00C9 +FE75048B05D51226002800001007007A00A20000FFFF0071FE75047F047B122600480000 +1006007A7B000000FFFF0073FFE305D908331226003200001006174962000000FFFF0071 +FFE304750731122600B80000100700710073013BFFFF0073FFE305D90833122600320000 +1006175069000000FFFF0071FFE3047506E912260052000010071750FFB5FEB6FFFF0073 +FFE305D907501027171E05270175120600320000FFFF0071FFE304750614102702B80473 +0000120600520000FFFF0073FFE305D908331226003200001006174B6A000000FFFF0071 +FFE304750731122601F10000100700710073013BFFFFFFFC000004E70731102700710072 +013B1206003C0000FFFF003DFE56047F05F5102600715EFF1206005C000000000002008A +FF70035C060E000700190000251633323534272207363332151021222706072336372637 +113301CE1125A03434CA6E88F4FEAA49352218C41D433101B88205AF2D0120B8CEFEBF0F +483A45933C5A0530000200BAFF70064E047B0007002B0000251633323534272207363332 +151021222706072336372637113426232206151123113315363736333217161504C01125 +A03434CA6E88F4FEAA49352218C41D4331017C7C95ACB9B942595A75C163638205AF2D01 +20B8CEFEBF0F483A45933C5A01C09F9EBEA4FD870460AE6532327778E800000000020037 +FF700361059E000700210000251633323534272207363332151021222706072336372635 +1123353311331121152101D31125A03434CA6E88F4FEAA49362118C41D43318787B9017B +FE858205AF2D0120B8CEFEBF0F483A45933C5A02F38F013EFEC28F000001FFDBFE560179 +0460000B003840150B020700078705BD00BC0C080C05064F010800460C10FCECE4391239 +310010E4F4EC1112393930400B100D400D500D600D700D05015D13331114062B01353332 +3635C1B8A3B54631694C0460FB8CD6C09C61990000030071FFE3078C061400090023002F +00414013314525121447051B0D082B180E47011221453010FCECF43C3CFC3C3CF4ECEC31 +0040102808B90A2E04B9161D8C110AB80D97192FECE432F432EC3210EC32300010171620 +361026200713321711331136333200100223222715233506232227261037360010272620 +07061017162037012F53540124A8A8FEDC54B9F572B972F4CC00FFFFCCF472B972F5CB80 +7F7F80055D5354FEDC5453535401245402FAFE6A7473E70196E773010DC5025EFDA2C5FE +BCFDF0FEBCC5A8A8C5A2A20210A2A2FCE9019674737374FE6A74737300030071FE56078C +047B000B0025002F004440133145011224472B111D12070E1E47271217453010FCECF43C +3CFC3C3CF4ECEC310040120A2AB913042EB9211AB80C138C0FBD1DBC3010E4E4E432F43C +EC3210EC3230001027262007061017162037032227112311062322272610373633321735 +33153633320010020010171620361026200706CD5354FEDC54535354012454B9F472B972 +F5CB807F7F80CBF572B972F4CC00FFFFFAA253540124A8A8FEDC540164019674737374FE +6A747373FEF3C5FDAE0252C5A2A20210A2A2C5AAAAC5FEBCFDF0FEBC0317FE6A7473E701 +96E773000003FFFDFFBA057C06170012001600190000013313011709012303210F012307 +272337273709013301032103024AE586016166FE70017CD288FDD6CD32463B520201142F +0290FEEE16016FBD015D6A05D5FEA101A159FE27FC1B017FF18E464601113804C4FD1901 +B1FE4F011F0000000002000CFFBA058A06170022002C0000172713261110373621321716 +1737170715262701161716213237363715060706232027130123262320070611147266DC +75C3C3015386763D3A6566632E31FCF4090B880100827473666A777684FEB4C23902D801 +7482FF00888846580105BB01170168CFD024121B785976BB2B21FC660D0C9D2F2F5FD348 +2424C70115035C2F9C9DFED8AD00000000020009FFA2045D04BC0022002B000017273726 +351037362132171617371707152627011617163332373637150607062322271301262322 +070615146960BD559796010655512E2D595F761918FDD3070663B3504E4F4E4D52535DF0 +933701EE4747B363635E4EE68DCC01129D9D110A106C4F8F550E0BFD5E08087115162BAA +2412129001050256117172CD670000000001000A0000046A05D5000D003B40160C050A95 +020C06950081080305011C073A0C0A00040E10FC3CCCECFC3CCC31002FE4ECD43CEC3230 +400D300F500F800780087F047F0306015D1333113315231121152111233533C9CABFBF02 +D7FC5FBFBF05D5FD7790FDEEAA02BC900002FFB2FFBA05310617000F0012000001152301 +11231101270111213521371709012104E934FE22CBFE0D67025AFDEE04993866FDA6012C +FED405693EFDCCFD090207FDB35802C70252AA4259FE0B01620000000001006FFE100419 +047B003D0000013427262F0126272635343633321617152E0123220706151417161F0116 +171615140706071F01163315232227262F012627262726273516171633323736030A3233 +AB40AB4C4CE0CE66B44C4EA85A8944453131943FC650537B57849F932A4C2754724759ED +1E241011616C6663636182464601274B2828250F244A4B829EAC1E1EAE28282A2A544025 +24210E2C4B4C899C5B40139F7E249A3D265BF31E1003021223BE351A1B2D2C0000010058 +FE1004330460001800001321150116170117163B0115232227262F01262B013D01012171 +036AFD4E5C310108932A4C6C9354724759ED3D5A5E02B4FD650460A8FCDD1031FEF87E24 +9A3D265BF33F9C0C03250000000100500000048D05D500180036401112130C0B040F0005 +01081916011C040F1910D4D4ECD4EC1139391117393100400B0095050F95100B95128102 +2FF4ECD4ECD4EC3001231123113332363534262B0122060735363B013204151404029127 +CAF18D9A9A8DFE45AF4F98ABFEF40108FEF7025AFDA603009187888F2A2CB646DCE1D7E7 +000100500000038F047B0018003740100A08060F040C01000412131608000C1910D4D4EC +D4EC12391217393100400D16B901170C860D8808B90FB8172FF4ECF4EE10D4EC30013332 +36353427262322070607353633321716151406231123012F648D9A4C55864956564E98AB +FB7D84D4C2CA01A691878D414815152BB6466E74DBD5E5FEFC0000000003000A000004EC +05D5000C00150028005C401A150F0C06171D230500121C1A0919202E02040D001C262516 +042910FC3CCCEC3232CCFCECD4EC11173939393100401528019525040400051D00950E0D +95168105950EAD232FECECF4EC10EE391112392F3CEC3230B20F2A01015D011521152115 +2132363534262301112132363534262325213216151406071E0115140423211123353301 +93015BFEA50144A39D9DA3FEBC012B94919194FE0B0204E7FA807C95A5FEF0FBFDE8BFBF +02C9C990CA878B8C850266FE3E6F727170A6C0B189A21420CB98C8DA017090000002000C +FFE305CE05D50014001D005F400F15031C0709053816011C131100411E10FC4BB0105458 +B90000FFC038593CCCEC32FC3CCCEC32310040161D17100A000714039511091616001A95 +0D8C0400811E10E432F4EC11392F3C3CEC323211393939393001B61F1F8F1F9F1F035D13 +3311211133113315231510002120001135233533052115141633323635B2CB02E1CBA5A5 +FEDFFEE6FEE5FEDFA6A603ACFD1FAEC3C2AE05D5FD96026AFD96A496FEDCFED6012A0124 +96A4A47DF0D3D3F0FFFF00100000056805D5100603300000000300C9FF42048B06930013 +0017001B00000133073315230321152103211521072337231121011323110113211103B8 +AA41589297010AFEBCB9022EFD9841AA41B002AEFE3CB9D9011397FE560693BEAAFE46AA +FDE3AABEBE05D5FAD5021DFDE302C701BAFE460000040071FF42047F051E00050026002D +00310000012627262703051521031633323637150E012322270723132627261110003332 +17373307161716051326232206071B01231603C702530E106F019AFE2B944A616AC76263 +D06B7B6350AA6D211C9D0129FC383147AA5C392F83FDBC8714169AB90E5A6FCF0B029497 +5A100DFEF2365AFE971C3434AE2A2C21C20109171D9C010A0113014309ACE0223292C501 +4A02AE9EFE63010EAC0000000001FF96FE66025205D500130059401F0B02070C010C9512 +0F14079505B010811400110D0508063901111C0C10041410FC4BB0105458B90010004038 +593CEC32E43939C410C4310010E4FCEC10D43CEC32111239393001400D30154015501560 +158F159F15065D01231110062B01353332363511233533113311330252BFCDE34D3F866E +BFBFCABF0277FDF1FEF2F4AA96C2020FA602B8FD480000000002FFDBFE56021C06140013 +00170053402417BE14B1180F060B000B8709BD180213A9051000BC180C18090A4F150501 +08141000461810FC3C3CEC3232E4391239310010E4DC3CE43210F4EC1112393910F4EC30 +400B1019401950196019701905015D1333113315231114062B0135333236351123353311 +331523C1B8A3A3A3B54631694CB5B5B8B80460FE08A4FE28D6C09C619901D8A403ACE900 +00020073FE6606B005F10018002400434024030C0D069509B025229500161C950D108C16 +9101AF25090608021F0D001C02191913102510FCECD4EC323210CC3939310010ECE4F4C4 +EC10C4EE10E4EC113939300135331114163B011523222611350E01232000111000213216 +01101233321211100223220204B3C46E86454DE3CD4DECA5FEF2FEAC0154010EA5ECFCDF +EACCCDEBEBCDCCEA04EDE8FA93C296AAF4010E7F848001AB015C015C01AB80FD78FEE3FE +BB0145011D011D0145FEBB0000020071FE560540047B0018002400484022188700BD2522 +B9110E1CB905088C0EB812BC25011718131F041108134719120B452510FCECF4EC323210 +CC3939310010ECE4F4C4EC10C4EE10F4EC30B660268026A02603015D012322263D010E01 +2322021110003332161735331114163B01011416333236353426232206054046B5A33AB1 +7CCBFF00FFCB7CB13AB84C6931FBEFA79292A8A89292A7FE56C0D6BC6461014401080108 +01446164AAFB8C9961033DCBE7E7CBCBE7E700000002000A0000055405D50017002000BB +4018050603150900201A12050A1D1904153F180A1C0E110C042110FC3CCCEC32FCC4EC11 +17391139393931004021090807030A06110304030511040403420604001903041019950D +09189511810B042F3CF4ECD432EC32123912391239304B5358071005ED071005ED111739 +5922B2402201015D40427A17010500050105020603070415001501140216031704250025 +012502260327062607260826092022360136024601460268057504750577178806880798 +0698071F5D005D011E01171323032E012B01112311233533112120161514060111333236 +35342623038D417B3ECDD9BF4A8B78DCCABFBF01C80100FC83FD89FE9295959202BC1690 +7EFE68017F9662FD890277A602B8D6D88DBA024FFDEE8783838500000001000E0000034A +047B0018003D400A0A18030806120804461910FC3CC4C4FC3C3C3100401012110B15870E +B8030818A9050209BC032FE4D43CEC3210F4ECC4D4CC30B4501A9F1A02015D0115231123 +112335331133153E013332161F012E0123220615021EABB9ACACB93ABA85132E1C011F49 +2C9CA70268A4FE3C01C4A401F8AE66630505BD1211CEA1000002FFF6000004EC05D50011 +0014000003331721373307331521011123110121353305211704D997020C96D9979CFEF5 +FEF6CBFEF6FEF49D0277FED19805D5E0E0E0A4FE76FD3902C7018AA4A4E200000002000B +FE5604B504600018001B0000050E012B01353332363F0103213533033313211333033315 +212B011302934E947C936C4C543321CDFED6F0BEC3B8014CB8C3B9EFFED7C1DA6D68C87A +9A48865401F28F01CDFE3301CDFE338FFEF00000000200AEFFE30460047B000A002500B2 +40101700030A271F030814180A0D080C462610FCEC3232D4ECCCC41112393931401500A9 +170C0E06B911B82620861FBA1CB9238C0CBC260010E4F4ECFCEC10F4ECC410D4E4B6191F +0B17090E00121139113912393040313F1E3F1F3F203F214F1E4F1F4F204F215F1E5F1F5F +205F216F1E6F1F6F206F217F1E7F1F7F207F218F1E8F1F8F208F21185D40253F1D3F1E3F +1F3F203F213F224F1D4F1E4F1F4F204F214F225F1D5F1E5F1F5F205F215F2215015D0132 +363534262322061D01071133153E01333216151406232115141633323637150E01232226 +021DDFAC816F99B9B8B83FBC88ACCBFDFBFEFEA79760B65465BE5AF3F0022B667B6273D9 +B4294C027FAA6661C1A2BDC0127F8B2E2EAA2727FC00000000020071FFE3045A047B0010 +001C003840191AB9000E14B905088C0EB801BC0317040008024711120B451D10FCECF4EC +323231002FECE4F4C4EC10C4EE30B6601E801EA01E03015D0135331123350E0123220211 +100033321601141633323635342623220603A2B8B83AB17CCBFF00FFCB7CB1FDC7A79292 +A8A89292A703B6AAFBA0A86461014401080108014461FE15CBE7E7CBCBE7E700000200BA +FFE304A3047B000B001C0038401903B90C0F09B918158C0FB81BBC1900121247180C0608 +1A461D10FCEC3232F4EC31002FECE4F4C4EC10C6EE30B6601E801EA01E03015D01342623 +2206151416333236013E01333200111002232226271523113303E5A79292A8A89292A7FD +8D3AB17CCB00FFFFCB7CB13AB8B8022FCBE7E7CBCBE7E702526461FEBCFEF8FEF8FEBC61 +64AA0460000200BAFFE304A40614000B00240043401F03B90C0F09B918158C0FB81921A9 +1E9719001212471E211F180C06081A462510FCEC3232C43939F4EC31002FFCEC10E4F4C4 +EC10C6EE30B660268026A02603015D013426232206151416333236013E01333200111002 +2322262715231134363B01152322061503E5A79292A7A79292A7FD8E3AB17BCC00FFFFCC +7BB13AB9B3A5FEE95A5B022FCBE7E7CBCBE7E702526461FEBCFEF8FEF8FEBC6164A8047E +C3D39C7D7D0000000001007FFFE303F5047B00190030401B1986008816B903B81A0D860C +8810B9098C1A1B45131206480D001A10DC3CF4ECEC310010F4ECF4EC10F4ECF4EC30133E +0133320011100021222627351E01333236353426232206077F4DA55DFD012AFED3FEFA55 +A24C4E9D50B3C6C6B3509D4E04332424FEC2FEF2FEEEFEC62323AC2B2BE3CDCDE32B2B00 +00020071FF7303E7047B0027002F004F400F280B072C2C1213071213004822453010FCE4 +32EC10EC111239393100401300860188040FB92E2AB91704B925B81B178C3010E4CCF4EC +10FCDCEC10F5EE30400B0F31103180319031A03105015D01152E01232206151417161736 +373633321716151407062322270615233437262726111000213216011633323534232203 +E74E9D50B3C6630706273E496AA34A3F5F539B504906990C392F95012D010655A2FE8A3A +4D9284650435AC2B2BE3CDCD720806512C33483D597D2F2911394468512333A1010C0112 +013A23FC3A13394B00020071FE560540061400180024004B40240414120518A900BD2522 +B9110E1CB905088C0EB8129725184F1F041208134719120B452510FCECF4EC3232E43100 +10ECE4F4C4EC10C4EE10FCEC1112393930B6601E801EA01E03015D012322263D010E0123 +22021110003332161711331114163B01011416333236353426232206054046B5A33AB17C +CBFF00FFCB7CB13AB84C6931FBEFA79292A8A89292A7FE56C0D6BC646101440108010801 +446164025EF9D89961033DCBE7E7CBCBE7E7000000020071FFE305B9061400180024003D +401C22B900161CB90D108C16B82506A90597251F0C00080B47191213452510FCECF4EC32 +32310010FCE410E4F4C4EC10C4EE30B6601E801EA01E03015D013534363B011523220615 +1123350E0123220211100033321601141633323635342623220603A2A3B5BFAA694CB83A +B17CCBFF00FFCB7CB1FDC7A79292A8A89292A703B6C8D6C09C6199FB82A8646101440108 +0108014461FE15CBE7E7CBCBE7E7000000020071FFE3047F047B001900220072400D1B18 +1A1812084B1A081019452310FCC4ECF4EC111239314017001A190F861088141AA91914B9 +0C19BB1FB904B80C8C230010E4F4ECE410EC10EC10F4EC1112393040293F247024A024D0 +24F024053F003F193F183F1A3F1B052C112F102F0F2C0E6F006F196F186F1A6F1B095D71 +015D13343736333217161110070621222627351617163332373637213705262726232207 +06718384E2FC94959D9CFEF46BD0636264636AB766670CFCB2B802900E5D5C9A88525302 +5EFA9291A1A2FEEDFEF69C9C2C2AAE341A1A6364BE90019E57575A5A00020071FFE3047F +047B0014001B00414024001501098608880501A91518B91215BB05B90CB8128C1C02151B +1B080F4B15120801451C10FCC4ECF4EC111239310010E4F4ECE410EE10EE10F4EE111239 +301335212E0123220607353E01332000111000232200371E013332363771034E0CCDB76A +C76263D06B010C0139FED7FCE2FEF9B802A5889AB90E02005ABEC73434AE2A2CFEC8FEF6 +FEEDFEBD0123C497B4AE9E000002007CFFE30684047B000A003400774010362E28082734 +02120D4B05121F15453510FCC4ECFCECDC3CFCDCC4B626160B0404020D1112173931400F +2FA92E27221AB922B83509B9118C350010F4EC10F4EC10D4DCECB41F861E881A10F4EC40 +0B05150B0D020426160822111112173930400A340B0405112616152715070E103C3CFC3C +3C043C25362736270116171633320116151007062322272627012627260706070607353E +0133201716173733151417163B01152322272635034E6602010AFD971E205288A801601F +9594FCE4825C1C02FE131B4CD16C61646263D06B010C9C241BCBB82626692B40AF5752D6 +8ACF3E38FE9C45235A02906076FEEDA2A191679C01BB2723640101191A34AE2A2C9C2329 +75949931309C605AC8000000FFFF0085FFE303C8047C120603490000FFFF0085FFE303C8 +047C120603CB000000010085FFE3062A047C003E00694010403630083C2F1E122E131203 +19270B3F10DCC4C4D4ECD4ECDC3CFCDCC43140162686278822B92AB83F18A9193F0B860A +880FB9068C3F0010F4ECFCEC10D4EC10F4ECFCECB63D2E002A00181911123911123939B4 +37A936302A10D4DCEC30B33C3D2E2F0704103C011E011514042322272627351617163332 +3736353427262B013533323736353427262322070607353E013332171617373315141716 +3B01152322272635050602C27C8AFEFEEE5055545A4755555D9755544E4889949B744344 +4645774751506162AA4CC4715F0FECB82626692B40AF5752FEE040025C18926CADB60E0E +1CAB25131238385A583833982C2D46402E2E0D0D1DA718184E426A86949931309C605AC8 +A646000000020071FFE304C5047C001A002F003B400D17121F31120C122604122C453010 +FCECD4ECC4C4D4EC31400E00B91BB83011A9123008B9298C300010F4EC10D4EC10F4ECB2 +23121111123930012207061514171633323736353427262B013533323736353427262732 +171615140706071E011514042320001110373602F1FB60636368D29755544E4889949B74 +4344464568C471723C3C707C8AFEFEEEFEC6FED6979703DC6E72CDD06F7438385A583833 +982C2D46402E2EA04E4F8D5D40411818926CADB6013E010E01129D9E0001FFDBFE56021C +04600013004B401F0F060B000B8709BD140213A9051000BC140C14090A4F020501081310 +00461410FC3C3CEC3232E4391239310010E4DC3CE43210F4EC1112393930400B10154015 +50156015701505015D1333113315231114062B01353332363511233533C1B8A3A3A3B546 +31694CB5B50460FE08A4FE28D6C09C619901D8A400020071FE5605B80614000B00300055 +4029190C1D0912861316B90F03B92623B81D2DA92A9709B90FBD1A1D2A2D2B261900080C +4706121220453110FCC4ECF4EC3232C4393931002FC4E4ECF4EC10F4C4EC10FED5EE1112 +393930B660328032A03203015D01342623220615141633323617100221222627351E0133 +32363D010E01232202111012333216173534363B01152322061503A2A59594A5A59495A5 +B8FEFEFA61AC51519E52B5B439B27CCEFCFCCE7CB239A3B5BEA9694C023DC8DCDCC8C7DC +DCEBFEE2FEE91D1EB32C2ABDBF5B6362013A01030104013A6263C8D6C09C619900020071 +FE56045A0460000A00230043401F180B1C0811861215B90E02B923BC08B90EBD191C1800 +080B470512111F452410FCC4ECF4EC3231002FC4E4ECF4EC10FED5EE1112393930B66025 +8025A02503015D011121220615141633323617100221222627351E013332363D010E0123 +2202113412332103A2FEAA8796A59495A5B8FEFEFA61AC51519E52B5B439B27CCEFCFCCE +021F023D0188CDBBC7DCDCEBFEE2FEE91D1EB32C2ABDBF5B6362013A0103F9012A000000 +00010071FFE3044F047B001D0038401F00051B01A9031BB9081286118815B90EB8088C1E +02000811340418120B451E10FCECDCE4FCC4310010E4F4ECF4EC10FED4EE113939302511 +233521110E0123220011100021321617152E0123220615141633323603A99B014165D77B +FDFED6012D010668C55D5FC063B3C6C6B34F7C9E01118CFDF02424013E010E0112013A37 +37AA3E3EE3CDCDE30F00000000020060FE5204640460001300230079400A250218120720 +120D122410D4D4ECD4ECD4C4B5001C140318201112173931400A14B90ABD01130212BC24 +0010E4323232F4ECB30D071C0A1112393930B4131112121C070510ECB31112021C08103C +B4011102021C070510ECB500010302121C08103C04103CB303001300070E103CB3110001 +00070E103C09013301161716151406232226353437363701330132373635342726270607 +061514171602620142C0FE5F6A263B969696963B266AFE5FC00142431F1C1C283A3A281C +1C1F01E80278FCDCB153806381828281638053B10324FA8E1B182D45496463636449452D +181B000000020060FFE304640460001300230079400A250218120720120D122410D4D4EC +D4ECD4C4B5001C140318201112173931400A14B90A8C01130212BC240010E4323232F4EC +B30D071C0A1112393930B4131112121C070510ECB31112021C08103CB4011102021C0705 +10ECB500010302121C08103C04103CB303001300070E103CB311000100070E103C090133 +011617161514062322263534373637013301323736353427262706070615141716026201 +29D9FE72472C4596969696452C47FE72D90129431F1C271F38381F271C1F02D1018FFDEA +624C783E828181823E784C620216FC1F1B182D21403246463240212D181B0000000100AE +FE560458046000130039401B030900030E0106870E118C0A01BC0CBD140D09080B4E0208 +00461410FCECF4EC32310010E4E432F4C4EC1112173930B46015CF1502015D1311331114 +163332363511331123110E01232226AEB87C7C95ADB8B843B175C1C801BA02A6FD619F9F +BEA4027BF9F602566663F000000100BA000004640614001B004340210309000316010687 +1619B81C0C1512A90F970A010208004E0F12101509080B461C10FCEC32C43939F4EC3100 +2F3CFCEC393910F4C4EC1112173930B2601D01015D011123113426232206151123113436 +3B01152322061D013E013332160464B87C7C95ACB9A3B5FEE7694D42B375C1C602A4FD5C +029E9F9EBEA4FD87047ED6C09C6199CC6564EF00000100BAFE56046406140021004A4025 +030900031D010D871D1FB822131C19A916971207870412060A08004E1619171C10081246 +2210FCEC32C43939F4ECC431002FDCEC10FCEC393910F4C4EC1112173930B2602301015D +011114062B01353332363511102322061511231134363B01152322061D01363332160464 +A3B5FEE9694CF895ACB9A3B5FEE7694D83E7C1C602A4FD48D6C09C619902B2013DBEA4FD +87047ED6C09C6199CCC9EF000002000E0000021E0614000B000F003E40180EBE0CB10602 +0BA9050800BC0605020D0108080B0C00461010FC3C3C3CEC32323231002FE4DC3CEC3210 +FCEC30400B1011401150116011701105015D13331133152311231123353311331523C2B8 +A4A4B8B4B4B8B80460FE08A4FE3C01C4A403ACE9FFFF00A60000026E04601006034D0000 +00010074000002840460000B0027400A03060804080009080A0C10DCEC32FCEC32310040 +09040BA901BC0509A9082FEC32FCEC3230133521152311331521353311740210A8A8FDF0 +B003BCA4A4FCE8A4A40318000001004B000002DF06140023003C400D250B560A12010800 +131C561D2410DCFCDC3CFC3CDCFCD431401214110223040F2106C30F1D0B21C318009713 +002FE42FEC32D43CEC111217393001331116171633323736373306070623222711231126 +2726232207060723363736333217013DB80201110D261212027D0233335B1413B8060511 +0D261212027D0233335B19160614FCED01010925245287494A04FD850302040309252452 +87494A060002004D000003540614001100180035400B1A04050108120007160D1910DCDC +D43C32FC3CDCC431400E110FB9140A05A912020207009707002FE411392F3CEC32D4ECC4 +300133113315231123113427232037363332170726232207143301A2B8FAFAB8013DFEE8 +0101F5352A1017374D015C0614FCFEA0FD8E02540F0FBDF619FA844B39000000000100C1 +FE56025F0614000B002840070D0600080B460C10FCFCD4C43100400C0A0105000B970C05 +8706BD0C10F4EC10E41112393930011114163B0115232226351101793D783146BF990614 +F9CE7C749CCCCA0628000000000100C1FE4C05360614002400B2400E1B23151226060E23 +1D220820462510FCFC3CD4C4D4C4EC10CCB200231B1112393140181B4200A91A1A221F1D +A9220E860D9311B909BD22BC20971F002FE4E4FCECF4EC10EC1112392FECECB315060009 +111239393040081B11001C11241A23070510EC0410EC401B0C1C0A001B1C19002A1C2A00 +38003B1C49004C1C54005B1C71000D015D401B041B0424141B1424251B24243524371B45 +24461B54245C1B7F1B0D005D4009070B060C1A0C1A0F045D013217161716151404212227 +2627351E0133323736353427262B013501211123113311211503436981635551FED0FEE8 +5E63646A54C86DBE63645C5BA7AE01AEFD6AB8B8036501DC382B6C688ADDF2121325C331 +324B4B8F844B4AA601F3FC330614FE4CA8000000000100BAFFE6071D04620026005E4011 +0012141E1B081D50120814500A0808462710FCECFCFCFCFC3C11123931401607140A1A11 +00061F080D17871F04238C271B1208BC270010F43C3C10F43CC4EC321112173930401330 +28502870289028A028A028BF28DF28FF2809015D25060706232226351133111416333237 +363511331114163332363511331123350607062322272603AE43626082AFBEB972758F53 +53B972778DA6B9B93D5A58797A5655D8793D3CF6E202A4FD62A29C605EA4027AFD62A29C +C0A2027AFB9EB06533323E3E000100BAFE56071D04620026006140110012141E1B081D50 +120814500A0808462710FCECFCFCFCFC3C11123931401807140A1A1100061F080D17871F +04238C271B1208BC1DBD270010ECF43C3C10F43CC4EC3211121739304013302850287028 +9028A028A028BF28DF28FF2809015D250607062322263511331114163332373635113311 +14163332363511331123110607062322272603AE43626082AFBEB972758F5353B972778D +A6B9B93D5A58797A5655D8793D3CF6E202A4FD62A29C605EA4027AFD62A29CC0A2027AF9 +F4025A6533323E3E000100BAFE56071D047B0030006340120E00110F130807501C081A50 +29250827463110FCEC32FCFCFCEC111239CC310040180E870D1B071D14251A00062A1B21 +17872A2D03B828BC261B2F3CE4F43CC4EC321112173910D4EC3001401330325032703290 +32A032A032BF32DF32FF32095D013E013332171615111407062B01353332373635033426 +23220615112311342726232207061511231133153E0133321716042945C082AF5F5F5251 +B5FEE96926260172758FA6B93939778D5353B9B93FB0797A555603897C767B7AE2FD48D6 +60609C30319902B2A19CBEA4FD87029EA24E4D5F60A3FD870460AE67623E3E000001FFDB +FE56046B047B001B0051400F0208004E101C0D0E4F0A150814461C10FCEC32E4391239F4 +EC3100400E03090003160106871619B814BC012FE4F4C4EC111217394009130A0F140F87 +0DBD1C10F4EC1112393930B4601DCF1D02015D011123113426232206151114062B013533 +3236351133153E01333216046BB87C7C95ADA3B54631694CB942B375C1C602A4FD5C029E +9F9EBEA4FD73D6C09C61990474AE6564EF000000000100BAFE56054A047B001D003B400C +171A0308154E090D080C461E10FCEC32F4ECDCC431400D06870E11B80CBC0B1AA91BBD0B +002FFCEC10E4F4C4ECB5090314030E0A1112173930012635113426232206151123113315 +3E0133321615111417163B0115232203FE527C7C95ACB9B942B375C1C62626693146B5FE +B660D602B29F9EBEA4FD870460AE6564EFE8FD489931309C000100B30000046404600009 +0079401E071101020102110607064207020300BC08050601070208044E070800460A10FC +ECFCEC11393931002F3CEC323939304B5358071004ED071004ED5922B21F0B01015D4030 +3602380748024707690266078002070601090615011A0646014906570158066501690679 +0685018A0695019A069F0B105D005D13210111331121011123B3011001DDC4FEF0FE23C4 +0460FC790387FBA0036CFC9400030071FFE30475047B0006000D0019002C401804A90B07 +B91400B90EB8148C1A0A041211510B031217451A10FCEC32F4EC32310010E4F4EC10EEDC +EC3001220607212E01033236352114161332001110002322001110000271939512027412 +959295A8FD86A896F00112FEEEF0F1FEEF011103DFC17F7FC1FCA0E89494E803FCFEC8FE +ECFEEDFEC70139011301140138000000000200710000062404600012001D0049400D1F04 +00090602081318120E451E10FCECD4EC32D4C4C4C4B30A1202131112393931400A0213A9 +12BC0A1D07A90A002FFC3C10F4FC3C400803A906060E0E130A11123910D02FEC30011521 +112115211121152120272611103736211723220706151417163B010616FDD40215FDEB02 +3AFCE1FEBBA7A8A8A701452A25F078787878F02504609AFEDD9BFE949C8E8F011401128E +8F826C6BD8D96C6D00020094FFDC053E047C001300240032400D26450712191308000C12 +14452510FCECD4FCD4ECEC3100400D000A8717030F871E238C17B82510E4F43CEC3210EC +C4300115141632373635100220021114171632363D01051000200011140607062226270E +0122260348606B2649D0FE6ECA49266B60FE040142022201463A2E61D7A20C129DD6D702 +94C4A3B5305B9D010F0131FED0FEF09D5B30B5A3C4C80154015CFEA4FE806CB23670A375 +799FED00FFFF0070FE5604D1061412060369000000010000FFE502900460000E002F4009 +0702040A0E080D040F102FDCEC321139393100400D0A000B0504000787028C0CBC0D2FEC +F4ECC4D4CC11123930250621222F0116333236351133112301D772FEF92538013C589CA7 +B9B9AEC90ABD23CBBE024EFBA000000000010000FFE50290060A000E002F40090702040A +0E080D040F102FDCEC321139393100400D0A000B0504000787028C0C970D2FECF4ECC4D4 +CC11123930250621222F0116333236351133112301D772FEF92538013C589FA4B9B9AEC9 +0ABD23CEBB03F8F9F600000000010000FE560376046000160044400C114F0D0702040A16 +080D040F102FDCEC3211393910E431004016160D0B0011A912BD170A000B050400078702 +8C0CBC1710ECF4ECC4D4CC11123910FCEC1112393930250621222F011633323635113311 +14163B01152322263501D772FEF92538013C589CA7B94C693146B5A3AEC90ABD23CBBE02 +4EFB8C99619CC0D6000100BAFE58034A047B001100334016060B0700110B03870EB809BC +07BD120A06080008461210FCC4EC32310010ECE4F4ECC4D4CC11123930B450139F130201 +5D012E012322061511231133153E0133321617034A1F492C9CA7B9B93ABA85132E1C03B4 +1211CBBEFC0A0608AE66630505000000000100BAFE56034A047B0019003A401A0613070B +870CBD1A001913038718B811BC1A0B1206080010461A10FCC4EC32C4310010E4F4ECC4D4 +CC10F4EC11123930B4501B9F1B02015D012E01232206151114163B011523222635113315 +3E0133321617034A1F492C9DA74C69E9FEB5A3B93ABA85132E1C03B41211CBBEFD9E9961 +9CC0D60474AE666305050000000100840000037E047B000F00254007020C000805071010 +DCCCEC32CC3100400A00070C870BBC010687042FEC32FCEC393930011133152135331134 +363B011523220601E0A4FE00A4A3B5FEE9694C02E5FDBFA4A40241D6C09C610000010074 +0000037E047B000F002540070200080C05071010DCCCCCFCCC3100400A00070A870DBC01 +0687042FEC32FCEC393930011133152135331134262B013533321602CAB4FDF0A44C69E9 +FEB5A302E5FDBFA4A4024199619CC000000200BA0000049704600013001C00B040340908 +07030A061103040305110404034206040015030415A90914A90DBC0B040506031109001C +160E050A19120411140A080C461D10FCEC32DCC4EC1117391139393931002F3CF4ECD4EC +123912391239304B5358071005ED071005ED1117395922B2401E01015D40427A13010500 +05010502060307041500150114021603170425002501250226032706260726082609201E +3601360246014602680575047505771388068807980698071F5D005D011E01171323032E +012B011123112132161514060111333236353426230314307332AEC3A24A7B51A9B90184 +DAD670FDF5C6777F7581020D0A745DFECE011F803AFE2704609EA5698C019DFEAF564E4D +60000000000200BA0000049704600013001C004540150907060F030C1C16120502191208 +0F01140800461D10FCEC32DCC4EC111739113939393100400F06080C14090803A91415A9 +0800BC132FE432ECD4EC11391139113930133311333236371333030E01071E0115140623 +21131133323635342623BAB9A9517B4AA2C3AE3273306A70D6DAFE7CB9C681757F770460 +FE273A80011FFECE5D740A1B8C69A59E01ECFEAF604D4E560001006FFE5603C7047B0030 +008040430D0C020E0B532827080902070A532728274219A91ABD310A0B2728041F008601 +89041F8921B91104B92EB8118C311A15081E270A0B282407005224080E07081E2B453110 +FCC4ECD4ECE411123939393910ECCC310010E4F4EC10FEF510F5EE12173910FCEC304B53 +5807100EED111739070EED1117395922B2003001015D01152E012322061514161F011E01 +15140623222F011514163B01152322263D01163332363534262F012E0135343633321603 +8B4EA85A898962943FC4A5F7D86458154C69E9FEB5A3CCC1828C65AB40AB98E0CE66B404 +3FAE282854544049210E2A99899CB611040C99619CC0D6FB6A59514B50250F2495829EAC +1E0000000001FFD9FE5602D7061400130034400D11140E0F4F050B0A080100461410FC3C +EC3232E43912393100400D10870FBD140A0106068705971410FCEC12393910F4EC301711 +34363B0115232206151114062B0135333236BEAEBDAEB0634DA3B54631694B1404C2BBAB +995068FB29D6C09C610000000001FFD9FE5602D706140020004F40120D201C0116211314 +4F05100A08191E01462110FC3C3CEC3232E439123910CC32C4310040171EA900BC210C1C +A90F1B158714BD210A0106068705972110FCEC12393910F4ECD43CEC3210F4EC30133534 +363B01152322061511173315231114062B01353332363511233533112335BEAEBDAEB063 +4D01A2A3A3B54631694BB4B4AF04604EBBAB995068FDA803A4FE28D6C09C619901D8A401 +698F000000010037FE560335046500130022B60F4F0B0801061410D4DCFCEC3100400A0E +8710BD14048706BC1410F4EC10F4EC30051134262B0135333216151114163B0115232226 +01974D63B0AEBDAE4B693146B5A3140328685099ABBBFCED99619CC00002FEF2FE5602D7 +06140016001F0032400C1A090D0211031608170D4F2010FC32FC32CCCC10D4CC3100400C +1C0703188700138711970B002F3CF4EC10EC32D4CC302133152306070623203534213311 +34363B0115232206150323221716333237360177B7BF123552B5FED1010EBEAEBDAEB063 +4DC3B37703037C6E21119B6F4060D8D204AEBBAB995068FAA33341301800000000010037 +FEC002F2045E001300334009080B0E1208050109022F3CD43CEC3239393100400C0E0500 +08A90BBC0F03A912022F3CEC32F4ECC439393001B2AF15015D01112135211134262B0135 +33321615113315231101B2FE85017B4B73BDBDD5A28787FEC0013E8F0260894E9A9FD2FD +A08FFEC200010037FE5602F6059E0013003D401C0E05080F03A9001101BC1408870BBD14 +0B08090204000810120E461410FC3CC4FC3CC4323939310010FCEC10F43CC4EC32113939 +30B2AF1501015D01112115211114163B01152322263511233533110177017BFE854C69CA +E0B5A38787059EFEC28FFC1B99619CC0D603E58F013E000000020000FFE3051204600016 +001E0043401F0D011C8710000704A90A14170D108C0501BC0B170C0408064E1802080046 +1F10FCEC32F4EC323231002FE432F4C4DC3232EC323210FC111230B46020CF2002015D13 +113311211133113315231123350E012322263D0123350521151416333236AEB8023AB8BA +BAB843B175C1C8AE039FFDC77C7C8FB2026801F8FE0801F8FE08A4FE3CAC6663F0E70AA4 +A5029F9FBA00000000010071FFE204840460001F0053400D1D1A122100041114120E0A04 +2010FCC4FCC4D4C4CCFCC43100400E111D0D01A91E10BC2017B9078C2010F4EC10FC3CEC +32323230014019E011E010EF1DEF1ED011D010DF1DDF1E401140104F1D4F1E0C5D01231E +0115140023220035343637233521150E011514163332363534262737210484EC617FFEE4 +E1E1FEE47F61ED01BA6688B09090B088660101B403BC48EB98EBFEDC0124EB98EB48A4DC +42D78B9FC2C29F8BD742DC00000100C10000045C0462001E002C400C200012141905100C +0809461F10FCFCC4C4C4D4EC393100400A11B90418B919B80BBC042FECF4EC10EC300114 +07062B0122272635113311141716373332363534272627351617161716045C8E91DE46B5 +5251B82628673390B04A496E6858A73322020FEE8F926060D602CAFD3699313202C49EE8 +65631E9608305BAB730000000001003D0000047F04600006006840270411030302051106 +0502020305110504010001061100010042050201BF0306050402010503000710D44BB00A +5458B90000004038594BB014544BB015545B58B90000FFC03859C4173931002FEC323930 +4B5358071005ED071008ED071008ED071005ED592201330133012309013D01A4FA01A4C3 +FEA2FEA20460FBA003ACFC5400010056000006350460000C01EF400F08090A0B0C010203 +0405060B00070D10D44BB00A544BB011545B4BB012545B4BB013545B4BB00B545B58B900 +0700403859014BB00C544BB00D545B4BB010545B58B90007FFC03859CC173931400A0A05 +02030C08BF070300002F3C3CEC321739304030025501020B0A0B03550A0B04550A090A05 +55060509090A0111000C00021103020C0C00051104050807080611070708424B53580710 +05ED071008ED071008ED071005ED071008ED071005ED0705ED071008ED59220140FF0A05 +190519022D0A3A0A46054602490A4F0A540554025A0A5F0A61056102690A760570057602 +70028805960597029B0AB305B302C105C804C0021D0505090406030B020A0C0B0B040905 +081505190416031A021B0C1B0B1409150825072506230527042103250222012200250C27 +0B240A2109230839043603360C3908300E4605480446034003420240014000400C440B44 +0A4409400E400E5607560656055003510252015200500C530B540A55096307640665056A +0465036A026A016A006E0B610967086F0E7507750679057D0478037D027A017F017A007F +00780C790B7F0B7B0A76097D08870588028F0E97079706940593049C039B029801980099 +0C402F96089F0EA607A606A405A404AB03AB02A901A900AB0CA408AF0EB505B104BD03BB +02B80BBF0EC405C304CC03CA02795D005D21230B01230B012301331B01330635B8E6E5D9 +E6E5B80125D9F1F2D9036AFC96036AFC960460FC6A0396000001003D0000047F06140011 +0046B413060E001210D4D4C4C431B507A906970E00002F3CF4EC30B7100D0C1111000100 +070510FC3C3C3CB608090A0B04070C011112173940091011110C0F110E0E0D0710EC08EC +33013637363B0115232207060F01012309013D01EC50484A7C936C4C2A2E2F2101C5C3FE +A1FEA304D2C73E3D9A2423875EFBB2036CFC9400000100660000046B046000080038400A +0208050A04050808000910D4DCFCD4C411123931B30400BC07002FE43230400C03110405 +0402110111000800070510EC04EC070510EC13330901330111231166D90125012ED9FE5D +CB0460FE3801C8FD90FE1001F000000000010058FE5604BF0460001300AA402212110203 +0203111112114209A90ABD0F12A900BC03A90F0A4F04120301000401101410DC4BB00B54 +4BB00C545B58B90010FFC038594BB0135458B9001000403859C432C411393910EC31002F +ECF4EC10FCEC304B5358071005ED071005ED592201404205021602260247024911050B12 +0F1518031B122B1220153603391230154001400245034004400F4312570359125F156001 +600266036004600562127F158015AF151B5D005D13211501211514163B01152322263D01 +2135012171036AFD4C02B44C692F46B5A3FD3702B4FD650460A8FCDBA799619CC0D614A8 +0325000000020058FF9103DB0460001A002100B14011160412111B1E120C190301000C01 +01172210DC4B544BB00C545B58B90017FFC038594BB0135458B9001700403859C432C411 +393910ECDC3CEC323100400D19A900BC131708B920031BA9172FFC3CDCEC10CCF4EC3040 +0B19110203020311181918424B5358071005ED071005ED592201403A0502160226024702 +4918050B190F2318031B192B1920233603391930234001400245034319570359195F2360 +016002660362197F238023AF23175D005D132115013336373633321716151607062B0106 +15233437213501210133323534070671036AFD4CAF22544160843A26013E527E69039903 +FE9602B4FD6501FF388A46580460A8FCDBA24737573957602F3D333C3B34A80325FCC636 +5D02020000010058FE4C042F0460002000A9400A1B1F151222061E1F0E2110DCD4C4D4C4 +EC10CCB2001F1B1112393140161B4200A91A1A1E211DA91E0E860D9311B909BD1EBC2100 +10E4FCECF4EC10EC1112392FECECB315060009111239393040081B11001C11201A1F0705 +10EC0410EC401B0C1C0A001B1C19002A1C2A0038003B1C49004C1C54005B1C71000D015D +401B041B0420141B1420251B24203520371B4520461B54205C1B7F1B0D005D4009070B06 +0C1A0C1A0F045D0132171617161514042122272627351E0133323736353427262B013501 +21352115023C6A80625651FED0FEE85E63646A54C86DBE63645C5DA5AE01AEFD65036A01 +DC382A6D688ADDF2121325C331324B4B8F844B4AA601F393A80000000002006DFE4C046C +04600024002D00000120373605161736353427262B013501213521150132171617161514 +07161523342730070637262322071433323701E7FEB10202012AF69E0C5C5EA4AE01AEFD +65036AFE656981645451276499281497097DC583019EBE63FE4CBDFB05043B2A31854A4A +A601F393A8FE24382B6C678B715565A452381179FA2A4B2F4B00000000010058000003A5 +0612001C003440091E0512161A08000D1D10DCDCFCDCECC431400E0D860E0C1209B91297 +1B1AA9001B002FD4E410F4EC104B5058DC1BD459EC300133323736352627262322070607 +35363736332017161514070607112301543FC0563A013963B3504F4E4E4C51515501138A +6D6C70AACA031E724C6285417216152BAC2311129D7DBAAA73771EFD7400000000010058 +000003A50612001C0035400A1E10000803181207451D10FCECDCFCDCC431400E10860F0C +0B14B90B970203A90002002FD4E410F4EC104B5058DC1BD459EC30011123112627263534 +373621321716171526272623220706071417163302A8CAAA706C6E8901135551514C4E4F +4E50B36339013A56C0031EFCE2028C1E7773AABA7D9D121123AC2B1516724185624C7200 +00010058000003A50612001C003740091E181207030800101D10DCDCFCDCECC431401010 +860F0C0B14B90B8C1D03A90001971D0010F4D4E410F4EC104B5058DC1BD459EC30011333 +1116171615140706212227262735161716333237363734272623015401CAAA706C6D8AFE +ED5551514C4E4E4F50B36339013A56C002F4031EFD741E7773AABA7D9D121123AC2B1516 +724185624C72000000010058FE4C03A506140023002DB6251A081212002410DCECDC32CC +310040100987080D870497241A871B16871FBD2410FCECD4EC10F4ECD4EC301334373621 +32171617152627262322070615111417163332373637150607062320272635586E890113 +5551514C49544E50B0663A3A66B0504E4F4E4C515155FEED896E0440BA7D9D121123AC28 +1816724185FBE085417216152BAC2311129D7DBA00030073FFE305D905F0000D00170022 +000001343736333217161514062227260020001110002000111001200011100021200010 +0002B52220302E2220425E2022014EFE48FEFD010301B80101FE23013A0178FE88FEC6FE +C5FE87017902E92E222222222E2F4221210292FEB8FEE5FEE6FEB80148011A011B01ECFE +5BFE9EFE9FFE5B01A402C401A5000000FFFF00BA0000043E0460100603C6000000020071 +FFE304C5047C001A002F003D400E3117121F092504122C0F1225453010FCECD4EC10C4D4 +ECC431400E00B91BB8300AA9093013B9228C300010F4EC10D4EC10F4ECB2280A09111239 +3001220706151417163B0115232207061514171633323736353427262520171611100021 +2224353436372627263534373602457745464443749B9489484E545597D268636360FEF6 +01619797FED6FEC6EEFEFE8A7C703C3C727103DC2E2E40462D2C983338585A3838746FD0 +CD726EA09E9DFEEEFEF2FEC2B6AD6C92181841405D8D4F4E00010071FFE305CB06140027 +00474027121B111C18A915972800052501A90325B908111C881FB90EB8088C280200081B +340422120B452810FCECDCE4FCC4310010E4F4FCEC3910FED4EE11393910FCEC11123939 +302511233521110E01232200111000213216173534363B011523220615112E0123220615 +141633323603A99B014165D77BFDFED6012D0106376931A3B5FEE7694D5FC063B3C6C6B3 +4F7C9E01118CFDF02424013E010E0112013A0F0F21D6C09C6199FEE53E3EE3CDCDE30F00 +FFFF00BA000004810460100603D100000003FEF2FE56022E061400030012001B00394011 +0913050416120F041D070105080004461C10FC3CFC3CDCC410DCEC1112393931400B180D +BD04BC00B109130612002F3CCC32E4E4FCC4301333152315331133152306070623203534 +2133072322171433323736C1B8B8B8B5BD12374BBCFED1010EC108B875017F5F2B1D0614 +E9CBFBA08B784760DDCD8B4241302000000100BAFE4C049C0460000A0000012311012309 +0133011133049CB9FDDBEB0252FD95F00239B9FE4C0397FE1D020C0254FDDD0223000000 +000100BA000003F104600005001B400D00BF03A906050703010800460610FCFCCCC43100 +2F10ECEC30133311211521BAB8027FFCC90460FC3393000000><00020071FE5605F80612 +000B00240043401E03B90C0F09B91815B80F8C23BD251F871C9725180C06082247001212 +452510FCECF4EC3232310010FCEC10E4E4F4C4EC10C6EE30400960268026A026E0260401 +5D011416333236353426232206010E01232202111000333216173534363B011523220615 +1123012FA79292A8A89292A702733AB17CCBFF00FFCB7CB13AA3B5FEE7694DB9022FCBE7 +E7CBCBE7E7FDAE646101440108010801446164C6D6C09C6199F9DA000000000100580000 +03A506120024004C400B260512161F1A0820000E2510DCDC3CFC3CDCECC4B31C1A230010 +CC10CC3140140D860E0C12241BA9211E1F09B912971F1AA9001F002FD4E410F4EC10DC3C +EC32104B5058DC1BD459EC30013332373635262726232207060735363736332017161514 +0706071533152311231123353301543FC0563A013963B3504F4E4E4C51515501138A6D6C +70AAE7E7CAE5E4031E724C6285417216152BAC2311129D7DBAAA73771ED4A4FEEC0114A4 +000000010058000003A506120024004D400C2610221D082303181207452510FCECDC3CFC +3CDCC4B32022002310CC10CC31401410860F0C0B021EA924212314B90B972303A91D2300 +2FD4E410F4EC10DC3CEC32104B5058DC1BD459EC30133533352627263534373621321716 +171526272623220706071417163B0111331523112311F7E7AA706C6E8901135551514C4E +4F4E50B36339013A56C03EE5E5CA0114A4D41E7773AABA7D9D121123AC2B151672418562 +4C72FE9AA4FEEC01140000030071FFE307C30614000B0026002900000010171620373610 +27262007251133112115012115212B01350607062322272610373633321716171101012F +5354012454545454FEDC540220B80369FD4C02B4FC971A9E3A58597CCB807F7F80CB7C59 +58F2029A02FAFE6A74737374019674737348025EFE4CA8FCDB93A8643031A2A20210A2A2 +31304DFCF9030700000000020071FE4C081C061400340040000001112335060706232227 +26103736333217161711331121150132171617161514042122272627351E013332373635 +3427262B013501041017162037361027262007045AB83A58597CCB807F7F80CB7C59583A +B8036AFE656A80625651FED0FEE85E63646A54C86DBE63645C5DA5AE01AEFA3A53540124 +54545454FEDC5403CDFC33A8643031A2A20210A2A2313064025EFE4CA8FE24382A6D688A +DDF2121325C331324B4B8F844B4AA601F3D3FE6A7473737401967473730000040071FF91 +07C20614000B000E0033003A000000101716203736102726200725110125211501331233 +321716212306152334372123350607062322272610373633321716171133013332353423 +06012F5354012454545454FEDC5402D80299FD670368FD4CAF3CDBE30101FEB229039903 +FE969D3A58597CCB807F7F80CB7C59583AB801FD14AE465802FAFE6A7473737401967473 +735FFCFA030693A8FCDB0120F6BD333C3B34A8643031A2A20210A2A2313064025EFA7F36 +5B020001003700000640059E0037000001112115211114171633213237363534262F012E +0135343633321617152E012322061514161F011E01151407062901222726351123353311 +0177017BFE8525267302408246465EB240AB98E0CE66B44C4EA85A898962943FC6A37C4C +FEF9FDC4D551518787059EFEC28FFDA08927272D2C34494D2A0F2495829EAC1E1EAE2828 +54544049210E2C978992653E504FD202608F013E000000020037FE56050806140026002F +000001112130353437363B01152322070615131407062B0135333237363D012322272635 +1123353311010211211114171633017701785751C3AEB0632627025152B54631692626BD +D551518787023302FE88252673059EFEC24EB55B5699282868FB29D660609C3031991450 +4FD202608F013EFAFC01A20195FDA089272700030037FF7005C9059E002D003900440000 +011121153621321716171526272623220706101F01363320171407062322270615073437 +212227263511233533110116333237362726232207060526351037211114171633017701 +7B9500FF5551514C4E4F4E50B3636363084FCE012B01654B9D5449029906FEEBD5515187 +87033B42535F151F010183722805FED6808BFE99252673059EFEC27A95111223AC2B1615 +7172FE667209ABF676291E12324C034F41504FD202608F013EFAF918070B274B560A099D +F801079BFDA0892727000001002FFE56066F06140035000001111407062B013533323736 +351134262322061511231121112311233533353437363B0115232207061D01213B011536 +373633321716066F5251B5FEE96926267C7C95ACB9FED3B9B0B05757BDAEB0632726012D +02B742595A75C1636302A4FD48D660609C30319902B29F9EBEA4FD8703D1FC2F03D18F4E +BB55569928286863AE6532327778000100C1000005410614002700001333112132373635 +34262F012E0135343633321617152E012322061514161F011E01151407062901C1B801FD +8246465EB240AB98E0CE66B44C4EA85A898962943FC6A37C4CFEF9FD4F0614FA862D2C34 +494D2A0F2495829EAC1E1EAE282854544049210E2C978992653E000200C1000004E20614 +000A000D008E400D0D05030B0603090B010800460E10FCFC3CD4C432111239393100400A +420DA902BC05A90097072FECECF4EC304B5358400A05110C0D0C0D11040504071005ED07 +1005ED59014042050416041B0C26044704490C060D0D0F0F18051D0D2B0D300F390D4003 +400440064007400F430D45055705590D6003600460066007600F620D6605AF0FBF0FDF0F +1A5D005D1333112115012115212B01131101C1B80369FD4C02B4FC971A9EB8029A0614FE +4CA8FCDB9303CDFCF9030700000000020036FFE203E9051F000C0019000013331B01331B +013303230B012303331B01331B013303230B01233674919089919074B988989988B97491 +9089919074B9889899880255FE1701E9FE1701E9FD8D0202FDFE053DFE1701E9FE1701E9 +FD8D0202FDFE0002003600AD03E9051F0007000F0033400C110C040809010D0508080010 +10DC3CEC32D43CEC32C431400C0B0E0DA90810030605A900100010D4FCCC3210D4FCCC32 +30132111231121112311211123112111233603B38FFD6B8F03B38FFD6B8F0255FE580105 +FEFB0472FE580105FEFB00010000FE4A0490061400190033400C1B001608174E0F080546 +0A1A10D4FCECF4EC32CC3100400F128700028C1A09870B9716BC18BD1A10ECECF4EC10F4 +CCEC302506232226351134262B0135333216151110333236351133112303D783E7C1C64C +693146B5A3F895ACB9B9ACC9EFE802C499619CC0D6FD42FEC3BEA40279F9EC0000000001 +0000FE56057606140021003A400D1D23001508174E0F0805460A2210D4FCECF4EC32CCCC +31004012128700028C220A870B97221C871EBD16BC2210ECFCEC10F4EC10F4CCEC302506 +232226351134262B01353332161511103332363511331106163B01152322262703D783E7 +C1C64C693146B5A3F895ACB90450683246B69E05ACC9EFE802C499619CC0D6FD42FEC3BE +A40279FB8E94669CB9DD00010075029C02C406030013003040071500030E0A0B1410D4DC +3C2FCCCC4BB00D5158B11540385931B27F15015D00400606110C020B1410D43CC4D4CC30 +0111231134262322061511231133113E0133321602C4744E4E5E6C757529714A797D0417 +FE85017759596B5CFE9E0367FEAB3838860000010075029C02C40603001B003A4BB00E53 +5840081D000310160A0B1C10D4DC3CCC2FCCCC4BB00D5158B11D40385931B27F1D015D00 +40070619110F020B1C10D43CD4CCD4CC30590111231134262322061511231134363B0115 +2322061D013E0133321602C4744E4E5E6C756772A092423029714A797D0417FE85017759 +596B5CFE9E0284786B5736567238388600000002FFE901AD00EE0603000D001100234007 +130F01080E001210DC3CCCDC3CCC310040070E11000807001210D4D4CC10DCCC30133311 +1407062B01353332373635113315237A743433722C1F4218187474050FFD82783636581B +1B560372820000010075029C0212051E0011001F4005110B07081210DCCC00CC31004007 +001107030E09082FC4D4CC10D4CC30012E012322061511231133153E0133321617021213 +2E1C626975752475540C1D1204AF0A09716BFEB60273613937020300000000010048028D +01E4050F0011001BB408060B111210DCDC3CCC3100B50011030E07092FCCD4CCD4CC3013 +1E013332363511331123350E012322262748132E1C626974742475540C1D1202FC0A0971 +6B014AFD8D61393702030001004801AD0275050F001B0027B61D0E090615001C10DCDC3C +DCDC3100400A0D0F14001B03180809142F3CCCD4CCD4CC10DCCC30131E01333236351133 +111514163B01152322263D020E012322262748132E1C62697430421F2C72672475540C1D +1202FC0A09716B014AFD8D0B5636586C780B613937020300000000020020029C028F050F +001600210000011E01151407062B01113311333237363F01330706070601333237363534 +27262B01019B4346434489F4746B3D252528667B6E212122FED77D4B272929274B7D03E9 +0F4E3B5B2D2D0273FEF715143FA1AB351E1EFF0017182F2E181900010036029C03E9050F +000C000013331B01331B013303230B01233674919089919074B988989988050FFE1701E9 +FE1701E9FD8D0202FDFE0001002601AD02D5050F00110000010607062B0135333237363F +0101331B0133019F312F2E4E5D44301A1B2015FEE27BDDDC7B02627022235714144B2F02 +69FE1601EA00FFFF00A00474019F0666100603120000FFFF00A004740313066610260312 +00001007031201740000FFFF00AE03E901D305D510060AFA0000FFFF00B203FE01D705D5 +10060AFB0000000100C404EE01E906DA00050017400A039E000603020019050610DCFCD4 +CC310010D4EC3001151323033501975281A406DAACFEC00140AC0001007503EF01870614 +000E0031B40007040C0F10DCB43F0C4F0C025DCCDCB6000710072007035D3C3100B60104 +000708970F10F4CCDCB20000015D39CC3013353236353426233532171614070675405858 +4073504F4F5003EF7B58403F587B504FE650500000000001007503EF01870614000E0031 +B400080B040F10D4CCDC400D0004000B1004100B2004200B065D3C3100B60E0B00080797 +0F10F4CCD4B20000015D39CC30012227263437363315220615141633018773504F4F5073 +4058584003EF5050E64F507B583F4058000000010075029C02890602001C002B40090105 +0005161A0E001D10D4C4DCDCCC111239310040091A00120D0E09121C1D10D4D4CCD4CC10 +DCCC30013332373635262726232207060735363736333217161514070607112301142779 +372401233F713232313130333335AE574444466B80045B402A374B24400C0C186014090A +5846685F404311FE930000010075029C02890602001C002D400A1C00180F00180700031D +10DCCCDCCC10C41112393100400903000B100F140B021D10D4D4CCD4CC10DCCC30011123 +112627263534373633321716171526272623220706071417163301EA7F6B47444557AD35 +33333031323132713E2401253679045BFE41016D1143405F6846580A091460180C0C4024 +4B372A4000000001010B043202F506B0000600000125150D011525010B01EAFE990167FE +1605BBF58BB4B48BF5000001010B043202F506B0000600000105352D01350502F5FE1601 +67FE9901EA0527F58BB4B48BF500000100C1047C033F06660006003DB4040275060710DC +EC393100B504050200B30710F4CC32B410021005025D3930004BB009544BB00E545B58BD +0007FFC000010007000700403811373859013313230B012301B694F58BB4B48B0666FE16 +0167FE990000000100C1047C033F06660006004CB4030575010710DCEC393100B5030004 +01B30710F43CD4B21000015D3930004BB009544BB00E545B58BD0007FFC0000100070007 +00403811373859400C35003A0635023A04043303015D015D0103331B01330301B6F58BB4 +B48BF5047C01EAFE990167FE1600000100C104EE033F066600060037400C040502B400B3 +07040275060710DCEC39310010F4EC323930004BB009544BB00E545B58BD0007FFC00001 +00070007004038113738590133132327072301B694F58BB4B48B0666FE88F5F500000001 +00C104EE033F066600060037400C0300B40401B307030575010710DCEC39310010F43CEC +3930004BB009544BB00E545B58BD0007FFC0000100070007004038113738590103331737 +330301B6F58BB4B48BF504EE0178F5F5FE88000100D603E7015E06120003001340040500 +030410DCDCCC3100400203022FC43001112311015E880612FDD5022B0000FFFF00D50562 +032B05F61006007100000001017304EE035206660003000001330123028BC7FEBA990666 +FE88000100AA04F0028906660003000009012301016F011A99FEBA0666FE8A0176000001 +00D6FED1015E00FC0003001340040500030410DCDCCC3100400203022FC4302511231101 +5E88FCFDD5022B000000FFFF00D5FEC0032BFF54100700710000F95E0000000100AAFE1C +0289FF920003000005012301016F011A99FEBA6EFE8A0176000000010173FE1C0352FF94 +0003000005330123028BC7FEBA996CFE88000002006F000001D40423000200050045400B +000103050300040205010610D43CC44BB0105058B30740024038385932393931002FC4D4 +C43040090F03000060006F03045D01400D500760076004600264036400065D0103210313 +210121B20165B3B3FE9B02D9014AFD27FEB60001006F02D901D4042300020034B6000103 +0002010310D4C44BB0105058B30440024038385939310010D4C430400500006000025D01 +40095004600460026400045D0103210121B2016502D9014A0000FFFF007501FE01870423 +100702800000FE0F0000FFFF007501FE01870423100702810000FE0F00000001011F01D4 +02E1039600070000011521353311331102E1FE3E9696026A9696012CFED40001011F01D4 +02E10396000700000135211523112311011F01C2969603009696FED4012C0001006400FF +02BA0355000B0000013533153315231523352335014496E0E096E00275E0E096E0E09600 +00000001006401DF0226027500030000012135210226FE3E01C201DF9600000100C70529 +03390648000D0057400E0BF0040700B30E0756080156000E10DCECD4EC310010F43CD4EC +30004BB0095458BD000EFFC00001000E000E00403811373859004BB00F544BB010545B4B +B011545B58BD000E00400001000E000EFFC0381137385913331E0133323637330E012322 +26C7760B615756600D760A9E91919E06484B4B4A4C8F909000000001019A054402660610 +0003004E400902CE00CD040164000410D4EC310010FCEC30004BB00A544BB00D545B58BD +00040040000100040004FFC0381137385901B00D4B54B00E4B545B58BD00040040000100 +040004FFC0381137385901331523019ACCCC0610CC00000200EE04E103120706000B0017 +0020401103C115F209C10FF11800560C780656121810D4ECF4EC310010F4ECF4EC300134 +26232206151416333236371406232226353436333216029858404157574140587A9F7373 +9F9F73739F05F43F5857404157584073A0A073739F9F0001014CFE7502C1000000130020 +400F0B0E0A07F30EF40001000A0427111410D4ECC4D4CC31002FFCFCC412393021330E01 +15141633323637150E0123222635343601B8772D2B3736203E1F26441E7A73353D581F2E +2E0F0F850A0A575D3069000100B6051D034A0637001B006340240012070E0B040112070F +0B0412C3190704C3150BED1C0F010E000715561677075608761C10F4ECFCEC1139393939 +310010FC3CFCD43CEC11123911123911123911123930004BB009544BB00C545B58BD001C +FFC00001001C001C0040381137385901272E0123220607233E013332161F011E01333236 +37330E0123222601FC3916210D2624027D02665B2640253916210D2624027D02665B2640 +055A371413495287931C21371413495287931C000000000200F004EE03AE066600030007 +004240110602B40400B3080407030005010305070810D4DCD4CC1139111239310010F43C +EC3230004BB009544BB00E545B58BD0008FFC00001000800080040381137385901330323 +0333032302FCB2F88781AADF890666FE880178FE88000001FFFF01DE02AD0408000F0000 +032533151417163B0115232227263505010116B82626692B40AF5752FEEB0364A4949931 +309C605AC8A2000100EF04EE03100666000B0000012707233727331737330717025C5C5D +B4B5B5B45D5CB4B6B604EE6161BBBD6060BDBB0000000002007501AB02FD050F000D0015 +0000011615142320353437033317373301061514333235340205B2F2FEF5B5FA89BFB789 +FEBC89888103DCF9B48484BFF2012FE0E0FE8AAE984D4D8900000001007A029C00EE0603 +0003000DB102032FCC3100B100032FC430133311237A74740603FC99000000010075029C +0290052F00320000011526272623220706151417161F0116171615140706232227262735 +161716333236353427262F012627263534373633321716026A31353439572B2B1F1F5D28 +7D32344E4D88393E3D44403F3E3D5258201C6F286C3030474682403939050D61160B0B17 +182F2414151208182A2B4D5733330A0A136B1E0F0F322D2A1714170815292A4958303109 +080000010075029C0321050F000B00000103012327072301033317370314FF010C89CDCD +890112FB89BBBB050FFECFFEBEF6F60148012BDFDF00FFFF0075029C0289060210060283 +0000000100D60000031D055800050015400901A90300000804020610C4D4EC31002FD4EC +302111213521110295FE41024704D088FAA8000100D60000031D05580007002740183F04 +3F012F042F011F041F010601A904050000040806020810C4D4EC3231002FD4DCEC5D3021 +112135211133110295FE4101BF88039C880134FAA800000100D60000031D055800070019 +400B01A904050000040806020810C4D4EC3231002FD4DCEC3021112135211133110295FE +4101BF880268880268FAA8000000000100D60000031D0558000700274018700470013004 +3001100410010604A901050000040806020810C4D4EC3231002FD4DCEC5D302111213521 +1133110295FE4101BF88013488039CFAA800000100D60000031D05580005001540090100 +A904000802040610C4D4EC31002FECC430251133112135029588FDB98804D0FAA888FFFF +00C1FDEC033FFFD6100702870000F9700000FFFF00D504E2032B06761227007100000080 +120600710080FFFF00AE03E9036D05D512060AFF0000FFFF00EEFE14031200391007029C +0000F9330000000100B6FE76034AFF900021005F400E12011100091A561B7709560A7622 +10F4ECFCEC113939393931004015001609110E05011609120E0516C31F0905C31A0E2210 +D43CFCD43CEC11123911123911123911123930004BB0095458BD001CFFC00001001C001C +0040381137385901272627262322070607233637363332161F0116171633323736373306 +070623222601FC391611100D261212027D0233335B264025391611100D261212027D0233 +335B2640FEB337140A0925245287494A1C2137140A0925245287494A1C000002FCA8047B +FE870666000300040036400C01B400B304B805040344010510DCEC39310010E4F4EC3000 +4BB009544BB00E545B58BD0004FFC0000100040004004038113738590901230901FD6D01 +1A99FEBA01580666FE8A0176FE150002FD71047BFF500666000300040036400C02B400B3 +04B805040344010510D4EC39310010E4F4EC30004BB009544BB00E545B58BD0004FFC000 +0100040004004038113738590133012317FE89C7FEBA998F0666FE8873000002FCC1047B +FF3F066600060007003C400F040502B400B307B80807040275060810DCEC3939310010E4 +F4EC323930004BB009544BB00E545B58BD0007FFC0000100070007004038113738590133 +132327072305FDB694F58BB4B48B013F0666FE88F5F573000000FFFFFCB4051DFF480637 +1007029EFBFE00000000FFFFFCD90562FF2F05F610070071FC0400000000FFFFFBEC057C +0014060B10070B20FC0000000000FFFFFCBF0529FF3106481007029AFBF8000000000002 +FDA2047BFE5A0614000300040025400C02BE00B104B805040108000510D4EC39310010E4 +FCEC3000014007040434044404035D0133152317FDA2B8B85E0614E9B0000003FCD7047B +FF290610000300070008004940110602CE0400CD08B809016408000564040910DCFCD439 +EC310010E4FC3CEC32300140230408340844086001600260036000600160026005600660 +0870017002700570067008115D013315232533152305FE5ECBCBFE79CBCB01290610CACA +CACB0001FD3704F2FEF7067B00190022400914564005800C56190D2FCCEC1ADC1AEC3100 +400617C14002C00D2F1ADC1AEC30013633321615140F0106070615233534363F01363534 +26232207FD377069687F582C230407771E332D2E3E475A6406483355433D41201A091020 +0C283625222228152434FFFFFCEC04E1FF1007061007029CFBFE00000000FFFFFCF404EE +FFB206661007029FFC04000000000002FCC5047BFF43066600060007003C400F0300B404 +01B307B80807030575010810DCEC3939310010E4F43CEC3930004BB009544BB00E545B58 +BD0007FFC0000100070007004038113738590103331737330307FDBAF58BB4B48BF54E04 +EE0178F5F5FE887300000001FDBC04ECFE4406A80003000EB2021B002FEC3100B103012F +CC3001112311FE448806A8FE4401BC000000FFFFFCF004ECFF1006A8102702BEFF340000 +100702BE00CC000000000002FC5D04EEFF1B066600030007004240110602B40400B30804 +05010007030107050810D4DCD4CC1139111239310010F43CEC3230004BB009544BB00E54 +5B58BD0008FFC0000100080008004038113738590113230321132303FD0FCD87F80200BE +89DF0666FE880178FE8801780000FFFFFCBF0529FF310756102702B8000001421007029A +FBF8000000000001FCBF0529FF310648000C0018B50756080156002FECD4EC3100B40AF0 +0400072F3CDCEC3003232E0123220607233E012016CF760B615756600D760A9E01229E05 +294B4B4A4C8F909000000001FE1F03E9FF4405280003000A40030201040010D4CC300123 +1333FEF2D3A48103E9013F0000000001FD9004C2FE8206C1000800000110233516352335 +33FE82F27070F205C3FEFF7B0389FE0000000001FD9004C2FE8206C10008000001353315 +2314371522FD90F16F70F205C3FEFE89037B0001FF79049A008706120003000003330323 +40C775990612FE880000FFFFFCA8FDDFFE87FF5510070043FBFEF8EF0000FFFFFD71FDDD +FF50FF5510070076FBFEF8EF00000001FD24FE14FE3CFFCE000700000123353335331123 +FDC4A0A07878FEB578A1FE4600000001FDC4FE14FEDCFFCE000700000533153315231523 +FDC478A0A07832A178A10001FE550586003F07700005000003213521112349FE9E01EA88 +06E888FE16000001FEF0036B007B04E000130031400607560E0411002F4BB00C544BB00D +545B4BB00E545B58B9000000403859DC32DCEC310040050A04C100112FC4FCCC3001351E +0133323635342627331E01151406232226FEF03D581F2E2E0F0F850A0A575D306903D777 +2D2B3736203E1F26441E7A7335000001FD80FE12FE56FFBE000D001C40060D060A56030E +10D4FCCC323100400606C1070DC1002FFCDCEC300122263534363315220615141633FE56 +5A7C7C5A28353528FE127D5A597C78352728350000000001FD0BFE14FEF5FF4D00070000 +0133152135333533FE44B1FE16B188FE9C8888B100000001FD0BFE14FEF5FF4D00070000 +0123352115231523FDBCB101EAB188FEC58888B100000001FD24FE14FEDCFFCE000B0000 +012335333533153315231523FDC4A0A078A0A078FEB578A1A178A10000000001FD0BFE88 +FEF5FF100003000001352115FD0B01EAFE88888800000001FD7AFE56FFD00080000D0000 +27151407062B0135333237363D01305251B5FEE96926268094D660609C30319994000001 +FD77FE56FFCD0080000D00002533151417163B01152322272635FD77B8262669E9FEB551 +5280949931309C6060D60001FDA2FE89FE5AFF730003000005331523FDA2B8B88DEAFFFF +FCD5FE89FF27FF531007006AFBFEF94300000002FD28FE12FED4FFBE000B0017001E4008 +00560C780656121810D4ECF4EC3100400615C10309C10F2FFCDCEC300134262322061514 +16333236371406232226353436333216FE5B3627283535282736797C5A5A7C7C5A5A7CFE +EA26363527283536265A7D7D5A597C7C00000001FD6AFE14FE8FFF540003000A40030300 +040010D4CC3005330323FDBCD3A481ACFEC0FFFFFD23FE75FEC100001007007AFC000000 +0000FFFFFD4CFE75FEC100001007029DFC00000000000001FDBCFE14FE44FFA00003000E +B2021B002FEC3100B101032FCC3005112311FE448860FE74018C0001FCF0FE50FF17FF9A +000700000711233521152311E989FEEB8966FEB6C2C2014A00000001FC63FE39FF98FF58 +00140000010623220334353316333237331617323733020722FDFE3C74DA11750E68650F +760C69660F760FDC74FE8B52011A02039696950196FEE2010000FFFFFCC5FE14FF43FF8C +11070289FC04F9260027004BB009544BB00E544BB00B544BB00C545B5B5B58BD00070040 +000100070007FFC0381137385900FFFFFCBFFE14FF3DFF8C11070288FBFEF9260027004B +B009544BB00E544BB00B544BB00C545B5B5B58BD00070040000100070007FFC038113738 +5900FFFFFCBFFE39FF31FF581007029AFBF8F91000000001FCBFFE36FF31FF55000C0000 +03232E0123220607233E012016CF760B615756600D760A9E01229EFE364B4B4A4C8F9090 +0000FFFFFCB4FE39FF48FF531007029EFBFEF91C0000FFFFFCD9FEC0FF2FFF541007028F +FC0400000000FFFFFBECFE1D0014FEAC10070042FC0000000000FFFFFBECFE1D0014FFEE +10070AF9FC00000000000001FB8C01ECFFAD030C001B000003150E0123222726272E0123 +220607353E0133321617161716333236534B8F4F5A71160B4D67334F8D494E925335644A +0C15745D4689030CAE3B37330A0421183B3FAE3C36161F050A373D0000000001FD7801C4 +FF880268000300000315213578FDF00268A4A40000000001FAED01C4FFFF026800030000 +01352115FAED051201C4A4A400000001FB68FFA2FFBC04BC0003000005270117FBC86003 +F55F5E4E04CC4F0000000001FA12FFBAFF9106170003000005270117FA79670519664658 +0605590000000001FDACFE12FE82FFBE000D001C40060D060A56030E10D4FCCC32310040 +0600C10D07C1062FFCDCEC300532161514062335323635342623FDAC5A7C7C5A28353528 +427D5A597C78352728350001FCF1FE5BFF18FFA5000700000111331521353311FCF18901 +1589FE5B014AC2C2FEB60002FD21FE14FEE3FFD60003000700000511211101352315FEE3 +FE3E014AD22AFE3E01C2FEB6D2D20001FC63FE39FF98FF58001400000536333213141523 +26232207232627220723123732FDFE3B74DA11760D676610760B69660F760FDC74FA52FE +E602039696950196011E010000000001FD2B04F3FEE506AD000B00000107273727371737 +17071707FE087D607D7D607D7D607D7D6005707D607D7D607D7D607D7D600001FE0604C2 +FF2006D2001D0000012E0135343637150E01151417161F011E0115140607353E01353427 +2627FE43211C93875249090C1237211C93875249090C1205C71C301C5051026E021B1C0A +0C0F0E2B1C301C5051026E021B1C0A0C0F0EFFFFFBEC043A0014060B10270B20FC000000 +10070B20FC00FEBE0000FFFFFCA804F0FE87066610070043FBFE00000000FFFFFD7104EE +FF50066610070076FBFE00000000FFFFFCB4051DFF4806371007029EFBFE00000000FFFF +FD9004C2FE8206C1100602C40000FFFFFCE70546FF6207D21007031CFC1000000000FFFF +FDC6FE56FEA2FFA410070316FC10000000000001FCD5051DFF2B06490007000003233521 +15231121D596FED6960256051D9696012C000002FD1FFE32FEE1FFB80003000700000121 +352135213521FEE1FE3E01C2FE3E01C2FE32789678000002FD15FE14FEEBFFA000030007 +00000533112301331123FD1596960140969660FE74018CFE74000001FD1FFE14FEE1FFD6 +00050000052111231121FD1F01C296FED42AFE3E012C0001FCB604EEFF4A066600270000 +013733071617163332373637330607062322272627072337262726232207060723363736 +33321716FDFF426D6B0B16100D261212027D0233335B26201E21426E6B0D14100D261212 +027D0233335B26201E05FF67A9090E0A242552874A490E0D1D67A80B0D0A242552874A49 +0E0D0003FCB60489FF4A06CC001D00210025000001272E012322061D012334363332161F +011E013332363D01330E012322260733152313331523FDFC39191F0C24287D6756243D30 +3917220F20287D026754223BE89696D296960568210E0B322D066576101B1E0D0C332906 +6477102E960243960000FFFFFCB604C5FF4A06901022171800B710031718000000A70001 +FC63FE28FF9DFFC2000D00000137211723273733072127331707FE7084FE19847FAFAF7F +8401E7847EAFAFFE289B9BCDCD9B9BCDCD000001FD33FE14FECDFFA40008000001233507 +3537171527FE32649BCDCD9BFE14E7847EAFAF7E84000001FD7804E1FE88070600100000 +0106070615141716171526272634373637FE88402A2C2C2A40724E50504E72068B012A2C +40412B2B017B014F50E6504E0100FFFFFCBF0460FF3106D8102702C200000090100602B8 +00E5FFFFFD2BFE14FEE5FFCE100702EE0000F92100000001FD7804E1FE88070600120000 +01303516171614070607303536373635342726FD78724E50504E72402A2C2C2A068B7B01 +4E50E6504F017B012B2B41402C2AFFFFFF2E0544FFFA06101007029BFD94000000000003 +FC90FE12FF6FFFBF00070015001D00000016323E01262206373632161406222706222634 +36321236342622061416FD09354F3502374F35F73EB57C7CB63D3EB67C7CB6FE36364F35 +35FEC335354D37356D3F7CB37D41407DB37CFECE364D36354F35FFFFFC70FE1B0390FF85 +10070B21FCC900000000FFFFFC70066B039007D510070B21FCC9085000000001FC7006D7 +0390076B0003000001211521FC700720F8E0076B94000001FC70FEC00390FF5400030000 +05211521FC700720F8E0AC9400000001FD2A060D02D60727002300000327262726232207 +060723363736333217161F011617163332373637330607062322272604901C4F2C246535 +4605A2047170C85B3F395A901C4F2C2461394704A2047170C85B3F39064A370B120A2430 +47874A490E0D22370B120A242C4B874A490E0D000000FFFFFC7006040390076E10070B22 +FCC9000000000001FC77FE280393FFC200080000013521273317072337FC770673847EAF +AF7E84FEC3649BCDCD9BFFFF00C90000047105D5100611F00000FFFF00C1000003D00460 +100611F10000000100C90000061C05D5000B0000132111231121112311211123C90553CA +FE86CBFE86CA05D5FCF40262FAD5052BFD9E000100C90000046505D5000B000013211123 +1123112311231123C9039CB8B9B9B9B905D5FCF40262FAD5052BFD9E0000000100A00474 +019F066600030011400601000402000410D4CC310010D4CC301B013303A041BE6E047401 +F2FE0E000000000100A0FE56019F004800030011400602030400020410D4CC310010D4CC +3025032313019F41BE6E48FE0E01F2000000FFFF00C90000053305D5100603AC0000FFFF +00BA000004790460100603CC0000000101B6FE560292FFA4000D000001232227263D0133 +151417163B010292941A1A14950A0C0E23FE56211A2EE5E50E0C0D000000FFFF007FFFE3 +03F5047B100602160000FFFF0071FFE303E7047B10270079014FFF84100600460000FFFF +007FFFE303F5047B10270079008EFF84100602160000FFFF009EFF1201C304231206001E +00000001017304EE0352066600030031400902B400B3040344010410D4EC310010F4EC30 +004BB009544BB00E545B58BD0004FFC00001000400040040381137385901330123028BC7 +FEBA990666FE88000000FFFF00D70546035207D21226006A00001107031B0000016C0014 +004007AF089F085F08035D40055F080F080271300000FFFF00100000056806661027031B +FEDA0000100603260000FFFF00DB024801AE0346120600790000FFFFFFE7000005750666 +1027031BFE7400001007032A00EA00000000FFFFFFF30000061F06661027031BFE800000 +1007032C00E400000000FFFFFFED0000027D06661027031BFE7A00001007032E00EA0000 +0000FFFFFFF2FFE3060106661027031BFE7F0000100603342800FFFFFFE1000006910666 +1027031BFE6E00001007033901AA00000000FFFFFFDB0000060506661027031BFE680000 +1006033D3600FFFF00050000028007D21027031CFF2E00001206034D0F00FFFF00100000 +056805D5120600240000FFFF00C9000004EC05D5120600250000000100C90000046A05D5 +00050019400C04950181000702041C01040610FCFCCCC431002FF4EC30331121152111C9 +03A1FD2905D5AAFAD500000200100000056805D500020006003D400C4200950481019503 +0806030710D4C4C431002FECF4EC304B5358401200110504030211060605001104011103 +0304050710EC10EC0710EC0810EC590901210501330102BCFE660335FBB9023AE5023905 +0EFB9AA805D5FA2B0000FFFF00C90000048B05D5120600280000FFFF005C0000051F05D5 +1206003D0000FFFF00C90000053B05D51206002B000000030073FFE305D905F000030012 +00210032401C0495139122039500AD220B951A8C222310010F1916330008191E102210FC +ECC4F4ECC4EC310010F4EC10F4EC10F4EC30012115210122070611100033323736111027 +2627200011100706212027261110373601C502C2FD3E0162DC81820103DCDC81808081DC +013A0178BCBCFEC6FEC5BCBDBDBC0370AA0286A4A4FEE5FEE6FEB8A4A4011A011BA4A4A4 +FE5BFE9EFE9FD2D3D2D201620162D3D20000FFFF00C90000019305D51206002C0000FFFF +00C90000056A05D51206002E0000000100100000056805D50006003C400B420695028105 +010804010710D4C4C431002F3CF4EC304B53584012061103020105110404030611020011 +010102050710EC10EC0710EC0810EC5933230133012301E5D5023AE50239D2FE2605D5FA +2B050E000000FFFF00C90000061F05D5120600300000FFFF00C90000053305D512060031 +0000000300C90000046205D500030007000B002A4016079504810B039500AD08950B0D04 +010905000804040C10FC3CC4D43CC4EC31002FECF4EC10F4EC3001211521032115211121 +1521013202C7FD39690399FC670399FC670371AA030EAAFB7FAAFFFF0073FFE305D905F0 +120600320000FFFF00C90000053B05D5120603B30000FFFF00C90000048D05D512060033 +0000000100C90000048B05D5000B00464011420A06950781000495030D01080407040C10 +FC3CD43CCC31002FEC32F4EC32304B535840120B110505040A110606050B110500110405 +04050710EC10EC0710EC0810EC5925211521350901352115210101B102DAFC3E01DFFE21 +03B0FD3801DFAAAAAA02700211AAAAFDF300FFFFFFFA000004E905D5120600370000FFFF +FFFC000004E705D51206003C000000030073000005D905D5000800110027003C4010290D +1921121A001C251D11041916102810FCECD43C3CFC3C3CD4ECC43100400E1100951D1A1B +81270908952512272FD43CFC3C10F4D43CFC3C3001060706151417161733363736353427 +26270326272611103736373533151617161110070607152302C2966282826296CA966280 +806296CAF49EBDBD9DF5CAF49DBCBC9DF4CA048E155773C6C5735715155773C5C6735715 +FC101686A0010F010FA187169F9F1786A1FEF1FEF2A186179D00FFFF003D0000053B05D5 +1206003B000000010073000005DB05D5001D002E4017100D951B02150E0781001F151C16 +020E1C1B0F081C071E10DCECD43CFC3CD4ECCC31002FE43232DC3CEC3230213627222726 +03113311101716171133113637361901331102070623061702C20101D6BCB805D5826E8A +CA8A6E82D505B8BCD6010186B0D2CC01680199FE67FEE6A48C0E03F1FC0F0E8CA4011A01 +99FE67FE98CCD248EE000001004E000005CF05E700260033401B0B951E91260312159502 +1403071928100022331A120E19151A102710FCC4FCC410F4C4ECFCC431002F3CEC323232 +F4EC30251521353637363534272623220015141716171521352126272635103736212017 +16111407060705CFFDA8B163638484D8D8FEF76364B2FDA8013F9E4948C0BF0131012FC1 +C04747A1B2B2B261A6A6CAF09191FEDDEFCAA6A661B2B28B9595B8013EC5C5C5C4FECBC2 +94948D000000FFFF000600000258074E10271716032F01751306032E00000008B4090306 +08072B310000FFFFFFFC000004E7074E10271716047101751306033900000008B40C0207 +08072B310000FFFF0071FFE704E406661226034500001006031B6E000000FFFF0085FFE3 +03C806661026031B50001206034900000000FFFF00BAFE56046406661027031B00C60000 +1206034B0000FFFF00A60000029806661226034D00001007031BFF460000FFFF0095FFE3 +042A07D21226035900001006031C1B00000000020071FFE704E40479000D002A00C8400B +1211072C1017071225452B10FCECD4C4C4123939400A3F102F101F10038F10015D710040 +1112110B03B929B8190BB9218C0FBC1687192FECE4F4EC10F4EC1139390540141D110011 +0E11121111100F110E1100111D11111007103CECECEC0807103CECEC313001400B841286 +118801890D8010055D401349134912491C4A1D4E0D4C004E01490E4B11095D40113A0E39 +123A11381D38113F0D3C003E01085D400B2B0D2B012A00290E2911055D400D190F180E1B +0D1B011A001911065D0040052B1E2B1F025D01272623220706151417163332371B013303 +171617163B0115232227262706070623222726111037363320034E2C2DB2863D4D4B4C79 +8648A463A4CD2809232920586E5E5429112E5E2C8FEB72757F8DC601370209E7ED6E8AB6 +DC696BD501E70125FDA1DB3129309C542A586F5729989D011301268A9A00000200C0FE56 +04880621000E001C0037400F1812071E4513120B16001C0803461D10FCEC32C4D4ECE4D4 +EC3100400E1AB9050915B91611B90D8C02BD1D10ECF4ECD4FC39D4EC3025112311102120 +111007041110212203163320111005352011342320110179B901AA01B2AC0118FE1ED459 +6FC50120FE30016BEAFEFB45FE11060301C8FE7FFEEE645AFEF5FE26014AAD013A011A16 +AA0140DBFEC800010020FE56047F0460000E0040400710030708040C0F10D4D4FCD4C431 +004007020CBF06BD04072F3CECE432300540120111080702110304030E0D011100110708 +070710ECEC39390710EC08EC011301330111231101262B013533320169F5015EC3FE3BB8 +FEDA2C5F3146C503B0FD4C0364FBA0FE5601AA03447E9E00000000020071FFE3047505F0 +001C002D00544014060528042F451C28120A5112041218211212452E10FCECD4EC10F4B2 +7F0A015DECC4EC1112393900400E060525021C0002B91A25B90E8C2E10F4ECD4FCD4CC11 +1239394006161D53050605070E10EC393130012623221514051617161110070623222726 +1134373637263510213217010607061514171633323635342726272603EC66EFFD0108D0 +758E8989F0EF8A8989354B9C01B9DD78FE1844375655569593AC5B617E40051146755C30 +257087FEEBFEF79C9D9D9C0113CCA540244F8D011046FE281D4971CCCB7273E8BEC76067 +0B0600010085FFE303C8047C0032003D40220C860B8810B908B8331BA918332786288823 +B92C8C3334190B271408041F0830453310FCECD4ECD4C4C4C4310010F4ECF4EC10D4EC10 +F4ECF4EC300126272635343736333216171526272623220706151417163B011523220706 +1514171633323736371506070623222726353436018B703C3C7271C44CAA626150514777 +45464443749B9489484E5455975D5555475A545550EE81818A025C1841405D8D4F4E1818 +A71D0D0D2E2E40462D2C983338585A3838121325AB1C0E0E5B5BAD6C92000001006BFE52 +03F80614001D003E400B0A0E121F0419181C12141E10D4ECD4D4D4C4FCCC4BB0105158B9 +0016004038593100400E08B90A00B9128C1E1A178718971E10F4EC3210F4ECDCEC302516 +1716151407062334351637363534272623200310012135211500111002CA844F544A50A3 +452A20201F3AFDA201023BFDEC0366FD2C7F014B4F787350574B4C052C2325352C2A0233 +01EC0159B9B9FE94FE27FE690000000100BAFE560464047B00150031401606870E12B80C +BC02BD0B17460308004E090D080C461610FCEC32F4ECEC31002FECE4F4C4EC304005A017 +801702015D011123113426232206151123113315363736333217160464B87C7C95ACB9B9 +42595A75C1636302A4FBB204489F9EBEA4FD870460AE653232777800000000030071FFE9 +04750624000800110021004F401B0DB91297220195112205B91A8C222345000912165101 +11121E452210FCEC32F4B27F16015DEC32EC310010F4EC10D440073F111F110F11035DEC +10F4EC30400B190616047704A023802305015D0121121716333237361302272623220706 +030132171611100706232227261110373603B1FD830F455695965349091C365693995140 +13013DF089898989F0F18889898802C6FED57F9C9D8A01C9011C649E9C7EFEFC02B4D4D3 +FE8AFE8BD4D5D5D401750176D3D4000100A60000026E0460000D001B40070F0600080D46 +0E10FCFCD4C4310040050DBC0587082FECE43001111417163B0115232227263503016322 +246C596FB45252010460FD2B912E309C6062D402CA00000100BF000004850460000B0049 +40090D06040901080B460C10FCEC32C4D4C4310040050300BC070B2F3CE4323040160811 +0904050711060605080509040311040211090904071004EC1005EC093C3C071005EC1008 +EC133311013309012301071123BFBE01E3E0FE4701FEE1FE6289BE0460FE2F01D1FE5AFD +46024281FE3F0001003D0000047F0614000D004640050F010B050E10D4C4D4C431004006 +0A870B9702052F3CF4EC3040180311010006041105060507110611031101000002110001 +00071005EC1009ECEC05EC071005EC1008EC0901230901230127262B01351716027A0205 +C3FEC6FE7EC301EB4A2F6B6075E20565FA9B033CFCC40432C67E9E020300FFFF00AEFE56 +04E504601006007700000001004A0000041804600015004240071707121100011610D4C4 +D4ECC43140040B01BC00002FE43230401614131203111511060504030703110100000211 +010100071005EC1009EC12173905EC121739210133013637363736272627333116171615 +1407060701A0FEAAC6012178644C0402181C6ABA452E2A88B17B0460FC547CAC81703564 +7783597C724EC4AFE4740001006BFE520401061400260040400F0A0E122804221D1C2012 +182512142710D4ECD4ECD4D4C4D4C4FCCC31401208B90A00B9128C27162387221E1B871C +97270010F4FC3CD4EC3910F4ECDCEC302516171615140706233435163736353427262320 +1110252411343723352115201114051524131202DA844F544A50A3452A20201F3AFD9101 +4DFEE8DCD00315FD8B0210FDC602017F014B4F787350574B4C052C2325352C2A01B5012C +58240104C552B9B9FEDDBF09AA16FEBCFEF1FFFF0071FFE30475047B1206005200000001 +004AFFD9049804600017002F400B190A01120803130800161810DCC4ECD4ECC4C4CC3140 +0C07870E8C150313178700BC15002FF4EC323210F4EC301321152311141633323637150E +01232226351121112311234A04318D31370F2C07234A25785CFE63BC8F0460B8FD50483F +0501850D0C83B0029CFC5803A800000200BAFE5604A4047B0011001D0031401915B904B8 +1E1BB90A8C0FBD1E1F45121207510D08181210461E10FCECECF4B27F07015DECEC310010 +ECF4EC10F4EC300136373633320011100223222627112311340534262322061514163332 +3601143D973BB6CC00FFFFCC7BB13AB9032BA79292A7A79292A70398665A23FEBCFEF8FE +F8FEBC6164FDAE03CFE7DDCBE7E7CBCBE7E700010071FE5203E7047B00240036400C1D21 +1217260948101203452510FCECF4CCD4FCC43140111BB91D13B9008C2509860A880DB906 +B8250010F4FCF4EC10F4ECDCEC3005200011100021321617152E01232206151416333217 +16151407062334351637363534272602A8FEF3FED6012D010655A24C4E9D50B3C6C6AF83 +50544A50A3452A20201F1D013E010E0112013A2323AC2B2BE3CDCDE34C4F787350574B4C +052C2325352C2A00000000020071FFE304D60460000D001E0031400B200F0A1213510412 +1B451F10FCECF4B27F13015DECD4C431400C07B9178C1F118700B90EBC1F0010F4ECEC10 +F4EC30012207061514163332363534272627211523161510070623222726111037360273 +985256AB9593AC564F9A0263CE6D8989F0F18889897103CE6E73BEC9E7E8C8B77A6E92B8 +9CDDFEED9C9D9D9C011301159B81000100640000046D0460001100234008130D030F080C +0A1210D4C4FCC4C4C4310040080F0B870CBC02B9052FECF4EC323025163B011523222726 +35112135211521111402E6246C596FB45252FE5C0409FE57CC309C6062D40212B8B8FDE3 +910000010095FFE3042A0460001C002B400A1E4509121300081C461D10FCECD4ECE44007 +3F1E3F093F13035D310040060D1CBC05B9172FECF43C3001111417163332373637362726 +27333116171615140706272227263503015232376B96693B0F081E1C6ABA462D2A809CFE +B36562010460FD2B874045D076BB668077835A7B739AFDBBE4017876C502CA0000000002 +0070FE5604D10468000A0029003D40102B4507120F1302081E162823121A452A10FCECD4 +3CCCFC3CD4ECEC31004010001FB90B1EB82A03278713168C15BD2A10ECF43CEC3210F43C +EC3230012215113237363534272627321716111007062311231122272611103736371506 +070615141716331110033D415F5F555646368C7F898981CBB7C786888866A6423A56564D +7003CB91FD52685DDFD0705B9D848DFED9FEF1A198FE6E0191999C0113011E926D1CA317 +4E73BECA736702AF012E0001003BFE5504640461001700AE400C0410010D04090F140F03 +091810D43CD43C11121739B1190F10C43140130410010D04150F08A90F09BC1814A91502 +BD180010FC3CEC10FC3CEC1112173930B0254B535840120011110C1105030E00050E110F +021103030E070510EC10EC070810EC10ECB40D0C110E030FB40405000E030FB4100C1102 +0F0FB4010F0200050FB406070505040705111239B417161105040705111239B40B0A0C11 +0C0705111239B4121311110C070511123959050301230103262B01351704171301330113 +163B0115272402DC95FECDD901B2B6319A3146010241940133D9FE4EB6319A3146FEFEFA +017FFDD0031801D77E9E0207A7FE810230FCE8FE297E9E02070000010070FE5604D10460 +001B0036400D1D130814190D08000C0608051C10DCECD43CFC3CD4ECCC3100400E130C05 +BC1C0E0B8719008C1BBD1C10ECF43CEC3210F43C3C300526272635113311141716171133 +11363736351133111407060711230245E76B83BA554A7CB7834355BA8376DCB719256177 +F30289FD7EB74C420E03D5FC2C0E4254AF0281FD78FC6E6323FE6E00000000010087FFE3 +06270460001A003840141212131C451012150B080C07120205120402451B10FCDCEC10EC +D4FCD4ECECDCEC310040090B1204BC0E098717002F3CEC32F43CC4300520113413330215 +103332113310333211340333121510212003020226FE619BC68FDECBAACBDE8FC69BFE61 +FEF021291D0252EB0140FEC0F0FE4F021AFDE601B1F00140FEC0EBFDAE012BFED500FFFF +00050000027D06101226034D0F001007006AFF2E0000FFFF0095FFE3042A06101026006A +1D001206035900000000FFFF0071FFE3047506661026031B7D001206035300000000FFFF +0095FFE3042A06661026031B22001206035900000000FFFF0087FFE3062706661226035D +00001007031B01590000000100C9FE56056A05D5000C0000133311012109022309011123 +C9CA029E0104FD1B031AFE92860110FD0DCA05D5FD890277FD48FCE3FE56018402F5FD31 +0000000300A7FFE9044D0624000A001B00270047400E051C1A2945261C0D001E1C144628 +10FCEC32D4ECECD4B23F1A015DEC310040101C0B00B91E2822B9118C2807B917972810F4 +EC10F4EC10D4B63F1E1F1E0F1E035DEC3939300132363736353623220706011615140706 +202726023736171E0112060706231017163332373E0126016950CB447901CC7A5D3601EE +F63B7EFE0E8B6F027886D2A4DA025DFB59DF3A50AE8F571801AD0370043D6C93DEBA6CFE +C7A7E9825FD5D5A8032CBED50101E2FEE5B69614FEEA80B09C2DD19E000000020071FFE9 +04750624000A001F0037400F0B000821451507080F151A0819452010FCECCCDCEC10ECFC +3C3100400E048712972000870B1D87168C1A2010CCF4ECDCEC10F4EC3001342726232206 +1514171605202726113436333212100020001117151012201203AE3142955378794A0113 +FECC83D3D0AFDAF5FEE4FE23FEF5BCB0012FA50370F67EAA895AAA5A37AA41690136A0DE +FE64FCFCFE6501B601D201A0FEF3FEBD0142000100570000055105DF0020004940092200 +1B14041C0F072110DCCCFC39DCC4B43F00401B025DCC31004011070414050E950F1F0095 +0D9514181191052FF43CCCECECCCD4EC1112393940096F1F7F1F8F1FCF1F045D30010603 +0615112311342702272622073536321704131225363217161514070623220446A0522ACB +2A52A04D77281F6F550143486B011F265F2A5311194B85051148FEF38DA5FD76028AA58D +010D482309AA0A0D30FE72017B430920405B292F4200FFFFFFE1000006A106661027031B +FE6E000010070366015000000000FFFF005700000551074E1027171604C5017512060366 +000000030070FE5604D106140015001E002700414010291A120609011E080C1420241210 +452810FCECD43C3CFC3C3CD4ECC43140121E20870114B828161F87090C8C15970BBD2800 +10ECE4F43CFC3C10F43CFC3C300111321716111007062311231122272611103736331113 +323736373627262303112207061716171602FCC785898985C7B7C786888886C7B7714D54 +0101564D71B7714C570101554C0614FE63999CFEEDFEED9C99FE6F0191999C011301139C +99019DFA776773CAC87567FCB803486775C8CA73670000020041FFE3066D04600010001E +004540160411121004060E2045031D12061808191412010E451F10FCC4ECD4FCD4ECC4EC +111217393100400F181011038701BC1F1B1687080C8C1F10F43CEC3210F4EC3232CC3013 +35211523161510252403022120113437290106151033320333023736113441062C934DFE +61FEF12229FEF8FE614D043AFC9247DECF04AA04CFDE03A8B8B8CFA4FDAD0101012AFED5 +0252A4CFD1A7FE4F021AFDE3030301AEA70000010070FE5B04CD04670039000005262726 +343707020706232235340136353427262322073536333217041114073712373633321514 +01061514171633323702070622273516333203F9F6210A0DBD60C837223701243E0E1A8E +395B405B1A1B011D1DBD60C8372237FEDC3E0E1A8E395B26CD46A25D5F49A40A20F04981 +356CFEF77C224F9A01098A7A3A36686CE0300427FEC35B4D6C01097C224F9AFEF78A7A3A +36686CFDE0662431A03100020073FE5805D905F00011001F0044400E21101D190E0A001C +03161907102010FCECDCB6000310033003035DFC39DCB6000E100E300E035DECEC310040 +0C13950A91201A95000301BD2010ECD43CEC10F4EC300511231126272610373621201716 +1110070602200706111017162037361110270384B8FCA0BDBDBC013B013ABCBCBC9F7BFE +488182828101B881808018FE7001901AB3D202C4D3D2D2D3FE9EFE9FD2B30549A4A4FEE5 +FEE6A4A4A4A4011A011BA400000000020071FE560475047B000D001F003C401021450A12 +1C0019070E1211041215452010FCECDCB23011015DFC393939DCECEC3100400D00B919B8 +2007B90E118C0FBD2010ECF43CEC10F4EC30012207061017163332363534272603112311 +2627261110373633320011100706027394565655569593AC565639AABE6B898988F1F001 +12896A03DF7374FE6E7473E8C8C77475FC09FE6E01921B7D9C011301149C9CFEC8FEECFE +ED9C7B0000000001008BFE5204AB05D50024002E400A121612260C23041C1E2510DCECCC +D4CCFCC43100400D10951208951A8C25009522812510F4EC10F4ECDCEC30012007061110 +171633321716151407062334351637363534272623202726111037362901150346FEF360 +7B5B6DC87A59544A50A3452A20201F3AFEC08E95B98A01780165052B7798FECDFEB57F98 +544F787350574B4C052C2325352C2ACBD60165014EEDB1AA000000010071FE5204510460 +00200034400B191D122213070C1203452110FCECCCD4CCFCC4B20F07015D3100400D17B9 +190FB9008C2109B906B82110F4EC10F4ECDCEC3005220011100029011521220615141633 +32171615140706233435163736353427260267CCFED6012D010601ADFE5BB3C6C56F8350 +544A50A3452A2020201D013E010E0112011F9CC7CECDE34C4F787350574B4C052C232535 +2C2AFFFF00C90000042305D51206002900000001FF40FE560346061400270036B7091416 +131220002810DCCCFC3CCCCC310040141687130A8709130E8705972820871F24871BBD28 +10FCECD4EC10F4ECCCD4EC10EC3033113437363332171617152627262322070615112115 +211114070623222726273516171633323736EE8860A9313231332429292C783A4B0141FE +BF8B62AD3933332E313232305740520482A08E64090912A41C0E0F3E516FFEC98FFD3F92 +A5730A0B16A41F10114B5F000000000100B3FFFC04D405D5001700000103010306171637 +1522272637130113362726073532171602366E030CEA271B4283E6515F139AFD06AC271B +4283E6515F0487FE5B017EFD2C602A6C23BD4652B601DAFE910290602A6C23BD46520001 +00BF00000488061300070042400A0102060503070600040810DCCC17393100B64203A907 +0597012FE4D4EC304B5358401003110002110100010711040611050405070510EC10EC07 +0510EC10EC5909012313210133030488FEE7B8E2FD260119B8E20370FC9002C6034DFD5D +000000010072FE56066005F0002100000111231106073536212013121110032300111027 +0607061511233611343F010221220251AACD68D0018201D9EDD6F6E1010452525F40CD02 +B6BBD0FEA430053AFD8C02494B69C6CFFECEFEECFDC2FE58FE92014C01CA01D17D2F4D34 +D0FDC6210214F78F8D010400000000010077FE9004960478001600000103230126270123 +012627262335201716131211231027036EEFB901640E32FE46B9021F622EBCD3012DF2E0 +AC74A8600134FECC01C0234DFDD002B07F2184A4D8C8FE50FEDFFE89015EF60000000001 +0073FE4B070505D5003D0057401C3C0D0110080039123A3F10351C0D00112B1C2C221C19 +1E121D19103E10FCDCEC10ECD4FC39D439ECECDCEC10DC4B5358B0093C595D3100400F09 +083E2B391D813E0D263195158C3E10F4EC323910F43CCC10CC3930011007060706050607 +2736373637262726270607062322272611341336373306030615101716333237363D0133 +151417163332373611342702273316171207053D44DBB4FEEC768C618A79CDA467446427 +27646592D3797B643B5DF954874348497D724847C7464674864147438754FA5F386402E0 +FEEFCBE6A688642A17851830518017415EADAD5E5EB1B40198C9010E9F7F46FEBF9FB7FE +CD6B6D6968C6F1F1C668696D770127B79F014146829CFEEB000000010087FE5506270460 +002500534019102119161217274514120019020F08100B120609120806452610FCDCEC10 +ECD4FC39D439ECECDCEC10DC4B5358B0223C593100400F2221260F1608BC2600120D8704 +8C2610F4EC323910F43CC410CC3930212403022120113413330215021716033302373611 +34033312151607060706070607273637360488FEED1E23FEF2FE6187DA8F01DFD005AA03 +CEDE8FDA87013969C59AD26B705B518BC701010DFEED023AEB0140FEC0F0FE97010101D4 +FE2B02020168F00140FEC0EBD184F49E7B4925106C0B2B3F000000010073FE56054805F0 +001D002E400A0F1C110C00041C19451E10FCECDCDC3CEC3100400E00951D8C0E811E0895 +158C0FBD1E10ECF4EC10FCF4EC3001060706111417163332373619013311231106070623 +202726111013362502ECB460856E62C3C46263D9D9446868AAFF009CA2BA970128054A12 +84B9FEEEF9AB989899010B02ECF881029084403FD5DD014701360108D50100010071FE56 +048C047B001C002E400A0E08100B1C040818451D10FCECDCDC3CEC3100400E00871C8C0D +BC1D07A0148C0EBD1D10ECF4EC10FCF4EC30010607061514171620373635113311231106 +0706232227263534373633028B9A50725E53014C5454B8B83A585990DA85899E7FFD03FD +0E638DD0BD81747374CB0231F9F60252643031A2A8F8ECC8A200000100C9FE4B05E205D5 +00250039400E100D0C151C2745041D211C20042610FCEC32D4ECECDCC44B5358B10D0C10 +3C593100400B199500B81E0D0CBD20811E2FE4FCCC10F4EC300120171611140706070607 +060727363736373637363534272623220706151123113311363736034C0127B1BE3C43AA +C3F1B94961867DD998882C367E73CDCB7371CACA4E6969047BB3C2FEFDCCA1B280934535 +0C851632577A6D687FC09D9686817EDEFE2705D5FD9A874243000002002DFFE30492049A +0017004D0000012623220706070607061514171617161716333237363736251615140706 +212227262322072736333217163332373635342706070607062726272627262726353437 +363736373633321736371706032C7F8D1F371D251D100E0C0F181A23201E19473B492401 +0B6C7F6FFEF78D634B35415154875F82523F5B9D4F4B311B1F467566472948324030291E +1D1F2E3E50655ED290382A8837034498160B211A201B2120151C111406051914311832C0 +D4B09C882E2341934C2E235E597F8C711917342B2602010A07221A4834423B3B3D2F3F22 +2B9F566850920001004FFE56050B05F60021000005042120010037363534272623220706 +07233637362120171615140700011633203704FAFF00FEEDFEAFFEB902BAB36C6C63A4B4 +5E2318F02C56A301180113A1A2A2FEF7FE189CD50129E8ECBE01A301F1DB849C8D655D92 +363FA166C29091F1D8B6FEF2FE85B5B3000000010064FE56046A047B0020000001062320 +0100373635342726232207060723363736333204151407060116333237045CDAEAFEEEFE +DE0242A75C5C548B99501E14CC25498BEEE801148AAFFE2F91A9FDC5FEEF9901790159C2 +6B7D6F534B752C3281529CE8C2A49CC5FEE0BA90000000020073000005B605EF00020035 +00002521090326272623220F013536373633321716170901363736333217161715272623 +2207060709011617163B011521353332373601DA026EFECDFE140180FEDC131A223F1916 +4521201F1C724B2C2F0102010934274B721D1E20214417183C26131AFED2017406071D45 +47FAC347481A0CAA01CAFE68023D01BB1D1A22040ABB0B0505432846FE81017F4B234305 +050BBB0A04221126FE45FDC3090821AAAA210F00000000020036000004CB047B00020035 +000025210309010326272623220F013536373633321716171B0136373633321716171527 +26232207060703011617163B0115213533323736019A01CCE4FE5B0139EF15111D361513 +3A1C1B1A1883402528C5C528254083181A1B1C3A1315361D1115F6012D0605193A49FB6B +493A19059E0138FEEE01A4013D1C0E1903078D080404331E35FEFA0106351E330404088D +0703190E1CFEB8FE670805199E9E1905000000020073FFE305250610001D002B00000124 +070607363736333200100021202726111037362132373637150607061210262322070615 +141716333237032AFED657381651557B82F50132FECEFEF9FECEA4A38B7C01B07395A04B +5E976C8EC8BABC68696965BFBC62052D02734AA0562231FEBCFDF0FEBC9C9B015001DED2 +BB0A0A27B1240806FC410182E67374C0BD787373000000020071FFE3045B0610001F002F +000001260706073637363332171610070623222726111037362132373637150607061334 +2726232207061514171633323736029BE3492C1429655B78CC7F80807FDBFF8988746701 +4F5F5C53475D455AA8535492955658585497945253053702784AA9463631A2A2FDF0A2A2 +9C9B015001DED2BB0A0A27A7270506FCF8CD72737374CBC77873737400000001002CFE56 +04B705D5000F0034400D0312000F041C0708120B0C071010DC3CDCEC10FC3CDCEC310040 +0D02090407950F0CBC0D8105BD1010ECECF43CEC32CC3230011123352111231121152311 +2111331104B7CBFEEBCBFEEBCB01E0CB0460FEF264FAA0056064010E0175FE8B00000001 +0037FE55041405CF000F0033400D0308000F04080708080B0C071010DC3CDCEC10FC3CDC +EC3100400C02090407870F0CBC0D05BD1010ECCCF43CEC32CC3230011123352311231123 +152311211133110414ADE5B9E5AD0192B9045FFF0070FA86057A7001000170FE90000001 +0070FFF204CD046700330000010207062322353401363534272623220735363332170411 +14073712373633321514010615141716333237150623222724113437022860C837223701 +243E0E1A8E395B405B1A1B011D1DBD60C8372237FEDC3E0E1A8E395B405B1A1BFEE31D01 +99FEF77C224F9A01098A7A3A36686CE0300425FEC15B4D6C01097C224F9AFEF78A7A3A36 +686CE0300425013F5B4D000200BAFE5604A4047B00180024003A400E1426451A120A5111 +081F1200462510FCECECF4B27F0A015DECECC43100400F13B9161CB906B82522B90D8C16 +BD2510ECF4EC10F4EC10EC30133437363736333217161007062322272627122901152120 +11241027262007061017162037BA5A369E3BB6CC7F80807FCC785B593A05012001F4FE1C +FE12032B5354FEDC545353540124540225D0A3625E23A2A2FDF0A2A2313064FE58AA02DA +34019674737374FE6A7473730000FFFF0071FFE303E7047B120600460000FFFFFFDBFE56 +017906141206004D0000FFFF0073FFE305D905F012060161000000010071FFE303D8047B +0021000001262726232207060721152116171633323F0115070623202726103736213217 +161703D82525636AB7665F1202A5FD5B125F66B7804D4A4F686BFEF49C9D9D9C010C656E +282703AE0D0A1A635CA990A95C631A19A712169C9C02289C9C16080C0000000100C4FFE3 +042B047B0021000013353637363320171610070621222F01351716333237363721352126 +272623220706C427286E65010C9C9D9D9CFEF46B684F4A4D80B7665F12FD5B02A5125F66 +B76A632503AEA30C08169C9CFDD89C9C1612A7191A635CA990A95C631A0AFFFF00C90000 +048D05D5120600A00000FFFF00BAFE5604A40614120600C00000FFFF0073FFE3052705F0 +120600260000000100C90000061F05D5000C009440100908030201050A061C043E0A1C00 +040D10FCECFCEC1117393100400C420A070203080300AF080B052F3CC4EC32111739304B +5358401803110708070211010208080702110302090A0901110A0A09071005ED071008ED +071008ED071005ED59B2700E01015D401104020607060A36024907490A5907590A084015 +02010D03160819092601290335013A0345084A090A5D005D132109012111231101230111 +23C9012D017D017F012DC5FE7FCBFE7FC405D5FE2101DFFA2B051FFE1901E7FAE1000001 +007FFE5604B30460000C004F40090E460708040A08000D10DCECDCECEC3100400D420A07 +0203090300BC090CBD062FECC4EC32111739304B535840120211080A0903110708070211 +0901110A0A09050710ED10ED0710ED0810ED59132113012111231101230111237F011BFE +0100011BB9FEEC99FEEBB90460FE7B0185FBA003B2FE6001A0FAA400000000020055FE56 +04A4047B001B002700001711343736373633321716100706232227262711211521152335 +2335001027262007061017162037BA5A3D973BB6CC7F80807FCC7B58593A01E5FE1BB965 +03905354FEDC545353540124549002B5E78C665A23A2A2FDF0A2A2313064FEC8AA7070AA +01F4019674737374FE6A74737300FFFF0073FFE3052705F0120601480000FFFF0073FFE3 +052705F01226038D00001007007902330000FFFF0073FFE3052705F01027007900E40000 +120603910000FFFF00C90000048B076B122603A900001007171904EE0175FFFF00C90000 +048B074E122603A9000011071716049D01750085B1929742B093B09842B1800442B18100 +427CB000B0012349B013B00E234961B0806268B0134661B0004660B09243B001602342B0 +9243B0016043B0005558B00EB09243B001604338B00E11B0013559B1800042B181004218 +B00010B013B00EB0012349683BB01311B0023500B000B0132349B0405058B013B04038B0 +1311B00235B0013559000001FFFAFE6605AC05D5001B0034400B050A1C1B140E161C1311 +1C10D4CCFC3CCCDCFCCC3100400F059504B0100E9517101611951381102FF4EC3210D4EC +10F4EC302510062B01353332363511342623211123112135211521112132161505ACCCE4 +4C3E866F7C7CFE88CBFE52048BFDEE01A1BADE68FEF2F4AA96C201229F9EFD39052BAAAA +FE46E9EE0000FFFF00C90000046A076B122603A700001007171704AE017500010073FFE3 +052705F00018004E40091A120B00111419061910DCEC32D43CCCCC31004017139512AD19 +0CA10BAE0E9509911900A101AE1795038C1910F4ECF4EC10F4ECF4EC10F4ECB1120E49B1 +1713495058B3121340021738593001150621200011100021201715262120020721152116 +1221200527D4FEF5FEB1FE7A0186014F010FD0D3FF00FEF8EE16031EFCE216EE01080100 +0146D390019F01680167019F8ED5BDFEE3EFAAEFFEE4FFFF0087FFE304A205F012060036 +0000FFFF00C90000019305D51206002C0000FFFF000600000258074E100600910000FFFF +FF96FE66019305D51206002D0000000200540000082F05D50014001C0033400C17191000 +1C1B0B011C0A061D10D4D4ECD43CECDCEC3100400E1B950CAD1401950A811C069505142F +3CEC32F4EC10FCEC3001211510020535361211352111333204151404232125201134262B +01110470FE1BC8FE91D9950378EAFB0110FEF0FBFE4C01AA01409DA3E0052BB8FDCAFDFB +38AA2F01A60258FEFD9ADADDDEDAA601118B87FDDD00000200C9000007CC05D50012001B +0035400E13190F08001C170A07021C05041C10FCEC32DC3CEC32DCEC3100400D1701950B +07AD090581189500042F3CECE432FC3CEC32302111211123113311211133113332041514 +04230134262B0111333236040DFD86CACA027ACAEAFB0110FEF0FB01369DA3E0E0A19F02 +C7FD3905D5FD9C0264FD9ADADEDDDA01B78B87FDDD870001FFFA000005AC05D50013002C +400A061C03100A121C0E0D1410D4CCFC3CCCDCEC3100400B0A95130C120D950F81050C2F +3CF4EC3210D4EC3001321615112311342623211123112135211521110414BADEC97C7CFE +88CBFE52048BFDEE0371E9EEFE66018A9F9EFD39052BAAAAFE46FFFF00C900000586076B +122603AE00001007171704EE0175FFFF00C900000533076B122603AC00001007171904E5 +0175FFFF0023000004BD076D1027171D04720175120603B70000000100C9FEBF053B05D5 +000B0029400D0D04061C070B9509031C02040C10FCECD4FCD4ECEC3100B70B0495060281 +09012F3CE432ECCC3029011133112111331121112302ADFE1CCA02DECAFE1CAA05D5FAD5 +052BFA2BFEBFFFFF00100000056805D5120600240000000200C9000004EC05D500080015 +002E400C17090019102E040B1C15041610FCEC32F4ECC4CC3100400C0B9515811404950C +AD0595142FECF4EC10F4EC30013426232111213236131521112132041514042901110417 +9DA3FEBC0144A39D6CFD10014EFB0110FEF9FEFCFDE801B78B87FDDD8704A8A6FE40DADE +DDDA05D50000FFFF00C9000004EC05D5120600250000000100C90000046A05D500050019 +400C04950181000702041C01040610FCFCCCC431002FF4EC30331121152111C903A1FD29 +05D5AAFAD50000020065FEBF05DB05D5000700170034400F021C0E1395191017031C0D14 +95171810DCECD4EC10D4CCFC3CEC3100400B03950D8112160F001795142FEC3232CC32F4 +EC3025211121151003060536371219012111331123112111231101D30294FE1B7017FEB1 +8626610378AAAAFBDEAAAA0481D4FE0DFEB5442B3F7801340226011AFAD5FE150141FEBF +01EBFFFF00C90000048B05D5120600280000000100280000087605D500130098400B0805 +01040609011C0C001410DC3CEC32D4C411393931004011420D0C10130809050208120300 +AF0F0A062F3C3CEC32321739304B53584016071106081105090406050311040211050809 +090409040907103C3C04ED1005ED070810ED0510ED590140130D01080E01070F01061001 +051101041201030010493A493A493A493A493A493A004008130210050D080C09103C103C +103C103C013311013309012309011123110901230901330103EACA02AAF5FDDF0244D3FE +13FEFECAFEFEFE13D30244FDDFF502AA05D5FD1E02E2FDB3FC780301FEE9FE1601EA0117 +FCFF0388024DFD1E000000010087FFE3049A05F00028003F400C1B1F19032A1619092510 +062910FC32D4ECCCD4FCCC310040161A951B0C10A10FAE13950C25A126AE229500910C8C +2910E4F4ECF4EC10ECF4EC10D4EC30013204151406071E0115140423222427351E013332 +363534262B013533323635342623220607353E010249F601388E8391A3FE9DEE7AFEE42C +99A97CBCD0B9C3CCD4B39EA3C6865CCD71EC05F0D1B27CAB211FC490E6E9421CD0592B90 +958495A67770737B184DC5282200000100C90000053305D500090079401E031109090808 +110404034208030906AF0205090407031C0036071C06040A10FCECFCEC11393931002F3C +EC323939304B5358071004ED071004ED5922B21F0B01015D403036083803480847036908 +66038008070604090915041A09460449095704580965046909790985048A0995049A099F +0B105D005D011123110121113311010533C4FD6AFEF0C4029605D5FA2B04E1FB1F05D5FB +1F04E1000000FFFF00C900000533076D122603AC00001107171D04F501750023B4060A12 +00072BB00A4B54B00B4B545BB0104B545B58BB00120040000AFFC0383859310000000001 +00C90000058605D5000B0059400B080501040609011C00040C10FCEC32D4C41139393100 +400B4208090502040300AF0A062F3CEC321739304B535840160711060811050904060503 +11040211050809090409040907103C3C04ED1005ED070810ED0510ED5913331101210901 +2309011123C9CA02D20103FDBF025FDCFDFAFEEFCA05D5FD1E02E2FDB2FC790301FEE9FE +1600000100540000053A05D5000F0025400A11040A1C070B1C06011010D4D4ECD4ECEC31 +0040080B950681019500092F3CECF4EC303335363712113521112311211510030654D93E +570378CAFE1B6662AA2FA401020258FEFA2B052BB8FDCAFEF8FDFFFF00C90000061F05D5 +120600300000FFFF00C90000053B05D51206002B0000FFFF0073FFE305D905F012060032 +0000000100C90000053B05D50007001F40100495078102060904031C00041C07040810FC +ECD4ECEC31002F3CF4EC300111231121112311053BCAFD22CA05D5FA2B052BFAD505D500 +0000FFFF00C90000048D05D5120600330000FFFF0073FFE3052705F0120600260000FFFF +FFFA000004E905D512060037000000010023000004BD05D50011003EB41311060D1210D4 +C4D4C43100B642100D810695052FECEC32304B535840120F11000D0C10111111000F110C +0E110D0D0C050710EC10EC0710EC0810EC59250607062B0135333237363F010133090133 +028F15204FFB4D3F772E1C122DFE21D901730175D9B532265DAA1B112A6A046BFC94036C +0000000300790000066A05D50006000D001F003D401121100A191A0E00151C1D0D160319 +11102010FCECD43C3CFC3C3CD4ECEC3100400E0D0095171415811F0705951D0E1F2FDC3C +EC3210F4DC3CEC3230010E0115141617333E013534262703240011100025353315040011 +1000051523030DD9E6E6D9CBD9E4E4D9CBFEC3FEA90157013DCB013D0155FEABFEC3CB04 +A214CCC5C5CB1414CBC5C5CC14FC1017012B01090109012D178B8B17FED5FEF5FEF7FED5 +17B2FFFF003D0000053B05D51206003B0000000100C9FEBF05E505D5000B0029400C0D09 +9500061C07031C02040C10FCECD4EC3CFCCC310040080602810B080495012FEC32CCF43C +30290111331121113311331123053BFB8ECA02DECAAAAA05D5FAD5052BFAD5FE15000001 +00AF000004B305D5000F0024400A1104010D1C0E071C061010DCECD4EC32EC3100B70295 +0BAD0D0681002FE432F4EC302111212226351133111416332111331103E8FE5FBADEC97C +7C0178CB0264E9EE019AFE769F9E02C7FA2B000100C9000007C505D5000B002A400D0D04 +021C030A1C0B071C06040C10FCECD4FCD4ECEC310040080A020681000895052FEC32F43C +3C3025211133112111331121113304AC024FCAF904CA024FCAAA052BFA2B05D5FAD5052B +0000000100C9FEBF086F05D5000F0032400F110D95000A1C0B061C07031C02041010FCEC +D4FCD4EC3CFCCC3100400A060A02810F0C080495012FEC3232CCF43C3C30290111331121 +1133112111331133112307C5F904CA024FCA024FCAAAAA05D5FAD5052BFAD5052BFAD5FE +15000002003C0000061805D5000C0017002A40160295038100129505AD139500100D1909 +12041C01031810CCDCEC32D4ECCC31002FECF4EC10F4EC30211121352111213204151404 +23013427262321112132373601F5FE470283014EFB0110FEF0FB01364F4EA3FEBC0144A1 +504F052BAAFD9ADADEDDDA01B78B4443FDDD44430000FFFF00C90000064605D5102603C0 +00001007002C04B30000000200C9000004EC05D5000A00150024401305950DAD0B810695 +1517001911050C1C0B041610FCEC32D4ECCC31002FECE4F4EC3001342726232111213237 +36013311213204151404232104174F4EA3FEBC0144A34E4FFCB2CA014EFB0110FEF0FBFD +E801B78B4443FDDD444304A8FD9ADADEDDDA0001006FFFE3052305F00018004E40091A05 +08191307000E1910DC3CCCD4EC32CC31004017069507AD190DA10EAE0B9510911900A118 +AE0295168C1910F4ECF4EC10F4ECF4EC10F4ECB1070B49B10206495058B3070640021738 +5930131621201237213521260221200735362120001110002120276FD301000108EE16FC +E2031E16EEFEF8FF00D3D0010F014F0186FE7AFEB1FEF5D40146BD011CEFAAEF011DBDD5 +8EFE61FE99FE98FE6190000200D3FFE3083005F0000F00260038401F009514912708951C +8C27219526AD248123280C19180419201021251C24042710FCEC32D43CECD4ECCC31002F +E4F4EC10F4EC10F4EC300122070611101716333237361110272601123736212017161110 +07062120272603211123113311057EDC82818182DCDC80818180FC730EB4B4013B013ABC +BCBCBCFEC6FEC5B4B40EFED0CACA054CA4A4FEE5FEE6A4A4A4A4011A011BA4A4FDF30118 +CDCCD2D3FE9EFE9FD2D3CDCD0118FD6B05D5FD6A000000020088000004C605D500080016 +0040400B180414051C110019090D1710D4C4ECD4EC32EC3100400C420695108109159503 +AD13092F3CF4EC10F4EC304B5358B715110A1611090A09050710EC10EC59011416332111 +2122060901262435342429011123112101019B9592013AFEC69295FEED019864FF000104 +01020204CAFEF2FE7604278387021285FB56028D1AA9D7CEE0FA2B0277FD89000000FFFF +007BFFE3042D047B12060044000000020070FFE3047F0637001D0029003A400E13142B45 +271203511C211209452A10FCEC32F4ECECD4C43100401116A911972A24B9061EB9091C00 +B8068C2A10E4F43939EC10EE10F4EC300132001110002322000327263534373624253637 +17060F010607060F0136172206151416333236353426027DF00112FEEEF0F1FEF6070605 +3A5B013B01087A3633312DFA7E4CC7130782D394ACAB9593ACAC047BFEC8FEECFEEDFEC7 +0130011CE57729A076B9A002011192140111092C759938779CE7C9C9E7E8C8C7E9000003 +00BA0000043E0460000800110020002F400D0E12162205121C00090812462110FCEC32D4 +ECCCD4EC3100400B00A90A2009A912BC01A9202FECF4EC10D4EC30011121323635342623 +01113332363534262325213216151406071E011514062321017201067E84847EFEFAF268 +848468FE5601B6C5D46C6A7F8CE7D6FE390204FE8F5F5A5A5E01C9FECA534A4A4F939085 +67790F18987296A40000000100BA000003D0046000050019B60702040801460610FCFCDC +CC3100B404A901BC002FF4EC30331121152111BA0316FDA3046093FC33000002006BFEE5 +051D0460000600160034400F02080D12A9180F1603080C13A9161710DCECD4EC10D4C4FC +3CEC3100400B03A90CBC11150E0016A9132FEC3232CC32F4EC3025211121151007053637 +3611352111331123112111231101BB0216FE7D76FED85B286202F59393FC749393033A8C +FE64DC362855D301A9D4FC33FE52011BFEE501AE0000FFFF0071FFE3047F047B12060048 +000000010046000006EF046000130098400B08050104060901080C001410DC3CEC32D4C4 +11393931004011420D0C10130809050208120300BC0F0A062F3C3CEC32321739304B5358 +4016071106081105090406050311040211050809090409040907103C3C04ED1005ED0708 +10ED0510ED590140130D01080E01070F01061001051101041201030010493A493A493A49 +3A493A493A004008130210050D080C09103C103C103C103C013311013309012301071123 +1127012309013301033FB701E9D6FE6E01CCC5FE87BBB7BBFE87C501CCFE6ED601E90460 +FDF2020EFE51FD4F0236C9FE93016DC9FDCA02B101AFFDF2000000010085FFE303C8047C +0028004E400B1912262A10120315200A2910DCC4C4D4ECCCD4EC3100401620861F881CB9 +23B82914A9152909860A880DB9068C2910F4FCB00C4B5158FC1BF459EC10D4EC10F4FCB0 +0C4B5158FC1BF459EC30011E0115140423222627351E013332363534262B013533323635 +342623220607353E0133321615140602C27C8AFEFEEE50A95A47AA5D97A99689949B7487 +8B7747A16162AA4CC4E378025C18926CADB61C1CAB2525705A586B985946405C1A1DA718 +189D8D5D8100000100BA0000047904600009003F40154208030906BC02050B4609040703 +0800070806460A10FCECD4EC113939EC31002F3CE4323939304B5358400A031109090808 +110404030710EC0710EC59011123110123113311010479B7FDE4ECB7021B0460FBA00383 +FC7D0460FC7F03810000FFFF00BA000004790614122603CC00001107029A009AFFCC0023 +B4070A1203072BB00E4B54B0104B545BB0154B545B58BB00120040000AFFC03838593100 +0000000100BA000004910460000B0059400B080501040609010800460C10FCEC32D4C411 +39393100400B4208090502040300BC0A062F3CEC321739304B5358401607110608110509 +0406050311040211050809090409040907103C3C04ED1005ED070810ED0510ED59133311 +013309012301071123BAB70207E2FE5401E3CEFE73C5B70460FDF2020EFE4FFD510235C8 +FE930001004C000004730460000F0024400A11460A08070B0806011010D4D4ECD4ECEC31 +00B70BA906BC01A900092F3CECF4EC30333536373611352111231121151007064CB63844 +02F5B8FE7B585E991C7EB101C5B7FBA003CD6FFE50C2CF000000000100BA0000054F0460 +000C004D4016420A070203080300BC09060C0E460708040A0800460D10FCECDCECEC3100 +2F3CC4EC32111739304B535840120211080A09031107080702110901110A0A09050710ED +10ED0710ED0810ED5913210901211123110123011123BA010D013E013F010BB9FECBB8FE +CAB90460FD1202EEFBA003B0FD2702D9FC50000100BA000004810460000B0027401409A9 +020400BC070B0D460804080509010800460C10FCEC32DCEC32EC31002F3CE432DCEC3013 +3311211133112311211123BAB90255B9B9FDABB90460FE3701C9FBA00204FDFC0000FFFF +0071FFE30475047B120600520000000100BA0000048104600007001F401004A907BC0206 +0308094600040807460810FCECD4ECEC31002F3CF4EC3001112311211123110481B9FDAB +B90460FBA003CDFC330460000000FFFF00BAFE5604A4047B120600530000FFFF0071FFE3 +03E7047B1206004600000001003C0000046D04600007001CB60901030806000810DCD4FC +DCCC3100B50307A900BC052FF4EC323013211521112311213C0431FE42B5FE42046093FC +3303CD000000FFFF003DFE56047F04601206005C000000030070FE56066705D5000A0028 +00330042401135452912210C061908272E1A001212453410FCECD43C3CFC3C3CD4ECEC31 +0040122C08B91E15B81997343103B9240F8C0BBD3410ECF43CEC3210E4F43CEC32300114 +16333237112623220601110E01232202111012333216171133113E013332121110022322 +2627110134262322071116333236012F917B627272627B9101E0398353A7E9E9A7538339 +B9398353A7E9E9A753833901E0917B627272627B91022FEBC7A80214A8C7FB3C02395E4E +013501130113013D4C5E0204FDFC5E4CFEC3FEEDFEEDFECB4E5EFDC703D9EBC7A8FDECA8 +C700FFFF003B0000047904601206005B0000000100BAFEE505140460000B0028400C0D09 +A906080007030802460C10FCECD43CECFCCC3100B70602BC0B0804A9012FEC32CCF43C30 +2901113311211133113311230481FC39B90255B993930460FC3303CDFC33FE5200000001 +00960000040004600011003B401102A90D0F07BC001346010F08100808071210DCECD4EC +32EC31002FE432D4ECB000B0022349B00DB00F23495258B1020DB8FFC0B0021738593021 +11212227263511331114171633211133110348FEA999665CB83435680129B801D75F56B8 +011CFEF5753B3B01F6FBA0000000000100BA000006980460000B0029400D0D460208030A +080B070806460C10FCECD4FCD4ECEC3100B70A0206BC0008A9052FEC32F43C3C30252111 +331121113311211133040501DAB9FA22B901D9B99303CDFBA00460FC3303CD0000000001 +00BAFEE5072B0460000F0032400F110DA90A08000B060807030802461010FCECD4FCD43C +ECFCCC3100400A060A02BC0F0C0804A9012FEC3232CCF43C3C3029011133112111331121 +1133113311230698FA22B901D9B901DAB993930460FC3303CDFC3303CDFC33FE52000002 +003E0000052E0460000C0015002C400B17451312030E0B08080A1610C4DCEC32D4ECEC31 +00400B08A90BBC070EA90C0FA9072FECD4EC10F4EC300132161514062321112135211105 +21112132363534260371D6E7E7D6FE38FE9502240107FEF901077E83830297A3A8A8A403 +CD93FE3793FE8F5F5A5A5E000000FFFF00BA0000059B047B102700F304220000100603E0 +0000000200BA0000043E0460000800130025400B154500120F050B0809461410FCEC32D4 +ECEC3100B704A90B09BC05A9132FECE4D4EC300134262321112132360133112132161514 +062321037A837EFEFA01067E83FD40B9010ED6E7E7D6FE39014C5A5EFE8F5F036EFE37A3 +A8A8A400000000010071FFE303E7047B0018004D40090508121348070E001910DC3CCCF4 +EC32310040170E860D880B1886008802B91607A906BB0BB910B8168C1910E4F4ECF4EE10 +FEF4EE10F5EEB1070B49B10206495058B307064002173859303716333236372135212E01 +2322073536332000111000212227719E9D93D213FDC802320C9FC79AA19DA60106012DFE +DBFEFFBD93D556ABDA9369DF56AC46FEC3FEF1FEF2FEC2480000000200C1FFE3064C047B +000B001E003A400F20450912120312180C191D081C461F10FCEC32D43CECD4ECEC310040 +1000B90FB81B06B9158C1B19A91E1CBC1B2FE4D4EC10F4EC10F4EC300122061514163332 +3635342601361233320011100023220027231123113311044A94ACAB9593ACACFD7113F9 +F0F00112FEEEF0F1FEF909D0B8B803DFE7C9C9E7E8C8C7E9FEC2BE011CFEC8FEECFEEDFE +C7012EF8FDF70460FE410002007400000422046000080016003C4009140508110012090D +1710D4C4ECD4EC323100400B4206A910BC0915A90313092F3CD4EC10F4EC304B5358B715 +110A1611090A09050710EC10EC590114163B011123220609012E01353436332111231123 +01017A8077F8F87780FEFA0156749AD7D901B6B9E5FEB6031D535E01615CFC8F01EB1A89 +8FA2A1FBA001D9FE2700FFFF0071FFE3047F066B122603C90000100600435A050000FFFF +0071FFE3047F0610122603C900001107006A009600000085B1929742B093B09842B18004 +42B18100427CB00FB0012349B023B01E234961B0806268B0234661B00F4660B09243B001 +602342B09243B0016043B0005558B01EB09243B001604338B01E11B0023559B1800042B1 +81004218B00F10B023B01EB0012349683BB02311B0033500B012B0232349B0405058B023 +B04038B02311B00335B0023559000001002FFE5604900614001F003F400F141708104E08 +1D090508010300462010FC3CCCEC3232CCF4FCCC3100401114A9131F0801A90702041A87 +0A0D04971E2FECD4C4EC10DC3CEC3210D4EC3013233533113311211521113E0133321611 +140007353612353426232206151123DFB0B0B9021DFDE342B276B6D8FEA9D77AF57C7C9A +A7B903D18F01B4FE4C8FFE6D6564E9FEEAE2FE59298C16012ED2D09FC49EFEFB0000FFFF +00BA000003D8066D122603C70000100700760086000700010071FFE303E7047B0018004E +400A0A0B081210024816451910FCE432FC32CC310040170286038805118610880EB91309 +A90ABB05B900B8138C1910E4F4ECF4EE10FEF4EE10F5EEB1090549B10E0A495058B3090A +4002173859300132171526232206072115211E01333237150623200011100002A4A69DA1 +9AC79F0C0232FDC813D2939D9E93BDFEFFFEDB012D047B46AC56DF6993DAAB56AA48013E +010E010F013DFFFF006FFFE303C7047B120600560000FFFF00C10000017906141206004C +0000FFFFFFF4000002460610100600B10000FFFFFFDBFE56017906141206004D00000002 +004C000006BF04600016001F0036400E21451A120C11081E07120806012010D4D4ECD43C +ECD4ECEC3100400E1EA9091FA91012A906BC01A900102F3CECF4EC10ECD4EC3033353637 +361135211133321615140623211121151007062532363534262B01114CB6384402D8ABD6 +E8E7D6FE9BFE9A585E03787E84847EA3991C7EB101C5B7FE37A3A8A8A403CD6FFE50C2CF +765F5A5A5EFE8F000000000200BA000006B704600012001B003840101D451612050A1208 +1A000B0F080D461C10FCEC32DC3CEC32D4EC3100400D13A9091A0BA90110120EBC090D2F +3CE432DC3CEC3210EC30011133321615140623211121112311331121110132363534262B +0111044EABD6E8E7D6FE9BFDDEB9B90222015C7E84847EA30460FE37A3A8A8A40204FDFC +0460FE3701C9FC335F5A5A5EFE8F0001002F000004890614001B003A400F08191308104E +19090508010300461C10FC3CCCEC3232F4EC10CC3100400E0801A907020416870A0D0497 +121A2F3CECD4C4EC10DC3CEC323013233533113311211521113E01333216151123113426 +232206151123DFB0B0B9021DFDE342B375BDCAB87C7C98A9B903D18F01B4FE4C8FFE6D65 +64EAEDFED0012A9F9EC1A1FEFB00FFFF00BA00000491066D122603CE0000100600766F07 +0000FFFF00BA00000479066B122603CC0000100600435D050000FFFF003DFE56047F0614 +122603D700001006029A5ECC0000000100BAFEE504810460000B0029400D0D460608070B +A909030802460C10FCECD4FCD4ECEC3100B70B04A90602BC09012F3CE432ECCC30290111 +3311211133112111230254FE66B90255B9FE66930460FC3303CDFBA0FEE500010073FFE3 +070505D50034003840142412253610201C29161C170D1C0409120804103510FCDCEC10EC +D4FCD4ECECDCEC3100400916240881111C952D002F3CEC32F43CCC300522272611341336 +373306030615101716333237363D01331514171633323736113427022733161712151007 +062322272627060706023AD3797B643B5DF954874348497D724847C74646748641474387 +54FA5F38647B7BD1926564272764651DB1B40198C9010E9F7F46FEBF9FB7FECD6B6D6968 +C6F1F1C668696D770127B79F014146829CFEE7BEFE66B2B15E5EADAD5E5EFFFF0087FFE3 +062704601006035D00000002001E000005B105D50012001D003A400E1F1319050D11190F +001C0B090D1E10DC3CCCFC3C3CCC10D4ECCC3100400F0A12950C100E8109189501AD1995 +092FECF4EC10F4D43CEC3230011521320415140423211121352135331521150134272623 +2111213237360258014EFB0110FEF0FBFDE8FE900170CA017101134F4EA3FEBC0144A34E +4F0451E2DADEDDDA0451A4E0E0A4FD668B4443FDDD44430000000002001E000004E70614 +00070019003A400F1B45001217091105130F1C0B090D1A10DC3CCCFC3C3CCC10D4ECEC31 +00400E0911A90B0F0D04A9130D9705A9082FECE4D4EC10D43CEC32302434262321112132 +0511213521113311211521112132161006230423837EFEFA01067EFDC3FEBB0145B901A9 +FE57010ED6E7E7D6F2B45EFE8F9303CD9301B4FE4C93FECAA3FEB0A40000000100D3FFE3 +071B05F0002B000001112311331133123736213217161715262726232007060721152112 +17162132373637150607062320272603019DCACAD21E9DC301538676776866737482FF00 +88671902B2FD4607818900FF827473666A777684FEADC3BA0902C7FD3905D5FD9C0108A7 +D0242347D55F2F2F9C77C6AAFEF3949D2F2F5FD3482424CFC6014F000000000100C1FFE3 +0581047B0023000001321715262322070607211521161716333237150623202726272311 +2311331133363736043EA69DA19AE65C220C01CCFE2C0D9E55789D9E93BCFEF3947B0A93 +B8B898177A97047B46AC56B441578FF45E3356AA48AD90E4FDFC0460FE33CA809E000002 +0010000006F805D5000B000E000021230121112311210123013313090106F8E1FEEAFEE9 +CAFEE7FEEAE10302E5B1FEDCFEDC021BFDE5021BFDE505D5FCF30237FDC9000200330000 +06110460000B000E0000212303231123112303230133130B010611C3ECE3B8E5ECC3028E +C391F3F30195FE6B0195FE6B0460FDB901A1FE5F0000000200C90000091405D500130016 +000021230121112311210123012111231133112101331309010914E1FEEAFEE9CAFEE7FE +EAE1016FFDF8CACA025F013CE5B1FEDCFEDC021BFDE5021BFDE502C7FD3905D5FD9C0264 +FCF30237FDC9000200C1000007D004600013001600002123032311231123032301211123 +113311210133130B0107D0C3ECE3B8E5ECC3011CFE6BB8B801E9011EC391F3F30195FE6B +0195FE6B01E7FE190460FE1701E9FDB901A1FE5F000000020073000005D905D50017001A +00824014191A0E0D141C0F130E00071C0C080D18001C031B10DCEC39CCDCB40F084F0802 +5D39EC10CCDCB60013400E4013035D39EC111239393100400E420C0F1100031995180D81 +1408022F3C3CF439ECD43CEC32304B5358401418110F1A1819110E0F0E18110C19181A11 +0D0C0D070510ED0810ED070510ED0810ED59B2401C01015D011123110607061123103736 +370121011617161123102726270121038BCA936482D5BD78AAFE510512FE50A474BCD580 +60F8013EFD830259FDA702591C7EA4FEE50162D2863102EAFD133282D2FE9E011EA17ACA +02280002006B0000047B04600002001A007E40140001031A091C0408030D141C19151A02 +0D1C101B10DCEC39CCDCB28015015D39EC10CCDCB23003015D39EC111239393100400E42 +1904110D100095021ABC09150F2F3C3CF439ECD43CEC32304B5358401402110401020011 +030403021119000201111A191A070510ED0810ED070510ED0810ED59B4701C8F1C02015D +0121130901161716112334272627112311060706152310373637010345FE5DD10208FEBB +6B4B89C3563A56B8533856C2894A6CFEBB03B6FE960214FDCC26569CFEECC7744F1AFE5C +01A21A4B74C901149C5527023400000200C9000007C405D5001E00210000090121011617 +1611231027262711231106070611231037363721112311331105012103EFFE9F0512FE50 +A474BCD5806099CA936482D5BD556EFDB5CACA0384013EFD8303710264FD133282D2FE9E +011EA17A20FDA702591C7EA4FEE50162D25F34FD3905D5FD9C6E02280000000200C10000 +062E0460001E002100000901210116171611233427262711231106070615231037363721 +11231133110121130337FEE70410FEBB6B4B89C3563A56B8533856C2891B1FFE92B8B803 +7FFE5DD1027701E9FDCC26569CFEECC7744F1AFE5C01A21A4B74C901149C1F18FE190460 +FE17013FFE9600010073FE560473077A0053000001140706232226232215143332373617 +161715262322062322272635343736332132373635342726233532163332373635342122 +0735363703331337363736373633321715272623220F0116171615100516171604737398 +C644BA2360DC41807420625444743BFC3C7D4AA3353F75015F684641BB58F9125617A352 +75FEC5A5DEA0819F73A06A1E0F171723421A23270B0F22325AA66272FEEF8D525501BECF +67880882720C0B020725A7271B2C61927A515E58526ABD3719A60226368DEE4AB42D0D01 +83FE83DE401827121B0A5705026FCA185764A7FEFD451E5C60000001005BFE7403C80606 +004F00000114070623222623221514171633323633321715262322062322272635103321 +323635342726272223353217323320353427262322073536370333133736373633321715 +2623220F010415140716171603C8766DA244A819506221272CB22D63583B6231D232693F +89C4012C5A6E4E3C7205B20B21201501355E485C91B87E669F73A06A2F152B511A23320F +22325B0130E86F475001529E5E560881611B0924278B2217255297010C60594C382B0898 +01A0512A2137A71F0B0183FE83DE6317320A57076FCA2FF2C43216404900000100100000 +06C105D5001C000001272623220706070123112311230133013311331133133637363332 +1706C13A1920251D423CFEE4FACAFAFE5CC3015E7DCA7DE84E6842813338051407031938 +A1FD0AFECA01360460FC5403EBFC130272D45033100000010032FE5606D0061E001C0000 +012726232207060701231123112301330133113311331336373633321706D03A1920251D +423CFEE4FAB7FAFE5CC3015E7DB77DE84E684281333803DE07031938A1FD0AFE5601AA04 +60FC54056AFA940272D450331000FFFF0073FFE305D905F0120601610000FFFF0071FFE3 +0475047B120602370000000100100000062705F000120000013217152726232207060701 +2301330901123605A93F3F44161949224754FE81E5FDC6D301D9013873AE05F015BB0A04 +2243DDFC1405D5FB17033D013295000100320000051F047B001300000132171527262322 +07060701230133011336373604B433383A1326251D413DFEE4FAFE5CC3015EE850664204 +7B108D07031937A2FD0A0460FC540270D54F33000000FFFF001000000627077010271720 +04E4017A120604080000FFFF00320000051F0666102702C004C200001206040900000003 +0073FE5607B305F00011001E002C000009010607062B013533323736371301331B010110 +0702200326103712201316031027262007061110171620373607B3FE1452464A7C936C4C +2A26377CFEA2C3FDFDFD3F5F7EFE007F60607F02007E5FD51C38FE983A1C1D390168391B +0460FB38CB3A3D9A2421890137036BFD8A0276FE8AFEDDD0FEEC0113D10244D10114FEED +D1FEDE010672EAEA74FEFBFEFC74EAEA720000030071FE5606FF047B0011001F00250000 +09010607062B013533323736371301331B01001007062322272610373633321702102322 +103306FFFE1452464A7C936C4C2A26377CFEA2C3FDFDFD755F73CCCE74606074CECC7364 +DBE0E00460FB38CB3A3D9A2421890137036BFD8A0276FEABFE48A7C9C8A601BCA6C8C9FC +CD0360FCA00000020073FFE3072D05F00029005200002533323736353427262B01060706 +070622272627262723220706151417163B01363736373632171617160723202726103736 +213336373637363217161716173320171611100706212306070607062227262726045D1E +EB72808072EB1E0B0F161B1A3E1A1B16100A2EEB72828272EB2E0A10161B1A3E1A1B1610 +FE2EFEA198BDBD98015F2D0B10161B1A3E1A1F12100B1D015E98BCBC98FEA21D0A11161B +1A3E1A1F1210C291A4F2F3A491140E150C0B0B0C15101291A4F3F2A4911210150C0B0B0C +1510A1AAD20274D3AA150F150C0B0B0E131113AAD3FEC6FEC7D2AB1311150C0B0B0E1311 +000000020071FFE305A1047B0026005000002533323736353427262B0106070E01222627 +262723220706151417163B0136373E013217161716072320272635343736213336373637 +36321716171617332017161514070621230607060706222726272603722DA14856563FAA +2D070A122C342C120A072DA347565548A32D070A122C34161B0D09C92EFEFF7889897401 +052E07090D1B1634161B0D09072E010277898974FEFB2E07090D1B1634161B0D09A46074 +B7A783610B0A111414110A0B5F74B8BC705F0B0A11140A0C0F0A93899CEEE9A2880A0A0F +0C0A0A0C0F0A0A889CEFE8A2890A0A0F0C0A0A0C0F0AFFFF0076FFE308FA0774102612D2 +00001027041A069700001007041806300127FFFF0098FFE307A10610102612D300001027 +041A05FCFE9C100704180595FFC3FFFF0073FFE307050733102717E000630153100603F4 +0000FFFF0087FFE3062705E0102617E00000100603F50000000000010073FE56052705F0 +001D0039400A001C1B0D30161905101E10FCECFCD4B42F1B3F1B025DEC3100400C0EA10D +AE129509911C1A95002FECCCF4ECF4EC30B40F1F1F1F02015D2123202726111037362132 +1716171526272623200706111017163321112303FAAEFEA5BBC3C3C30153867677686673 +7482FF0088888898F0016BC9C6D001530168CFD0242347D55F2F2F9C9DFED8FED38294FD +B00000010071FE5603E7047B001D0039400A1D121A0C48151204451E10FCECF4D4EC3100 +400C0C860D8811B908B81C19A9002FECCCF4FCF5EE30400B0F1F101F801F901FA01F0501 +5D212027263510373621321716171526272623220706151417163B011123110298FEFB8D +95979601065551514C4E4F4E50B363636363B3F5C9969FFA01129D9D111223AC2B161571 +72CDB97271FDC301AA000001003BFFA503CA03A700130000010727071707270727372737 +173727371737170703CA64D869D864D87DAE7DD864D869D864D869AE690211AE7DB57DAE +7DD864D87DAE7DB57DAE7DB564B50001FBDA04DEFF42067A002F00000121140706070607 +062227262726272635343736373637363321343736373637363217161716171615140706 +07060706FEB9FE330A0B1314191838181914130B0A0A0B131419181C01CD0A0B13141918 +38181914140A0A0A0B1314191805671B191B12130B0A0A0B13121B191B1C191B12130B0A +1B191B12130B0A0A0B131518191C1B191B12130B0A000001FD0705290009064D000D0000 +1323262322070607353637363320097617A25D5B93888B4A777D012405299B2F4B178627 +2A430001FDB304C2FEA5066100080000012211353315231437FEA5F2F1858604C2010B94 +9E9D030000000001FDB304C2FEA5066100080000011023351635233533FEA5F28685F105 +CDFEF567039D9E0000000001F9CA04D90009064D000D0000011221320504251524272427 +2607F9CA8701AF72014501320120FE5FEFFED966DD980501014C7B740186175C71070CCF +00000008F7D6FE9003460760000C0019002600330040004D005A0067000001232E012322 +0607233E01201601232E0123220607233E01201605232E0123220607233E01201601232E +0123220607233E01201605232E0123220607233E01201601232E0123220607233E012016 +05232E0123220607233E01201601232E0123220607233E012016FEC7760B615756600D76 +0A9E01229E0338760B615756600D760A9E01229EF9AE760B615756600D760A9E01229E06 +66760B615756600D760A9E01229EF9AE760B615756600D760A9E01229E07B7760B615756 +600D760A9E01229EF70C760B615756600D760A9E01229E0489760B615756600D760A9E01 +229E06414B4B4A4C8F9090FE514B4B4A4C8F90908F4B4B4A4C8F9090FA014B4B4A4C8F90 +908F4B4B4A4C8F909002294B4B4A4C8F90908F4B4B4A4C8F9090FB984B4B4A4C8F909000 +00000008F858FDC302C2082D0005000B00110017001D00230029002F0000273717130703 +01072703371301273725170501170705272501353305152D01152325350501233513330B +0133150323136B96796F5CA9FB7796796F5CA9051F967A01565CFEE3FA4C9579FEA95B01 +1C0660AC0140FEC0F8C2ACFEC00140045FD3A48152D3D3A481525A9679FEA95C011D05B5 +967901575CFEE3FEF1957A6E5BA9FB7796796F5CA80218D4A48252D4D4A4825202DFAC01 +40FEC0F8C2ACFEC00140FFFF00C9FE5605FC076D102617E100001007171D04F50175FFFF +00C1FE5605380614102617E200001007029A00A0FFCC00020021000004EC05D50012001D +003A400E1F1319050D11190F001C0B090D1E10DC3CCCFC3C3CCC10D4ECCC3100400F0A12 +950C100E8109189501AD1995092FECF4EC10F4D43CEC3230011521320415140423211123 +3533353315331501342726232111213237360193014EFB0110FEF0FBFDE8A8A8CAA801DC +4F4EA3FEBC0144A34E4F0451E2DADEDDDA0451A4E0E0A4FD668B4443FDDD444300000002 +002600000445059E000A001E0039400F2045001211161E060C1C1C18161A1F10DC3CCCFC +3C3CCC10D4ECEC3100400D161EA9181C1A05A90C1A06A9152FECC4D4EC10D43CEC323001 +34272623211121323736011121321716100706232111233533113311331503813E4380FE +F9010781423EFDF8010FD079747473D6FE399B9BB89D014C5E2A2EFE972E2B02DFFECA55 +52FEB0525203D18F013EFEC28F00000200C9000004E105D5000F001C000001170727062B +0111231121321716151427363734262B0111333237273704558C6A927ED6FECA01C8FB80 +81E20C019A8DFEFE7247D76A0323757E7B53FDA805D57172DB922D2C398692FDCF2FB47E +0000000200BAFE5604A4047B001000290000252737173635342726200706101716333205 +170727062322272627112311331536373633321716100706032A8C6E8A4F5354FEDC5453 +53549246011B936F95576C7B58593AB9B93A59587BCC7F80800C98A75DA573C5CB747374 +73FE6A747314AE5DB32E303164FDAE060AAA643031A2A2FDF0A20F000000000100C90000 +046A07070007001B400D0306950181000304061C01040810FCFCDCCC31002FF4ECCC3033 +11211133112111C902F7AAFD2905D50132FE24FAD500000100BA000003D0059A0007001D +B7090304060801460810FCFCDCCCCC3100B50306A901BC002FF4ECCC3033112111331121 +11BA028393FDA20460013AFE33FC3300000000010047000004EF05D5000D00294014010C +95090408950581000F060A0C091C0204010E10DC3CCCFC3CCCCCC431002FF4FCDC3CEC32 +302111213521112115211121152111014EFEF9010703A1FD290223FDDD0294AA0297AAFE +13AAFD6C000000010038000004550460000D002B400A0F060A0C09080204010E10DC3CCC +FC3CCCDCCC3100400A010CA9090408A905BC002FF4FCDC3CEC3230211121352111211521 +1121152111013FFEF901070316FDA201A0FE6001F4AA01C29DFEDBAAFE0C000100C9FE66 +04CC05D5001B0033400C12181C041D0C00061C03041C10FCFC3CDCCCC4FCCC3100400E12 +9511B0020095070206950381022FF4EC10D4EC10F4EC3001112311211521112132171615 +1110062B01353332373635113426230193CA03A1FD2901A1BA716DCCE44C3E8638377C7C +02C7FD3905D5AAFE467772EEFECEFEF2F4AA4B4BC201229F9E00000100BAFE56040B0460 +001D0033400C131908041F0C00060803461E10FCFC3CDCCCC4FCCC3100400E13A912BD01 +00A9070106A903BC012FF4EC10D4EC10FCEC300111231121152111332017161511140706 +2B0135333237363511342726230172B80316FDA2FA010746525251B5C1AC6E2126263186 +01E7FE190460AAFEC14751E5FEF2D660609C3037930108AA202900010028FEBF089105D5 +0017000001331101330901331123112309011123110901230901330103EACA02AAF5FDDF +01D788C529FE13FEFECAFEFEFE13D30244FDDFF502AA05D5FD1E02E2FDB3FD22FE150141 +0301FEE9FE1601EA0117FCFF0388024DFD1E00010046FEE5070304600017000001331101 +3309013311231123010711231127012309013301033FB701E9D6FE6E01667AB821FE87BB +B7BBFE87C501CCFE6ED601E90460FDF2020EFE51FDE8FE4C011B0236C9FE93016DC9FDCA +02B101AFFDF2FFFF0087FE75049A05F01026007A3900120603AB00000000FFFF0085FE75 +03C8047C1026007ACE00120603CB00000000000100C9FEBF05B405D5000F000013331101 +210901331123112309011123C9CA02D20103FDBF01EDA0C545FDFAFEEFCA05D5FD1E02E2 +FDB2FD23FE1501410301FEE9FE16000100BAFEE504B30460000F00001333110133090133 +1123112301071123BAB70207E2FE5401778EB838FE73C5B70460FDF2020EFE4FFDEAFE4C +011B0235C8FE93000000000100C90000058605D500120000133311371133150121090123 +01112311071123C9CAAD6401C10103FDBF025FDCFDFA64ADCA05D5FD1EB10154EE01CBFD +B2FC790301FE250175B1FE160000000100BA000004910460001200001333113735331501 +3309012301112335071123BAB760650142E2FE5401E3CEFE736560B70460FDF261DD7601 +46FE4FFD510235FEC5D461FE9300000100210000058605D5001300001333153315231101 +210901230901112311233533C9CAA8A802D20103FDBF025FDCFDFAFEEFCAA8A805D5E090 +FE8E02E2FDB2FC790301FEE9FE16046590000001003D0000049106140013000013331521 +15211101330901230107112311233533BAB70164FE9C0207E2FE5401E3CEFE73C5B77D7D +06147A7DFD35020EFE4FFD510235C8FE93051D7D000000010032000006B205D5000D005F +400B080501040609011C0C000E10D4DCEC32D4C41139393100400E420DA0000809050204 +0300AF0A062F3CEC32173910EC304B535840160711060811050904060503110402110508 +09090409040907103C3C04ED1005ED070810ED0510ED5913211101210901230901112311 +2132028D02D20103FDBF025FDCFDFAFEEFCAFE3D05D5FD1E02E2FDB2FC790301FEE9FE16 +052B0001002A000005820460000D005F400B08050104060901080B000E10D4DCEC32D4C4 +1139393100400E420DA00008090502040300BC0A062F3CEC32173910EC304B5358401607 +1106081105090406050311040211050809090409040907103C3C04ED1005ED070810ED05 +10ED5913211101330901230107112311212A02380207E2FE5401E3CEFE73C5B7FE7F0460 +FDF2020EFE4FFD510235C8FE9303C6000000000100C9FEBF060405D5000F0036401A0C95 +02AD0400810695090E0A07950A0B031C05380D011C00041010FCEC32FCEC323CEC31002F +3CCCECE432FCEC30B2501101015D13331121113311331123112311211123C9CA02DECAC9 +C9CAFD22CA05D5FD9C0264FAD5FE15014102C7FD3900000100C1FEE505400460000F0031 +401A0DA9020400BC06A9090B0F11460C040807A90A050D010800461010FCEC32DC3CECEC +32EC31002F3CCCECE432DCEC3013331121113311331123112311211123C1B80257B8B8B8 +B8FDA9B80460FE3301CDFC39FE4C011B0204FDFC0000000100C90000081205D5000D002D +40180695040A9502AD0400810C080509031C07380B011C00040E10FCEC32FCEC32C43100 +2F3CE432FCEC10EC301333112111211521112311211123C9CA02DE03A1FD29CAFD22CA05 +D5FD9C0264AAFAD502C7FD390000000100C1000006E60460000D002B401606A9040BA902 +0400BC090D050A0408070B010800460E10FCEC32DCEC32C431002F3CE432DCEC10EC3013 +33112111211521112311211123C1B802570316FDA2B8FDA9B80460FE3301CDAAFC4A0204 +FDFC000100C9FE66087405D5001D0038400E1F0F1C131A031C0008041C07041E10FCECD4 +3CECDCCCFCCC3100400F1D950AAD02039507810613951406022F3CDCEC10F4EC10F4EC30 +0111231121112311211121321716151110062B0135333237363511342623053BCAFD22CA +047201A1BA716DCCE44C3E8638377C7C02C7FD39052BFAD505D5FD9C7772EEFECEFEF2F4 +AA4B4BC201229F9E0000000100C1FE5607210460001F0033400E210F08141B0308000804 +0807462010FCECD43CECDCCCFCCC3100400B1F0903A907BC02131502062F3CDCCC10F4EC +DCCC30011123112111231121113320171615111407062B01353332373635113427260704 +88B8FDA9B803C7FA010746525251B5C1AC6E212626318601E7FE1903C6FC3A0460FE1747 +51E5FEF2D660609C3037930108A4262E050000020073FFE306F705F1004100590000252E +0335343E0433321E0415140E02071E0133323637150E0123222E02270E01232224260235 +34123E0137150E0315141E02333236373E0335342E0423220E0415141E02042B396C5232 +132A446181534E7D61462D151A3F6A5126683B3E65332E783D265155572B42C379AAFEF4 +BC635BACF79D73AB70383C7BBE815280B13F5330140B1724303E26314833211207284254 +AE3189AAC46B428A837457323254707B7F3A53B4B2AA4A1A15131AA817120814231B2634 +74CF011DA8A00110CB7D0EA716669ACD7C7DDEA762196D3A868E91452F66635943282B47 +5B605F2662AB8E6E000000020071FFE30578047A003F0050000013343E0237150E031514 +1E02333236372E0335343E0233321E02151406071E0133323E0237150E03232226270E03 +23222E02053E0335342E0223220E021514714B8FD1865D8A5B2D32608A5827561C243F2F +1B2850744D4270502D5C5F23441E1B2D292A1811252D38253782431E4649471F87D4914C +0356222D1A0B162128121A2C2113022883D89C58039B064672985862A0723E0B11256778 +82405D9F7341396892599BF15E130A040A130F9D0A110C071C2C121B12095499D6B8265E +64632B4B69421F26486943F70000FFFF0073FE75052705F01027007A012D0000120603B5 +0000FFFF0071FE7503E7047B1027007A008F0000120603D500000001FFFAFEBF04E905D5 +000B002C400D0D0A40011C040B1C084005080C10C4DCECFC3CECFCC4310040090A069509 +81019503052FCCECF4EC323025331123112311213521152102D7C9C9CBFDEE04EFFDEEAA +FE150141052BAAAA00000001003CFEE5046D0460000B0028400A0D090108040B0806080C +10DCDCFC3CECDCCC310040090B07A908BC00A903052FCCECF4EC32302533112311231121 +3521152102AFB8B8B5FE420431FE4299FE4C011B03B6AAAA0000FFFFFFFC000004E705D5 +1206003C00000001003DFE56047F04600008006F40100408BC0209060300080304000803 +040910D44BB00A544BB008545B58B9000B004038594BB0145458B9000BFFC03859D4FC49 +3A111239310010CCE4323040190711080008061105060008000611060703030405110404 +03424B5358071005ED071008ED071008ED071005ED592225112311013309013302C5C3FE +3BC3015E015EC312FE4401BC044EFC94036C0001FFFC000004E705D50010000001211123 +1121352135013309013301152103DFFEF8CBFEF90107FDF0D9019E019BD9FDF001080173 +FE8D0173AAAA030EFD9A0266FCF2AA0000000001003DFE56047F04600010000009011521 +15211523352135213501330901047FFE460106FEFAC3FEEF0111FE3BC3015E015E0460FB +B258AABABAAA58044EFC94036C000001003DFEBF053B05D5000F00002533112311230901 +230901330901330104CB70C514FE5CFE59DA0215FE2FD901730175D9FE20AAFE15014102 +7BFD85031D02B8FDD5022BFD33000001003BFEE504790460000F00002533112311230901 +2309013309013301040871B821FEBAFEBAD901B3FE72D901290129D9FE6B99FE4C011B01 +B8FE48024A0216FE71018FFDDF000001FFFAFEBF074705D5000F0035401011059508021C +030A0D400F1C0C400A1010D4E4FCE410D4EC3CFCCC3100400B0F0A95020C810704009509 +2FEC32CCF43CEC32302521113311331123112111213521152102D602DECAC9C9FB8EFDEE +04EFFDEEAA052BFAD5FE150141052BAAAA0000010005FEE506420460000F0033400E1105 +A9020808030A0D0F080C0A1010DCC4FCC410D43CECFCCC3100400B0F0BA9020CBC070400 +A9092FEC32CCF43CEC3230252111331133112311211121352115210278025AB8B8B8FC39 +FE420431FE429903C7FC39FE4C011B03B6AAAA000000000100AFFEBF057C05D50014002E +400C01160406131C04140D1C0C1510DCECD43CEC32EC323100400B079511AD130C810095 +03052FCCECE432F4EC3025331123112311212227263511331114163321113304B3C9C9CB +FE5FBA716DC97C7C0178CBAAFE15014102C77772EE0137FED99F9E02640000010096FEE5 +04B8046000150046400C01174606140804150D080C1610DCECD43CEC32EC323100400A07 +A912140CBC00A903052FCCECE432D4ECB005B0072349B012B01423495258B10712B8FFC0 +B0021738593025331123112311212227263D013315141716332111330400B8B8B8FEA999 +665CB83435680129B899FE4C011B02095F56B8EAD3753B3B01BE000100AF000004B305D5 +0018000001232227263511331114163B0111331133113311231123112302823BBA716DC9 +7C7C1290D6CBCBD69002C77772EE0137FED99F9E0139FEC70264FA2B02C7FECF00000001 +0096000004000460001800000135331533113311231123152335232227263D0133151417 +1601F9A0AFB8B8AFA00899665CB8342B02A4C2C401BEFBA00209C4C45F56B8EAD3753B30 +0000000100AF000004B305D5000F0024400A11081C060C001C0F041010FCEC32D4ECCC31 +00B702950BAD0F81070E2F3CF4F4EC3001112132161511231134262321112311017A01A1 +BADEC97C7CFE88CB05D5FD9CE9EEFE66018A9F9EFD3905D50000FFFF00BA000004640614 +1206004B000000020014FFE3071405F00022002A004940112324090F241908330919181E +121D00182B10DC32DCEC10ECF4ECC41112393100401610A10FAE0C1E1808950024AD0C95 +1428950491148C2B10E4F4EC10ECF43CEC32CC10F4EC3001123736212017161321100021 +3236371506070623202726030627263D013315141716252126272620070601B22296BC01 +3A0143B5BB01FB70011201128BFC706F838492FEA2C5BC0AAA767AAA4B42014003AD1862 +82FE488061036D010AA7D2D2DBFE84FEF4FECE605FD7462424CDC2015501676BDF4C3EA0 +413902BF7CA4A47C00000002000FFFE30566047B0025002E006940112E26151D2608134B +0006120515120B002F10DC32ECDC400B000570307F05B030CF30055DEC10F4ECC4111239 +3100401A1326141E861D8819060B26A91419B9220014BB2AB90FB8228C2F10E4F4ECE4B2 +6F14015D3210EC10FC3CCC10F4B22F1D015DEC11123930012227263D0133151417163336 +3736213217161D0121161716333237363715060706232027260126272623220706070158 +9059609C30394A1A749200FFE28384FCB20C6667B76A64636268636E65FEF39C94034E02 +5253889A5D5C0E0204525AAC4631972126C582A19192FA5ABE64631A1A34AE2C14169C94 +0181975A5A57579E000000020014FE87071405F00007002D000001212627262007060712 +37362120171613211000213236371506070607112311242726030627263D013315141716 +028B03AD186282FE488061F12296BC013A0143B5BB01FB70011201128BFC706F836D77B2 +FEFDA0BC0AAA767AAA4B42036DBF7CA4A47CBF010AA7D2D2DBFE84FEF4FECE605FD74624 +1E05FEA3016320A6C3015401676BDF4C3EA0413900000002000FFEB70566047B00080031 +000001262726232207060F012227263D01331514171633363736213217161D0121161716 +3332373637150607060711231126272604AE025253889A5D5C0EC69059609C30394A1A74 +9200FFE28384FCB20C6667B76A64636268634F4AA6C27B940294975A5A57579E8F525AAC +4631972126C582A19192FA5ABE64631A1A34AE2C141004FED201331A7B94FFFF00C90000 +019305D51206002C0000FFFF002800000876076D1027171D065B0175120603AA0000FFFF +0046000006EF06481027029A01A80000120603CA0000000100C9FE66053505D5001C0000 +0133321716151110062B0135333237363511342623211123113311012102A98BBA716DCC +E44C3E8638377C7CFE88CACA029E010403717772EEFECEFEF2F4AA4B4BC201229F9EFD39 +05D5FD890277000100BFFE5604880460001E0000013320171615111407062B0135333237 +363511342726232111231133110133025E14010548525251B5C1AC6E2126262C8BFEFCB9 +B90225EB02774751E5FEF2D660609C3037930108A62429FE190460FE1D01E30000000001 +0036FE56060305D500140000212311211510030605353637121901211133150123053ACA +FE1B8462FE91D443750378C9FE9286052BD4FE18FEAAFD38A72EA801250235011AFAD5AA +FE560001002EFE56052B0460001400002533150123132311211510030605353637361135 +210473B8FEDE7BE5B8FE7B765EFECCB33B6202F59999FE5601AA03C786FE92FEFCCF1D99 +1B7FCF01A7D4000100C9FE66053B05D500140031400E0F08001C16040A3807031C050415 +10FCEC32FCECFC3CCC3100400B0E1004029508AD090681042FE432FCEC10DCCC30251121 +11231133112111331110062B0135333237360471FD22CACA02DECACEE34C3E8638376802 +5FFD3905D5FD9C0264FA93FEF2F4AA4B4B00000100C1FE56048804600015002F400D1008 +000817460A07030805461610FCEC32DCECFC3CCC3100400A0F110402A9080906BC042FF4 +3CDCEC10DCCC300511211123113311211133111407062B01353332373603D0FDA9B8B802 +57B85251B5C1AC6E2126140218FDFC0460FE3301CDFB8CD660609C303700000100C9FE56 +060405D5001000002123112111231133112111331133150123053BCAFD22CACA02DECAC9 +FE928602C7FD3905D5FD9C0264FAD5AAFE56000100C1FE56054004600010000021231121 +112311331121113311331501230488B8FDA9B8B80257B8B8FEDE7B0204FDFC0460FE3301 +CDFC3999FE56000100AFFEBF04B305D50014002F400D141C11010E1C16040F081C071510 +DCECD4ECFC3232EC3100400B02950CAD0E0781009513112FCCECE432F4EC302511212227 +2635113311141633211133112311231103E8FE5FBA716DC97C7C0178CBCBC9AA021D7772 +EE0137FED99F9E0264FA2BFEBF01EB00000000010096FEE50400046000150047400D1508 +12010F081746100808071610DCECD4ECFC3232EC3100400A02A90D0F07BC00A914122FCC +ECE432D4ECB012B0022349B00DB00F23495258B1020DB8FFC0B002173859302511212227 +263D0133151417163321113311231123110348FEA999665CB83435680129B8B8B8990170 +5F56B8EAD3753B3B01BEFBA0FEE501B40000000100C9FE5606E805D50011000025331501 +2301231101230111231121090121061FC9FE9286012BC5FE7FCBFE7FC4012D017D017F01 +2DAAAAFE5601AA051FFC000400FAE105D5FC0803F800000100C1FE560600046000110000 +2533150123132311012301112311210901210548B8FEDE7BE5B2FECBB8FECAB20106013E +013F01049999FE5601AA03B0FD2702D9FC500460FD1202EE0000FFFF00C1000001790614 +1206004F0000FFFF00100000056807921027029A00CE014A130603A400000012B4180008 +13072B310040056F006F08025D30FFFF007BFFE3042D061F1026029A4FD7130603C40000 +0008B422000819072B31FFFF001000000568074E122603A400001107171604BC01750014 +B40A120D05072B400930123F0D00120F0D045D310000FFFF007BFFE3042D0610122603C4 +00001106006A52000020B4142D280B072B40157F286F28502D5F28402D4F28302D3F2800 +2D0F280A5D31FFFF00080000074805D5120600880000FFFF007BFFE3076F047B120600A8 +0000FFFF00C90000048B076D1027171D04A10175130603A90000000740034000015D3100 +0000FFFF0071FFE3047F06481027029A00960000130603C90000000740037000015D3100 +0000FFFF0075FFE305D905F0120601510000FFFF0071FFE3047F047B1206021B0000FFFF +0075FFE305D9074E10271716052001751206046C0000FFFF0071FFE3047F06101026006A +54001206046D00000000FFFF002800000876074E1027171606510175120603AA0000FFFF +0046000006EF06101027006A019E0000120603CA0000FFFF0087FFE3049A074E10271716 +04870175120603AB0000FFFF0085FFE303C806101026006A3A00120603CB00000000FFFF +00A0FFC104F805D5120601790000FFFF0058FE4C042F0460120602540000FFFF00C90000 +053307311027007100F5013B120603AC0000FFFF00BA0000047905F5102700710092FFFF +120603CC0000FFFF00C900000533074E1027171604F50175120603AC0000FFFF00BA0000 +047906101027006A00920000120603CC0000FFFF0073FFE305D9074E122603B200001107 +1716052701750014B4031F1A09072B4009401F4F1A101F1F1A045D310000FFFF0071FFE3 +04750610122603D200001106006A73000014B4031F1A09072B4009401F4F1A301F3F1A04 +5D31FFFF0073FFE305D905F0120601610000FFFF0071FFE30475047B120602370000FFFF +0073FFE305D9074E1226047C00001007171605270175FFFF0071FFE3047506101226047D +00001006006A73000000FFFF006FFFE30523074E1027171604670175120603C10000FFFF +0071FFE303E706101026006AE200120603E100000000FFFF0023000004BD073110270071 +0072013B120603B70000FFFF003DFE56047F05F5102600715EFF120603D700000000FFFF +0023000004BD074E1027171604720175120603B70000FFFF003DFE56047F06101026006A +5E00120603D700000000FFFF0023000004BD076B1027171F04720175120603B70000FFFF +003DFE56047F06661026029F5E00120603D700000000FFFF00AF000004B3074E10271716 +04CC0175120603BB0000FFFF00960000040006101026006A5E00120603DB000000000001 +00C9FEBF046A05D500090023400A0B02069509041C01040A10FCFC3CECCCC43100B60495 +01810608002FCCCCF4EC3033112115211133112311C903A1FD29C9C905D5AAFB7FFE1501 +4100000100BAFEE503D0046000090023400A0B0206A909040801460A10FCFC3CECDCCC31 +00B604A901BC0608002FCCCCF4EC3033112115211133112311BA0316FDA2B8B80460AAFC +E3FE4C011B00FFFF00C900000646074E122603BF00001007171605B70175FFFF00BA0000 +059B0610122603DF00001007006A0108000000010047FE5604EF05D500190039400D190E +1B060A0C091C021504011A10DC3C3CCCFC3CCCCCC4DCCC3100400E14150E00010C950904 +08950581002FF4FCDC3CEC3210CCDCCC3021112135211121152111211521112115140706 +2B013533323635014EFEF9010703A1FD290223FDDD01694752BFFEE9694C0294AA0297AA +FE13AAFDEC94C8606E9C61AD000000010038FE56045504600019003B400E1B0609190E0A +0C0908021504011A10DC3CC4CCFC3CCCDCCC10DCCC3100400E14150E00010CA9090408A9 +05BC002FF4ECDC3CFC3C10CCDCCC30211121352111211521112115211121151407062B01 +3533323635013FFEF901070316FDA201A0FE60016E4652C0FEE96A4B01F4AA01C29DFEDB +AAFE8C94C8606E9C61AD0001003DFE66052A05D5001700002516070607062B0135333237 +363709012309013309013301052A01020F5366E44C3E8737280BFE5EFE59DA0215FE2FD9 +01730175D9FE201A0218BE627AAA4B35730278FD85031D02B8FDD5022BFD330000000001 +003BFE560464046000170000090216151407062B013532373E0135090123090133090104 +64FE6B016B1B4351C4C1C4194F35FEBDFEBAD901B3FE72D9012901290460FDDFFE172639 +CD61739C030A6D9801B4FE48024A0216FE71018F00000001003D0000053B05D500110000 +13330901330121152101230901230121352181D901730175D9FE4E0174FE9001CED9FE5C +FE59DA01D4FE8C019605D5FDD5022BFD7790FD44027BFD8502BC900000000001003B0000 +047904600011000009013309013301211521012309012301213501B7FEA9D901290129D9 +FEAA010DFEE0017ED9FEBAFEBAD9017FFEDF029401CCFE71018FFE3490FDFC01B8FE4802 +049000020091000004B405D5000A00150026400A170405141C0B0019101610DCECD4EC32 +EC31004009069514AD0B8105950C2FECE4F4EC3001141716332111212207060111212224 +35342433211101664F4EA30144FEBCA34E4F034EFDE8FBFEF00110FB014E01B78A434402 +2343440393FA2BDADDDEDA02660000020071000003F50460000A00160025400B18460515 +080B001211451710FCECD4EC32EC3100B706A9150BBC05A90C2FECE4D4EC300114171633 +21112122070601112122272610373633211101353E42810107FEF980433E02C0FE39D673 +747479D0010F014C5A2B2E01692E2A02B6FBA052520150525501C500000000020091FFE3 +074305D5000C00300039400E3204261C290D1B0C1C1D0519173110DCECD4EC3239D4ECEC +310040102208951301951AAD2D138C28BC1D813110ECECE432F4EC10EC32300121220706 +101716333237363513060706070623222726353424332111331114171633323736351133 +111407062322272603EAFEBCA34E4F4F5F81B44B56210C0E336A5E6EEE81880110FB014E +C93F3470693B3FCA6E68D7D9663102C94344FEEA505F6D7D9FFEDD1D1C6036318189CADE +DA0266FBEC8F5B4A4A4F9B029FFD5AE07F787839000000020071FFE306730460000D0030 +0038400F32462608290E0D1B081E051216453110FCECD4EC3239D4ECEC3100400E2209A9 +3101A91A2D128C281DBC3110ECCCE432D4EC10EC32300121220706151417163332373635 +130607062322272635343736332111331114171633323736351133111407062322272603 +3DFEF980433E41406A945C2D31435D5E88AC66657479D0010FB83E3C6A683C3EB86468CE +D3641F02022E2A5E5C3A396D349CFEF66C30316160A6AA525501C5FD619F504F4F529D01 +41FEB8EC737878250000000100C9FFE3070305F000370040400F392E1C2C060B191B2C00 +192312063810FCD4ECCCD4FCCC10ECCC310040131F05950627953212A113AE0F9517912D +328C3810E4CCF4ECF4EC10ECD4EC3930013427262B013533323736353427262322060735 +363736333217161514070607161716151417163332373635113311140706232227262726 +03AA5C5DA5AEB6954F4F51529853BE7273646559E686864747839152513F3470693B3FCA +6E68D7D966301C2101B2844A4BA63B3C70733D3E2426B42010106869B27C5556211F6262 +90805B4A4A4F9B029FFD5AE07F7878385061000100ABFFE30646047C00350047400E372E +122C0B121B2C00122306143610DCC4D4ECCCD4EC10ECCC31004013148613880FB917B836 +05A9063627B9328C2C3610CCF4EC10D4EC10F4FCB00C4B5158FC1BF459EC30013427262B +013533323736353427262322070607353E01333217161514070607161716151417163332 +3736351133111407062322272603134E4889949B7443444645774751506162AA4CC47172 +3C3C708140453E3D69683C3EB86468CEC770620138663833982C2D46402E2E0D0D1DA718 +184E4F8D5D40411819484F485844454F529D0141FEB8EC73787565000000000100C9FE56 +053C05F00029003A400D080D191D271C2B02192514062A10FCD4ECCCECD4FCCC31004012 +210795080014A115AE11951991279528BD002FECECF4ECF4EC10D4EC3930212311342726 +2B0135333237363534272623220607353637363332171615140706071617161511331123 +0473C95C5DA5AEB6954F4F51529853BE7273646559E68686474783915251C9C901B1854A +4BA63B3C70733D3E2426B42010106869B27C5556211F626192FEF9FDAC00000100ABFE56 +0483047C0029003F400C11122101122B0612290C1A2A10DCC4D4ECCCECD4EC310040111A +86198815B91DB8040BA90C00A902BD042FECECD4EC10F4FCB00C4B5158FC1BF459EC3025 +3311231123113427262B013533323736353427262322070607353E013332171615140706 +071617161503C8BBB8B84E4889949B7443444645774751506162AA4CC471723C3C707E43 +4599FDBD01AA0146583833982C2D46402E2E0D0D1DA718184E4F8D5D40411818494B6A00 +000000010036FFE307CA05D500210034400D23040B1C0A151C00161C211C2210D4D4ECD4 +ECD4FCEC3100400E16952181220595108C1C951B0A2210CC3CECF4EC10F4EC3001111417 +163332373635113311140706232227263511211510030605353637121901053A3F347069 +3B3FCA6E68D7D6696EFE1B8462FE91D4437505D5FBEC8F5B4A4A4F9B029FFD5AE07F7878 +7DE20371D4FE18FEAAFD38A72EA801250235011A00000001002EFFE306EE046000200034 +400D22460A08091408001508201B2110D4D4ECD4ECD4FCEC3100400E15A920BC2104A90F +8C1BA91A092110CC3CECF4EC10F4EC300111141633323736351133111407062322272635 +1121151003060535363736113504737A67683C3EB86468CEC77062FE7B765EFECCB33B62 +0460FD04578A4F529D0141FEB8EC737875657B028F86FE92FEFCCF1D991B7FCF01A7D400 +0000FFFF00C9FFE3082D05D5120601B80000000100C1FFE307030460001C0036400F1E46 +0A0809141C080015190818461D10FCEC32DCEC32D4FCEC3100400D15A91A1C18BC1704A9 +0F8C09172FCCF4EC10E432DCEC30011114163332373635113311140706232227263D0121 +1123113311211104887A67683C3EB86468CEC77062FDA9B8B802570460FD04578A4F529D +0141FEB8EC737875657BCCFDFC0460FE3301CD00000000010073FFE3058905F0001B0030 +400B1D0410191C1B141C0A101C10FCECDCECC4EC3100400E1695061A10950F12950D9106 +8C1C10E4F4ECD4FCCC10EC30011407060706232027261037362120171526212011102132 +363511330589642D897C97FE9BC4C0BFC501650127E1E1FEEAFDDB0225D77BCA01BAE07F +39211ED2CC02D0CDD28ED7BFFD9FFDA094A401F0000000010071FFE30446047B001B0030 +400B1D45061212140D1200451C10FCECD4ECC4EC3100400E10B9181307B90609B904B818 +8C1C10E4F4ECD4FCCC10EC30131037362132171526232207061017163332113533151406 +23202726719296010BD0BABEC4BD625A5A62BDE2B8C9E5FEFC958E022F010E9DA16EAA7C +7C72FE7C727C013EBEC5ECE7A69E0001FFFAFFE3056605D50019002F400C091C0B1B1840 +001C1740141A10D4E4FCE4CCDCEC3100400C0595101500951781108C0A1A10CCE4F4EC32 +10EC30011114171633323736351133111407062322272635112135211502D73F346F693B +3FCA6E68D7D6696EFDEE04EF052BFC968F5B4A4A4F9B029FFD5AE07F78787DE20371AAAA +000000010005FFE304F6046000190032400A0A080B1B18000815171A10D4DCFCCCCCDCEC +3100400C05A9108C1A0016A917BC0A1A10CCF4EC3210F4ECB2100A015D30011114171633 +323736351133111407062322272635112135211502783E3D69683C3EB86468CEC77062FE +42043103B6FDAE5646454F529D0141FEB8EC737875657B027EAAAA000000FFFF00A4FFE3 +047B05F0120601520000FFFF0085FFE303C8047C12060349000000010054FE66053A05D5 +0018002F400B0D1A04131C07141C06011910D4D4ECD4ECECCC3100400D0D950CBD191495 +0681019500191032ECF4EC10F4EC3033353637361135211110062B013533323736351121 +1510030654DD3A570378CDE34D3F863737FE1B6662AA30A3F60264FEFA93FEF2F4AA4B4C +C104C3B8FDCAFEF8FD000001004CFE56047304600018002F400B0D1A4613080714080601 +1910D4D4ECD4ECECCC3100400D0DA90CBD1914A906BC01A900191032ECF4EC10F4EC3033 +353637361135211114062B013533323736351121151007064CBB334402F5A3B54631612E +26FE7B585E991D7DA601D0B7FB8CD6C09C3029A103E16FFE50C2CF000000000100540000 +091C05D50017000033353637121135210901330901230901230901211510030654D93E57 +037901730175D9FE200200D9FE5CFE59DA0215FEA0FDB86662AA2FA401020258FEFDD502 +2BFD33FCF8027BFD85031D020EB8FDCAFEF8FD0000000001004C000007B2046000180000 +090223090123090121151007060535363736113521170901079DFE6B01AAD9FEBAFEBAD9 +01B3FEDFFE30585EFECCB6384402F501012701290460FDDFFDC101B8FE48024A01836FFE +50C2CF1D991C7EB101C5B703FE74018F0000000200C9000006E805D50008001A00000111 +333236353426230106212311231121320415140701330901230193FE8D9A9A8D01957FFE +EAFECA01C8FB0101060168D9FE200200D9052FFDCF92878692FDB38AFDA805D5E3DB302A +0218FD33FCF8000200BAFE5606A8047B0018002000000902230106070623222627112311 +33153E0133321716170100102620061016200693FE6B01AAD9FECB1A5C7FCC7BB13AB9B9 +3AB17BCC7F541D0125FE2BA7FEDCA7A701240460FDDFFDC101A1A874A26164FDAE060AAA +6461A26B970189FD040196E7E7FE6AE7000000020088000007BC05D50015001D00003301 +2624353424290115211121152111211521112101121016332111212288019864FF000104 +010204E8FD1C02C5FD3B02F6FC3EFEF4FE763795920138FEC892028D1AA9D7CEE0AAFE46 +AAFDE3AA0277FD8904AAFEFA87021200000000030074FFE30777047B001F0026002F0000 +0115211E0133323637150E012320272627230123012E01353436332136333200072E0123 +2206072514163B01112322060777FCB20CCDB76AC76263D06BFEF49D9804E5FEB6C60156 +749AD7D902655667E20107B802A5889AB90EFD4B8077F8F87780025E5ABEC73434AE2A2C +9C98C2FE2701EB1A898FA2A11BFEDDC497B4AE9E8A535E01615CFFFF0073FEF805D905F0 +100600340000FFFF0071FE56045A047B100600540000FFFF0044000007A605D51006003A +0000FFFF00560000063504601006005A0000000100C90000058605D50014000001331737 +2101172327070123090111231133110127026A8797DD0103FEA3C88E809E025FDCFDFAFE +EFCACA01AFDB058A97E2FE9BC880A1FC790301FEE9FE1605D5FD1E01B8DC000100BA0000 +0491046000130000013317373307172327070123010711231133110102358C4176E2E693 +8E4C7F01E3CEFE73C5B7B7014B04294077E9934C81FD510235C8FE930460FDF201500001 +0054FE66087305D500250039400E0E1C271319201C1D07211C06012610D4D4ECD43CECDC +C4CCEC3100400F1395121D95082195068112BD08001F2F3CCCECF4EC10EC10EC30333536 +37121135211121321716151110062B013533323736351134262321112311211510030654 +D93E57037801A1BA716DCCE44C3E8638377C7CFE88CAFE1B6662AA2FA401020258FEFD9C +7772EEFECEFEF2F4AA4B4BC201229F9EFD39052BB8FDCAFEF8FD0001004CFE56070C0460 +00270039400E0E0829141A22081F07230806012810D4D4ECD43CECDCC4CCEC3100400F14 +A9131FA90823A906BC13BD0800212F3CCCECF4EC10EC10EC303335363736113521113320 +171615111407062B0135333237363511342726232111231121151007064CB6384402F5FA +010746525251B5C1AC6E2126262C8BFEFCB8FE7B585E991C7EB101C5B7FE174751E5FEF2 +D561609C3037930108A62429FE1903CD6FFE50C2CF00000100C9FE66087405D50021003F +4011100C1C23171D031C1B05381F011C00042210FCEC32FC3CEC32D4CCECCC3100401012 +9510BD1C1B1E950602AD040081201C2F3CE432FC3CEC3210FCEC30133311211133112132 +1716151110062B013533323736351134262321112311211123C9CA02DECA01A1BA716DCC +E44C3E8638377C7CFE88CAFD22CA05D5FD9C0264FD9C7772EEFECEFEF2F4AA4B4BC20122 +9F9EFD3902C7FD390000000100BAFE56071A04600023004040100C082512182004081D05 +21010800462410FCEC32DC3CEC32DCC4CCEC3100401112A9111DA90621A9020400BC1F11 +BD06232FCCEC3CE432DCEC10EC10EC30133311211133113320171615111407062B013533 +32373635113427262321112311211123BAB90255B9FA010746525251B5C1AC6E2126262C +8BFEFCB9FDABB90460FE3701C9FE174751E5FEF2D561609C3037930108A62429FE190204 +FDFC000100C9FEBF060405D5000B00002111211123112111331123110471FD22CA0472C9 +C9052BFAD505D5FAD5FE15014100000100BAFEE505390460000B00000111331123112311 +211123110481B8B8B9FDABB90460FC39FE4C011B03CDFC330460000100B2FFC4057005D5 +00230000011E01173635113311140716170726270E0123200019013311141E0233323637 +2E0127038827552E2CCB614F5946716C45AF6BFEEBFED9CB2A598C623F6629365E2601E3 +345A2965B9038BFC5CE59033279E34482E2F0129012503A4FC7578AB6D3312142D603400 +0000000100B20000053305F2001900003311100021200011152335342E0223220E021511 +21152111B20122011801190124CB2B5A8B60628B5A2A03B6FC4A03A401250129FED9FED9 +392079AB6D32326DAB79FEE7AFFE3D0000000002005D000005D505F20013002300000133 +152311231121222E02343E02332000110311342E0223220E02141E023304DBFAFACBFE91 +8BD8944D4D94D88C01170122CB2A5A8A60698F56252C5B8B5F0272AFFE3D01C3508EC4E8 +C58F51FED6FEDCFECE011979AB6D323A64889E845F350001005A000005CB05F200190000 +2111342E0223220E021D012335100021200019013315231104062A5A8C61608B5A2BCB01 +24011901180122FAFA038B79AB6D32326DAB79203901270127FED7FEDBFECEAFFE3D0001 +00B2FFE3053305D500190000011121152111141E0233323E023D01331510002120001901 +017D03B6FC4A2A5A8B62608B5A2BCBFEDCFEE7FEE8FEDE05D5FE8BAFFE9979AB6D32326D +AB792039FED9FED90129012503A4000100B20000058A05F30029000001140E0407211521 +11331533323E0435342E02220E021D012335343E02201E02058A33546D716E2C01E4FB64 +CB563D999D97754730669EDC9C632ECB4C99E50132EB9F52038167B4997C5D3D0DAA014C +A22B537BA2C77567AB794340709756474B79D29A585AA5E70000000100BC000004ED05D5 +0009000025211521113311211521018702DBFC5ACB0366FC9AAFAF05D5FE8BAF00000001 +00B20000053305F2001700003311100021200011152335342E0223220E0215112115B201 +22011801190124CB2B5A8B60628B5A2A03B603A401250129FED9FED9392079AB6D32326D +AB79FD24AF00000200B2FFE306AE05F200290038000000220E0215112311343E02201E02 +1D0133152311140E0223222E02343E023B0135342E0100141E02323E02351123220E0103 +C3F0AF7136CB5BABF60136F6AB5BCECE4274A15E5F9F744144759F5BEA356FFEC8254158 +68563E22EA395A3E054E326DAB79FC7503A493DD944A4A94DD9334AFFED76BA36E393A73 +AFEAA86C33187AAD6DFCE1A870421C1B416B500123193F0000000002005DFFE305D505D5 +00150023000001140E0223222E02343E02332111331133152321220E02141E0233323635 +1104DB4990D58B8CD8944D4D94D88B016FCBFAFAFDC55F8B5B2C25568F69C0AE025792E9 +A2575D9DD1E8D09E5C0175FE8BAF426E909E947345F7F2014100000100BC0000053305D5 +001800001333113E0133321E021D012335342E02220E02151123BCCB3FBC768AD4924BCB +2B5A8BC08B5B2BCB05D5FE094D494893DD964B3279AB6D32326CAC79FDF3000100BC0000 +044405D500050000011121152111018702BDFC7805D5FADAAF05D5000000000100BCFFE3 +06CB05D5001D00000111141E02323E0235113311140E02222E02351121112311331103CA +36546660625133CB4880B0D2B3844BFE88CBCB0460FD314B663E1B1B3E664B02CFFD4977 +AB6F35356FAB770208FC4F05D5FE8B00000000020108FFE6061A05F0002B003E00000133 +15333E0333321E0217152E032322060716041E0115140E0423222E02353436372313141E +0233323E0235342E01242B010E01010CCB4E4299A3A8523C6458542D356A655C2761B34F +9B0111CC76274C6F93B46A9FEFA1504C438BCB2A65A67D7CAC6A2F429FFEF6C81D4C5705 +D5F53F65472509121B12D7232F1C0B39340959A1E899539D8B75542F63AEED8980E765FE +345FAF85504C7FA85C60AE844E61EB000000000100B20000052905D5001800002123110E +0123222E0235113311141E02323E023D01330529CB45B6768AD4924BCB2B5A8BC08B5B2B +CB01E33F434893DD960226FDF379AB6D32326CAB7A9800010046FFCA051A05D500190000 +2515012E033E01373624370333010E03070E021617051AFC163E673F0B3B8B779B0138A4 +CFEE010371CBC1BB60675F0D382F98CE014013394C60758A5068A647012FFE892B58616D +40455C3E260F000200A8FFD0058005F300350043000013343E02333216173E0335342E02 +220E021D012335343E02201E0215140E02071E0117072E01270E0123222E0225220E0215 +1416333236372E01B73B65844878ED7628412F1A2F649FE09D622CCB4E9AE5012CEBA054 +223F5736386D3888366F386EF97E4784663D016C213F311D585856B0515EAF010949724E +28605032737C864667AE7D4640709756474B79D29A5858A3E79056A59C8E3F2D5F338E34 +65305964244B71DC13243320414F4840425000010064000005D505F20017000025331521 +11342E0223220E021D01233510002120001104DBFAFE3B2B5A8C61628B5929CB0120011C +011A0121AFAF038B79AB6D32346DAB77203901270127FED6FEDC00020069000005B905E2 +001A0024000001220E0207011521222E023E01373E01370133133E0333010E011E013321 +010E0105262D707C85430274FBC4426E4B2314514D43A159FEE4F3BC509F968838FC9150 +440E594D02F4FDEA4781052923405B39FC72A420456C99C87E6DC9580197FEEE426B4A28 +FC7E85A9602302FB4AA6000100B2FFE3062305D500170000011110002120001901331114 +1E0233323E02351121150529FEDFFEE6FEE4FEE0CB2A598C62618C592A01C50526FD0BFE +DDFED50127012703A4FC7578AB6D33336DAB78038BAF00010092FFE3055905F100370000 +01140E02202E023533141E0233323E0235342623213521323E0235342620061523343E02 +321E0215140E02071E0305594D99E6FECEE5984CCB2B609B71709B5E2ACAC9FDB0025051 +774D259FFECAA0CB4A88BFEAC0894C2540542E4475573201BC67AD7E474A82AF65417257 +323256703E807FAF27435C357671727B5F9567363566935D3B68533C0F113F5B76000001 +0000FFE3057105D5001700000111141E0233323E023D01331510002120001901233501C5 +295A8C63628B5929CBFEDFFEE5FEE6FEDFFA05D5FC7578AB6D33336EAA782039FEDCFED6 +012D012102F4B0000000000100A0FFE2057905D5002E0000012E012B0135333216170115 +252E0123220E0215141633323E023D013315140E0223222E0235343E02370264388A558D +DB6AA247026BFE961A402A6FBA874BD0D3699B6532CB529CE49192E9A3584E8ABD6F0500 +1912AA181EFEFED4940B0D4886C079F0F83C6D985C474B80D3975356A5F29C7ED7A56B13 +0000000100B20000052905F2001500003311100021200019012311342E0223220E021511 +B20120011C011A0121CB2B5A8B60628B5A2A03A401270127FED6FEDCFC5C038B79AB6D32 +326DAB79FC7500010078FFC6055005F3002500002517150135171E0133323E0235342623 +220E021D012335343E02201E0215140E02034DC3FCFBED1A4A2A72BB8449D2D0689B6632 +CB529CE30124E9A3574B89BDE955CE0145D4670B0D5694C671DFEF3C6D985C474B80D397 +5353A0E99674DEB67D000001005A000005B505F2001C0000011123110E031D012335343E +023332041E0115112311342E020367CB568B6234CB66B6F8939D00FFB563CB38668E0547 +FC94036C0C497095582A3982D99C57579CD982FC5C03954F95764F000000000200A80000 +057D05F30022003B000001222E0435263E0233321E0215140E0207211521113315333236 +37362625342E0223220E02171E0333321E02073E0302125678502E1707034F9CE593A0EC +9C4D4C85B76C01D9FB64CB4E51C35F28A201D530659E6F6B9E652E0603122B4B3B79B16D +27112B48351D029B2339474740156BC294585EA7E58879DDBC9431AA014CA23F3DB2C3E6 +64AA7B453964874D2637251240729C5B2D6A78870000000100B20000062305F200190000 +2111342E0223220E02151123111000212000190133152311045E2A5A8C61608B5A2BCB01 +24011901180122FAFA038B79AB6D32326DAB79FC7503A401270127FED7FEDBFECEAFFE3D +0000FFFF00B2FFE3052905D512060038000000010064000005D505D5001A00002901110E +0123222E023D013315141E02323E02351133113305D5FE3B41BA768AD4924BCB2B5A8BC0 +8B5B2BCBFA01F3484A4893DD96B19879AB6D32326CAB7A020DFADA00000000010096FFE3 +050E05EE003D00001333141E0233323E0235342E02272E0527263E02321E0215232E0323 +220E02171E03171E0315140E02202E0296CB356087525088643836618954437F715F4628 +02024987BFEAC38D4ECB03274E7A564F784F240404264B715088D5944E5296D5FEFCD295 +5001C344745430204469493A553D280D0A1E2D3F58734B5A9A72403D6C91542C54422927 +465F37324935250D174269996E66A4733D4B82AF0000000100B20000052905F200150000 +3311100021200011152335342E0223220E021511B20122011601170128CB2C5C8C60628A +582903A401250129FEDDFED5392079AB6D32326DAB79FC750000000200A0FFE3056705F1 +00310040000001140E02202E023533141E0233323E02353426232135332E0335343E0232 +1E0215140E02071E0301323E02353426200615141E0205674D99E6FECEE5984CCB2B609B +71709B5E2ACAC9FDB0E0203728174A88BFEAC0894C2540542E44755732FD9B51774D259F +FECAA018447C01BC67AD7E474A82AF65417257323256703E807FAF11323F4A295F956736 +3566935D3B68533C0F113F5B76016727435C357673747B26554A30000000000100BC0000 +044D05D5000700000111211521112311018702C6FD3ACB05D5FE8BAFFC4F05D500000003 +00780000060405D50005000F00270000010E01101617333E03342E0227032E03103E0237 +3533151E03100E0207152302D7C1D1D1C1CB5D9568383868955DCB87E0A05858A0E087CB +87E0A15A5AA1E087CB048E07D9FE76D807043B6B98C4996C3B03FC10065496D5010CD698 +54059F9F055498D6FEF4D59854059D000000000200320000056E05F2001B002B00000122 +2E02271121152115233523353311100021321E02140E0200141E02323E02342E02220E01 +032A3E6E5E4B1A0398FC68CBBEBE012201178BD9944D4D94D8FE0624568DD28E55252657 +8ED08C5601D71525341EFEE6AF9A9AAF0265012401204E8BC2E8C18A4D025A9C815C3232 +5B819E85613637610000FFFF0073FFE305D905F01206003200000003006EFFE605F805D5 +001F00290033000000140E010420242E013D013315141E021711222E023534363B011132 +1E012511220E02141E0200342E0223113E0205F864BAFEF9FEBAFEFAB762CB3C6B945777 +AF7338EDEFC085E0A2FD2E4F663C17173C6602AC3869955C5A95690284FEC98C4B4D8EC8 +7C272756875D340402E62A5070479992FE514B8DD8010B1021334632200FFD8AAA896033 +FD1D04345F000001007503EF01870614000E000001222726343736331522061514163301 +8773504F4F50734058584003EF5050E64F507B583F4058000000000100B203FE01D705D5 +000500000133150323130104D3A4815205D598FEC1013F0000000001000004F501DF066D +00030000013301230118C7FEBA99066DFE880001000504F102D9072500150000010E0315 +23343E02373E033533140E02016F3A5A3D1F7A335E8653425B391A7A2E5A8805C3052439 +4828407B623F060425394828477B5E3B0000FFFFFFFF04F001DE066610070043FF550000 +00000001000804E8033506ED002900001323263E0233321E0215140E0223222E0227331E +0333323E0235342E0227260E02756D01477EAE663F7A603C203D5B3C274F433107860313 +1B1E0E1A26190C1C3042264A85653B04F079BE824422466E4C30523E23132B47330F1812 +09131E2512273C2A1803062E63930001005A04F103B20614000500001311331521155A8C +02CC04F10123A97A0000000100AEFFE407110460002A0000250E0123222E023511331114 +1E0233323E0235113311141633323E023511331123350E0123222603A245BF8357885D31 +B91C3A563B4772512BB971784672502BB9B93FB0797CA8D67E743F78B07102A4FD4E5171 +4820315C8352027AFD62A39B325D8251027AFBA0AE69617A0000000100BAFE560464047B +00160000013510232206151121152111231133153E013332161D0103ACF895AC02F1FD0F +B9B942B276C2C501C2DC013DBEA4FE27A0FE56060AAE6663EEE9E200000000020071FE56 +052F047B00100028000000141E02323E0235342E02220E010123110E0123222E02103E02 +33321617353311331523012F2B51749275512B2C51749274510300B83AB07D66A8794343 +79A8667DB03AB8D5D50294CAA1703C3C70A16564A1713C3C70FB2102526461559BD90106 +D99B556164AAFC40A000000100BAFE56053A047B00160000211123111023220615112311 +33153E01333216151133150465B9F895ACB9B942B276C2C6D5FE560448013DBEA4FD8704 +60AE6663EEE9FDFCA000000100AEFFE304620614001A0000131133112115211114171633 +3237363D01331123350E0123222726AEB802FCFD043E3D7D985456B8B843B076C1646401 +BA045AFE4CA0FE019F504F5F62A1EBFD30AC6762787700020071FE56052F047B0015002D +000001331521110E0123222E02103E0233321617353301141E0233323E0435342E022322 +0E04045AD5FE733AB07D66A879434379A8667DB03AB8FCD5214978573C5D46301E0D214B +77573C5D45301E0DFEF6A002526461559BD90106D99B556164AAFDCF4E9C7B4D25415561 +66304E9B7C4D2640566066000000000100BA000003EC0614000900002901113311211521 +11210397FD23B8027AFD8602250614FE4CA0FCE00000000100BAFE560464047B00140000 +21111023220615112115211133153E01333216151103ACF895AC02F1FC56B942B276C2C5 +029E013DBEA4FC7DA0060AAE6663EEE9FD5C000200BAFE5605E8047B002C003C00000121 +342E0223220E021511231133153E0333321E04153315230E0323222E02343E020521220E +0215141E0233323E02036601314876974F528C673BB9B92A636D763D44887E6D512E9296 +1260819446487F6038375A730166FED61A322818172B402919515144022F64A1703D295A +8E65FBEB060AAA3C4C2C11274868839B57A071A1693124496E946F4925A00F2133241D31 +2615173D690000020071FFE3052F06140010002600000121220E0215141E0233323E0235 +1311140E02222E0235343E023321113311331503A2FEC64974512B2B51744951764D26B8 +427FBAF2BB7F42407FBB7B013CB8D503C03566956165A1703C3C71A0650191FE3972C390 +51569BD88371CB9A5B01B4FE4CA0000100BAFE5604640614001200000111231110232206 +1511231133113E013332160464B8F895ACB9B942B276C2C502A4FD5C029E013DBEA4FBDD +07BEFD9E6663EE000000000100BAFE56026C0460000500000133152111330172FAFE4EB8 +FEF6A0060A00000100BAFE56071D0614002A00000115141633323E023511331123350E01 +23222E0235111023220E021511231133113E03333216044871784672502BB9B93FB0793D +816B44F74B6E4824B9B9134157683BC3C402A4E2A39B325D8251027AFBA0AE69612965A9 +810102013D325C8351FBDD07BEFD9E27493722EC000000020071FFE3047406140018002D +000001133307052115231E0115140E02222E0235343E023703141E0233323E0235342E02 +2723220E020106C2D4A4014201377D433D4483C0F6C082443E6F9A5BE1285177504F7850 +28122B4734874E785129050A010AE2D2A04ED7847ACE97555597CE7A78C6945E10FDC053 +956F41416F95534D7763562C45759A000000000100AEFE56045806140013000013113311 +10333237363511331123110E01232226AEB8F8955657B8B843B076C2C701BA045AFBADFE +C25F5EA5027BF9F602566762EF00FFFF00BA0000046406141206004B00000002006AFFE2 +04300614002D0042000001150E01071612151123350E0323222E02373E03372E0335343F +0133070E0107061E02373E0101141E0233323E023D01342E02270E03033C1A341AA9B3B8 +1B485C7043629C6A3703023E6D9A5E445D3B1A2224C72A130F01022E4757281B39FE0A21 +4365444D78522B1E4570513D6C5230052CB110201264FEE5B9FDFFAC2D4A361D4278AA69 +60B7ADA34B0A344753294435393F1D3B172D3F2202111222FCA04573532D375D7B445635 +777568263275879A0000000100BAFE560539047B00140000011521111023220615112311 +33153E0133321615110539FE73F895ACB9B942B276C2C5FEF6A00448013DBEA4FD870460 +AE6663EEE9FC520000000002008CFFE3045A06240028003E00001335333E033332161715 +2623220E020733321E02151123350E0323222E0227261237131E0333323E023D01342E02 +2B010E038CC4378DA7C16B192D163432477F72622A0377D19C5AB818465C744566976635 +0305363856062D4961384673532E2A629F75471D2A1B0A03B6AA60A57A450604BD1D2A4B +683D4C8AC277FDAFAE2C4A361F41729B5A83011D8BFDFD5875461D335B7C496840866D45 +4187847E0000000100AEFFE3052D061400160000131133111033323E0235112115231123 +350E01232226AEB8F84B77532D018DD5B843B076C3C601BA02A6FD61FEC2325C8351042F +A0FA8CAC6762EE0000000001FFD4FE5601720460000B000013331114062B013533323635 +BAB8A3B54631694C0460FB8CD6C09C6199000001FFD9FFE3045806140016000003211110 +33323E023511331123350E0123222635112327018DF84B77532DB8B843B076C3C6D50614 +FBADFEC2325C8351027BFBA0AC6762EEE903BA00000000010000FE56037B047B00310000 +17141E0233211521222E0235343E0635342E0223220607353E0333321E0215140E06C104 +0A14110250FD742A44301A3C627D837D623C2D5273475B9A4E264A4F593573B881453C62 +7E827E623CD908110F09A01B2F3F24316A71767C7F83854348694421262AAE0E170F0835 +6CA26D4B9590877C6F5E4A000000FFFF00BA00000464047B1206005100000001000AFE56 +02F604600031000013211521222E0235343E04372E0335343E0237330E0315141E02373E +0137070E0315141E02FC01FAFDD624463721233C53606A352F6E5F3F0B203B30E73B4D2C +11263F522D1942230166A97942040B11FEF6A0162E462F3A878E90897D330220446B4C1E +3F464D2C2548433E1A3044280E05031512B759CBC8B8450C1A140D000000000100AEFE56 +07110460002A0000250E0123222E0235113311141E0233323E0235113311141633323E02 +3511331123110E0123222603A245BF8357885D31B91C3A563B4772512BB971784672502B +B9B93FB0797CA8D67E743F78B07102A4FD4E51714820315C8352027AFD62A39B325D8251 +027AF9F6025869617A000002006EFE5603F4047C002C0044000001140E06151416332115 +21222E02353436373E03272E0535343E0233321E0225220E0215141E041514073E033534 +2E0203F43C627E837E623C1F150250FD742643311D52481923140406082A363B31204978 +9A506EB07B42FE252B56442A1F2E372E1F323C826C45284B6A02D24E9991897D6E5D4919 +171AA0192B392042904919444C4F242F524B484D54324F8660363A6E9EAA1A344D332142 +44484C532C514F3A7E848A48456948240000000100BA000004E0047B0024000001220E02 +1511231133153E0133321E0215140E0207211521353E0335342E0202C14F7C562DB9B93F +BD7963A2743F213D56350129FDFD3F60422223476C03DB345A7A46FD730460AC6166467D +AE67538B7B7139A084346D798A5049816138FFFF00AEFFE30458047B1206005800000001 +00AEFE56052D061400160000131133111033323E0235113311331521110E01232226AEB8 +F84B77532DB8D5FE7343B076C3C601BA02A6FD61FEC2325C8351042FF8E2A002566762EE +0000000100AEFFE3071C047B00270000011133153E03333216151123111023220E021511 +23350E01232226351133111033323E02038FB8214F575B2CC3C4B8F83C6B502EB843B164 +C3C6B8F83F6F533001E5027BAE334B3219ECEBFD5C029E013D325C8351FD87AC6762EEE9 +02A6FD61FEC22F5B8400000100BAFE560464047B00140000011123111023220E02151123 +1133153E013332160464B8F84B77532CB9B942B276C3C402A4FD5C029E013D325C8351FB +DD060AAE6762EC000000FFFF006FFE560458047B1006004AFE00000100BA000003980460 +0005000025211521113301720226FD22B8A0A0046000000100AEFE56071C061400270000 +011133113E03333216151123111023220E02151123110E01232226351133111033323E02 +038FB8214F575B2CC3C4B8F8396A5131B843B164C3C6B8F83F6F533001E5042FFD9E334B +3219ECEBFD5C029E013D2F5A8455FBDD02566762EEE902A6FD61FEC22F5B840000000002 +0029FE5604A4047B001B002C000025112115211523352335331133153E0133321E02100E +0223222601342E02220E02141E02323E0201730315FCEBB99191B93AB07C66A87A43437A +A8667CB002382B51749274512B2B51749275502BA8FEA89B5F5F9B0510AA6461569BD8FE +FAD89B566101EB64A1703D3C70A1CAA1703C3C71A000FFFF006FFFE30473047B10060052 +FE0000030046FE56062106140027002E0038000000100E0223112311222E043D01331514 +1E023311222E0235343E023B0111321E012511220615141600342E022311323E01062167 +B4F38BB857A491785630B45083A95A629C6F3B3B6F9C62B88EF3B2FD157D747B030A457D +B06A6AB07D02C7FEF8D69752FE5601AA2749678095535D5657987242031A2B4F6E434870 +4D27FE4C4689CF01264F48424DFD80C08F5F2FFCE6396B000000000100AEFFE3067E0614 +0016000025211521350E01232226351133111033323E0237113304580226FD2243B076C3 +C6B8F84975542E02B8A0A0AC6762EEE9045AFBADFEC22F587D4D028C0000000200F00000 +01C303520003000700003733152311331523F0D3D3D3D3FEFE0352FE00000001006401B2 +027F0283000600001304251506242764010F010C88FEF58802835A5AA42D012C00000002 +0244FE4302DAFFD300030007001CB4040305010910DC3CCC323100B60602000402000810 +DCDCDC493A3005331523153315230244969696962D9664960000FFFF00ABFE430382FFD3 +1027051000A8000011070516FEE400000013B0104B5258BB000000040004004038103C31 +5900000300FFFE4303A2FFD300030007000B0025B60003080409050D10DC3CDC3CDCCC31 +0040090A060408060304010C10DC3CDC3CDC493A3005352115373315231533152300FF01 +907D96969696C396969696649600000300FFFE4303A2FFD300030007000F002E40090C0B +080F040005011110DC3CDC3CDCDCDCCC3100400B070200050A02080B000D1010DC3CDC3C +3CCCDC493A3005331523153315232715233523352115030C96969696FA967D01902D9664 +96FAC8C8969600010244FEBB02DAFF5100030010B502000400010510DCCC310010DCCC30 +0533152302449696AF96FFFF01C7FEBB0357FF51102605147D00110605148300000FB200 +0005495358B90000004038593100000301C7FE430357FFD300030007000B004F4009040B +0A0500010B0A0D10D4CCDCCCDC493A31B2000805495358410C000B000A00090008004000 +040007000600050004FFC000041738173859004009030B08000B0708040C10DC3CDC3CDC +493A30013315230333152337331523024496967D9696FA9696FED9960190969696000001 +0163FEBB03BBFF5100030010B502000400020510DCCC310010DCCC300521152101630258 +FDA8AF96000000010163FE7503BBFFA100070019B4050200060910DCDCDCCC3100B40100 +03050810DCDC3CCC30051523352335211502DA96E10258F5969696960000000100000500 +0096059600030010B501030401030410D4CC310010DCCC3011331523969605969600FFFF +0000050000960596100605190000FFFF012FFE1B03B9FFDD1027051400DFFF6010260514 +E5F611070514FEEB008C003DB2000A04495358B9000AFFC03859B300040A0510493A3100 +B200090C495358B90009FFC03859B2000409495358B90004FFC03859B30004090410493A +30000001024E01E502E4027B00030010B501030403010510D4CC310010DCCC3001331523 +024E9696027B9600000000010244FE4302DAFFD300030010B502000400020510DCCC3100 +10DCCC3005331123024496962DFE700000000001006403C6027F046A0003000013211521 +64021BFDE5046AA4000000010163050003BB059600030010B501030403010510D4CC3100 +10DCCC300121152101630258FDA805969600000100D1FF38018B05280003001CB4050208 +000410D4ECCC3100B201020410CCCC30B44005500502015D13331123D1BABA0528FA1000 +000000010519050005AF059600030010B501030401030410D4CC310010DCCC3001331523 +05199696059696000000000100C50500015B059600030010B501030401030410D4CC3100 +10DCCC3013331523C59696059696000200D10000018B0460000300070023B60902060800 +040810D432EC32CC3100B40301BC05072FCCF4CC30B44009500902015D13331523113315 +23D1BABABABA0460CAFD34CA000000010066000002DC0460000D001DB60F050C09080D0E +10D4ECD4CCCC3100B605A904BC0AA90C2FECF4EC301310363B0115232206151121152166 +CDE39294866E01BCFD8A025E010EF48F96C2FE168F0000010163FE4303BBFFD300070019 +B4050200060910DCDCDCCC3100B4010003050810DCDC3CCC30051523352335211502DA96 +E10258C3FAFA96960000000100BA0000049F04600027006FB729461F081E010802B71608 +150A080B462810FCECD4FCDCB77F027F026F025F02B23F02055DECD44BB01D5358B9001E +FFC03859FCFCB74A033A0329037A03B24403055D4BB00A5158B90029FFC038593100B41F +16BC0A012F2FFCC4B73A003A03551D5B09B665176A037909075D30090123010E04151123 +35343E05370133013E0435113315140E050380011DD9FE601C2338211AB8141F322D412C +1FFEE4D901A01B2338221AB8141F322E402C019BFE6502580E1535416E45FEF4B9518A60 +51322C150D019BFDA80D1536416E45010CB952896151312C150000010058000004480460 +00150036B7171311080008151610DCD4DCFCDCDCB4740A6F07025D314BB00A5158B90017 +FFC0385900B708A909BC11A915A9B0142FECECFCEC302511342E0323213521321E031511 +3315213502E80B26457957FEB6014A7BB174451BA6FC108F01CF4B63663A258F2E558BA6 +72FE558F8F0000010058FFF603110460001F003FB7210308040008161BB10D2010DCD4D4 +ECDCECCCB4741D5C10025D4BB00F514BB00D535A587DB0062F18593100B71BA91CBC2010 +A90BB0032F2FEC10FCEC30011412172326270E03232227351633323E0335342E012B0135 +333216027F454DC73A1921465050372E3322242C4556382833655C5A60DDCB025E90FEC0 +8E8770516736130EA90A0D345BAA7597A13B8FF400000001005800000417046000070028 +B60900010804050810DCDCFCDCDC314BB00A5158B90009FFC0385900B50104A906BC032F +FCFCC43001231123112135210417C6BAFDC103BF03D1FC2F03D18F000000000200BA0000 +048004600003000F003CB711460408070B0108B202461010FCECD4D4FCFCB4700D6A0A02 +5D4BB00A5158B90011FFC038593100B70BA90CBC02000302B0062F2FD4C410FCFC300111 +23110511231134262321352120160188B903B1B981BAFE2E01D00115E102ACFD54026026 +FDC60260D8998FFA0000000100BA00000174046000030021B60546010802460410FCFCFC +4BB00A5158B90005FFC038593100B203BC012FE430011123110174BA0460FBA004600001 +00580000026D0460000D0045B60F010508080D0E10DCDCFCDCDC314BB00E534BB010515A +587CB0022F18B36A035A03B46A0B5A0B045D31594BB00A5158B9000FFFC0385900B5020C +A90DBC072FFCFCC4300115232206151123113436372335026D505741BA4D38F804608F9B +BDFD8702797FB2278F00000100BA000004800460000D0037B70F46010802080809B1460E +10FCFCDCFCFCB2700B015D4BB00A5158B9000FFFC038593100B507A90ABC08022F2FFCEC +B27400015D003001112311342623211123112120160480BA82B8FEE7B901D00114E2025E +FDA20279C692FC2F0460F0000000000100B9FFE304BF046B001F005BB72119080A110108 +1EB1462010FCFCDCDCB7741470147F0A2F0AB2A00A055DFCDC314BB00A5158B90021FFC0 +385900B70FA9141FBC2005A9B01C2FEC10FCC4ECB56C0A6E196E1EB55C0A5E195E1EB744 +0A4401340A3401B00A5D300111141E02323E0235342E02232207353633321E0215100220 +02190101722D5974A074592D13305A43425D725766905425FCFDF2FC0460FDB474A25B28 +285BA274719D7D3D1E8F1E519FD28DFED4FEF3010D012C0244000001008801A201420460 +00030023B4050108020410DCFCDC314BB00A5158B90005FFC0385900B4010200BC0410E4 +2FC430011107110142BA0460FD8E4C02BE0000010058FE560392046000100038B6124600 +08030A1110DCDCFCFCB4610C5F09025D4BB00A5158B90012FFC038593100B60AA90BBC01 +BD1110ECFCECB65F034F033F03035D3001112311342E0323213521321E020392BA153258 +7F5DFEFB0101A8D6863501EEFC680398729B75421F8F3D97E80000010058000003CA0460 +00190035B61B0708130E191A10DCD4DCFCDCB74F0F4F183F0F3F18B0045D314BB00A5158 +B9001BFFC0385900B619A900BC0EA90D2FECFCEC301321321E03140E0323213521323E02 +342E02232158015875BB785022225078BB75FEA80158648D4E22224E8D64FEA80460406B +929DAC9D926B408E3E7591BC91753E00000000010058000003F005D500080036B70A0708 +0108080005B208020910DCFCDCFCD4B27F01015DFCDC314BB00A5158B9000AFFC0385900 +B60702A90405BC002FFCCCFCC43021012111331105070101810194FD43BB02DD03FE5703 +D10204FE8B016EFC0F00000200BA0000049504600008000F0034B7114600080B0A0801B1 +461010FCFCDCFCFC314BB00A5158B90011FFC0385900B609A902BC0AA9012FECF4ECB46F +0C7E0C025D3029011121321E0215011121113426230495FC2501E38DC07734FCDF026782 +B904603A81BD8A0173FCBE01EAC69200000000010058000004B5047000250064B727460C +08110E1B08B41C0108002610DCECD4B2701C015DFCDCDCFCFCB74A233A232A231C23B768 +235A234A046804B473040C23085D4BB00A5158B90027FFC038593100B714A90A00BC0FA9 +0EB01B2F2FECFCD4FCB758046C115C117604B27F11055D3013331E01173E043320190121 +3521113426232207060706070323133E0435340258D90442161C4D4C624C330192FDCE01 +787A61B456302A020562BA50030F0507035D04600B9D4E4664361E08FDEEFDA28E01D0C3 +BBA05BCE0E17FE1201A210491B332716400107000000000100BAFE560174046000030024 +B60546010802460410FCFCFC4BB00A5158B90005FFC038593100B402BD03BC0410E4E430 +011123110174BA0460F9F6060A0000010058000002780460000D0031B70F460B0800050D +0E10DCD4DCFCFC4BB00A5158B9000FFFC038593100B605A906BC0DA90C2FECFCECB4100A +000A025D30251134262B01353332161511213501BE646A7E7ECDBBFDE08F02587B6F8FB1 +DDFD2E8F0000000200B9FFE304BF046000090013005DB71504080F0C080946B01410FCFC +DCB27F0F015DFCDCB64A0A3C0A6001035D31B75907540669076406B0045D4BB00A5158B9 +0015FFC0385900B70AA900BC140EA9062FEC10FCFCB7340F340C260F260CB7420F420C52 +036403B0085D3013212004111000200011012111102011342E02B901E7011D0102FF00FD +FAFF0001E7FED202942D5F7F0460FAFED4FED0FED901270130018DFE6BFE4901BF759C58 +240000010058FF42044804600013004BB715460208010F080CB10E1410DCC4FCDCFCFCB6 +3E0D1D0D0F0D035D314BB00A5158B90015FFC0385900B710A90BA90C01BC0EB1BC1510E4 +E4D4ECE4B414100510025D30B4670D470D025D01113311140E04070535250133013E0112 +0388C013315888C282FE800124FED4C201148490460344011CFEE476B2AF887F6A2F8BA9 +68040DFC323996010800000100BAFE56046404600017005EB719460008030D0808B21346 +1810FCFCD4DCFCFC314BB00A5158B90019FFC0385900B710A90B07A914BC02B1BD1810EC +FCFCDCB74A131F0B0F0B2F0BB0045D4BB017504BB012535A58B9000B00403859FCB76A03 +5C034A037F04B0045D300111231134262B01151416333237150623222635112120160464 +BA91A9FD474A29435243869B01B60110E4025EFBF8040AD0A1F049450D981094B00164F5 +0000000100BA0000048E0460001C006AB71E03080C16100807B200461D10FCC4FCD4DCB4 +5F0C3F0C025DFCDCB73D0E3D0A4B0E4B0AB0045D314BB00A5158B9001EFFC0385900B719 +A91410A900BC09B1A9062FECFCFC7DDCB25F14017118B6701450144A1C035D4BB019504B +B012535A58B9001400403859FC301321200010002901352132363534262B010706163332 +37150623222635BA01AA01020128FED7FEFFFE56015EECD1D8E4A50101484A2943524386 +9B0460FEDCFDE8FEDC8EC0EEDBBADE49450D981094B000010058FE5603F9046300160047 +B718080807030E0811B30008161710DCFCD4FCC4DCB27F07015DFCDCB736034403540305 +03B0045D314BB00A5158B90018FFC0385900B50700BC10BD1710ECFCC4B23703015D3001 +1716173E013D013315140E0207112311342E01270301259F663A7568B83D688048BA393D +31D30463E492992EC39784846FBA7E5312FC86032650AB6A490139000000000100580000 +04050460001A006CB71C46170D080C0208B21A011B10DCC4FCDCB00C4B51B0104B535A58 +B9000C004038B10C002F1059FCC4FCB73C163B033F004803B76C0059005F004A16B66916 +7C003D00095D314BB00A5158B9001CFFC0385900B50C01BC1AA9192FECFCC4B754163803 +54037403B0045D302501330136373E0435113315140E05070115213502F4FD64DA018304 +07181A2C1813B8121A2E263D241E0103FC538E03D2FDC90305111634375A37010CB94A7D +584D2E2F1510FE8E478E000200BAFE560511045F0003000A0052B007B70C0808050A0804 +01B4080206460B10FCD4FCDCFCD4B74F055F056F057F05B0045DFCCC314BB00A5158B900 +0CFFC0385900B7020106A907BC00BDB0042FECFCFCDCC4B2AF01015DB440095009027130 +0111071109012135211501018CBA01D30194FC810457FE57FE5604564CFBF601AA03D18E +6EFC0F00000000010058000003CA046000110040B613460108020A1210DCDCFCEC31B00E +4B54B00F4B545B58B00C2F31594BB00A5158B90013FFC0385900B40AA90BBC022FFCEC30 +4BB0105058B103002F2F305901112311342E0323213521321E0303CABA1533507E52FEB0 +014F7EBF7A4D1F023AFDC6023A49766946298F355D8DA100000000010058000005530460 +002800A2B72A20081F0F080E03B60802160801022910DCD4FC10FCDCB4100E000E025DFC +DCB748063B062B06101FB7001F500060007000B0085DFCDC4BB00B5058BB0016FFC00001 +FFC0383831B104152F7D2F1859B72000300057155704B0045D31B76613670477047613B2 +8704055D4BB00A5158B9002AFFC0385900B715A9041F0E02BC16B1A9002FECFCC4C4DCB7 +AF041F042F049F04B0045DECB6431D43224825035D3021230333133E0837330207060706 +0713323E0637330E0701AEBC9AB949304B38281D110F080D06A51218296C5FA72B629792 +6B634538220AB9122A3D4B6981ABCB0460FDF1010F212340355D45792BFEFC67AB453D06 +FECD0F28436992C5FA9DA6FCE4A389573D1A00010014FFF804880460001E0049B7204601 +08030A0817B30818111F10DCD4FCFCDCFCFC31B4D019D018025D4BB00A5158B90020FFC0 +385900B713A90E010AA918A9B219BC012FFCECFC10D4FCB67F036A035B03035D30011123 +11342E032B0111140623222735163332363511233521321E020488BA0A24407050F67B98 +354E4126472EAD025D8FC16C2C025EFDA202604A63653A25FDCDD6D0108F0E72A302338F +438AB4000000FFFF00BA0000030A04601027052B019600001006052B0000FFFF00880000 +02A804601027052B013400001006052F0000FFFF008801A2027F04601027052F013D0000 +1006052F0000000100BA02E40299046000030014400902B400BC040344010410D4EC3100 +10F4EC300133012301D2C7FEBA990460FE84000200BA02E4046E046000030007001D400E +0307B40105BC080344010744050810D4ECDCEC310010F43CEC3230013301230333012303 +A7C7FEBA99BDC7FEBA990460FE84017CFE8400020000FFD704DC07220025003000000116 +171615233427262733161716373637363533141716171637363533140706232227060706 +250901050727012301233502B10F0A1472171F627B1E190D1D2E050471090E1D26090871 +182D61541520211DFDF602040100011B197DFEC142FDAE73061631326AFFEE7BA2CD4045 +230407241C5D7D0D150101181871AB213C221C070569FA8D02C562502DFC8D0646600002 +0000FFD704DC0729002A0035000001150607061514171617152207061514171617323736 +371506070623222726353437363726272635263736050901050727012301233503A84D40 +563A31313855461E252F3E3750362E4E403F5E473F4429372D1C23018E46FD9602040100 +011B197DFEC142FDAE7307295E0A1D262314201B025C2A24402E2229010D13186E14100D +463E5D4B482A0E121A21235448239DFA8D02C562502DFC8D064660000000000400850000 +057A051400030007000B000F00002533152325331523013315232533012304C6B4B4FE3E +B4B4FD8FB4B40294A0FD5EA0FAFAFAFA0514FAFAFAEC000500850000073C051400030007 +000B000F0013000025331523253315232533152301331523253301230688B4B4FE3EB4B4 +FE3EB4B4FD8FB4B40294A0FD5EA0FAFAFAFAFAFA0514FAFAFAEC000100DB0000020001EC +0005000021233513330301AED3A48152AC0140FEC000000200FC04FD030506F1000A001B +00000133323736353427260706172B01353311331136373617161514070601CD374E1635 +1E01473418AF70516624683F4641403605620D21152010011C15A865018FFEBF4928181A +19525B322A00000200DB00000200051100050009000001233513330B0133152301AED3A4 +8152D3D3D30325AC0140FEC0FD2DFE00000000020093000003B005F00003002400002515 +233537353426272E012F012E0135343633321617152E012322061514161F011E011D0102 +BCCB06060608272F585A48DFB867C15E61B34F6C8333395A5A38FEFEFE937B343C151935 +2F5656894C9FC23839BC43466E59315E35595682659A000100A30055031E03DE00220000 +3735363736372627263534373637363332171526070607061714171637363715060706A3 +2F5344348E3335151E6763626E5A644633316001C8393A483A5ACDE755B006191421184C +4F54414B763F3D16B91F02011A307073320E0F1323B93C505A00FFFFFFB5000002850783 +1027057BFF1D01C2100605540000FFFF006C000001C307FD1027057CFF1D018610060554 +0000FFFFFFABFE0C034004B51027057CFFC2FE3E100605700000FFFF006CFE0C01C30614 +1027057DFF1D0000100605540000FFFF0082FEF305C004B51027057CFFF4FE3E10060571 +0000000100C10000017906140003000013331123C1B8B80614F9EC000000FFFF0082FEA2 +06EB029D1026058E0000100717210339FEA2FFFF008BFFC603A0041A1026056F00001007 +172200FA0384FFFF0082FFEC06EB03201026058E00001007172202BC028AFFFF0082FFEC +06EB041A1026058E00001007172302BC028AFFFF009DFE0C052803661026055A00001007 +1721030700190001009DFE0C05280366001E000013243320171520070611141716213237 +1506232027263510373637220706079D0114C30124C4FEDAD7E04A7F014BC1D496FAFE5E +A983D46089659F7E6803273F369AA7AEFEFB8760A476B863C296E00102DF6534130F2D00 +0000FFFF009DFE0C052804B01026055A000010071721023F041A0001007DFFDA031B0352 +0019000025363736353427262733161716151407060506232227351633320187AC23083C +42ADE37142522050FEFA2E2D66677354219731701B2A4E7481925B7C9869634BC2290726 +B82AFFFF007DFFDA031B04B01026055C0000100717210145041A0001FFABFE0C03620226 +0011000025363534273316151407020504213520373602A30A35B832082EFEDDFEE4FEBE +0130CBDA9E3A487E887684523EFEA29B97B8808A0000FFFFFFABFE0C036203B61026055E +0000100717210271032000010082FE0C091A02EE003F0000250607060706232627241134 +3733061716171633323736373627262F0133171617163332373635331417163332190133 +1114070607062322272627060706070604FC185485C15078806DFEED69B86C0101935F51 +625F795E4001011040B824101C3B73522C25B813406E8EB85C4B66252049308A11315F32 +46842CB36BAA3E1A011C470148F6B4CEDCB3261825309E6C8E7D3DEA9C4A3C817A67C2CD +32A901180126FEAAC7715C180919467B9F1E0F030600FFFF0082FE0C091A04B010260560 +00001007172304E2032000020082FE0C091302E50032003F000005060706232627241134 +373306171617163332373637363534273306171617363736373617161716151407062901 +222726351401220706073332373627262726049058EE5078806DFEED69B86C0101935F51 +665BA22B2127AB010E0A28737B7E814F517D61BAB8CAFEE4FEEC26342D02A14C7EA891BB +ED81BB01028925F29B4D1A011C470148F6B4CEDCB3261825448A6C7F938A0F372832926C +6E362201022547E9A96D781E1A10BA02A9516CC23F5B46871305FFFF0082FE0C091303B6 +1026056200001007172104FB032000020090000006DC0614000C001F0000253332373627 +26272623220706132901352111331112253633321716151407060341BBED81BB01028925 +30507AB175FDC1FE91016FB8D901145C447866BAB8CAB83F5D448713055178FE92B8055C +FB0E013F63212745EBA96D78000000><000100AF +FFEC064905AE001F00000121152117161716172115212224023534122433211521060706 +070607211521015704F2FB43114A8C8A9302B9FD47C0FE9DBEBE0163C002B9FD47938A8B +4B0E0C04C6FB0E0273A0228B4E4C019FC60160BBB90160C89F014D4F8A1B1AA000010058 +FFEC07A805AE002100000121152106070604232135213637363736372135212627262726 +272135213204171605EC01BCFE460F4D5EFE9CC0FD4802B9938A8A4B4108FB0C04F20740 +4A8B8A93FD4702B8C001645E480327A0948FB0C89F014D4F8A785DA04A788B4E4C019FC6 +B0850000000100AFFFEC064905AE001B0000012602242721352132041210020423213521 +3624123721112311331105A10789FEEA93FD4702B9C00163BEBEFE9DC0FD4702B9930115 +8B09FBABA0A003274A01039A019FC6FEA0FE8CFEA0C89F019C01025DFEF002C0FEF00000 +000100D9009B04E504670019000001200410042901352132373637211523113315212627 +2623253502930128012AFED6FED8FE4601BAE66B4E1DFD188E8E02E8254672DFFE460467 +F6FE20F68E513A85AC01E6AC913050018E000000000200AFFFEC064906D2001C00200000 +01262726272627213521320412151402042321352136373637363721350121352105A107 +3F4A8C8994FD4702B9C00163BEBEFE9DC0FD4702B994898B4B4009FB0B055BFAA5055B03 +274A788B4E4C019FC6FEA0BBB9FEA0C89F014D4F8A785DA0030BA000000200D9009B04E5 +057D0016001A000001200415140429013525323736372135052627260721352521352102 +930128012AFED6FED8FE4601BAE8694E1DFC8A0376254674DDFE4603E5FC1B03E50467F6 +F1EFF68E01503A858E01913152018E8A8C000000000100D90000061F05C2000B00000121 +11211521112115211121061FFB64049CFABA0546FB64049C0282FE28AA05C2AAFE140000 +0003004AFFDC0489041C0013001B00230000013217371707161514002322270727372635 +3400052623220615141F01163332363534270268BA8F7563766EFEC4DDB88D7663756F01 +3C01C06480A2E94763637EA3E9450417717663768DBADDFEC46F7663768DBADF013CD548 +E9A580636247E9A38062000000010072014C0452038C00070000011101350511011502A2 +FDD001B00230027AFED2014AC2FA012EFEB6C200000200920000048204C4000400090000 +331109011125211109019201F801F8FCB602A4FEAEFEAE02A00224FDDCFD60AA01D50179 +FE870000000101A303DA050F05DC000700000901270133010701032DFEEE78018A5A0188 +78FEF004EAFEF078018AFE7678011000000101A30000050F020200070000253301170123 +0137032D5A011078FE785AFE7678F2011078FE76018A7800FFFF01A30000050F033F1026 +1774000010070D8D0000FC26FFFF01A30000050F041B10271774000000DC1026177400D7 +10070D8D0000FC260001013BFFC502AD064E001900000117061417161407061417161407 +27363427263437363427263401B77A4C4C7C7C4C4C7C7C7A4C4C7C7C4C4C7C064E764F70 +5081F881506F5081F981764F705081F88150705081F80000000100B0FEF2025806140005 +0000132115231123B001A8F0B806148FF96D0000000100C7FEF2026F0614000500000111 +23112335026FB8F00614F8DE06938F00000100B0FEF20258061400050000131133113315 +B0B8F0FEF20722F96D8F0000000100C7FEF2026F061400050000012135331133026FFE58 +F0B8FEF28F069300000202F4FF6206130282000300070000013311231335211502F49090 +C8025701BAFDA8029090900000020064FF62038402820003000700000115213505331123 +02BCFDA80290909002829090C8FDA800000202F401F20613051200030007000001352115 +2523113303BC0257FD71909001F29090C80258000002006401F203840512000300070000 +011521352523113302BCFDA8032090900282909038025800000100D9011F05DB035E0005 +000001152111231105DBFBA6A8035EAAFE6B023F000200060102041505120007000F0000 +132405021304251201120304250213248C018101818989FE7FFE7F89FEF1B8B802070208 +B8B8FDF8018A8989018001828A8AFE7EFDF801F4021CB1B1FE0CFDE5B1000000000600F7 +00010709061300030031003B0046004F00590000012111211115140620263534363B0111 +23222635343620161D01213534363332161514062B01113332161514062322263D010135 +342623220614163313232206151416333236350133323634262206151115141632363534 +2623036C0128FED8B9FEFCB8B87FAAAA7FB8B80104B90128B98283B7B780AAAA80B7B783 +82B9FE44624544626245A6A64562624544620250A74462618A62628A61624402760128FE +44AA80B7B88380BA0128BA8182B8B780AAAA80B7B88281BAFED8BA8083B8B780AA0250A7 +4561618A62FDB062444562624402F7628A616145FD09A7446262454462000000000100D9 +011F05DB035E0005000001211133112105DBFAFEA8045A011F023FFE6B000000000100B0 +0367033A061400050000012111231121033AFE0690028A0584FDE302AD00000000010086 +0367031006140005000013352111231186028A90058490FD53021D00000100B0FF70033A +021D00050000211521113311033AFD76909002ADFDE3000000010086FF700310021D0005 +00003321113311218601FA90FD76021DFD530000000101AFFE0003FA076C001900000111 +34371A0133321615140623222726272E012322030215301101AF030CBECA506440372B1C +180F060910681108FE0005082481020301BC5441363F1310260F48FD95FED302FA980000 +0001002AFE1A0275078900190000011114070A0123222635343633321716171E01333213 +123530110275030CBECA506440372B1C180F0609106811080789FAF52481FDFDFE445441 +363F1310260F48026B012D02056B00000003009C01D0089C049A0007000B000F00000901 +27013301070125213529021521046FFE267802525A025078FE28FE8DFD4602BA028A02BC +FD4403A8FE28780252FDAE7801D848AAAA0000000002009C0000089C049A0007000B0000 +1321012115210121252115219C02BA02E40262FD46FD1CFD9E054402BCFD44049AFC10AA +03F0AAAA0005009C00000B4F061400040009000C000F0015000033112109021133090129 +0109012109033309019C07A9030AFCF6F8EB8D0276FD8A04EDFBE6020DFDF3041AFDF302 +DDFD8A02768E0276FD8A0614FCF6FCF60580FB1402760276FDF3FD21020D02DFFD8AFD8A +027402780005009C0000089C061400030008000B000E0013000033112111011133090129 +01090121090333119C0800F8948D0276FD8A04EDFBE6020DFDF3041AFDF302DDFD8A0276 +8E0614F9EC0580FB1402760276FDF3FD21020D02DFFD8AFD8A04EC00002B007800000B14 +05D5000B00170023002F003B00470053005F006B00770083008F009B00A700B300BF00CB +00D700E300EF00FB01070113011F012B01370143014F015B01670173017F018B019701A3 +01AF01BB01C701D301E401F001FC02080000012132151114232122351134171114332132 +3511342321220115142B01223D01343B01321715142B01223D01343B01322515142B0122 +3D01343B01320515142B01223D01343B01321715142B01223D01343B01320515142B0122 +3D01343B01321715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01321715142B01223D01343B01320515142B01223D01343B01322515142B0122 +3D01343B01321715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01321715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01321715142B01223D01343B01322715142B01223D01343B01320715142B0122 +3D01343B01320715142B01223D01343B01320715142B01223D01343B01320715142B0122 +3D01343B01320715142B01223D01343B01320715142B01223D01343B01320715142B0122 +3D01343B01322715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01321715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01321715142B01223D01343B01321715142B01223D01343B01321715142B0122 +3D01343B01320515142B01223D01343B013207321511142B01223D013423223D01343313 +15142B01223D01343B01321715142B01223D01343B013205223D01343321321D01142301 +5508E2DDDDF71EDD934A08E24949F71E4A0103254A25254A25DF254B24244B25014A254A +25254A25FE46254A25254A25DD254A25254A2501B9254A25254A25DD254A25254A25DD25 +4A25254A25DD254A25254A25DD254A25254A25018E25FB2525FB25F843254A25254A25DD +254A25254A25DD254A25254A25DC254A25254A25DD254A25254A25DD254A25254A25DD25 +4A25254A25DD254A25254A2524254A25254A25DD254A25254A25DD254A25254A25DD254A +25254A25DC254A25254A25DD254A25254A25DD254A25254A25DD254A25254A2524254A25 +254A25DD254A25254A25DD254A25254A25DD254A25254A25DC254A25254A25DD254A2525 +4A25DD254A25254A25DD254A25254A25DD254A25254A25011E258B25258B25252525F62A +24252594254A25254A25DF254B24244B25F9A525250404252505D5DDFBE5DDDD041BDDDD +FBE54A4A041B4AFC1C4925254926254A25254A25B74A25254A25254A25254A25254A2525 +4A25254A25254A25254A25254A25254A25254A25254A25254A25254A25254A25254A2525 +4A25B74A25254A25254A25254A25254A25254A25254A25254A25254A25254A25254A2525 +4A25254A25254A25254A25254A25B74A25254A25254A25254A25254A25254A25254A2525 +4A25254A25254A25254A25254A25254A25254A25254A25254A25B44A25254A25254A2525 +4A25254A25254A25254A25254A25254A25254A25254A25254A25254A25254A25254A2525 +4A25254A25254A25254A25254A25DB25FEDE25259520254925FD484A25254A25254A2525 +4A2594254A25254A250000000005000100000AB4061400040009000C000F001500002901 +090121072309013309021109010323090133010AB4F857FCF6030A07A9948DFD8A02768D +FA86020D020DFDF3FDF3D08EFD8A02768E0276030A030A94FD8AFD8A04ECFDF3020DFB14 +020DFDF304ECFD88FD8C027600050096FF46066605FC0005000B000F0013001700000902 +110901031109011101033701071117012701331123010802760276FD8AFD8A7202E802E8 +FD18263901DE3939FE2239FE2272720135FE95016B02D8016BFE95FCE6035C01ADFE53FC +A4FE53054163FEEC63FE5C63FEEC6302FAFDD800FFFF00A60000026E04601006034D0000 +FFFF00BAFE5604A4047B100603550000FFFF0087FFE3062704601006035D0000FFFF0071 +FFE704E404791006034500000001001AFE2E05F500D0000B000001211121152311211123 +352101B802A0019DF5FC10F6019EFED801F8AAFE0801F8AA0002009C000008C505FB000D +0011000001212737011501273721012135210535211505E201CBE9780189FE7778E9FE8D +FD1CFD46026202E202BC049AE978FE775AFE7778E9FC10AAAAAAAA0000020023000006D9 +05D00005000B000025210901210903210901021202D8016CFE94FD28FE94012BFE5201AD +035B01AEFE537202760276FD8AFD1802E802E8FD18FD1800000100B0FDFC03500792000B +00000123351013121333000302110173C3A0BAA6A0FEFC5A7FFDFCEA039701E202300103 +FDF3FE86FDEEFCED000100B0FDFC017307890003000013331123B0C3C30789F673000000 +000100B0FE1403500789000B000001151013121323020302113501737F93CBA0D090A007 +89EAFCA5FE57FE14FE65014501EE02260332EA00000100B0FDFC03500792000B00000135 +10030201331213121115028D7F5AFEFCA0A6BAA0FDFCEA031302120179020EFEFDFDD0FE +1EFC69EA0001028DFDFC035007890004000001112311300350C30789F673098D000100B0 +FE1403500789000B0000013315100302032312131211028DC3A090D0A0CB937F0789EAFC +CDFDDBFE12FEBB019B01EC01A9035B00000100B0FDFC0350076D00050000012311211521 +0173C302A0FE23FDFC0971C3000100B0FDFC017307890003000013331123B0C3C30789F6 +73000000000100B0FE140350078900050000011121152111017301DDFD600789F74EC309 +75000000000100B0FDFC0350076D00050000011121352111028DFE2302A0FDFC08AEC3F6 +8F0000000001028DFDFC0350077A0003000001331123028DC3C3077AF6820000000100B0 +FE140350077A00050000013311213521028DC3FD6001DD077AF69AC3000102A3FDEA0558 +076D000D00000123113437363321152122070615035DBA6F79BA0113FEE7654439FDEA07 +75DF919EB0665799000100A8FDFC035D0786001800000116171619012311102726252735 +332037361901331110070602943A2A65BA6E4BFEFB3D3D01034D6EBA652802C1203D93FE +43FDE8020C01B75F410401BB456301B3020CFDE8FE48983C000102A3FE1405580786000D +00000111141716332115212227263511035D3944650119FEEDB87B6F0786F8949A5666B0 +9E8FE10764000000000102A3FDF4035D078C0003000001231133035DBABAFDF409980000 +000100A8FDEA035D076D000D0000011134272623213521321716151102A3394465FEE701 +13BA796FFDEA077D995766B09E91DFF88B000000000102A3FDFC05580786001800000126 +2726190133111017162133150704070619012311103736036C3C2865BA6E4D01033D3DFE +FB4B6EBA652A02C1213C9801B80218FDF4FE4D6345BB0104415FFE49FDF4021801BD933D +000100A8FE14035D0786000D0000013311140706232135213237363502A3BA6F7BB8FEED +01196544390786F89CE18F9EB066569A000101AFFE0002750789000300000111331101AF +C6FE000989F67700000200370086064005D5000800110000250901112111210321033509 +0135211321030233FE0401FC023701D601FBF464FEF6010A040C01FEF2018601FC01FCFE +EF0268FBC2017283FEF6FEF6830376FD98000000000200BA000006D504C4000200060000 +0121090121112106D5F9E5030D030EF9E5061B02A00224FB3C01F80000040096FF460666 +05FC0005000B001F002B0000090211090103110901110100141716171632373637363427 +2627262207060702103E01201E01100E012026010802760276FD8AFD8A7202E802E8FD18 +FE6E36365C5DDA5D5C363636365C5DDA5D5C36A88AEE0118EE8A8AEEFEE8EE0135FE9501 +6B02D8016BFE95FCE6035C01ADFE53FCA4FE5303C8DA5D5C363636365C5DDA5D5C363636 +365CFEAA0118EE8A8AEEFEE8EE8A8A00FFFF0006009A0621038E10060E88000000030059 +FEF704CF025A000D001900200000002207061514171632373635342F0132161514062322 +263534360111073537331103E9CA32333332CA32333397A1AAAAA1A2AAAAFE56DFE68902 +015656ACAD56565656ADAC56AFDED3D4DEDED4D3DEFCAC02D1297427FCBD00000002FF82 +FFE304A406140017001F0000013E01333200100223222627152311052725353315251705 +001026200610162001733AB17BCC00FFFFCC7BB13AB9FEE9210138B9012321FEBC0272A7 +FEDCA7A7012403B66461FEBCFDF0FEBC6164A804E65D6368C08361616DFC400196E7E7FE +6AE7000000010092FE2E048200D0000700000121113311211133013A02A0A8FC10A8FED8 +01F8FD5E02A2000000030098FFEC069405E8000D001B002600DBBA000E000600032BB800 +0E10BA0023001D00032BB8002310BA0000001400032BB8000010411B0016000E0026000E +0036000E0046000E0056000E0066000E0076000E0086000E0096000E00A6000E00B6000E +00C6000E00D6000E000D5D410500E5000E00F5000E00025D410500EA001400FA00140002 +5D411B001900140029001400390014004900140059001400690014007900140089001400 +99001400A9001400B9001400C9001400D90014000D5D00BA0011000300032BB8001110BA +000A001800032BB8000A10BA0024002500032BB8002410B8001CD0303101100021200011 +34122433320412051000212000113402242322040201331107352533113315210694FE3F +FEC2FEC4FE3FCE0171BEC10171CDFA57018F011C011C018FB6FEB8ADADFEB8B6017CD9EC +0101A1DAFD9702EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1 +B1FEB9FDFF027E2B982FFCE68E00000000030098FFEC069405E8000D001B0038013FBA00 +0E000600032BB8000E10BA0033002600032BB8003310BA0000001400032BB8000010411B +0016000E0026000E0036000E0046000E0056000E0066000E0076000E0086000E0096000E +00A6000E00B6000E00C6000E00D6000E000D5D410500E5000E00F5000E00025D410500EA +001400FA001400025D411B00190014002900140039001400490014005900140069001400 +790014008900140099001400A9001400B9001400C9001400D90014000D5DB8003310B800 +1DD0B8001D2F410500EA002600FA002600025D411B001900260029002600390026004900 +26005900260069002600790026008900260099002600A9002600B9002600C9002600D900 +26000D5DBA002C0006000011123900BA0011000300032BB8001110BA000A001800032BB8 +000A10BA001D001E00032BB8001D10BA0030002900032BB8003010303101100021200011 +34122433320412051000212000113402242322040201211521353624373E013534262322 +0607353E01333216151406070E010694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA57018F +011C011C018FB6FEB8ADADFEB8B6024F01B4FD5C520106213E2F5F4E3B847361913DA3C5 +303E11B202EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1FE +B9FDFF8E814DF1223F55283F4E263AAB241F977D3A694612A700000000030098FFEC0694 +05E8000D001B004401B5BA000E000600032BB8000E10BA0042003500032BB8004210BA00 +00001400032BB8000010411B0016000E0026000E0036000E0046000E0056000E0066000E +0076000E0086000E0096000E00A6000E00B6000E00C6000E00D6000E000D5D410500E500 +0E00F5000E00025D410500EA001400FA001400025D411B00190014002900140039001400 +490014005900140069001400790014008900140099001400A9001400B9001400C9001400 +D90014000D5D410500EA003500FA003500025D411B001900350029003500390035004900 +35005900350069003500790035008900350099003500A9003500B9003500C9003500D900 +35000D5DBA001C00350042111239BA002C00350042111239B8002C2F410500EA002C00FA +002C00025D411B0019002C0029002C0039002C0049002C0059002C0069002C0079002C00 +89002C0099002C00A9002C00B9002C00C9002C00D9002C000D5DB8001FDCBA0026000600 +00111239BA003B0006000011123900BA0011000300032BB8001110BA000A001800032BB8 +000A10BA0029002200032BB8002910BA003F003800032BB8003F10BA0032002F00032BB8 +003210BA001C002F00321112393031011000212000113412243332041205100021200011 +34022423220402051E0115140623222627351E013332363534262B013533323635342623 +220607353E013332161514060694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA57018F011C +011C018FB6FEB8ADADFEB8B603B90D76D8C34088585B7D4475736B638C915A585C5B3479 +6B5F883DA1C16802EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147 +B1B1FEB99603815D8D9C171BA8301C4F4C474E8C3C3A3C3F152097181489735172000000 +00040098FFEC069405E8000D001B001E002900F3BA000E000600032BB8000E10BA002000 +1C00032BB8002010BA0000001400032BB8000010411B0016000E0026000E0036000E0046 +000E0056000E0066000E0076000E0086000E0096000E00A6000E00B6000E00C6000E00D6 +000E000D5D410500E5000E00F5000E00025D410500EA001400FA001400025D411B001900 +14002900140039001400490014005900140069001400790014008900140099001400A900 +1400B9001400C9001400D90014000D5DB8002010B80024D0B8001C10B80026D000BA0011 +000300032BB8001110BA000A001800032BB8000A10BA0022002300032BB8002210B8001D +D0B8002310B80027D0303101100021200011341224333204120510002120001134022423 +22040225012103331133152315233521350694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA +57018F011C011C018FB6FEB8ADADFEB8B602BFFEF3010D18CE8D8DB6FE4302EAFEC1FE41 +01BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1FEB94FFE820248FDB88DD3 +D38E000000030098FFEC069405E8000D001B0039014BBA000E000600032BB8000E10BA00 +1F001C00032BB8001F10BA0026003300032BB8002610BA0000001400032BB8000010411B +0016000E0026000E0036000E0046000E0056000E0066000E0076000E0086000E0096000E +00A6000E00B6000E00C6000E00D6000E000D5D410500E5000E00F5000E00025D410500EA +001400FA001400025D411B00190014002900140039001400490014005900140069001400 +790014008900140099001400A9001400B9001400C9001400D90014000D5DBA002D000600 +00111239410500EA003300FA003300025D411B0019003300290033003900330049003300 +5900330069003300790033008900330099003300A9003300B9003300C9003300D9003300 +0D5D00BA0011000300032BB8001110BA000A001800032BB8000A10BA0030002900032BB8 +003010BA001D001E00032BB8001D10BA0023003600032BB8002310303101100021200011 +341224333204120510002120001134022423220402012115211506363332161514062322 +2627351E01333236353426232206070694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA5701 +8F011C011C018FB6FEB8ADADFEB8B60198023DFE6F033F1FB0CFD5BE4085585F77446876 +766832655902EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1 +FEB901198EAB010AB09598AC1418AC2F1B6155566114250000040098FFEC069405E8000D +001B002700400191BA000E000600032BB8000E10BA001F003B00032BB8001F10BA003500 +2500032BB8003510BA0000001400032BB8000010411B0016000E0026000E0036000E0046 +000E0056000E0066000E0076000E0086000E0096000E00A6000E00B6000E00C6000E00D6 +000E000D5D410500E5000E00F5000E00025D410500EA001400FA001400025D411B001900 +14002900140039001400490014005900140069001400790014008900140099001400A900 +1400B9001400C9001400D90014000D5D411B0016001F0026001F0036001F0046001F0056 +001F0066001F0076001F0086001F0096001F00A6001F00B6001F00C6001F00D6001F000D +5D410500E5001F00F5001F00025D410500EA002500FA002500025D411B00190025002900 +250039002500490025005900250069002500790025008900250099002500A9002500B900 +2500C9002500D90025000D5DBA00290025003511123900BA0011000300032BB8001110BA +000A001800032BB8000A10BA0022003800032BB8002210BA003E002C00032BB8003E10BA +0032001C00032BB800321030310110002120001134122433320412051000212000113402 +242322040205220615141633323635342613152E01232206070636333216151406232226 +3534123332160694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA57018F011C011C018FB6FE +B8ADADFEB8B602A14E5C5C4E4E5C5CD454612F777F0509804EA0BAC2A0B9C0EAC8356A02 +EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1FEB99F625B5A +62625A5B62019D9C231694500B3DB19491B3FDE7DA010B1300030098FFEC069405E8000D +001B002200EBB800232FB800242FB80000DCB8002310B80006D0B800062FB8000EDC411B +0016000E0026000E0036000E0046000E0056000E0066000E0076000E0086000E0096000E +00A6000E00B6000E00C6000E00D6000E000D5D410500E5000E00F5000E00025DB8000010 +B80014DC410500EA001400FA001400025D411B0019001400290014003900140049001400 +5900140069001400790014008900140099001400A9001400B9001400C9001400D9001400 +0D5DBA002000060000111239BA00210006000011123900BA0011000300032BB8001110BA +000A001800032BB8000A10BA001D002100032BB8001D1030310110002120001134122433 +3204120510002120001134022423220402012115012301210694FE3FFEC2FEC4FE3FCE01 +71BEC10171CDFA57018F011C011C018FB6FEB8ADADFEB8B6016B02ABFE94C10151FE3102 +EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1FEB901194BFC +A3031A0000050098FFEC069405E8000D001B0027003F004B020DBA000E000600032BB800 +0E10BA001F003D00032BB8001F10BA0031004600032BB8003110BA0000001400032BB800 +0010411B0016000E0026000E0036000E0046000E0056000E0066000E0076000E0086000E +0096000E00A6000E00B6000E00C6000E00D6000E000D5D410500E5000E00F5000E00025D +410500EA001400FA001400025D411B001900140029001400390014004900140059001400 +69001400790014008900140099001400A9001400B9001400C9001400D90014000D5D411B +0016001F0026001F0036001F0046001F0056001F0066001F0076001F0086001F0096001F +00A6001F00B6001F00C6001F00D6001F000D5D410500E5001F00F5001F00025D410500EA +004600FA004600025D411B00190046002900460039004600490046005900460069004600 +790046008900460099004600A9004600B9004600C9004600D90046000D5DBA0025004600 +31111239B800252F410500EA002500FA002500025D411B00190025002900250039002500 +490025005900250069002500790025008900250099002500A9002500B9002500C9002500 +D90025000D5DBA002B003D001F111239B8002B2FBA003400460031111239B8002510B800 +37DCB8002B10B80040DC00BA0011000300032BB8001110BA000A001800032BB8000A10BA +0022003A00032BB8002210BA002E004900032BB8002E10BA0043001C00032BB8004310BA +0034001C0043111239303101100021200011341224333204120510002120001134022423 +220402052206151416333236353426252E01353436333216151406071E01151406232226 +3534363714163332363534262322060694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA5701 +8F011C011C018FB6FEB8ADADFEB8B602AF545F5F54545F5FFEC6046AB79D9DB669040F76 +BEADADBE7657514D4B52524B4D5102EAFEC1FE4101BF013FC60172C6C6FE90C8FEE4FE70 +0190011CB30147B1B1FEB9E65049495051484950490176537488887453760103835C8A97 +978A5C83C13D42423D3E424200040098FFEC069405E8000D001B003400400191BA000E00 +0600032BB8000E10BA003E002900032BB8003E10BA0000001400032BB8000010BA002F00 +3800032BB8002F10411B0016000E0026000E0036000E0046000E0056000E0066000E0076 +000E0086000E0096000E00A6000E00B6000E00C6000E00D6000E000D5D410500E5000E00 +F5000E00025D410500EA001400FA001400025D411B001900140029001400390014004900 +14005900140069001400790014008900140099001400A9001400B9001400C9001400D900 +14000D5D411B0016003E0026003E0036003E0046003E0056003E0066003E0076003E0086 +003E0096003E00A6003E00B6003E00C6003E00D6003E000D5D410500E5003E00F5003E00 +025DBA001D0029003E111239410500EA003800FA003800025D411B001900380029003800 +39003800490038005900380069003800790038008900380099003800A9003800B9003800 +C9003800D90038000D5D00BA0011000300032BB8001110BA000A001800032BB8000A10BA +0020003200032BB8002010BA002C003B00032BB8002C10BA0035002600032BB800351030 +310110002120001134122433320412051000212000113402242322040201351E01333236 +3736062322263534363332161514022322261332363534262322061514160694FE3FFEC2 +FEC4FE3FCE0171BEC10171CDFA57018F011C011C018FB6FEB8ADADFEB8B6019055612E77 +7F050A804F9FBAC2A0B9BFE9C8356BD94E5B5B4E4E5C5C02EAFEC1FE4101BF013FC60172 +C6C6FE90C8FEE4FE700190011CB30147B1B1FEB9FD8B9C2415934F0D3CAF9491B4FDE8DA +FEF61301B4625B5B62625B5B6200000000050098FFEC069405E8000D001B00260032003E +019BBA000E000600032BB8000E10BA0023001D00032BB8002310BA002A003C00032BB800 +2A10BA0036003000032BB8003610BA0000001400032BB8000010411B0016000E0026000E +0036000E0046000E0056000E0066000E0076000E0086000E0096000E00A6000E00B6000E +00C6000E00D6000E000D5D410500E5000E00F5000E00025D410500EA001400FA00140002 +5D411B001900140029001400390014004900140059001400690014007900140089001400 +99001400A9001400B9001400C9001400D90014000D5D410500EA003000FA003000025D41 +1B0019003000290030003900300049003000590030006900300079003000890030009900 +3000A9003000B9003000C9003000D90030000D5D410500EA003C00FA003C00025D411B00 +19003C0029003C0039003C0049003C0059003C0069003C0079003C0089003C0099003C00 +A9003C00B9003C00C9003C00D9003C000D5D00BA0011000300032BB8001110BA000A0018 +00032BB8000A10BA002D003900032BB8002D10BA0024002500032BB8002410BA00330027 +00032BB8003310B8002410B8001CD0303101100021200011341224333204120510002120 +001134022423220402133311073537331133152101220615141633323635342627321615 +14062322263534360694FE3FFEC2FEC4FE3FCE0171BEC10171CDFA57018F011C011C018F +B6FEB8ADADFEB8B6CD9EACBC759FFE3E0304404545403F46463F8288888283888802EAFE +C1FE4101BF013FC60172C6C6FE90C8FEE4FE700190011CB30147B1B1FEB9FE3E023E2789 +2AFD368002DE97A3A29797A2A3977BE4D1D0E4E4D0D1E4000001FFEC026A04E503160003 +0000033521151404F9026AACAC0000000001FFEC021404E5036C00030000031121111404 +F902140158FEA80000010218FE0002B8078100030000011133110218A0FE000981F67F00 +000101C8FE0003080781000300000111211101C80140FE000981F67F0003003C026A0495 +031600030007000B000001352115213521152135211503720123FD420123FD420123026A +ACACACACACAC00000003003C02140495036C00030007000B000001112111211121112111 +211103720123FD420123FD42012302140158FEA80158FEA80158FEA800030218FE6D02B8 +071300030007000B00000111331103113311031133110218A0A0A0A0A0FE6D026AFD9603 +1E026AFD96031E026AFD9600000301C8FE6D0308071300030007000B0000011121110111 +21110111211101C80140FEC00140FEC00140FE6D026AFD96031E026AFD96031E026AFD96 +0004003C026A0495031600030007000B000F000013353315333533153335331533353315 +3CBC78BC78BC78BD026AACACACACACACACAC00000004003C02140495036C00030007000B +000F0000131133113311331133113311331133113CBC78BC78BC78BD02140158FEA80158 +FEA80158FEA80158FEA8000000040218FE6E02B8071200030007000B000F000001113311 +0311331103113311031133110218A0A0A0A0A0A0A0057001A2FE5EF8FE01A2FE5E04AC01 +A2FE5EFDAA01A2FE5E000000000401C8FE6E0308071200030007000B000F000001112111 +01112111011121110111211101C80140FEC00140FEC00140FEC00140057001A2FE5EF8FE +01A2FE5E04AC01A2FE5EFDAA01A2FE5E00010218FE0004E5031600050000011121152111 +021802CDFDD3FE000516ACFB9600000000010218FE0004E5036C00050000011121112111 +021802CDFDD3FE00056CFEA8FBEC0000000101C8FE0004E5031600050000011121152111 +01C8031DFE23FE000516ACFB96000000000101C8FE0004E5036C00050000011121112111 +01C8031DFE23FE00056CFEA8FBEC00000001FFECFE0002B8031600050000011121352111 +0218FDD402CCFE00046AACFAEA0000000001FFECFE0002B8036C00050000011121112111 +0218FDD402CCFE0004140158FA9400000001FFECFE000308031600050000011121352111 +01C8FE24031CFE00046AACFAEA0000000001FFECFE000308036C00050000011121112111 +01C8FE24031CFE0004140158FA94000000010218026A04E5078100050000011133112115 +0218A0022D026A0517FB95AC00010218021404E50781000500000111331121110218A002 +2D0214056DFBEBFEA8000000000101C8026A04E507810005000001112111211501C80140 +01DD026A0517FB95AC000000000101C8021404E507810005000001112111211101C80140 +01DD0214056DFBEBFEA800000001FFEC026A02B807810005000003352111331114022CA0 +026AAC046BFAE9000001FFEC021402B807810005000003112111331114022CA002140158 +0415FA930001FFEC026A03080781000500000335211121111401DC0140026AAC046BFAE9 +0001FFEC021403080781000500000311211121111401DC0140021401580415FA93000000 +00010218FE0004E507810007000001113311211521110218A0022DFDD3FE000981FB95AC +FB96000000010218FE0004E507810007000001113311211121110218A0022DFDD3FE0009 +81FBEBFEA8FBEC00000101C8FE0004E50781000900000111231121112115211102185001 +4001DDFDD3FE00046A0517FB95ACFB96000101C8FE0004E5078100090000011133113311 +2115211101C850A0022DFE23FE000516046BFB95ACFB9600000101C8FE0004E507810007 +0000011121112115211101C8014001DDFE23FE000981FB95ACFB9600000101C8FE0004E5 +07810009000001112311211121112111021850014001DDFDD3FE000414056DFBEBFEA8FB +EC000000000101C8FE0004E50781000900000111331133112111211101C850A0022DFE23 +FE00056C0415FBEBFEA8FBEC000101C8FE0004E5078100070000011121112111211101C8 +014001DDFE23FE000981FBEBFEA8FBEC0001FFECFE0002B8078100070000011121352111 +33110218FDD4022CA0FE00046AAC046BF67F00000001FFECFE0002B80781000700000111 +2111211133110218FDD4022CA0FE00041401580415F67F000001FFECFE00030807810009 +0000011121352111211123110218FDD401DC014050FE00046AAC046BFAE9FB960001FFEC +FE0003080781000900000111213521113311331101C8FE24022CA050FE00046AAC046BFB +95FAEA000001FFECFE000308078100070000011121352111211101C8FE2401DC0140FE00 +046AAC046BF67F000001FFECFE000308078100090000011121112111211123110218FDD4 +01DC014050FE00041401580415FA93FBEC0000000001FFECFE0003080781000900000111 +211121113311331101C8FE24022CA050FE00041401580415FBEBFA940001FFECFE000308 +078100070000011121112111211101C8FE2401DC0140FE00041401580415F67F0001FFEC +FE0004E503160007000001112135211521110218FDD404F9FDD3FE00046AACACFB960000 +0001FFECFE0004E5036C00090000011121112115211521110218FDD402CC022DFDD3FE00 +0414015856ACFB960001FFECFE0004E5036C00090000011121352135211121110218FDD4 +022C02CDFDD3FE00046AAC56FEA8FBEC0001FFECFE0004E5036C00070000011121112111 +21110218FDD404F9FDD3FE0004140158FEA8FBEC0001FFECFE0004E50316000700000111 +21352115211101C8FE2404F9FE23FE00046AACACFB9600000001FFECFE0004E5036C0009 +00000111211121152115211101C8FE24031C01DDFE23FE000414015856ACFB960001FFEC +FE0004E5036C000900000111213521352111211101C8FE2401DC031DFE23FE00046AAC56 +FEA8FBEC0001FFECFE0004E5036C00070000011121112111211101C8FE2404F9FE23FE00 +04140158FEA8FBEC0001FFEC026A04E5078100070000033521113311211514022CA0022D +026AAC046BFB95AC0001FFEC021404E50781000900000311211133112115211514022CA0 +022DFDD3021401580415FB95AC5600000001FFEC021404E5078100090000033521113311 +2111213514022CA0022DFD33026AAC046BFBEBFEA85600000001FFEC021404E507810007 +0000031121113311211114022CA0022D021401580415FBEBFEA800000001FFEC026A04E5 +07810007000003352111211121151401DC014001DD026AAC046BFB95AC0000000001FFEC +021404E5078100090000031121112111211521151401DC014001DDFE23021401580415FB +95AC56000001FFEC021404E5078100090000033521112111211121351401DC014001DDFC +E3026AAC046BFBEBFEA856000001FFEC021404E507810007000003112111211121111401 +DC014001DD021401580415FBEBFEA8000001FFECFE0004E50781000B0000011123112135 +21113311211502B8A0FDD4022CA0022D026AFB96046AAC046BFB95AC0001FFECFE0004E5 +0781000B00000111211121113311211521110218FDD4022CA0022DFDD3FE000414015804 +15FB95ACFB9600000001FFECFE0004E50781000B00000111213521113311211121110218 +FDD4022CA0022DFDD3FE00046AAC046BFBEBFEA8FBEC00000001FFECFE0004E50781000B +00000111211121113311211121110218FDD4022CA0022DFDD3FE00041401580415FBEBFE +A8FBEC000001FFECFE0004E50781000B00000111213521112111211521110218FDD401DC +014001DDFDD3FE00046AAC046BFB95ACFB9600000001FFECFE0004E50781000B00000111 +2135211133112115211101C8FE24022CA0022DFE23FE00046AAC046BFB95ACFB96000000 +0001FFECFE0004E50781000B000001112135211121112115211101C8FE2401DC014001DD +FE23FE00046AAC046BFB95ACFB9600000001FFECFE0004E50781000D0000011121112111 +21112115211523110218FDD401DC014001DDFE2350FE00041401580415FB95AC56FBEC00 +0001FFECFE0004E50781000D00000111233521352111211121112111021850FE2401DC01 +4001DDFDD3FE00041456AC046BFBEBFEA8FBEC000001FFECFE0004E50781000D00000111 +21112111331133152115211101C8FE24022CA05001DDFE23FE00041401580415FBEB56AC +FB9600000001FFECFE0004E50781000D0000011121352135331133112111211101C8FE24 +01DC50A0022DFE23FE00046AAC560415FBEBFEA8FBEC00000001FFECFE0004E50781000B +00000111211121112111211121110218FDD401DC014001DDFDD3FE00041401580415FBEB +FEA8FBEC0001FFECFE0004E50781000B000001112111211133112111211101C8FE24022C +A0022DFE23FE00041401580415FBEBFEA8FBEC000001FFECFE0004E50781000B00000111 +2111211121112115211101C8FE2401DC014001DDFE23FE00041401580415FB95ACFB9600 +0001FFECFE0004E50781000B000001112135211121112111211101C8FE2401DC014001DD +FE23FE00046AAC046BFBEBFEA8FBEC000001FFECFE0004E50781000B0000011121112111 +21112111211101C8FE2401DC014001DDFE23FE00041401580415FBEBFEA8FBEC0002003C +026A0495031600030007000013352115333521153C01F07901F0026AACACACAC0002003C +02140495036C000300070000011121112111211102A501F0FBA701F002140158FEA80158 +FEA8000000020218FEC002B806C100030007000001113311031133110218A0A0A0036C03 +55FCABFB540354FCAC000000000201C8FEC0030806C10003000700000111211101112111 +01C80140FEC00140036C0355FCABFB540354FCAC0002FFEC01BE04E503C2000300070000 +03352115013521151404F9FB0704F90316ACACFEA8ACAC0000020178FE00035807810003 +0007000001113311331133110178A0A0A0FE000981F67F0981F67F0000010218FE0004E5 +03C20009000001112115211521152111021802CDFDD3022DFDD3FE0005C2ACACACFC4200 +00010178FE0004E5031600090000011121152111231123110178036DFE73A0A0FE000516 +ACFB96046AFB960000020178FE0004E503C20005000B0000011121152111331121152111 +0178036DFD33A0022DFE73FE0005C2ACFAEA046AACFC42000001FFECFE0002B803C20009 +0000011121352135213521110218FDD4022CFDD402CCFE0003BEACACACFA3E000001FFEC +FE0003580316000900000335211123112311231114036CA0A0A0026AACFAEA046AFB9604 +6A0000000002FFECFE00035803C20005000B000001112135211121112135211102B8FD34 +036CFE20FE74022CFE000516ACFA3E03BEACFB960001021801BE04E50781000900000111 +33112115211521150218A0022DFDD3022D01BE05C3FC41ACACAC000000010178026A04E5 +078100090000011133113311331121150178A0A0A0018D026A0517FB95046BFB95AC0000 +0002017801BE04E507810005000B000001113311211501113311211502B8A0018DFC93A0 +02CD0316046BFC41ACFEA805C3FAE9AC0001FFEC01BE02B8078100090000033521352135 +2111331114022CFDD4022CA001BEACACAC03BFFA3D0000000001FFEC026A035807810009 +00000335211133113311331114018CA0A0A0026AAC046BFB95046BFAE90000000002FFEC +01BE035807810005000B000003352111331101352111331114018CA0FDD402CCA00316AC +03BFFB95FEA8AC0517FA3D0000010218FE0004E50781000B000001113311211521152115 +21110218A0022DFDD3022DFDD3FE000981FC41ACACACFC4200020178FE0004E507810003 +000B00000111331133113311211521110178A0A0A0018DFE73FE000981F67F0981FB95AC +FB96000000030178FE0004E5078100050009000F00000111331121150111331133112115 +211102B8A0018DFC93A0A0022DFE730316046BFC41ACFAEA0981F67F046AACFC42000000 +0001FFECFE0002B80781000B00000111213521352135211133110218FDD4022CFDD4022C +A0FE0003BEACACAC03BFF67F0002FFECFE00035807810007000B00000111213521113311 +331133110178FE74018CA0A0A0FE00046AAC046BF67F0981F67F00000003FFECFE000358 +07810005000B000F00000335211133110311213521113311331114018CA0A0FE74022CA0 +A00316AC03BFFB95FAEA03BEACFB960981F67F000002FFECFE0004E503C20007000B0000 +0111213521152111013521150218FDD404F9FDD3FD3404F9FE0003BEACACFC420516ACAC +0001FFECFE0004E50316000B00000335211521112311231123111404F9FE73A0A0A0026A +ACACFB96046AFB96046A00000003FFECFE0004E503C200030009000F0000033521150111 +213521113311211521111404F9FC93FE74022CA0022DFE730316ACACFAEA03BEACFB9604 +6AACFC420002FFEC01BE04E507810003000B00000335211501352111331121151404F9FB +07022CA0022D01BEACAC0158AC03BFFC41AC00000001FFEC026A04E50781000B00000335 +2111331133113311211514018CA0A0A0018D026AAC046BFB95046BFB95AC00000003FFEC +01BE04E5078100030009000F0000033521150135211133113311331121151404F9FB0701 +8CA0A0A0018D01BEACAC0158AC03BFFB95046BFC41AC00000001FFECFE0004E507810013 +000001112135213521352111331121152115211521110218FDD4022CFDD4022CA0022DFD +D3022DFDD3FE0003BEACACAC03BFFC41ACACACFC420000000001FFECFE0004E507810013 +0000033521113311331133112115211123112311231114018CA0A0A0018DFE73A0A0A002 +6AAC046BFB95046BFB95ACFB96046AFB96046A000004FFECFE0004E507810005000B0011 +0017000001112115211121112135211101352111331133113311211502B8022DFE73FE20 +FE74022CFDD4018CA0A0A0018DFE00046AACFC4203BEACFB960516AC03BFFB95046BFC41 +AC00000000010218FE0004E50316000B00000111341233211521220615110218AAAA0179 +FE87595BFE000370A50101AC7E7CFC900001FFECFE0002B80316000B0000011134262321 +35213216151102185B59FE880178A8ACFE0003707E7CACFEA8FC90000001FFEC026A02B8 +0781000B0000033521323635113311140623140178595BA0ACA8026AAC7E7C0371FC8FA8 +FE00000000010218026A04E50781000B000001212226351133111416332104E5FE87A8AC +A05B590179026AFEA80371FC8F7C7E000001FFA7FE14052A076D00030000030133015904 +D1B2FB2FFE140959F6A700000001FFA7FE14052A076D0003000001230133052AB2FB2FB2 +FE1409590001FFA7FE14052A076D000B0000012309012309013309013301052AB2FDF0FD +F1B20269FD97B2020F0210B2FD98FE140400FC0004AC04ADFC000400FB5300000001FFEC +026A02680316000300000335211514027C026AACAC0000000001021802C002B807810003 +0000011133110218A002C004C1FB3F0000010268026A04E5031600030000013521150268 +027D026AACAC000000010218FE0002B802C000030000011133110218A0FE0004C0FB4000 +0001FFEC02130268036C000300000311051114027C0214015801FEA8000101C802C00308 +0781000300000111211101C8014002C004C1FB3F00010268021404E5036C000300000111 +21110268027D02140158FEA8000101C8FE00030802C0000300000111211101C80140FE00 +04C0FB400001FFEC021404E5036C0007000003352135211121351402900269FD97026AAC +56FEA856000101C8FE000308078100070000011133113311331101C850A050FE0004C004 +C1FB3FFB400000000001FFEC021404E5036C0007000003112115211521151402900269FD +970214015856AC56000101C8FE0003080781000700000111231121112311021850014050 +FE0004C004C1FB3FFB400000FFFFFFEC0214063B062810070E5B0000041400000001FFEC +FE00063BFF05000300000311211114064FFE000105FEFB000001FFECFE00063BFFF60003 +00000311211114064FFE0001F6FE0A000001FFECFE00063B010F00030000031121111406 +4FFE00030FFCF1000001FFECFE00063B0214000300000311211114064FFE000414FBEC00 +0001FFECFE00063B0319000300000311211114064FFE000519FAE7000001FFECFE00063B +041E000300000311211114064FFE00061EF9E2000001FFECFE00063B0523000300000311 +211114064FFE000723F8DD000001FFECFE00063B0628000300000311211114064FFE0008 +28F7D8000001FFECFE00057106280003000003112111140585FE000828F7D8000001FFEC +FE0004A7062800030000031121111404BBFE000828F7D8000001FFECFE0003DD06280003 +0000031121111403F1FE000828F7D8000001FFECFE000313062800030000031121111403 +27FE000828F7D8000001FFECFE0002490628000300000311211114025DFE000828F7D800 +0001FFECFE00017F06280003000003112111140193FE000828F7D8000001FFECFE0000B5 +0628000300000311331114C9FE000828F7D80000FFFF0313FE00063A062810070E630327 +00000000000CFFECFE000571062800030007000B000F00130017001B001F00230027002B +002F00000111331121113311131133112111331101113311211133111311331121113311 +0111331121113311131133112111331104A7CAFC0ECACACAFC0FC903F2CAFC0ECACACAFC +0FC903F2CAFC0ECACACAFC0FC9FE000105FEFB0105FEFB016D0105FEFB0105FEFB016E01 +05FEFB0105FEFB016D0105FEFB0105FEFB016E0105FEFB0105FEFB016D0105FEFB0105FE +FB0000000020FFECFE00063406280007000F0017001F0027002F0037003F0047004F0057 +005F0067006F0077007F0087008F0097009F00A700AF00B700BF00C700CF00D700DF00E7 +00EF00F700FF000013072327353733170507232735373317050723273537331705072327 +353733170107232735373317050723273537331705072327353733170507232735373317 +010723273537331705072327353733170507232735373317050723273537331701072327 +353733170507232735373317050723273537331705072327353733170107232735373317 +050723273537331705072327353733170507232735373317010723273537331705072327 +353733170507232735373317050723273537331701072327353733170507232735373317 +050723273537331705072327353733170107232735373317050723273537331705072327 +353733170507232735373317B505BF0505BF05019205BF0505BF05019205BF0505BF0501 +9205BF0505BF05FC1305BF0505BF05019205BF0505BF05019205BF0505BF05019205BF05 +05BF05FA8105BF0505BF05019205BF0505BF05019205BF0505BF05019205BF0505BF05FC +1305BF0505BF05019205BF0505BF05019205BF0505BF05019205BF0505BF05FA8105BF05 +05BF05019205BF0505BF05019205BF0505BF05019205BF0505BF05FC1305BF0505BF0501 +9205BF0505BF05019205BF0505BF05019205BF0505BF05FA8105BF0505BF05019205BF05 +05BF05019205BF0505BF05019205BF0505BF05FC1305BF0505BF05019205BF0505BF0501 +9205BF0505BF05019205BF0505BF0505280505FB0505FB0505FB0505FB0505FB0505FB05 +05FB0505FE000505FB0505FB0505FB0505FB0505FB0505FB0505FB0505FE000505FB0505 +FB0505FB0505FB0505FB0505FB0505FB0505FE000505FB0505FB0505FB0505FB0505FB05 +05FB0505FB0505FE000505FB0505FB0505FB0505FB0505FB0505FB0505FB0505FE000505 +FB0505FB0505FB0505FB0505FB0505FB0505FB0505FE000505FB0505FB0505FB0505FB05 +05FB0505FB0505FB0505FE000505FB0505FB0505FB0505FB0505FB0505FB0505FB050500 +0007FFECFE00063B06280019001D002100250029002D0031000003113311231133112311 +331121113311211123112311211123190133112301331123013311230133112301331123 +0133112314C9C9C9C9C9025ECA025ECACAFDA2CACACA0328CACAFE6CCACAFE6CCACA0328 +CACAFE6CCACAFE00016D010501D6010501D60105FEFB0105F7D80105FEFB0105FEFB05B6 +0105FEFB0105FD8D0105FD8E0105FEFB0105FD8D01050000FFFFFFEC0523063B06281007 +0E58000007230000FFFF0571FE00063A062810070E660585000000000001FFECFE000314 +02140003000003112111140328FE000414FBEC0000010313FE00063B0214000300000111 +211103130328FE000414FBEC0001FFEC0214031406280003000003112111140328021404 +14FBEC000001FFECFE00063B062800050000012111211121063BF9B103280327FE000828 +FBEC00000001FFECFE00063B06280007000003112111211121111403280327FCD8021404 +14FBECFBEC0414000001FFECFE00063B062800050000011121112111063BFCD9FCD80628 +FBECFBEC082800000001FFECFE00063B06280005000003211121112114064FFCD8FCD906 +28F7D80414000000000103130214063B062800030000011121110313032802140414FBEC +0001FFECFE00063B06280007000003211121112111211403270328FCD9FCD802140414FB +ECFBEC000001FFECFE00063B0628000500000311211121111403270328FE0004140414F7 +D8000000000100BAFF0406D505240003000017112111BA061BFC0620F9E00000000200BA +FF0406D505240003000700000521112103112111012C0537FAC972061B8A053CFA520620 +F9E00000000200BAFF0406D50524000B0017000025143321323511342321221503111029 +0120190110290120012CE4036FE4E4FC91E4720156036F0156FEAAFC91FEAA5AE4E40374 +E4E4FC8C03740156FEAAFC8CFEAA0000FFFF00BAFF0406D5052410270E81011100001006 +0E780000000600BAFF0406D5052400030007000B000F0013001700001711211125213521 +35213521352135213521352135213521BA061BFA570537FAC90537FAC90537FAC90537FA +C90537FAC9FC0620F9E072B072B272B072B272B0000600BAFF0406D5052400030007000B +000F001300170000171121112533112301331123013311230133112301331123BA061BFE +E1B0B0FEDCB2B2FEDEB0B0FEDCB2B2FEDEB0B0FC0620F9E0740538FAC80538FAC80538FA +C80538FAC8053800001A00BAFF0406D5052400030007000B000F00130017001B001F0023 +0027002B002F00330037003B003F00430047004B004F00530057005B005F006300670000 +053335230533352305333523013335231133352311333523113335231133352301333523 +113335231133352311333523013335231133352311333523113335230133352311333523 +1133352311333523013335231133352311333523113335231133352301112111024CB2B2 +0124B0B00122B2B2FC9AAEAEAEAEAEAEAEAEAEAE0120B2B2B2B2B2B2B2B20124B0B0B0B0 +B0B0B0B00122B2B2B2B2B2B2B2B20124ADADADADADADADADADADFB04061B88AEAEAEAEAE +03DCAEFE2EB2FE2CB0FE2CB2FE2EAE03DCAEFE2EB2FE2CB0FE2CB202B8AEFE2EB2FE2CB0 +FE2CB202B8AEFE2EB2FE2CB0FE2CB202B8AEFE2EB2FE2CB0FE2CB2FE2EAEFEDE0620F9E0 +000800BAFF0406D5052400030006000A000E00140018001C001F00001711211101153303 +150133011501370115013735013301350133013501331735BA061BFA57E2E20184FBFD81 +0321FBFBE404BE79FB42A2041CFCDFA2027FFE7CA2E2FC0620F9E00154E2027FFCFE7D04 +1CFCFCDF01053C7FFB42017F04BDFBE4FC0320FD81FC0183E2E20000000800BAFF0406D5 +052400030006000A000E00140018001C001F000017112111253335053301350117013501 +17013523013501230135012301353723BA061BFEACE2FD81FB0184FBE4FB0321FAC97904 +BE79FB42041CFBFCDF027FFBFE7CE2E2FC0620F9E072E2E20183FCFD81010321FCFBE401 +04BE7FFB43A1041CFCE0A1027FFE7DA1E2000000001A00BAFF0406D5052400040009000E +00120017001C002000240028002D003100350039003D00410046004B004F00530057005C +00610065006A006F00730000011737272311173727070117372723011737270317372707 +011737272301173727011737270117372701173735230117372701173727011737270117 +372701173727013337270701173735270117372701173727011737270117333727011737 +3527011737270117333727051733352701112111012C327C3579327E7E32014F7E7E3592 +FEFE7E7C7EFE327E7E3202EC7E7E3592FEFD7E7D7DFEB57D7D7EFEB57E7E7E03897D3179 +FEFD7C7E7CFEB37C7E7CFEB57D7C7CFEB57C7E7CFEB37D7D7CFF007A347C3204887E3132 +FEB47E7E7EFEB57E7C7EFEB67E7E7EFEB33593347E02BB7D3232FEB57E7D7EFEB5349335 +7E011F347A32FA89061B0433317C34FDE4327E7E31014F7E7E34FEFF7E7C7EFCCC317E7D +3102EC7E7E34FEFD7D7D7EFEB57E7D7DFEB67E7E7E01EC7C317FFEFD7C7E7CFEB47D7E7C +FEB67C7C7CFEB57C7E7CFEB47C7D7DFE81347C3102ED7E329931FEB47E7E7EFEB57E7C7E +FEB57E7E7EFEB434347E011E7E319931FEB57D7C7EFEB534347E7E347F31FEDE0620F9E0 +000100BA001604B204120003000037112111BA03F81603FCFC040000000200BA001604B2 +04120003000700002521112103112111012C0314FCEC7203F8880318FC7603FCFC040000 +000100BA009A06D5038E000300002521112106D5F9E5061B9A02F400000200BA009A06D5 +038E00030007000001112111052111210663FAC905A9F9E5061B010C0210FDF07202F400 +000100BAFF0603AD05220003000017112111BA02F3FA061CF9E40000000200BAFF0603AD +05220003000700000521112103112111012C020FFDF17202F3880538FA56061CF9E40000 +00010006009A0621038E00030000252101210498FB6E018A04919A02F400000000020006 +009A0621038E000300070000090121010521012104620110FC53FEEF03E4FB6E018A0491 +010C0210FDF07202F400000000010006FF04062105240002000017090106030D030EFC06 +20F9E00000020006FF040621052400020005000017210903B104C5FD9DFCF3030D030E8A +04CAFAC40620F9E000010006001603FE0412000200003709010601FC01FC1603FCFC0400 +00020006001603FE041200020005000037210903B102A2FEAFFE0401FC01FC8802A6FCE8 +03FCFC0400010006FF04062105240002000017110106061BFC0620FCF000000000020006 +FF04062105240002000500001709010311017804C5FB3B72061B5202660266FA8A0620FC +F000000000010006001603FE0412000200003711010603F81603FCFE0200000000020006 +001603FE04120002000500003709010311017802A2FD5E7203F8C101530153FCAF03FCFE +02000000000100060016062104120002000037110106061B1603FCFE0200000000020006 +00160621041200020005000037090103110178048CFB7472061BC101530153FCAF03FCFE +0200000000010006FF04062105240002000013210106061BFCF20524F9E0000000020006 +FF0406210524000200050000130901252101B102620263FA90061BFCF204B2FB3604CA72 +F9E0000000010006001603FE0412000200001321010603F8FE040412FC04000000020006 +001603FE0412000200050000130901252101B101510151FCB303F8FE0403A0FD5A02A672 +FC04000000010006FF04062105240002000013011106061B02140310F9E0000000020006 +FF0406210524000200050000130111090111EA04C5FA57061B0214FD9A04CCFD9A0310F9 +E000000000010006001603FE0412000200001301110603F8021401FEFC04000000020006 +001603FE0412000200050000130111090111EA02A2FC7A03F80214FEAD02A6FEAD01FEFC +04000000000100060016062104120002000013011106061B021401FEFC04000000020006 +0016062104120002000500000901110901110123048CFA57061B0214FEAD02A6FEAD01FE +FC04000000010006FF04062105240003000013090206030D030EFCF202140310FCF0FCF0 +00020006FF04062105240003000700001309069E02750276FD8AFCF3030D030EFCF20214 +FD8802780278FD880310FCF0FCF0000000030006FF040621052400030007000B0000090B +013E01D501D5FE2BFD8B02750276FD8AFCF3030D030EFCF2021401D7FE29FE2901D7FD88 +02780278FD880310FCF0FCF000030070FEFF068B0529000D001B00290000241037363332 +171610070623222700100516333237241025262322070010253633321704100506232227 +0182FE7E7F807EFEFE7E807F7EFE65014DA5A6A7A5014DFEB3A5A7A6A5FE3E0187C3C3C4 +C30187FE79C3C4C3C3EF024A924A4A92FDB6924A4A0336FD02C06060C002FEC06060FBFF +0384E27171E2FC7CE271710000020006FE2303EE06750003000700224011020600080406 +080604030201000605070810D4CC1739310010D4CC1139123930090701FAFE7F01810181 +FE7F01F4FE0CFE0C0581FCCFFCC703390425FBDBFBD3042D00020070FEFF068B0529000D +001B000012100516333237241025262322070010253633321704100506232227E5014DA5 +A6A7A5014DFEB3A5A7A6A5FE3E0187C3C3C4C30187FE79C3C4C3C30393FD02C06060C002 +FEC06060FBFF0384E27171E2FC7CE2717100000000080072FF010689052700090013001D +0027002F0037003F00470000251617161707262726270536373637170607060713262726 +273716171617250607060727363736371316323717062227013634273716140701262207 +273632170106141707263437015C2B3B2E383146394B3503DA382E3432643B4539479C2C +3A2E383047394A36FC26382E3B2B64314F3946ED4C9A4C265FC060034A10106E1414FDA1 +4C9A4C2760C05FFCB810106E14148F3C3126206A28303D4A7520262C413D4E3A30280427 +3C3126206A28303D4A752026313C3E45423028FAD416166E1B1B025F49A049275BCA5B03 +4A16166E1B1BFDA149A049275BCA5B0000060070FEFF068B0529000D0017001B0025002F +003300003610253633321704100506232227131116171617110607060706101701111633 +3237112623221711363736371126272617113610700187C3C3C4C30187FE79C3C4C3C30B +1719414141411989ABAB01962C2B2D2C2C2D2BF641401A17171A40E3AA520384E27171E2 +FC7CE2717104C5FBBE0F0F251704F617250F6DB1FDDCB1045BFAD0070705300724FB0A17 +250F100440100F25A2FC7CB10222000000040070FEFF068B0529000D001B002900370000 +001017163332373610272623220702103736333217161007062322270010051633323724 +102526232207001025363332170410050623222702997239393A397272393A3939EDB058 +575858B0B058585758FE17014DA5A6A7A5014DFEB3A5A7A6A5FE3E0187C3C3C4C30187FE +79C3C4C3C30298FEF8422121420108422121FE6F019665333365FE6A65333302AFFD02C0 +6060C002FEC06060FBFF0384E27171E2FC7CE2717100000000010070FF04068B05200017 +00134007061218190C001810DCD4CC310010D4C430133437363736333217161716151407 +060706232227262726706968B6B5D2D1B5B668696968B6B5D1D2B5B668690212D1B6B569 +696969B5B6D1D1B6B569696969B5B60000020070FF04068B0520000D0015000012101224 +33320412100204232224053237241025262370D1016BD2D1016BD1D1FE95D1D2FE95023C +A7A5014DFEB3A5A7014101A2016BD2D2FE95FE5EFE95D2D26160C002FEC0600000020070 +FF04068B0520000D001500001210122433320412100204232224012207041005163370D1 +016BD2D1016BD1D1FE95D1D2FE95023CA6A5FEB3014DA5A6014101A2016BD2D2FE95FE5E +FE95D2D204DD60C0FD02C06000020070FF04068B0520000D001600001210122433320412 +10020423222401102526232207041170D1016BD2D1016BD1D1FE95D1D2FE9504D5FEB3A5 +A7A6A5FEB3014101A2016BD2D2FE95FE5EFE95D2D2023E017FC06060C0FE810000020070 +FF04068B0520000D00160000121012243332041210020423222403100516333237241170 +D1016BD2D1016BD1D1FE95D1D2FE955C014DA5A6A7A5014D014101A2016BD2D2FE95FE5E +FE95D2D2023EFE81C06060C0017F000000020070FF04068B0520000B0018000012101224 +20041210020420240122070410051633323724112170D1016B01A3016BD1D1FE95FE5DFE +95023CA6A5FEB3014DA5A6A7A5014DFD67014101A2016BD2D2FE95FE5EFE95D2D204DD60 +C0FD02C06060C0017F00000000020070FF04068B0520000B001100001210122420041210 +0204202401220704112170D1016B01A3016BD1D1FE95FE5DFE95023CA6A5FEB302980141 +01A2016BD2D2FE95FE5EFE95D2D204DD60C0FE8100010070FEFF037D0529000700003610 +253633112227700187C3C3C3C3520384E271F9D671000000000100BAFEFF03C705290007 +0000001005062311321703C7FE79C3C3C3C303D6FC7CE271062A7100000200BAFFEC059A +0628000A000E00000114163236353426232206011121110201ACFAACAB7C7EADFEB904E0 +02FA7DACAC7D7CABABFC76063CF9C400000300BAFE0007090628000D001B001F00002410 +2536333217041005062322270010051633323724102526232207011121110149014DA5A6 +A7A5014DFEB3A5A7A6A5FE3E0187C3C3C4C30187FE79C3C4C3C3FE5F064F9502FEC06060 +C0FD02C060600401FC7CE27171E20384E27171F9480828F7D8000000000200BA02140709 +0628000C0015000013112111231025262322070411290110253633321704BA064F1AFE79 +C3C4C3C3FE7905A6FACF014DA6A5A6A6014D02140414FBEC01C2E27171E2FE3E017EC160 +60C10000000200BAFE0007090214000C0015000013113310051633323724113311012110 +050623222724BA1A0187C3C3C4C301871AFA400531FEB3A6A6A5A6FEB3FE000414FE3EE2 +7171E201C2FBEC0414FE82C16060C1000001000602140313052900090000131025363315 +22070411060187C3C3A6A5FEB3021401C2E2717660C0FE81000100060214031305290009 +00001332170411231025262306C3C3018775FEB3A5A6052971E2FE3E017FC06000010006 +FEFF03130214000900001335323724113310050606A6A5014D75FE79C3FEFF7660C0017F +FE3EE27100010006FEFF0313021400090000012227241133100516330313C3C3FE797501 +4DA5A6FEFF71E201C2FE81C060000000000100700214068B052900110000131025363332 +170411231025262322070411700187C3C3C4C3018775FEB3A5A7A6A5FEB3021401C2E271 +71E2FE3E017FC06060C0FE8100010070FEFF068B02140012000013303310051633323724 +1133100506232227247075014DA5A6A7A5014D75FE79C3C4C3C3FE790214FE81C06060C0 +017FFE3EE27171E200010006FF04062105240002000017011106061BFC0620F9E0000000 +00010006FF04062105240002000017110106061BFC0620F9E000000000010006FF040621 +05240002000017112106061BFC06200000010006FF04062105240002000013211106061B +0524F9E00002013301D103850421000A0015000001141632363534262322060734363332 +161514062226016E8AC88A8963658B3BAD7E7CABACFAAC02FA648A8A64638989637CABAB +7C7DACAC000200BAFF0406D505240003000700001711211125211121BA061BFCF2029CFD +64FC0620F9E072053C000000000200BAFF0406D505240003000700001711211125211121 +BA061BFA57029BFD65FC0620F9E072053C000000000200BAFF0406D50524000300060000 +17112111252111BA061BFA570537FC0620F9E072053C0000000200BAFF0406D505240003 +0006000017112111250121BA061BFA570537FAC9FC0620F9E072053C000300BAFF0406D5 +052400030007000B0000171121112521112101211121BA061BFD2B0263FD9DFD2C0262FD +9EFC0620F9E072053CFAC4053C00000000030006FF04062105240007000A000D00000034 +36321614062201210903027F577C56567DFDDC04C5FD9DFCF3030D030E012C7C56567C56 +FEA004CAFAC40620F9E0000000020006FF04062105240002000500000521090303130263 +FD9DFCF3030D030E8A04CAFAC40620F9E000000000020006FF0406210524000200050000 +1721110902B10262FCF3030D030E8A04CAFAC40620F9E00000020070FE0008840628000B +00170000121001162037001001262007001001242005001001042025F101C5E201C4E201 +C5FE3BE2FE3CE2FDBA02050103020401030205FDFBFEFDFDFCFEFD041EFBECFEFB838301 +05041401058383FA9D04A8012A9696FED6FB58FED6969600000300BAFF0406D505240005 +0009000D00000521112111210311211101211121012C0537FD9DFD2C72061BFA570262FD +9E8A053CFD29FD290620F9E003490265000300BAFF0406D5052400050009000D00000121 +112111210311211125211121012C02D40263FAC972061BFA570262FD9E024DFD29053CFA +520620F9E0720265000300BAFF0406D5052400050009000D000005211121112103112111 +25211121012C026202D5FAC972061BFD2B0263FD9D8A02D70265FA520620F9E072026500 +000300BAFF0406D5052400050009000D00000521112111210311211101211121012C0537 +FD2BFD9E72061BFD2B0263FD9D8A026502D7FA520620F9E00349026500030070FF04068B +0520000D0013002000001210122433320412100204232224010607040321051205163332 +3724102526271170D1016BD2D1016BD1D1FE95D1D2FE9502038989FECC17025DFDA31701 +34A6A5A6A6014DFEB3898A014101A2016BD2D2FE95FE5EFE95D2D204DA0E4FB2FEAC72FE +ACB26060C102FCC14F0EFD2B00030070FF04068B0520000D001A00200000121012243332 +04121002042322240536372410252623220704032105120516171170D1016BD2D1016BD1 +D1FE95D1D2FE9502758A89014DFEB3A6A6A5A6FECC1702CFFD311701348989014101A201 +6BD2D2FE95FE5EFE95D2D25E0E4FC102FCC16060B2FEAC72FEACB24F0E02630000030070 +FF04068B0520000D001A0020000012101224333204121002042322240210051617112102 +252623220701363724132170D1016BD2D1016BD1D1FE95D1D2FE955C014D898902D017FE +CCA6A6A5A601848A89013417FDA2014101A2016BD2D2FE95FE5EFE95D2D203BCFD04C14F +0E02D50154B26060FB250E4FB201540000030070FF04068B0520000D001A002000001210 +1224333204121002042322240210051633323724132111060701022526271170D1016BD2 +D1016BD1D1FE95D1D2FE955C014DA6A5A6A6013417FD30898903E217FECC898A014101A2 +016BD2D2FE95FE5EFE95D2D203BCFD04C16060B2015402D50E4FFDFA0154B24F0EFD9D00 +00020006FF040621052400020005000037012103112178048CFB7472061B200492FA5206 +2000000000020006FF04062105240002000500000901112521110123048CFA57061B04B2 +FB6E049272F9E00000020006FF040621052400020005000017210103110178048CFB7472 +061B8A0492FAFC0620F9E000000200BAFF7905EA04AF0003000700000521112103112111 +012C044CFBB4720530150452FB3C0536FACA0000000100BAFF7905EA04AF000300001711 +2111BA0530870536FACA0000000200BAFFDD0522044B0003000700002521112103112111 +012C0384FC7C7204684F038AFC04046EFB920000000100BAFFDD0522044B000300001711 +2111BA046823046EFB92000000020006FF04062105240002000500000521110901110123 +048CFA57061B8A0492FAFC0620F9E000000900AB0000068005D50007000C00130022002A +0032003A004100490000013317110723271105171507272517072326273505321F011407 +062322272635343736012117150721273525211715072127350333161715072735253317 +15072735253317110723271103734D06064D0602373AF83DFCFDFC3D03C82D0230D0590D +BE472EAF6223B743FD4B01530606FEAD06047501590707FEA706470386723DF8FDB5033B +F63E021B4D06064D0605D506FEA20606015E9B3F03FE3FEEFE40C73704B5E160BD6417A7 +3F5CB5671BFEED064F06064F06064F06064FFEE88279033EFD042A3C03FE3F047606FEA2 +0606015E00010068FFFB079702E1002200000133321F01363316151407161D0106232135 +3237363B0127343F011727343F0132173604F516D9751527368722671250F93339862E34 +210CA0272A05CC43302E7802E1E856231B6D313417481A6509AE27316C3104040C935A08 +2B64000000010064000006C805D5003F000001331715332001161D01232627262B012207 +15140727262311140F01222F013537331715163B01323F01112207062327353723262723 +220F0123353637362135038B3A060201B801182B02161323392C9670082197886A1E5B25 +02062B050B3E0A371406D356110E07070443C543632214033AC0EF013805D5067DFE1A56 +0D080F2E1865330F02423DFD53651802601C1A06060C65392A02A43D39060C33401B3D12 +0290DADF7D000000001A00AAFFFF0682076B000D0015001D002500430060008C00B700E3 +010E013A0164019001BB01E6020F023B0265026D0275027D02A902D302FD032703530000 +011633323733060726273316333237262736371617060526273637161706032627363716 +170627061514163332363534272627323332373635342623220615141716330613363732 +1F0116140706071617161514042024353437363726272634370117272633320F01373633 +3215140F011716151423222F011716232235370706232235343F01272635343332011727 +26320F013736333215140F011716151423222F011716232235370706232235343F012726 +353433320517272633320F013736333215140F011716151423222F011716232235370706 +232235343F0127263534333213172726320F013736333215140F011716151423222F0117 +16232235370706232235343F012726353433320117272633320F013736333215140F0117 +16151423222F011714232235370706232235343F0127263534333201172726320F013736 +333215140F011716151423222F0117142235370706232235343F01272635343332051727 +34333215073736333215140F011716151423222F011716232235370706232235343F0127 +263534333203172726320F013736333215140F011716151423222F011716232235370706 +232235343F0127263534333205172726320F013736333215140F011716151423222F0117 +16232235370706232235343F012726353433321F012726320F013736333215140F011716 +1423222F0117142322353707062322343F012726353433323717273433320F0137363332 +15140F011716151423222F011716232235370706232235343F0127263534333237172726 +320F013736333215140F011716151423222F0117162235370706232235343F0127263534 +33321326273637161706052627363716170617262736371617060117272633320F013736 +333215140F011716151423222F011716232235370706232235343F012726353433320117 +2726320F013736333215140F011716151423222F0117142235370706232235343F012726 +3534333205172726320F013736333215140F011716151423222F01171622353707062322 +35343F0127263534333201172726320F013736333215140F011716151423222F01171622 +35370706232235343F012726353433320117272633320F013736333215140F0117161514 +23222F011716232235370706232235343F0127263534333203FC080842191905807E0918 +253C077A2A01032B270303FEEB2903032B2803034026020327240202CA7CF7AFAEF87C65 +8B02033C2D417D5D5C8A422D3790151A93843102534C14186F578EFEE4FE6AFEE38F5C76 +15134C4CFDA92906010F1102062804041008302F09100404280601100E052803050E092E +31070F0501282506021E02062504040E072C2A070C05032304010E0D052403040D08292A +080E0403662405020E1002062503050D072B29080C06022405010F0C042304030E082A2B +080F03662506021E02062504040E072C2A070C06022405010E0D052403040D08292A080E +04FBCB130201070801031401020803181704080201130207060213020306041718030801 +031715030112010416020209041A1805080202160410031502020804191A040902FCBB13 +040708041303020805161604070203130301080704130302070416160508025C14040110 +0103130302070417160507020313030108070313020208051616050802011C1302011001 +031301030804171605080301130301080702130201080516170408016F13020210010314 +01020804171704080201130207060213020306041617030603F715030709010316010408 +051A1906080402140201090703150302080518190408025B16030112010416020208031A +180508020216030110021501030805191A040902AE25020229230303FEB7260203272501 +01FC2602022923030301602305010E0F01062403050D072A29080D05032404020E0D0523 +04040E082B2C070E04FC5515030112010415030209051918050802031504100315020208 +04191A040902FE5117040112010315020208031A19040802021502011004170102090618 +1A040902043017040112010315020208031A190408020215020110031601030806181905 +0902FC452505020E1003052503050D072B29080C06022404020F0C042403030E082A2B08 +0F0303890126450507432656022A2C0303292D0402292D03032A2CFE1202252602022526 +9E669391D0D09193665520334868666565666848321E01D9701875135EE85118101C4673 +A5A3E6E6A3A5734B1A0F1651E847FD6821390F0F392104110B031516030C0F0221371010 +3721020F0C031615030B1101D11F340D0D341F03100705141303090E021E320F0F321E02 +0E090313140507104B1F340E0E341F03100703151304090E021E320E0E321E020E090413 +15030710011B1E320E0E321E020E0904131403090F031D310E0E311D030F090314130409 +0E029A101C06061C1001070602090B020408020E190707190E020804020B09020607FEE3 +111D0A0A1D11030A05010D0A03060701111E07071E11010706030A0D01050AA8101C0707 +1C1002070602090B020508020F1A08081A0F020805020B090206070118101C06061C1002 +0804030A0A020507010F190808190F010705020A0A030408180F1B07071B0F020805010A +0B01060701101B08081B10010706010B0A010607A10F1B07071B0F010704010C0A010C01 +0F1B07071B0F010C010A0C01040737121F07071F12010805020C0C02050801121D0A0A1D +12010805020C0C020508D9121F07071F12010805020B0C02050A03101C09091C10030A05 +020C0B020607FAE002252602022526E202252702032526020225270203252601811D320E +0E321D030F09031314030A0E041B2F0F0F2F1B040E0A03141303090F0267121E08081E12 +010A04020B0C02060802111D09091D11020806020C0B02040AFB121F07071F1201080503 +0B0C02050901121C0A0A1C12010905020C0B0305080182111E08081E11020807010B0C02 +050901111C09091C11010905020C0B010708FD7F1F340E0E341F031007041413030A0E03 +1D320E0E321D030E0A03131404071000000F0083000006A9070B0017002D003E004F0060 +00710082009300A400B500C600D700E800F9010A00000116151407060F01062B01262726 +35343F0236333233160506151417161F011633323F013635342F0126232207133217161D +0114070623222F0135343736133217161D0114070623222F0135343736133217161D0114 +070623222F0135343736133217161D0114070623222F0135343736133217161D01140706 +23222F0135343736033217161D0114070623222F0135343736013217161D011407062322 +2F0135343736373217161D0114070623222F0135343736133217161D0114070623222F01 +35343736013217161D0114070623222F0135343736253217161D0114070623222F013534 +3736253217161D0114070623222F0135343736253217161D0114070623222F0135343736 +02442E1A143215607B1634262F3C290C658804033EFEFA4F09050E0E1E2C344833520420 +2030374BF71D0F071A0B0D240D011A0DA81D0F071A0B0D240D011A0D8A1C0F071A0A0E23 +0E011A0D9B1C0F071A0A0E230E011A0D031C0F071A0A0E230E011A0DBD1D0F071A0B0D24 +0D011A0D018A1C0F071A0A0E230E011A0DC21D0F071A0B0D240D011A0DC21D0F071A0B0D +240D011A0DFD551D0F071A0B0D240D011A0D01121C0F071A0A0E230E011A0D01221D0F07 +1A0B0D240D011A0D01031C0F071A0A0E230E011A0D022037513D4B473C1A73072D38505A +79390E7A0C805F652222161012232F3264741A1B302632016A1F0C0F061F12072C0E031F +1408013A1E0C0F071E13082E0C051E140801341F0C0E071E14072E0C051E1408013D1F0C +0F061F12072C0D041F1408FD021F0C0F061F12072C0E03201308FEFC1F0C0E062012072D +0D041F130802071E0C0F071E13072D0C051E1507FA1E0C0F071E13072D0C051E15070104 +200C0E061F12082D0D041F1309FB0C1E0C0F071E13082E0C051E1408D31E0C0E071E1407 +2E0C051E1407E71F0C0E062012072C0E03201308CA1F0C0E062012072D0D032013080000 +00010085FFF706A705C90009000013211B01210113090113850257BABB0256FE1CBAFE19 +FE1AB903900239FDC7FE9FFDC80160FEA002380000020085FFF706A705C9000900130000 +13211B01210113090113370309010301210B0121850257BABB0256FE1CBAFE19FE1AB944 +8C0175018098018FFE1A918EFE1803900239FDC7FE9FFDC80160FEA0023815FE4E010CFE +E601C2011801B7FE49000000000100AA000403EB05D50011000001161714070901371325 +370126353437013603B0390209FD7D01EE5626FE37E0FDDD134102930B05D50236240CFD +85FDF4E6FE38275E023417171B3F02860A000000000100AA0000068105D9001900001321 +321514070901371325370126353437012111060726271136ED0549401BFDC101E75925FE +31E7FDEF0F3401E5FBA8014846010105D953221BFDC5FE1BD5FE3D226C0216102D1C3201 +DFFAFC44010143055B390000000300AA0000068205D8000D001900260000011000212000 +113412243332041205100020001134022420040205140623222635343E01321E010682FE +4AFEC9FECBFE4AC90168BABD0168C8FA71018A0230018AB4FEBCFEACFEBCB4035E6D4E4D +6E325A5E5A3202ECFEC9FE4B01B50137C20169C1C1FE99C4FEE8FE7701890118B10142AE +AEFEBEB14E6D6D4E315A30305A0000000003007D0000069F05D1000A0016004300000022 +061514163332363534252206151416333236353426251617161514062322263534373637 +36353424200415141716171610062322261037363726353400212000151405B19467674A +4968FB9C496868494A6767041719165BB68281B65A516F79FEB1FE20FEB1797D575AB682 +80B75B12137A01C00141014401C001E9674A496767494A67674A496868494A675511165C +8281B7B781825C51097399B4FDFDB4997204575CFEFCB7B701045C110F8EB6F8015EFEA2 +F8B100000003007D0000069F05C9000A0016004100000134262322061514163236253426 +232206151416333236011400212000353437262726103633321610070607061514042024 +35342726272610363332161007060716061868494A67679467FC4D674A496868494A6704 +23FE40FEBCFEBFFE407A13125BB78082B65A577D79014F01E0014F796F515AB68182B65B +1619730491496868494A67674A4A67674A496767FE0CF7FEA3015DF7B48D0F125B0102B6 +B6FEFE5B58037297B4FBFBB4987109515C0102B6B6FEFE5C16118B000002007DFFFD04E2 +05C6000B001B000001220615141633323635342637161716100023220010003332170117 +02868FC9C98F8EC9C7BE131298FECED9D7FECE012FDA6054010E9A035FC8908EC9C98E90 +C83B101399FE50FECF013101B001321D01D359000003007D0001079E05C9000800110031 +000001220614163236342600220614163332363401161514002322003534003332171617 +37263534003332001514002322272627023378A9A9F0A9A803B3F2A8A87978A9FCBE26FE +FFB7B5FEFF00FFB7B9800202DC1C00FFB7B80100FEFFB7B5810B0B02DAA9F0A9A9F0A902 +5AA9F0A9A9F0FDE45464B5FEFF0101B5B70101800302894955B70101FEFFB7B5FEFF810B +0C000000000E008C0000096B05D5002300350041004D0059006500720080008D009A00A7 +00B500C500D9000001330405041506232227252635343736353427210615141716151407 +050623222734252401353315141716190121111037363D01331503141633323635342623 +220605140623222635343633321627140623222635343633321611140623222635343633 +3216021606070626272636373633320116060706232226272636373616011E010E01272E +013534373E01041E011514070E01272E0137362436161716060706262726353425361617 +16151406070626272636013E01171E01070E012322272E013534013E013332171E011514 +070E012322272E01353404F709025B0101010F24670607FE693C0C4422FB9C22440C3DFE +690706662501100100034C90F1E7F957E8F091ABECA5A8EAE9A9A7EA0272835E5D83835D +5E839F20181721211718202018172121171820D11A0613132E0E0E08130F1105019B0F07 +140F11051B0F0D0713132EFE0C18140E2A16161203082902582C110308291616150708FD +AF2C2A0707141618290803027F162908031116162B060715FE150E2E1313080F0D1C0511 +0F1305017D0F1B05110F14040C0D1B05120F130405D5119BA4AA99018C122B1319472017 +030317204718142B128C0199AAA499FE764B984B395FFEE2FE1601EA011E5F394B984BFD +DDA7EAEAA7A6ECECA55D84845D5F8383DE16222216182222FD7318212118172222025726 +2E0D0F0713142D0F0AFDEC132E0F0A0414132D0E0D06016108292C150607220D09091615 +C30E220D080916150807291716060E141618290806151609080DEC07151609080D210807 +15161629FE9412070D0E2F1114040A0F1C0511020C13040A0F1B04121013040C0D1C0511 +001000910000097005D50011001D0025002D00350041004D005900640070007C00880094 +00A600CA00F0000001352315060706151121113427262735231505321615140623222635 +3436042206141632363402220614163236340222061416323634010E01171E01373E0127 +2E01010E01171E01373E01272E0101061617163637362627260605061617163E01262726 +06051E01373E01272E01070E01251E01373E01272E01070E010116363736262726060706 +1601163637362627260607061613353315141716190121111037363D0133152506151417 +16151407052322273637362516323704171617062B012526353437363534273716151407 +06151417051633323726272425271523040506071633323725363534272635343703EC31 +0FCBC3059DC4CA0838FEFAA6E5E6A5A3E7E60100B88181B881BC2E20202E20202E20202E +20FEF212080D0F2D1312060D0D2D015913070D0E2D1313070E0D2DFDFC07161516290707 +14171629024707141615290E14161529FDAB07291716140707291615160255062A151614 +070729151614FE3E132D0D0F0812132D0F0D08017E132D0D0E0713132D0E0D075686E1D8 +F9C9D8E186FEA91A4B1C3BFE8A0A79410DF8F802431830180242F8F80D41780AFE893B1C +4B191927361F1F01460F0F4A3E0AECFEC7FE243036FE23FEC7EB0A3E4A0F0F01461F1F36 +2603C2408E403141F1FE65019BF14131408E4048E7A3A4E6E6A4A3E7AD81B88080B80114 +202E20202EFDB5202E20202E02450D2D1312080D0F2D131107FDFF0E2D1313070F0D2C13 +130701311629070714171629070716D515290707152C2808071439171407072915161607 +0828A91516070729161516070729FE750F0713132D0D0E0713132C01E60D0812132D0D0F +0812132D0120479C373757FEF5FE3601CA010B5737379C47E506122044261A260BA5CBC7 +658B110101118B65C7CBA50B261A26442012061C1222283D161514138806979D70811901 +011981709D970688131415163D282212000200B80000067505D50007000B000013211711 +0721271117112111BE05B00707FA50066E04E005D507FA38060605C868FB0804F8000000 +000300B70000067605D50007000B00220000132117110721271117112111071506070003 +06230623022B0135373217333637363736BD05B30606FA4D066F04E0874C29FEB93E0610 +750D65CD17A2974906333F629B6105D506FA38070705C868FB0804F89E06413AFE46FEBD +3B2F0102074983A674B3BB78000300B70000067605D50007000B001A0000132117110721 +271117112111050901170901150701230123270901BD05B30606FA4D066F04E0FBF7019B +01975FFE6801985BFE6504FE69045B0197FE6905D506FA38070705C868FB0804F886FE69 +01975BFE65FE66035B0197FE6C5B019A019B00000001009A00A003A70540001400001333 +01360132373317090115070123012327010035F10501290B011A04060455FED2012E57FE +D402FED203570131FECF0540FE0B0801DE0F55FE04FE07035301F7FE0B5601F901F50700 +00080064000006F5077600080011001A0023002C0035003E007E00000133062B01263D01 +340133062B01263D01342533062B01263D01341733062B01263D01340133062B01263D01 +340533062B01263D01341733062B01263D013425331715332001161D01232627262B0122 +0715140727262311140F01222F013537331715163B01323F011122070623273537232627 +23220F012335363736213501D6031E5C063A0256021D5C0739021E031E5C063AF7041E5C +063A021E031D5D063A0126021C5D07395B031E5C063AFDAB3A060201B801182B02161323 +392C9670082197886A1E5B2502062B050B3E0A371406D356110E07070443C54363221403 +3AC0EF01380601CF0A2302380164CF08230337E3CF08240336B9CF08240238013BCE0724 +033769CF07240337EBCF092302399A0570FE504C0C070D29165A2E0D023B36FD9F5A1502 +56181706060A5A332502593633050B2E381937100280C2C6700000000009009800000693 +05D9000B0011001C00330044004D0079007F008F00000114151417213637342321061316 +1721363F0106073637363534352623270607333215140706070607333237363736373635 +342301331406071416172326272627343534360116333237363721160536373217151405 +0717140721263D013735242F0135363316172611343321161533321514070607062B0127 +0116172136370106070607233E0135342635331E011501230F04810B031EFBA01C3F0510 +03F40D0949093A57301D0120390201363B452C512032073A26136C361D1878FD30096201 +880408136D210A6FFEDBB8BDAEB1225CFC3E250345FE661206FE997E0210FDF11104FE65 +3203041255D6C936046721368F56237035430711FC2B060A04430B04FE9806133F0B0603 +5039050A4002B40607374834581604FECD1A121616C76D86014D3752060511561C202F82 +4D28013D483A0D1F2C714C55440329388857716BB192652D5005066578FAF30D0B10974E +57180A0A0A103D240A0F08071006020255120D040B0914FE012F300218639B9929164804 +013F1A12131901F5321C405B6E44463854242E4A3F000000000200AC0001068105D90007 +000C000009011307212713090103210301039E028F5403FA3406580297FDCD4B04F347FD +CF05D9FE77FBB40306044C0186FE35FC5F03A1014B000000000100AC0000068105D00009 +000009011317072127130037039B028C570303FA34065A027E1405D0FE77FBBF03030604 +440180060001009F0000068D05D400530000010326353437363332163236333217161514 +070325363332171617161514070E01151416151407060706232227262703161510072336 +1134270306070623222726272635343635342627263534373637363332170378D84A5825 +2658412C423A5C380D5BC701454D48393523234607126C250C0B21394021236A4CCF0970 +5F9207CE4D6923223F38220C0B256C12074623233538484E03080134686F6B4016525272 +2A257F74FEEB651C110C1B4242151539271D11372A1F24251B290C226601165853FEDDE7 +F201354549FEEA66220C291B25231E2C37111D2739151443421B0C111C000000000200AB +011E06810498003E00450000011617140706070413331707230607222734331633323332 +372427232207161506052403343716171507141732371225161D01140736373235363736 +3534260306151417352605A76F144AB0140100371B132E031198621914103902014D0DFE +FA070383448609FEB2FD992C4233080D71590D35012AC42A5D643D484E39258701E52004 +9805735719242C1BFEF04A0DAE095D542D4D4DF16D4065F6100301643A09032C17275A0C +4D01DE1406A00827634D1426542924240D3CFEA50809844903B50000000100AC00E70681 +048D003200002521222635343637363733372326272635343E013B013721222635343637 +3637213721222635343E01332136373633213216150636FD502B3D1C1912133604882F19 +1F1D321AB401FEFF443E1D191416015202FD032B3E1D321A032C08101F2B01762A3EE73E +2B1B320D0A030C021A1E2C1B321C0A3D2C1A320E0B020A3D2C1B331A16111F3E2C000000 +000100AC00E70681048D0032000013343633213217161721321E01151406232117211617 +1E0115140623211733321E01151407060723173316171E011514062321AC3E2A01762B1E +1108032C1A311E3E2BFD030201521614191C3D44FEFF01B31B321C1E1930870436131218 +1D3D2BFD5004232C3E1F11161A331B2C3D0A020B0E321A2C3D0A1C321B2C1E1A020C030A +0D321B2B3E000000000200B200D4067B048D002200440000251334262321220721302322 +1433210721221514332107232215143B0107232215143307262736373337232627363733 +3721222736372137212627363321363721321615030608493021FE9F3617FCD9044F5302 +FF0DFE9E5353013A09DD5353AE133E53531C5D01015D4A04AB5D01015DE402FEC65D0101 +5D016804FD045C01015C032C204201762A3E4BF10315253339924C4B494F4B484C4B491D +016867010E01686701136867010F01686745023F2DFCB300000200930000044C05CA0021 +0044000037053236351134271136232215112711342322151127353423221D0127353423 +221527363716171517353637161715171134371617111711363716151116171114062325 +13B1031524343A024A494C4C484F4B484C4B491D016867010E01686701126967010F0168 +6744023E2DFCB201734A31200162361603275353FD020D01635252FEC409DF5353AE123F +53531C5C01015C4B04AC5C01015CE403013B5C01015CFE960402FD5D01015DFCD42141FE +8A2A3E4B029B0000000200B100D3067B048D002100430000253235342B0127333235342B +0127213235342321272132342B0121262321220615130703343633211617213217060721 +172116170623211733161706072317331617060703C153533F12AE5353DF09013C5252FE +9D0D02FE534F04FCD91636FE9E20314A284B3E2A01764121032C5D01015DFD0304016A5C +01015CFEC503E45C01015CAC044B5C01015CF1494B4C484B4F494B4C92393325FCEB1E03 +4E2D3F02456768010F01676813016768010E01676901000000020093FFF9044C05C20021 +00430000131433323D0137151433323D0137111433323511371114333227113635113426 +230527253216151106071114072627110711060726351107150607262735071506072627 +B1494B4C484B4F484C4C494A023A3424FCEB1E034E2D3E02446768010F01676912016768 +010E0167680102B353533D14AE5353DD09FEC6535301620DFD0153530327173601612130 +4A294B3E2AFE8A4220FCD45C01015C02FC04FE985D01015D013A02E45D01015DAB044A5D +01015D00001D007D000006AF05D8004F0080008B009500A400B300BA00C100D600DC00E3 +00E700EB00EF00F300F700FB00FF01030107010B011A0126012A012E01320136013A013E +0000012017161D01140717151407333635363B013215161D010623270607151737161514 +07142B01222F01062322270615062B0122352635343717373525072227353437363B0132 +1F01352737263536253601141733352635363B011707171507151417161D011733373437 +36352737352737161D010717363D013427262B012007060516151407062B012227342116 +170623222734353413332433173635263526230607061525062306151737161735342726 +352605171507263534371615140727350116333637263534370607062B01262726271615 +143716173335260515323F01220705151735251537350515173533153727071537270715 +333533153335071517351715333507153335052707151615163332373637263506250716 +151417323F0126230725071735071517352715173517153735371537353715373503A701 +31923A65061906A02C24083F461A3F59F83EEA465F4C41063345BE4CACAF48D1332C0E38 +4C594CE4FED06C450746042721303C85190C6B23010767FE9A46071F0B0D0D130D201399 +4565597FC4211A210D1F191F0647E0807026FEAD691401712E02385A064E2401CFCB0625 +604243EB0601181866204607141C34F9FC22110E342E5F3FF165CB1902650E33267B4528 +31FEF52DD6D92B011C241C4B7438BF0C072D16101419060901870F03190D18FE96120124 +1AFED619CC1B0247170373201419A51973194D20FDF9720745060E13469B2213BA030F12 +E332150C3E061F5FFDA3031D19197019CC1B0F19141A05D8C16C544572665E06352E3E3D +397C3522182D076C2912640D122C263E76765D636363195770432D2B0D0D64129507380D +1B4270703E06575D6FD8E6611DFE78767550703832132C8E44A80C5E1E382C180D1A5E42 +244BBB9C122B140B2231AE18525B2BD2652AF13BD00D320B0D9B9B570A51979B030250FE +128205080A49263F09677013EB6F25190B0512770D5E195341240D079F320C39692D356E +300832A2FE86C70EBF0A0C416926AE4916595E505D44139B25885C3F3F5C4F57430D2C05 +2C05310A2F0F32053131082F063D023B06373737373D3305320E31380731317B05051339 +3038684E1D251E625C44682C0F29683F1205CC3106374932063121310732183206320432 +05330A3207310000000100AC0000068105D8002600000132171615140701071417253217 +151423052227263D013437003736353427230522273534373304428F71347CFD6C0B6103 +6E4E0969FCA1A16C1B8B026D140B620BFCB5390B620E05D88D4D4F8A62FDA03E591AD161 +0359C6AE3538188A6C02371B1B165020CC501E400C000000000600AA0000068205D80011 +00240030003C00460058000001200116151005062B012001263D01100136052623200306 +15100516332013363D010225260123222713163B013237130601061D0121351025363713 +062516172126272627131601321716151407062B012227263D013437360389017F010872 +FEC3C2D420FE84FEFA630152B9021EA6A7FED4EB7E0117B2DF0155EA5901FEAD05FED41C +A88DD1303808322ED48DFEE05FFE57011F1213D20702865F03FE56011F222BD27DFE4952 +381944292E07503914482705D8FE8DAFD6FEB0FD930183AEB21C0153010284AC5DFED9B1 +C2FEC4E28A015A96A60F0159E004FB4859016F1F1DFE92580328495F05100120DB0D0CFE +92048F91B03B2E3219016C4FFECF50252E47361F5225260549381C0000060064000006C8 +05D8000F001D0029004E006C008D0000013E013534273637161514060726353403262322 +060726273633321617060114161706072E01353437160106232227262707060706232227 +163332363736353427371633323717061514171E01333201161716151407331617161115 +2E012306072736353427353637363534260126353437363B0127262712250E0115141716 +1F010615141707262B0122232206043844480124240279550A0E3F4C075B3E2115577407 +854A19FE285B460310686F022A03C16DA62E32A45C013D775C607C80555037B83A2F1B45 +22201F1D401A373FA04550FEA6A25D1045046978C30DD270B782460140B6543518FB8604 +796DB412022C02110114B1222665BF0142024071A60E030480B20191288B410C0C0E0513 +125FC32518190E020D23042B161C42053721FEEA4E8F2026222ECB4F100F06FDB2530624 +AA0258452F4B2841615D644C502A18172746436159723D05A043D934346D6F0A4382FEFF +0A9DB60A9328070746194B1F9C57591A8EFC46181995A176026289010E7068B427554498 +27521D3C090A24889300000000170064FFFC04F605D10007001D0023002B003F0056006D +008300A200AD00B200B700BC00C100C900D000D500DA00DF00E500ED00F4012300000106 +151433323726172207062322273727363332171617060726272435340106072326370316 +333235342726131506232227352635363716170615140407232627152735242736371617 +061514041514072736353427360315273524273637161706151417060417060727363534 +072427363716170615140417060726273635342715271315273524353637333217071706 +232227262322151404170607262724273601141E0133263437220E010516173726271617 +372627161737262716073726271415140717353427060717363534051736370607173637 +0607173637061F013534370617372635343506153F012627061514171527352623220720 +273625161716333526272E0135343E01321E011514060706073314231536370417062126 +23220357040B0C190A4F0708423719165B6C233E0B0DCC0D028D469D0112FE885F1F0F01 +56681B0D0A0522EF0B1A2203AE04402E2C3D0136020F1A744AFEE80402803456AE01B670 +31450201514AFE930305714D69CE010A027602059668A3FDFEF30501704849A302150304 +AE213AAFBD4A4A4AFE810DCD083D346D5C1718384208072A02D20607AE3F6F0107020CFE +761221120F0F11221201BA742C1B609773262A5F7E5011313E8135013D2767163D3E3234 +3C2BFD131B2D735B2C2A2673641431115054103E334A403D1627683C343201E54A2D4823 +2AFEF55954015F175902010302141717292A29171614030301015818015E5459FEF52A24 +4404200804090A143F02180517193E02047D3E201109142A30FC74223C482303A60B0805 +0909FC2D381A38291E3631180D06151529366638AD5A0D5C2B48442013091A272A3E4A2E +0E0B111D05051701998C0D892662351614070F1D020234435545201B271A27A42160391E +0C07212C284D5445150F0B1E2A211D850C02047F087D15717E043D18170518022F2B2966 +5B14100F1C341F026612201121422211203C4030263915633F22562B8047136F4D934B08 +7E61110F843F0B0C7D62893919655C0D50263040116B223F632A89134780458908064A8E +588D0B3F840F11657D041939890D0D5CABCE06C2340CA55C06361E011801020A27161527 +1616271516270A020101161E36065CA50D000000000200AA0000054E05D9000800280000 +0126270607161736350326273637161715140733323711262B02111417213635112B0122 +0711163303790279790208737BC5770C03CACA0383A3B5AEA9B306A463FEA663A806B3A9 +AEB504DEB10202B18E71589FFEF165B8F30202F30EB75863FEF736FE0CB5AEAEB501F436 +01096300000300AAFFFD048805D900030020002C00000115333527353315331523153305 +15211117152703230327351711213521352335133311213521352315211521022DCAB7AB +7D7D090194FE638F8801B1018686FE6B019676A447010CFEF447FEF5010B05403C3C3366 +66A25801AFFDE88B9C85FEEE01BC869B820171B058A2FCCF01BB476363470000000200AA +0000059B074A000700200000011133323610262301112132161514062B01113733090123 +0311231103230901330328D1747F7F74FE8A0176CED4D4CED1DADCFE6701AEDBEFA6FEDB +01B7FE6EDC06A7FDD890010890FC04049FDFD8D9DEFECDEDFE42FE280106FEFA0117FEE9 +01E101B5000100AA000003B205D7001F0000013317153317150723152117150721110723 +271121273537213523273537333501D7AB06A50505A501240606FEDC06AB06FEDE050501 +22A30505A305D7059B06940671069805FC820505037E059806710694069B0000000100AA +FFFD068205D5002B00000121171507231121353733171107232735211133171507212735 +37331121150723271137331715211123273502BB01B60606A0023B066A06066A06FDC5A0 +0606FE4A0606AAFDC4067306067306023CAA0605D5067706FDD1A10606FE4906069EFDC8 +066A06066A0602389E060601B70606A1022F0677000200B20000067B05D8001B002C0000 +013332172327200306151005163325150607062B01200326351025360133321733150716 +152707233537273533039C15DDD206EFFEA9D15E0156889C0101A08A51361CFE91EE8401 +86AC027F03063ED1A841B1A90342ACD505D89236FECA9EB9FE92D64732066B1A0B013EC0 +E901AAEE59FE33C9037EBB117C7C03C97E030000000400AA0000068305D90016004F0062 +0073000001163332371633323735331715140F0122270623222734173316173215033324 +113427351611151007060715163B0115062B012207272322273533323735272627263510 +3F011506151005350335373225150607061514171523262726351025363334251617161D +01140706073437363D01102502EA1B402E21252C42150502451E2C23212D5110B2023222 +080A0201246EDEE04C2F50A51A7548811645449038883C7956587D2463C7217501261203 +23FF005F418692029E5D26010C7923027B8482B0A6591F365CFED605D94B303044072805 +3B1B042C2C652287400B05FC20E40170C2CF03D7FEEB24FEF1DD42160528052163631C05 +2A0340724AB1B10101E61F05C0E2FEA7F502032616A92A064563CCEFE9D6024AE96B6701 +0CCD4604021E70B7D82FD4B651060752AFBF02016DF40000000300AA0000050505D9005B +0066006E00000132170F0116171615140717231532373635342737331611100326232215 +1417161514150623062322272627151415141714232235363D010615062B012227343635 +34232207021134373317061514171617352335263534372F013603141733363534270607 +06250615141736353402D84252130F7F3338CD0178798E877D1505EA95784F38781E0D2A +0C092C063B5E50686F509A073906290E962E52749AE405157DD63F7977CCEE181057CE87 +04461D593328017022419005D94B0F7C2E505155C7624B78819DC6B07213A5FEFEFEE0FE +C9AE2D1C93262A02033B0222EE3196030446713B3C744D925DBF233B40A4222BAD012101 +4DE9A61379AEFBA32F12784B5CCCBE677C1248FDFF924A698B537E194A3FA2736389663D +A8980000000100AB0000068105D700320000013217161514071707232706232227150123 +26273601363316333237270723262713041507161735363D013427262B0122073603E8ED +D1967DC28C03B9A5D9AA8FFEFC03CC041A01591807719EBF97D96C06BB09E9011B7EE61D +42FB8977256278AA05D7BD9BD7C98A9483AA6E5803FE318D071901981D5568C673740C01 +020E059FAF0E025A7C24FE6545378F00000500AA0000068205D8000B00120016001A0021 +0000131000212000111000212000131417010306001316171B0136370901363534002711 +AA01B60136013701B5FE4BFEC9FECAFE4A987901AA02C4FEA3ADB0C0025FCDAAFE8901BB +71FEA1D302EA013701B7FE49FEC9FECAFE4C01B40136E57801F501C204FEA3FD62B10602 +68FD9608A401B6FE9770F5F9015D04FE42000000000400AA0000068205D80012001E0028 +0034000001151E01333236351000212000033E0133321605140623222635343633321605 +34262206151416323625100021200011100021200003960AC59092C9FE62FEE4FEDEFE7B +1418B1958FC601CA3D262A3C3C2A283BFD453B523A3A523B03E5FE4BFEC9FECAFE4A01B6 +0136013701B502F81A8EC1CF9A01160198FE7BFEC9ACB2C09E293A3A292A3C3C2A2A3C3C +2A293A3C27FECAFE4C01B40136013701B7FE4900FFFF00AA0000068205D512260F610000 +10270F610000028610070F610000050DFFFF00AA0000068305D412260F61000010270F61 +0001028510070F620000050CFFFF00AA0000068205D512260F61000010270F6200000285 +10070F610000050DFFFF00AA0000068205D512260F61000010270F620000028510070F62 +0000050DFFFF00AA0000068205D512260F62000010270F610000028510070F610000050D +FFFF00AA0000068205D512260F62000010270F610000028510070F620000050DFFFF00AA +0000068205D512260F62000010270F620000028510070F610000050DFFFF00AA00000682 +05D512260F62000010270F620000028510070F620000050D000A0087FFEA06A505E1000C +00400046004C00520058005E0064006A0070000000141716333236342726232207052634 +373336372736371736373536321715161737161707161733161407230607170607270607 +150623273526270726273726270136370306070516173726270136372706072516172526 +2701262705161725060717363705060713363725262707161702F4302F43425E302F4143 +2FFD7F1C1C701B6B540F63527CC2274E28B6894F661155691C701C1C7014715511664F8A +B627274EB58A51611151681B01FB21240C8E620180291DB7648CFEAF0816CB4C1702F718 +0601121351FD6B140AFEF0164B02B40816CC5211FE7421250C8D63FE7F1D27B8648D0326 +842F2F5E84303030C3274E27B77C505F1050661E701C1C70166F510F605179B9274E27B0 +83505C0B51671D701C1C70176D510B5C5079BA012C130A010D164DAA0A13C64E15FE3E26 +1AAB6082371E220A7F62FE4B1E2A118C56F42622AC677B89120BFEF01E4DA50914C24D1E +000500AAFF6A07AD066E000A00150021002D003D00000134363332161406232226253436 +321615140623222605100021200011100021200013100021200011100021200013363736 +2017161707262726200706070282513B3A52523A3B510242527453533A3B51FBE6020E01 +740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE41EC2433B60204 +B63225731D278EFE6C8E281C03FD3B51517652533A3B51513B3A5353D90175020FFDF1FE +8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FD253833B5B53338482C278E8D +282B0000000500AAFF6A07AD066E000A00150021002D003D000001343633321614062322 +262534363216151406232226051000212000111000212000131000212000111000212000 +133716171620373637170607062027260282513B3A52523A3B510242527453533A3B51FB +E6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE41EC73 +1C288E01948E271D732532B6FDFCB63303FD3B51517652533A3B51513B3A5353D9017502 +0FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FDAA472B278E8E272C +483833B5B5330000000400AAFF6A07AD066E000A00150021003100000114163332363426 +232206051416333236353426220601100021200011100021200025161716203736372706 +0706202726270282513B3A52523A3B510242513B3A53537452FBE6020E01740175020CFD +F4FE8BFE8CFDF201732433B60204B63225731D278EFE6C8E281C03FD3A53527651513B3A +53533A3B5151FEB20175020FFDF1FE8BFE8CFDF4020C5B3833B5B53338482C278E8E272B +000A00AA0000068205D80007000C00130022002A0032003A004100490058000001331711 +0723271105171507272517072326273505321F0114070623222726353437360121171507 +212735252117150721273503331617150727352533171507273525331711072327110306 +1514171633323736352726232203734E06064E06023939F83DFCFBFD3E03C82D0231D75C +0DC44830B56525BD46FD4B01530606FEAD060477015A0707FEA606470386723DF8FDB403 +3BF73D021B4E06064E06248219457C203186093F922705D806FEA10606015F9C3E03FF3F +EEFE40C73704B5E260BD6418A8405CB4681BFEED0650060650060650060650FEE7827903 +3FFD042B3C03FE3E047706FEA10606015F0256477B3F2C72114382419A000000000202DD +0000068305D70017002B000001200116151001062B012227352437361110252627353437 +17150411100507153332373637363D0110012623037E018B01106AFE74B3B0312F510115 +7D9BFEB2885D952801C9FEAF2B06569EE9521DFEA5A39E05D7FE87ADC6FE78FEFF620F06 +4FB1CA010C0190E05010030B0E3E03C5FE14FE6EE818045398F45D5D09013D0105660000 +000200AA0000045005D80017002B000021200126351001363B0132171504070611100516 +1715140727352411102537352322070607061D011001163303AFFE75FEEF69018CB3B131 +2E50FEEC7E9A014E885D9528FE3701502C06569FE8531C015AA49E0179ADC60188010163 +0F074FB1CBFEF5FE70E14F10040A0E3E03C501ED0191E818035396F55C5E09FEC2FEFC66 +000200AFFF30043A05DB0031003E00000116171615140706071537150717232723353327 +26272635343736372627263D01331514171617333637363D013315140706052306070610 +163332361027260369282485846C8FD8DC018101CACA02936D858524282824858E5B5575 +2674555B8E8524FEF72675555BB7827FB75B5504561A2483BDBC7C640ED1026401CACB62 +D10D657CBCBD83241A192583BC080A825A530707535A820A08BC83255F065459FEFCAAAA +01045954000200AFFEFF052D05DA000B0023000001220615141633323635342601263534 +003332001007060711211521112311213521112602EEA4E7E7A4A1EAEAFDC9A90150EFEC +0153A988B40110FEF0B4FEF00110B50527E4A4A3D8D8A3A4E4FCED9DEEEF014CFEB4FE22 +9C7E12FEF87CFEFF01017C0109110000000200AFFFE3052D06BE000B0023000025323635 +342623220615141601161514002322001037363711213521113311211521111602EEA4E7 +E7A4A1EAEA0237A9FEB0EFECFEADA988B4FEF1010FB40110FEF0B596E4A4A3D8D8A3A4E4 +03139DEEEFFEB4014C01DE9C7E1201087C0101FEFF7CFEF711000000000200A2FFE306A5 +05C30009001E000001220610163332361026130623220010001716170121372111071101 +16151402E1A4E7E7A4A1EAEAFBA8F4EBFEAC0159E6E4700169FE8D8C01EE8FFE9A700396 +E4FEB8D8D80148E4FCF1A4013D01DC0155090867015C8EFE148F0175FEA492C1E8000000 +00010153000005D705D8001B00000135323634262322061D012334003332001514073311 +331123112135030D7AAAA97B79AB960102B8BB01026DE39797FC13021301ABF2ABAB7902 +B90104FEFCB9A87B03C5FA28017C9700000101C00000056A05D8001F0000012335333533 +1533152311363332121514061D01233436353426232206151123022A6A6A98B2B26F98B2 +EF8C978D9B6F6E9A98048D97B4B497FE7D6DFEFEBB5AE5790284EE487BA9AA7AFE460000 +000200F80000063205D8000A003100000022061514163332363534271617161406232226 +34373637112111213533112335211121113311211121152311331521112103CD704F4F38 +374F3A3D304C986D6B994C2F3EFEE0FECE9B9B0132012097012001319999FECFFEE0018C +4F37394E4E3937C212304CD89999D84C3111018CFE4B9702D398FE4A01B6FE4A01B698FD +2D9701B5000101040000062705D8003C0000251523352335333526272627350727373317 +152715331523161716171107273733171527113637363D01333507273733171527153315 +0607060715331503E297B0B0A57A9601900191989090020201684E67910192979191654D +6A019001919890900101967AA5AFBBBBBB978B167A96D7548E8C99998C8E540297684F14 +02CA8D8B9A9A8B8DFD37144E6A9601598E8C99998C8E5904D39679178B970000000201EC +0000053F05D80012001D0000012120171615100706232111211715072127111711213237 +36373427262301F40199012E6420FB3B36FEAA027A0808FCFD0891014682481008A22551 +05D8D04D5FFEF65C11FDAD0882080805C783FE24821A50A43C1000000001005D000006CE +05DA00240000211000232206151417232635343633201316173637122132161514072336 +353426232200110356FEE2A9812254865DA3920174830805040884017393A25D86552282 +A8FEE202580305C522808084A184BBFCFB312828310305BB84A184808122C4FCFBFDA800 +000200B70000067505D80008002E00000122061016203610262736373E02331522020706 +071617161514002322003534373637262726022335321E011716039593CECE0126CFCD95 +834C5661A1B97BA78315172E2B92FED9D3D0FED8942930181581AB7AB9BA48554D035CCF +FEDACECE0126CF96053D42B4AE6CFEC86C130F1E2995D2D1FED90127D1D2952A1E0E136C +01386CAEB4423D00000200C00000066A05DA0013001B0000012627350420251506071116 +171524200535363F01363317110623270212A8AA015B02F80157B0A6A6B0FEA9FD08FEA5 +AAA8987475E97475E904FC1634945A5A972F18FBE2172F985B5B9435150C0D0D04070D0D +000400E700400645056F0008001D0026003C0000002206141633323634012C0127351604 +3332272635343633321610070E01013236342622061416010C0117152624232217161514 +062322263534373E010542BE84845F5E85FE5EFEC8FE9499FD016C3B820883CD9395CE68 +49F6FDAB5E8585BC8685011D0139016C99FDFE963A850883CD9395CE6849F502B884BE85 +85BEFE0C02282B7C54161A6CA793CFCFFEDA674D4C02B684BE8484BE84027802292B7B54 +151A6CA694CFCF9492674D4D0002011E0000060D05D80013004D0000013E013534272E01 +2322070E011514171E013332133436333216151400151617323717062322263534003534 +26232206151416171E011514070607062322262726353437363736333217323534260294 +4B320E1D712724234B310E1E6F272330DFB4ACDAFEDE04864764417F6F6390011FA4847F +962E19250920337438383CAD2F161B337537374C4A084D018C1D712723234B320E1E6F27 +23244C3102F0A1C9DDB0BBFE0AB47C03584877796FC701E1C286A59B813C913E62801157 +44732D164B7836383C3C722E15290D1AB80000000002006DFE8F06BE05D8003A00440000 +013216153E013332161D013E01333216111001161723262706073536372711343510270E +010711231110262B0222060711231134273316173E010111241334262B01220602A83F77 +28C64B3E523083443C64FE830192A82E4AA7AFBF7C092E3A853F971F23010129B3139598 +8E762417B702C40102182D1C041E8F05D8C68077CFA7DBCA7D77E8FEEFFE68FEBF5EC129 +A16507741C57D90357121101190101D9C8FC61039A01416AE2D7FC740441BEBF4FB05DBC +FC92FDD0FA0152C18BCE0000000200AB006B068105390003002300001321152101213521 +26353400333200151407211521352336373635342622061514171617AB05D6FA2A022DFD +D30162430101B9BA0103440184FDAF01121156AAF4AA551013010398018F986883B90103 +FEFDB9836898980D1155797AAAAA7A7955110D0000010045FF3C06E805D8003800000114 +161733353317072735232227262703343510270E010711231110262B0222060711231134 +273316173E01333216153E013332161D0113050067595904CBCB0459CB442C08152E3A85 +3F971F23010129B31395988E762417B7453F7728C64B3E5201010F6F7D0176ADAF047678 +4A760316121101190101D9C8FC61039A01416AE2D7FC740441BEBF4FB05DBCC68077CFA7 +DBCAFD84000100A9FFFF068205D800140000011109010709012F0209013F020901213521 +1105EAFD3F013D6CFEC3FE58016A0101A8FEC3016A01013D02C1FE04030002D801FCFD3F +FEC36C013DFE58016A0101A8013D016A01FEC302C198FD00000200C00000066A05D80029 +0031000001321237363332161514062322270E0107233537361326022322021514150734 +02273532161336373E01011633323726272203536B51196DC379998F8E9B7A2C7E8AA89A +7A6932403A249A9E8D8493A1282E2D297A01575D82A109079F9805D8FC9E04DDBE6791AB +85A0D7047601040178100315FE2DC30F0E02FD01F00661DEFEEDC9585B96FC2691C59D13 +000200B00139067B04A20027005100001300333217163236333217163237363332130726 +232207062227262322070623222726232204232711003332171633323736321716333237 +3633321307262322070623222726232207062322272622042327B00145732D0D1870AC38 +36181A72562B297C6D5E3F5F333C5670191C3B375357373819183637FEFE05350144742C +0E18383756576E171B3839562B297D6D5E3F5F333C5737381A1B3B375456383719186EFE +FF0535019D010F28478E47474724FEC433B9354C4B4E43474846D56201D9010D27464648 +48464623FEC532B9364D4C4E44474848D561000000010141000005EA05D8001C00000107 +020107001321352102011700133312013700032107211201270003040EF120FEEAA50133 +1BFED801281CFECDA6011620F21F0117A5FECD1B012701FED81D0133A6FEE91F029903FE +AEFEBF03015F0137AC0136016003FEC0FEAD0153014003FEA0FECAACFEC9FEA103014101 +52000000000500C90000066305D8000F00200024005400640000013637262726220E0115 +141617161721332136373E0135342E012322070607161701112111011126272E0135343E +01333217353436373637352335333533153315231516171E011D013633321E0115140607 +06071101141E0133323E0135342E0123220E010350010223334A9C9354524A161701578D +015618164A5153934F4D4A34220201FE3F02F5FCB92A26456C6EC668504C24200D106C6C +696C6C100D1F254C4F68C66E6C442729FDD6192D17182D18182D18172D1903C703032B1C +295195505192280E08080E289251509551291C2B0303FDB8FED1012FFE81017F0D1636C4 +6B6CC56A1F02243F1207043646575746360407123F24021F6AC56C6BC436160DFE81047D +192B19192B19182D17172D00000800E20000064A05D80048004E00520068007C009000A5 +00BB0000012627343E0133321E0115060713032227343E0133321E011506071B01262734 +3E0133321E011506071B012635343E0133321E0115060703012635343E0133321E011506 +230311211101350721271D02213501170336373E0135342E0123220E0115141E01333237 +01170336373E01342E0123220E01141E013332370137131633323E01342E0123220E0114 +1617161701330332373E0135342E0123220E011514161716170137131633323E0135342E +0123220E01151416171617014D5F0C1D361D1C361C0439EF3453121D351F1C351D022EBD +2B43021E351B1D351E034326B92E1D361B1E361D0B565C01012B1D351C1E351D0D63A0FC +A30324B5FE95B702D7FCFF4DE7070712141423141223151424120709013688BB06051113 +132412132314132413070801AD7360050513251315231312241414120807FE9A64301310 +111516221312251313130E0F012968A60B0B1323141522131423131312040303FB025F1C +331C1C331C4016FE0602BE5D1C321C1C321C2D22FD1403341E301C341A1A341C3618FCCA +02E42F2A1B331C1C331B5510FD4A0203222B1C331B1B331C5EFE31FDD1022FFEDFE14B3D +D330A4A401542A020E03040A231314231414231413231401FDF82002F902030A23262413 +132426231401FD131B02D1011423262413132426230A0502FD0A03420A0A231413231414 +231314230A0802FCDA2A01EA0315221413241313241314220B02020000050155FFFE05D6 +05D6001D00210027002D0032000001211533352115231507111715331521353335373311 +232735233521153301352115013735211517110715213527351137211103080115AB010E +735C5C73FB7F735D01015D730109AA0297FBEE034A52FCDA524903144A01FD7E05D69B9B +A29096FDB7958FA3A38F9802459790A29BFAFB373703EA835F5F83FD7674808074340221 +02FDDD00000401B60000057605D8003700430051005E0000012635343736372627262735 +36373637363726272635343E0133321E0115140706071617161716171506070607161716 +1514070607012109013533352335231523153315133635342E01220E0115141703210014 +1E01323E01342E0123220602CB1F20080B3C24290101292B4E1C1E1A11162D4E2A294E2D +15101A1C1D4D2C2A01012A253A0B081F200202011AFC40010F010CACAC76ACACA23C2D4D +544D2D3FDE028AFE4B1D353A361D1E361C1D34019435393C35110E263F48500450494B2A +0E0A121D27292B4B2A2A4B2B29271D13090E2A4B49500450483E260F11353C3A360304FE +750194014E7C6C7C7C6C7CFEB22D492B4E29294E2B4036FEAC05193A351C1C353A351B1B +00040151000005DB05D800290035003E006F000001363534263534371615140700132126 +3534003D0106070607270726273635342736372E01353437161703363B010615142B0122 +27350317140723223D013613262706151416151407060716151407161737073637363716 +151400151417250201323523363534262706151416151407031E0416467A0801EA2DFC36 +0D018A5A382E9E092C8B1F4E0230481507216836A22560062B451508040607480A1C29D8 +234D191D0B3B2C044A19676123871D69520BFE820C036B27FE1201041F0D4D1F120804E8 +080B152B0E315E68671A1BFEBDFC6F383AA00128400673038E12161E2E50F9C01E1C6D7A +2013051B6E1B78FEEB3F2E23311E0FFE7023340F1E173102A760263E251B2112100F6163 +2C2CC5C04C18484A0A9004AD1E1E78FEEC7D3634010337017C010A2B0C4F4A4A29132113 +110F000000040130FFFF05FB05D7000D001D0050005B000000141E0133323E01342E0123 +220613141E0133323E0135342E0123220E01011521350726353412373637262726353436 +373637262726343E0133321E011407060716171E01151407060716171612151407272627 +2E0123220607060702BF3A6537396538396637366560101C0F101C10101D0F0E1D10025B +FBD9530BA59226281E152A554D1B1D1A0F172C4E29284E2D1511191B1E4B5629161E2626 +93A60C5C044045F58081F543410403C1706438386470673636010E101B0F0F1B100F1C10 +101CFABB010101393AA001254E150F1D264B5253972A0E0A131D27544D2A2A4D54271D14 +090E2A9753524B261D10144EFEDBA03A39847D7179838379717D0000000800C900000663 +05D8000F002000240054006400680079008A0000013637262726220E0115141617161721 +332136373E0135342E012322070607161701112111011126272E0135343E013332173534 +36373637352335333533153315231516171E011D013633321E011514060706071101141E +0133323E0135342E0123220E0101152135372126272E0135343E01333217161706153334 +2736373633321E01151406070607210350010223334A9C9354524A161701578D01561816 +4A5153934F4D4A34220201FE3F02F5FCB92A26456C6EC668504C24200D106C6C696C6C10 +0D1F254C4F68C66E6C442729FDD6192D17182D18182D18172D1901A4FD73CEFEE812113D +424477403E3D291D02F1021D293D3D417744423D1212FEE903C703032B1C295195505192 +280E08080E289251509551291C2B0303FDB8FED1012FFE81017F0D1636C46B6CC56A1F02 +243F1207043646575746360407123F24021F6AC56C6BC436160DFE81047D192B19192B19 +182D17172DFCB6C8C8CE070C217943427B4321172403020203241721437B424379210C07 +000300E20000064A05D80048004C00500000012627343E0133321E011506071303222734 +3E0133321E011506071B012627343E0133321E011506071B012635343E0133321E011506 +0703012635343E0133321E01150623031121110535211501352115014D5F0C1D361D1C36 +1C0439EF3453121D351F1C351D022EBD2B43021E351B1D351E034326B92E1D361B1E361D +0B565C01012B1D351C1E351D0D63A0FCA30324FD2902D7FD2903FB025F1C331C1C331C40 +16FE0602BE5D1C321C1C321C2D22FD1403341E301C341A1A341C3618FCCA02E42F2A1B33 +1C1C331B5510FD4A0203222B1C331B1B331C5EFE31FDD1022FCB4E4EFED64F4F00040155 +FFFE05D605D6000300070025002900000135211501352115132115333521152315071117 +1533152135333537331123273523352115330135211504D6FD8002CAFCECFC0115AB010E +735C5C73FB7F735D01015D730109AA0297FBEE04845E5EFC2F6F6F05239B9BA29096FDB7 +958FA3A38F9802459790A29BFAFB3737000201B60000057605D800340040000001343736 +37262726273536373637363726272635343E0133321E0115140706071617161716171506 +0706071617161514070121012601353335233523152315331502AC20080B3C2429010129 +2B4E1C1E1A11162D4E2A294E2D15101A1C1D4D2C2A01012A253A0B081F200116FC40010F +190125ACAC76ACAC02023C35110E263F48500450494B2A0E0A121D27292B4B2A2A4B2B29 +271D13090E2A4B49500450483E260F11353C3A36FE6E01943501197C6C7C7C6C7C000000 +0004014C000005E005D80033003F0048004E000001363534263534371E01151407331423 +00132126353400353427062B010623222337072627363534273637363534263534371617 +0715163B01323534372322070315143B01363527220117001333020316251E3A550F2204 +0202182DFC360D01950142750A5A670403306B721B58023063032D146838AA040815452B +066025731C0A48073E01C9340177064D2D04E617271939162E1E51590D2F0C01FEACFC6F +383AA0011B710807A79268501C53E5BE1B1A6D680A091C1A2325421B7AFF101E31223040 +FE29181D0F3522024059FEF3FD04035200010130FFFF05FB05D7002E0000052635341237 +3637262726353436373637262726343E0133321E011407060716171E0115140706071617 +1612151407013B0BA59226281E152A554D1B1D1A0F172C4E29284E2D1511191B1E4B5629 +161E262693A60C01393AA001254E150F1D264B5253972A0E0A131D27544D2A2A4D54271D +14090E2A9753524B261D10144EFEDBA03A39000000010143000005E805D4002300000116 +131217161D0114070623222723151017233536112723070623222F01353437003F010398 +5ED8ED0627A235409A5F2A53D75503262F557FBB4C09BB013B590305D4A7FEECFEEA3955 +5028A76016DD0CFEE5960697011D035885D1492EB7CB0182A5030000000200B900010673 +05D10018003200000132131615323712333217161D011007000723012635343736031514 +17013301363D0134272623200306152334272623220706022ADA741C060D70EBD17A26DD +FE3D3C06FDA078C751E5D001DB03023E68BD473AFEF8560A064865BDAE6B2505D1FEED50 +18460135D34E500DFEFEE7FDD94202EDB2B6D27C2DFE8F10DBE4FDBC02BBA19726AC701C +FE41261A4ED1E0B14600000000020158000005D205D40008000D00000901150007260126 +3509040396023CFDE31F0FFE1746023AFE0302010201FE5E05D4FD1A07FD3B220B028056 +0D0296FD67FD64029C021E00000100E40000064705D4003700000132171617140F013336 +3B013217161514070607220323151017152327361135230223222726353437363B013217 +33352627263D013437360397D6650D03783C034A5413EE6017D12847F2650352D0065503 +64F5BF6619F6423309564E03541D3CD74705D4E3343875A03C1CCA423CCD680C06011209 +FEA96D060680014D03FEEBC9443AE9530C1C0347456E510EC968130000030142000105EA +05D50022003C00420000011601161D011407062B01222723151017233536112307062B01 +22272635373534370037060106151617163B013237333217163B013237363D0134270203 +332627220703977E017F56B522301691622A53D7562A38556916A0511903CE01225F7CFE +AE510A941629107760B7071F53671389410ACBE68E3F0D0F061005D5E1FE368B8603D14E +10DA0DFEE89B069701236575AE423F0D03C8D4016749C7FE6F8285BB3509DA4694A7281E +1FB0CF011FFB70175A520000000100B60000067705D40017000001321716173336373633 +32171615140701230126353437360228CB742A0203235C6C87D17E20D0FDEF06FDCEA8C7 +5405D4EB5D31A26B6CE05349FAE3FD8502B1CDDAD37C2D0000010158000005D205D40008 +0000090116150126013437039601F745FDC40CFDCE6F05D4FD73550DFD1B0802DD0E8A00 +000300E40000064805DC00320060006700000132171615140F01363B0132171615140706 +07220323151017152327361135230223222726353437363B01321726273534373603141F +0115262B01220F01141716333213363733161716333237363D01342F0123220735363736 +3534272623220701140733352635039BCB681972161F3D29F85C10D22249F1660352D106 +550364F6C06719F74333263D1F631CD847F0A90C6B7609F33D07883D4ED2721016072553 +5A9EA8550CFD3606766B81201ABB3629B85301071C3C1C05DCCE463984A61706D7383AD3 +640C07011409FEA76D060680014F04FEE8CB453AEB520C0670B126CB6913FEB39BCC0C03 +09D443945D2601271B41788685BE2E2B06DE30030903896E4D2CB65710C8FBCE1C600363 +16000000000400D8FFFE065405D4001000210031004D0000013314060714161723262726 +273035343625331406071416132326272627343534360533140607141617232627262735 +3436131E011514060420242635343637330607061416042024363427262702760D9102CC +070D1DA3300FA501410D9202CD060D1DA2310FA601590D9202CD060D1CA3310FA62CA8BE +B9FEB1FE94FEB1B9BDA803834B56A7012E014A012FA6564C8305393F9B638279C9A67433 +5A0E7288ED60ED97C5BBFECEFBB24E8B0809B1D15B4FC47DA19AFDD09142720E91ADFE1E +31B16160B06262B06061B13129454DA898555598A84D4529000100ACFFF602B705D50011 +000001331711140F0123222F01343F013217331102684807C43023D41C04D321635A0405 +D508FAFDA32D04851F8218054004D400000100ACFFF6046F05D5001D0000013317151617 +161514072327363534272627111407232235343733321711025857081BADF07A041E3DE4 +5C19E039F2D417605905D50727363F468A926020315446472A1AFBD6992DA883174004D6 +00010178FF2F05B305D5001E000001051711140723223D01343F013217112511140F0123 +223D01343F0132171102FD02B303C036D5B1275651FDDC96431AD8AA2B595005D5CA04FA +ED9B2AA4047E16033A040BA9FBDB8938089B077A22044304D3000000000200BCFFF6066F +05D50018001C000001171114052227343732173311211106072320353437321711171521 +35066B04FEFBE912E96D4F03FCA508F204FEFEEC645858035B05D504FB12D515A48A0E3B +0295FD5ACF24B17D104004E1CF9C9C00000200B5FFFA032205D9000C0015000013331711 +333633161302052711131124113635342722BB2D0503AA91E3141DFDB3033801A2019577 +05D906FC3FED03FEFFFE57580305D6FBD4FE9D6301310D0C94080000000200AC00000230 +05D9000D001300001333171125171107232711052311131133251123B12906014E020428 +05FEAF023402011D0205D905FE2FA102FB63050501E5A0048AFD99FE7D90017F000200AD +0000033405D90028002C0000013317113717150607113715060711072327110511072327 +11073534371123073537113733171125110111251102872E05770303777A0179052E05FE +E0052A058181027F81052A050120FEE0012005D905FED03C028D073CFE8B37870443FED7 +040401118CFEA6040401413C8E063C017B388B41011F0404FEFA86014DFD96FE878B0174 +000100AC0000055005D900480000012330232207232227353635342735363733163B0135 +34273536373316323733161715061D01333237331617150615141715062B01262B021114 +171506072326220723262735363502B6A804B35038180B636302203953B5A783031D3A2D +8C2D3A1D0383A3B55338210263630B193750B304A483031D3A2D8C2D3A1D038303115A23 +3B3137462D3A1D0383AFB553382102636302213853B5AF83031D3A2D4637313B235AFE52 +B553392002636302203953B5000600840000059C05D90006000D0014001B003B00470000 +010607161737350515173637262701262706071733032307161736370333371617060727 +23111706072627371123072627363717333527363716170F011523153311331133352335 +013B16302F175F02ED5E172F2F17FE862C2F2C2D442D012D432D2C2E2D07E4D00A7A7A0A +D0E47E6A6968687EE4D00B7A7A0BD0E47E6869686A7E6E747436747403B42D2E2C2D432E +012E432E2C2E2D016E172F2F175FFC525F172F2F1702F77E696A68687EFE4ED00B79790B +D001B27E686969697ED6D10B7A7A0BD1739F2EFE8001802E9F000000000C00AC00000681 +05AC00140023002E00360043004D00680077008C009700A600AF00000121321F01373332 +15032127353637350227263D010733321716150207232427351237360515130715333707 +262F01011713262B01220701161317151407062B01260335171333323F01342F01062533 +32171615072327020F011707232603263D01343F01232735371715173315140715161713 +33173527013317152132373317150603062B01150723023D012717110721222F01353437 +0507173335343B01323F0121263D0105060715141F0121110328018B3A1D4D620805ADFE +A4051252BB102E711770371EB01003FED3049E112C0112CA16CA5F1C711014FD1EF0A03E +4D1C542C039B129505501724920CB4369C85362402305FE4FD1314097533050564B40F03 +05070316AD14551402640580120374019CC8021C5F027D050501732A24020506C41922C3 +0508AC520805FED67430065802585F5F0508E7162F69FE6B08FDB83D065A1E010E05AC47 +863807FED40505112A050146141604050D80310AFEC611AC0802011A082E1702FE9D0F03 +AD0DC50D05FED18A01179066FEF511FEEF29144D43120B013D050FFEEA671A3945A2816B +D74F0D0539FECB211528051B012D1A1F071C83293B050526050D0208C60A1AFE015D0803 +ACFED4057421050215FEB11D790501220D03B307FEA50586260D327C0CACAA081C62B301 +14081D61240A552B060115000004009C0020068F05D9000A00230038004D000001331107 +35373311331521052635343F012737132707061514171633323721152122272625060723 +152737153336373635342703371316151401161F01370327372726232207060703271336 +373602D5ABBACA7FABFE1BFDF32C26543EEB183D630717235D0F100196FE1C4B392E058B +55AFA7D2D2C63A190731CA88F325FCE6A960543F19EA3C63223407073F35CC88F127382F +01EA019A1A611EFE015CB84D4F484B92246BFEFF24AC141323202C019F211B61930A4997 +95460B371113344801604FFE5D424136049E01929124FEFF6B23AC2801096BFEA14E01A4 +41211B000004009C0020068F05D9001C0035004A005F000001211521353E01373E013534 +2623220607353E01333216151406070E01012635343F0127371327070615141716333237 +21152122272625060723152737153336373635342703371316151401161F013703273727 +262322070607032713363736035D0154FDF140CD1931244A3C2F67594C70307F9A26300E +8BFD0A2C26543EEB183D630717235D0F100196FE1C4B392E058B55AFA7D2D2C63A190731 +CA88F325FCE6A960543F19EA3C63223407073F35CC88F127382F01D25D55319E1529381A +283318266E1914635126442D0D6DFEBA4D4F484B92246BFEFF24AC141323202C019F211B +61930A499795460B371113344801604FFE5D424136049E01929124FEFF6B23AC2801096B +FEA14E01A441211B0004009C0020068F05D9002800410056006B0000011E011514062322 +2627351E013332363534262B013533323635342623220607353E01333216151406012635 +343F01273713270706151417163332372115212227262506072315273715333637363534 +2703371316151401161F013703273727262322070607032713363736045A0B5FAD9D336C +464865355E5C564F6F7348474A492960564B6D31819A53FC6A2C26543EEB183D63071723 +5D0F100196FE1C4B392E058B55AFA7D2D2C63A190731CA88F325FCE6A960543F19EA3C63 +223407073F35CC88F127382F02BE02533C5C650E126C1E1232332D325B272627290E1462 +0F0D594B344AFE184D4F484B92246BFEFF24AC141323202C019F211B61930A499795460B +371113344801604FFE5D424136049E01929124FEFF6B23AC2801096BFEA14E01A441211B +0005009C0020068F05D90002000D0026003B005000000103330333113315231523352135 +012635343F01273713270706151417163332372115212227262506072315273715333637 +3635342703371316151401161F01370327372726232207060703271336373603B6E6E614 +B179799DFE82FE902C26543EEB183D630717235D0F100196FE1C4B392E058B55AFA7D2D2 +C63A190731CA88F325FCE6A960543F19EA3C63223407073F35CC88F127382F0352FEFF01 +8AFE765F8E8E60FE844D4F484B92246BFEFF24AC141323202C019F211B61930A49979546 +0B371113344801604FFE5D424136049E01929124FEFF6B23AC2801096BFEA14E01A44121 +1B0000000004009C0020068F05D9001D0036004B00600000012115211506363332161514 +0623222627351E0133323635342623220607012635343F01273713270706151417163332 +3721152122272625060723152737153336373635342703371316151401161F0137032737 +2726232207060703271336373602BB01C3FEC50231188BA2A7963268464C5D35525D5D52 +275046FE0D2C26543EEB183D630717235D0F100196FE1C4B392E058B55AFA7D2D2C63A19 +0731CA88F325FCE6A960543F19EA3C63223407073F35CC88F127382F03D05F7001077463 +64720E0F721F12403839400D19FE534D4F484B92246BFEFF24AC141323202C019F211B61 +930A499795460B371113344801604FFE5D424136049E01929124FEFF6B23AC2801096BFE +A14E01A441211B000005009C0020068F05D900090022003B005000650000012206141633 +3236342613152E01232206070636333216151406232226353436333216012635343F0127 +371327070615141716333237211521222726250607231527371533363736353427033713 +16151401161F0137032737272623220706070327133637360386374444373A4242993D46 +22575C03075D3875878D75858CA992264DFCA52C26543EEB183D630717235D0F100196FE +1C4B392E058B55AFA7D2D2C63A190731CA88F325FCE6A960543F19EA3C63223407073F35 +CC88F127382F02BC417640407641010E66160E6035072873615F74A5968FAE0CFCFD4D4F +484B92246BFEFF24AC141323202C019F211B61930A499795460B371113344801604FFE5D +424136049E01929124FEFF6B23AC2801096BFEA14E01A441211B00000004009C0020068F +05D90006001F00340049000001211501231321012635343F012737132707061514171633 +323721152122272625060723152737153336373635342703371316151401161F01370327 +372726232207060703271336373602A20202FEF093FEFEA3FE262C26543EEB183D630717 +235D0F100196FE1C4B392E058B55AFA7D2D2C63A190731CA88F325FCE6A960543F19EA3C +63223407073F35CC88F127382F03BA32FDB9021AFD7B4D4F484B92246BFEFF24AC141323 +202C019F211B61930A499795460B371113344801604FFE5D424136049E01929124FEFF6B +23AC2801096BFEA14E01A441211B00000003009C0020068F05D90018002D004200003726 +35343F012737132707061514171633323721152122272625060723152737153336373635 +342703371316151401161F013703273727262322070607032713363736C82C26543EEB18 +3D630717235D0F100196FE1C4B392E058B55AFA7D2D2C63A190731CA88F325FCE6A96054 +3F19EA3C63223407073F35CC88F127382FD64D4F484B92246BFEFF24AC141323202C019F +211B61930A499795460B371113344801604FFE5D424136049E01929124FEFF6B23AC2801 +096BFEA14E01A441211B0000000600AC0000067F05A10010001A00270039004800520000 +0121321F0137331502072135363703262307321F01032427123736011215171407062B01 +2603363705121727020F011723032736373635273533013315213237331403062B011523 +032717110721222F013437032A01863A214A63069D10FEA96105C6230D66692A31C1FEF5 +1B951631035AA602610F287D0EAF18C9FC79AB0270C004030505C90804501A64E402D802 +018A211E03C9181FCC05A85E0505FEDE742F055405A1547D3702FEED1505350901621A05 +5C56FEB69A1301041C2FFE56FEEA1727663A080F013A17701BFEE00D3FFEB610291D015C +201F80290B3903FEA5731A09FEA417730125B006FEA9058030308200000700AA00000683 +05D9001300250032003C004A005C0065000001332013161D011005062B012003263D0110 +25361715321F011506071521363723072627262301163336132623262322070607051513 +333237342F012205170607141F0133343F013317270107161733353733323F0135230623 +21273505061D01141F01331103891B018BF361FE6FA3B703FE50E853013CBE892633791B +3A01180A8403534F12151AFD88F209049542171D325F354E0D02819976621A65290EFBCD +504B0B8B1D031B8E094C8B01F98706810306AC243D6A031622FEC406FDEA416A1AFC05D9 +FE92AECF06FE4FEB4C0186A8A335015FF18393036BCE03161F0308F02C89160CFEDF8B0F +01017E12797D1FDE03FEF48A2F9D4D3C347B3116E83B4319F82CF1FEE5FB03EC5B0680B1 +0310065F6569270C5B29040124000000000800AA0000068305D9000F0020002F003A0042 +00520060006A000001201316151005062320032635102536011005163B01201336351025 +26232003060121321F0137150607213537262F0123321F010323262736373601161D0106 +2B0103252117072327230207152603273437270133152132371503072307152327252117 +110723222F013603980197ED67FE84A4D1FE5EE75F0188AAFDF901629DAA300168E55CFE +96A0B8FE7BDF5C025B013A35183A516D1BFEF04D921A1B4D5B1E2D9A03E312771C2C02A3 +89156C649CFCD801149103034E03A6060C9C02564E02FD0601471418AD17A9060687FE8A +012B0606F55127060105D9FE97B2D4FE58E9590174A2DB01A9EB54FD12FE70D8580159A5 +C5018CE053FEA69F016D45622C03CC230334FE270C4656FEFE7F10D51A20FEA7E2211782 +010959F40331FEEA151A0B0112111C8D31FEEC5F1406FEDC090655EE8C05FEEB05672C31 +000500AA0000068305D900070017001F0041005200000116173237262706012013161510 +050623200326351025360106071633363726011523062B012E0135343734353436373217 +36331E01151415161514060723222723251005163B01201336351025262320030601C202 +B7906157A2AF01D40197ED67FE84A4D1FE5EE75F0188AA01E1A2576190B70202FE2B0376 +B20E8C7A019B879D7F809E879B017A8C0EB27702FD3C01629DAA300168E55CFE96A0B8FE +7BDF5C02F4CD02CBC204030226FE97B2D4FE58E9590174A2DB01A9EB54FDDD04C2CB02CD +BFFEF31CBD0FB95007080405749901AAAA0199740504080750B90FBD61FE70D8580159A5 +C5018CE053FEA69F00020131000205FA05D90018002E0000013215060717213217072117 +21161337170605260321220334010623222E013534373637170607141E01323637170602 +9A9901801801130C1004FEED0C01BD0C8D863005FEEE1095FE230C5501F8717B7CE27D40 +212F2819025398A6972A452E05D98EA302E692046506FE72288D08591801AD032485FA68 +3F7DE17D7C723C2ED93C43539853534CA9320000000300960000066005CD0016001A001E +00000134373637363332171617161514070607062227262726011121112521112102EF13 +141F1B2B2A1C1E151313141F1C541C1E1513FDA705CAFADB0480FB8002E92A1C1E151313 +141F1B2B2A1C1E151313141F1BFD4205CDFA33A504830000000400960000066005CD0016 +002D00310035000001343736373632171617161514070607062322272627260134373637 +363332171617161514070607062227262726011121112521112101B913141F1C541C1E15 +1313141F1B2B2A1C1E1513026C13141F1B2B2A1C1E151313141F1C541C1E1513FC7105CA +FADB0480FB8001AE2A1C1E151313141F1B2B2A1C1E151313141F1B029C2A1C1E15131314 +1F1B2B2A1C1E151313141F1BFC0C05CDFA33A50483000000000500960000066005CD0016 +002D00440048004C00000134373637363217161716151407060706232227262726013437 +363736333217161716151407060706222726272601343736373633321716171615140706 +07062227262726011121112521112101B913141F1C541C1E151313141F1B2B2A1C1E1513 +013613141F1B2B2A1C1E151313141F1C541C1E1513013613141F1B2B2A1C1E151313141F +1C541C1E1513FC7105CAFADB0480FB8001AE2A1C1E151313141F1B2B2A1C1E151313141F +1B01662A1C1E151313141F1B2B2A1C1E151313141F1B01612A1C1E151313141F1B2B2A1C +1E151313141F1BFC0C05CDFA33A50483000600960000066005CD0016002D0044005B005F +006300000134373637363217161716151407060706232227262726013437363736333217 +161716151407060706222726272625343736373632171617161514070607062322272627 +260134373637363332171617161514070607062227262726011121112521112101B91314 +1F1C541C1E151313141F1B2B2A1C1E1513026C13141F1B2B2A1C1E151313141F1C541C1E +1513FD9413141F1C541C1E151313141F1B2B2A1C1E1513026C13141F1B2B2A1C1E151313 +141F1C541C1E1513FC7105CAFADB0480FB80041F2A1C1E151313141F1B2B2A1C1E151313 +141F1BFDBA2A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C1E151313141F1B2B2A +1C1E151313141F1B029C2A1C1E151313141F1B2B2A1C1E151313141F1BFC0C05CDFA33A5 +04830000000700960000066005CD0016002D0044005B00720076007A0000013437363736 +321716171615140706070623222726272601343736373633321716171615140706070622 +272627262534373637363217161716151407060706232227262726013437363736333217 +161716151407060706222726272601343736373633321716171615140706070622272627 +26011121112521112101B913141F1C541C1E151313141F1B2B2A1C1E1513026C13141F1B +2B2A1C1E151313141F1C541C1E1513FD9413141F1C541C1E151313141F1B2B2A1C1E1513 +026C13141F1B2B2A1C1E151313141F1C541C1E1513FECA13141F1B2B2A1C1E151313141F +1C541C1E1513FDA705CAFADB0480FB80041F2A1C1E151313141F1B2B2A1C1E151313141F +1BFDBA2A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C1E +151313141F1B029C2A1C1E151313141F1B2B2A1C1E151313141F1BFEF52A1C1E15131314 +1F1B2B2A1C1E151313141F1BFD4205CDFA33A50483000000000800960000066005CD0017 +002F0046005D0074008B008F009300000134373637363332171617161514070607062322 +272627262534373637363332171617161514070607062322272627261134373637363217 +161716151407060706232227262726013437363736333217161716151407060706222726 +272625343736373632171617161514070607062322272627260134373637363332171617 +1615140706070622272627260111211125211121042413141F1B2B2A1C1E151313141F1B +2B2A1C1E1513FD9513141F1B2B2A1C1E151313141F1B2B2A1C1E151313141F1C541C1E15 +1313141F1B2B2A1C1E1513026C13141F1B2B2A1C1E151313141F1C541C1E1513FD941314 +1F1C541C1E151313141F1B2B2A1C1E1513026C13141F1B2B2A1C1E151313141F1C541C1E +1513FC7105CAFADB0480FB8002E92A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C +1E151313141F1B2B2A1C1E151313141F1B01932A1C1E151313141F1B2B2A1C1E15131314 +1F1BFD562A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C1E151313141F1B2B2A1C +1E151313141F1B03002A1C1E151313141F1B2B2A1C1E151313141F1BFBDA05CDFA33A504 +83000000000300AA0001068205D9000C001B002900000132041210020420240210122401 +141204202412353402242322040204343E0133321E01140E012322260396BC0165CBC5FE +9BFE7CFE9BC5C90165FE28AE013C0158013CAEB3FEC4A7A8FEC3B10393223C21203C2223 +3C1F213B05D9C1FE98FE7AFE9DC6C6016301860168C1FD14ADFEC5AEAE013BADAC013FAB +ABFEC1CD423C21213C423D2020000000000400AA0001068205D9000C001B002900360000 +0132041210020420240210122401141204202412353402242322040204343E0133321E01 +140E0123222624321E01140E0123222E0134360396BC0165CBC5FE9BFE7CFE9BC5C90165 +FE28AE013C0158013CAEB3FEC4A7A8FEC3B10393223C21203C22233C1F213BFD3E403C23 +223C21203C222305D9C1FE98FE7AFE9DC6C6016301860168C1FD14ADFEC5AEAE013BADAC +013FABABFEC1CD423C21213C423D2020DC203D423C21213C423D0000000200AA00010682 +05D9000C001A00000132041210020420240210122401323E01342E0123220E01141E0103 +96BC0165CBC5FE9BFE7CFE9BC5C90165023A1F3C23223C20213C22233B05D9C1FE98FE7A +FE9DC6C6016301860168C1FC96203D423C21213C423D2000000300AA0001068205D9000C +001A002700000132041210020420240210122401323E01342E0123220E01141E0124141E +0133323E01342E0122060396BC0165CBC5FE9BFE7CFE9BC5C90165023A1F3C23223C2021 +3C22233BFCA5223C20213C22233C403B05D9C1FE98FE7AFE9DC6C6016301860168C1FC96 +203D423C21213C423D209F423C21213C423D2020000100AA0000068200C8000300003721 +1521AA05D8FA28C8C8000000000200AA0000068200C80003000700003721152125211521 +AA0260FDA003780260FDA0C8C8C8C800FFFF00AA00000682034D12260F61000010070F61 +00000285FFFF00AA00000682034D12260F61000010070F6200000285FFFF00AA00000682 +034E12260F62000010070F6100000286FFFF00AA00000682034E12260F62000010070F62 +0000028600020158000605D205D90011002A000001363332043332371106232227262322 +072F012327211523153633321716333237110623222423220711230207343C80014EBB46 +483435A7B18BBF323614634B01010E4C3533C2ABA9A14E4B6A66ABFEC380504660029F11 +A30C02620A5F7E09025D35351A08785C15FD0D1C9A1EFDC600010158000605D205D90018 +00000123272115231536333217163332371106232224232207112301A44B01010E4C3533 +C2ABA9A14E4B6A66ABFEC38050466005A435351A08785C15FD0D1C9A1EFDC6000001006A +000106C105DA001F00000901072737273717371707090127371737170716130901170107 +0127070127010336FEB3B9C6BA2E8A2BB9C5B8014D014DB8C6B82C882C4523FED3FEB3B2 +013FB9FEF2B3B2FEF2B8013D027E0151BBC7BB2C8A2CBAC8BAFEB0014FBBC8BB2D8A2C71 +FE740135FEAFB3FEF0B90140B4B4FEC0B9011000000200C6FFEC066505DB000F00520000 +01141E0133323E0135342E0123220E011315230623222735231523222427262707030507 +301716171617112135213526272E01343E0133321E011406070607152115211136373637 +36372725032706070607060310274826284728294727254828E308252703030707A9FECB +56100D571D0133570649846D75FE8901771A193E44457D41427C47453D1B1A016DFE9364 +5E844A02035701341E570C11569B7304D62748272748272848272748FAF2010301020260 +58111041014B7C40065831280702D48133080E227C887D44447D887C220E093281FD2F0A +2231580303407CFEB5411011583123000001010D0000061F05D6002F0000090136373306 +071716170E0107262F0106072736370B011617072627070607222627363F012627331617 +09012709010703E201164B1B5B195D9C3E02063C2B47049D675B276642F4F24265265C68 +9C013F284B04013F9C5D1A5C1B4B0116FDE3200288028A2102BDFED03B3C7549B603402A +2201044FA838084211310122FEDE3111420838A8480B1D244C03B649753C3B0130028495 +FD3A02C6950000000005007DFFEC03D505DB0007001A002E004F00600000011633323534 +272613150623222735263534371706151404072326032435342515061514041706052736 +353427112713112711242736373233321707170623222726232215140417060535242736 +35342527032E0135343E0133321E011514060701541C0D0B0524F90C1B2303B5A4014101 +43020F1CC5FEE2010EAB022C0304FEED01B6C54D4D4DFE76040DD504034135705F181A39 +4408072D02EF0607FE9601120201FEE34D051518182A16172A18171504A40C09060A0BFB +94421D40302240521A2F18182F3E774101B7266F632F4426332E5961512F3522312722FE +B4130301FE81100175138A9005451E19061B0236322F758A103D203D0102216D07012D0B +2E18192E18182E19182E0B0000030079FFEC06B205DB0004000900370000250901162025 +090116200133321D01142B010106070E0123222E013D0101210115140E01222627262701 +23263D01343B013717331B013337065FFEECFEEC8B0114FD25FEEDFEEC8A01140309D638 +29C2011402302FAD5F5EAB5E0110FD4D01125EACBCAD312F010113CC2039D73533FC8181 +FC34ED033CFCC42626033CFCC42603D12A1928FCC7443E3F47477A42080336FCCA06437B +47473F3E440339111C0F2F9B9B0143FEBD9B00000002007D000006AE05DB001A00260000 +0136333217161716151407060706232226272E0123220706032712011323032111230321 +03231302D2B89E3632C94B23112B7E4B46309D522139272F4EA3C778E504ED5F7F54FEE2 +8A01FEE3567F5F0587540927A64E51393B8D3A2338B3484C2243FEDF440199FD7CFDA601 +D7FE2901D7FE29025A000000000201290000060105DB000D0035000000141E0133323E01 +342E0123220613153B01323637140407060723263D012624031E01333237333526272E01 +343E01321E011406070602E6325B30325B32345A312F5BBB060396E1ADFE292735022A2B +05FE0F58C7EB810D0E06413C4E57589DA69E5A584E3904C46459323259645B3030FE2D9C +BF26E79A9788F5A89A14848F013102E9019B07232B9CAC9E55559EAC9C2B2000000300C2 +FFDD066A05D0003F0047004F000000321F01161737161707161737161707161407170607 +27060717060727060F0106222F0126270726273726270726273726343727363717363727 +363717363F0100200010002000100414062226343632036A582B1A43407E4C40542F21A5 +2510940404941025A5212F54404C7E40431A2B582B1A43407E4C40542F21A52510940404 +941025A5212F54404C7E40431A010DFE94FEFE0102016C0102FEE35B805B5B8005D005BB +0D1E842D409C323C1F545C45234423465B541F3C339B402E841D0EBA0606BA0E1D842E40 +9B323D1F545B46234423455C541F3C339B402D831D0DBBFEC4FEFDFE95FEFE0102016B75 +805B5B805B000000000F0106FFED062505DD0009000E00130018001D0025002C00310036 +003B00400048004F005B009F000000141E01332634372206051617372627161737262716 +173726271607372627141514071735342706071736353405173637060717363706071736 +37061F0126370617372635343706153F0126270615140522061514163332363534262735 +26272E01343E0133321E0114060706073314231516171617363704170623262207161514 +07060703150623222F0103262726353437262322072227362516173637360356111F120E +0E112001F36E2A1A5B916E25275A784C10303C7B32013B2662153B3C2F323A28FC731A2A +6E572927246F601430104C500F3B0132463C3B160126633A322F01011C374C4C37354D4C +5A03021315152715142616151302030101342903035016014E5055FE28441C0434283401 +0B1719080102332834031F292228FE5550014E155207092805A0221F1220422211763E30 +263814613E21552A7E45136D4C914B087D601110813F0A0C7B6188371A635B0D4F26303E +106A213E61298613457E4388084B91578B0A3D7B1415647B031A37880E0D5B2B4D35374C +4C37354D2D5A01020A272A271515272A270A02010159092803048035065BA30C0C14154A +352809FCD65C161C64031C0928354A1211110CA35B06348C0A092700000F010500000627 +05D3000F001F002F0037004F005F009F00AB00B300BB00CB00D300DB00E300EF00000132 +1E0115140E0123222E0135343E0107262706070615141716333233363726250607161732 +333237363534272627062716151407363726272627262706070607061514171617161736 +3736373635341316171617363736353427262322070605373E0133321617153633323316 +17161514070607171617161514070E012322272306070623222726270623222627263534 +373637262726353437363732333205262726232207060716173617060716171617262506 +073637363726272627262322070615141716173637360706071617263534171617363726 +2726171617363706070607060716171633323736372603961324141424131424141424E1 +4F477F3325081756080963980E01E2060E9963090856170825347F474C02023934346D2F +3132323130322F02022E3330333131322E03190F064F467D3626081756090B65FDB40122 +773F3E7622A470030474200C233888018A37210C1F76066EA401223A3B40413C3A22A26E +06781F0B21368A8836230B207304037002251B2E2F31332F2E1B5556545C3C3E22242121 +05FE950905212223223E6B95630B0956160926367C464F060939333339013905093C3E23 +2223D63E3C09052022245A54551A2E2F34332E2E1A560335132415142314142314152413 +D9353967543B26120E2704395A5E5E5A3904270E12273A546739A727282727262829491E +1C1C1A1A1C1C1E383A39381E1C1D1A1A1D1C1E38393A010C5A5F353965553B27130E2801 +050902B3C0C0B3023A0238141B2E3E65750176633E2D1B1437033AB062636362B03B0437 +141A2D3F637775643F2F1A1438024BA2575A5A57A2202A2A10191D121415144440404414 +1514121D2B380501280F13273A546539355E9D26292826272728D64441191D1214143A1D +1941441514142E2A219E595B5B599E21000701050000062605D6000B00140021002D0078 +008600940000011417363726273637262706053426150615161736250615141736372E01 +270615142534270607161706071617360126232207060726273637161736372635343F01 +15161514071E011736371617060726272623220716171614070607161706232627060706 +0723262735262706072227363726272634373606141E0133323E01342E0123220625321E +01140E0123222E01343E0103CE27372C33090333252F30FEDC292C08371501DF01113D03 +032A0123FED7302A2B33030E2E323227FEF738200E092059660202B7A9542C3127A501A4 +27292E0555A8B80102665920060E1D3E1A14393818202232010D42323B465F1920205842 +3F32430D01332220173839143469BC6867BC696BBE6365BD0122539F5B589F56579F585A +9F0284A3A70F23658DAA6A1C0F70CD4D63014E54764A4B721B195A31566C4C57024D4909 +11A9700D1E6AAA856B250FA7016E410D5602027B7702067719115B3187F40101F487315B +0D1A03770602777B0202560D401E2366DC642D234A4D0C37422F15228989220114304337 +0C4E4B222C64DC662388CEBD6868BDCEBF67675155A0AE9D58589DAEA0550000000300ED +005305E8059E00030007000B00000901070103211521130117010159048F2CFB714004B7 +FB4940048F2CFB71059EFEC8A40137FE54AAFE550137A4FEC8000000000300ED005305E8 +059E00030007000B0000011701270115213501070137057C2CFB712C04FBFB4904772CFB +712C059EA5FEC9A4FEE7AAAAFDABA50138A4000000040064000006C805D40007000D0015 +002500000133011521272300170115213500073215022322033613321E0115140E012322 +2E0135343E01039F070322F9AD0809031B17FD680539FD7C144D34191736193313251514 +2613142415152405D4FA35090905AA9BFB4F1109049DC244FD9B026E3BFD0D1424151424 +1414241415241400000100AA000404F405D8001300000133171501143304151701273500 +353423242F0104D10E15FD8E5B018307FC510E028777FE93100705D81507FDE247530E47 +FD55150702570D474A114700000500AFFEFF075A05DA001E002A0031003D004300000126 +100033321736333200100706071121152111231121112311213521112601220610163332 +3726103726130607112111263716333236102623220716100536102706100158A90150EF +997D7D9BEC0152A888B40110FEF0B4FE86B4FEF00110B5010FA4E7E7A43F398A8B39D758 +65017A6648393FA0EAEAA0413A8CFED775757402149D01DD014C4646FEB4FE229C7E12FE +F87CFEFF0101FEFF01017C0109110391E4FEB9D8119501AE9D12FC942D0AFEF801090AA5 +10D80147E4129DFE50386C01477272FEB9000000000500A2FE5B083005C3000B0024002C +0038003E0000012620061017161736003726011400202726272627261000041701213721 +11211107110116030116171617012103060007161716203610272601323635220603F775 +FEBBE7742E37100137CC1C0285FEB5FE21AA7524866CAA015901CB6F0169FE8D8C01EE01 +8B8FFE9A7095FE9A401B71440169FEEDF70EFED0DE1D34740145EA752EFD8DA1EAA4E303 +2472E4FEB86C2C1ACE01330736FD90E8FEBD9F6C9225659E01DC01551167015C8EFE78FE +148F0175FEA4920376FEA453631B3F015CFE37CCFED70B3D316CD80148722DFEC9D8A4E0 +000400AFFE8308DF06D90005002D00390045000000021736102701140023222706071121 +152111231121352111262726100033321736171617012137211107110116252206101633 +3237261037260116333236102623220716100392017375750357FEB3F39A7E57640110FE +F0B4FEF00110B587A90150EF977C809AE56F016AFE8C8C01EE8EFE9A70FB93A4E7E7A43F +39898B3A0176383FA2EAEAA2413A8D03C8FEB86C6C014772FEEBE7FEBC452C0AFEF87CFE +FF01017C0109117E9D01DD014C444A060867015C8EFE148F0175FEA492C6E4FEB9D81195 +01AC9F12FD0D10D80148E4129DFE4F00000200AFFEFF06B2075500070028000000262006 +101620360526100033321701213721110711011615140706071121152111231121352111 +260479EAFEBBE7E70145EAFCDFA90150EFC0950168FE8D8C01EE8FFE9A70A988B40110FE +F0B4FEF00110B50443E4E4FEB9D8D8E89D01DD014C6E015B8EFE148F0175FEA492C2EF9C +7E12FEF87CFEFF01017C0109110000000002010DFFE305D006F4002B0044000001071617 +16171614070E010706232227262726272634373E01373633321737273717130727251307 +27031707011632373E013736342726272E0122070E010706141716171604284633224422 +2625237D6256605A5A5E3B4422262624835B585D3A3347FE39FE86E13A01CEC08B5D86FE +3AFD463D7E3D3C5A1A1919172F2A7E7A3F3C591B1918182E280400AA212448535CB45B56 +832A2525273E48535DB35E588226250CAB698A6901445E8BBFFE3239E1FEBD698BFCFC1A +19195C3D3C803C39322D311919583F3A843C39312A000000000200CFFEA305EE0712003D +0058000001363217130727251307270316171E0115140607060706071521152115233521 +352135262726272E0135343637363727072737270727130507271737170717220706070E +0115141617161716323736373E013534262726272602EE347036B3E23901CEC08C5DB32E +2845474745405A2D3C0113FEED96FEED0113383156444547464627311FFE39FE545D8CC0 +01CE39E254FE3AFE8A47353E2B2E31312E2F3A398A353E2B2E31312E2F3A3904870C0C01 +9E5C8ABFFE323AE2FE611D2845AB625FAB454028140AB896F0F096B80915254345AB5F62 +A947281E48698A6ACBE23A01CEBF8A5CCB698A6AD3181C2B2E774241772E2F1818181C2B +2E774142772E2F181800000000020180FFE3053406F4002B004600000111211521151617 +16171E011514060706070623222726272E01353436373637363735213521110727090107 +03323736373E0135342627262726220706070E011514161716171603A50113FEED3C2D5A +404547474544565262664E5A404547474544563138FEED0113AC6B016201626AF843393A +2F2E31312E2B3E358A393A2F2E31312E2B3E3505D5FEE796910A14284045AB5F62AB4543 +252323284045AB625FAB454325150991960119AD6B0161FE9F6AFB5018182F2E77424177 +2E2B1C1818182F2E774142772E2B1C1800020009011006A104C4002B0046000001231123 +1123060706070E012322262726272635343736373E013332161716171617331133113327 +3709012725141716171E0133323637363736342726272E01232206070607060582DC9655 +0A14284045AB5F62AB4543252323284045AB625FAB45432515095596DCAD6B0161FE9F6A +FBC918182F2E774241772E2B1C1818182F2E774142772E2B1C18029FFEED01133C2D5A40 +4547474544565262664E5A4045474745445631380113FEEDAC6BFE9EFE9E6AF843393A2F +2E31312E2B3E358A393A2F2E31312E2B3E3500000002017F0110053504C5000B0017001F +400F1512030F120918191212060C12001810DCECD4ECC4310010D4ECD4EC300134003332 +00151400232200371416333236353426232206017F0117C4C40117FEE9C4C4FEE999BD85 +85BDBD8585BD02EBC40116FEEAC4C5FEEA0116C285BDBD8586BDBD000001017F01100535 +04C5000B0013400703090C0D06000C10DCD4CC310010D4C4300134003332001514002322 +00017F0117C4C40117FEE9C4C4FEE902EBC40116FEEAC4C5FEEA0116000201FA018D04BA +044B000B0017002B400F156B030F6B091819126B060C6B001810DCECD44BB0105458B900 +06FFC03859ECC4310010D4ECD4EC30013436333216151406232226371416333236353426 +23220601FACF9191CFCF9191CF98755353757553537502EC92CDCD9291CECE9053757553 +5376760000040164018D0550044B001300210029003700654BB00B5258401F03070D111D +2A192E081B2C0F2622050428240A6B332C6B28246B1B146B003810D4ECD4ECD4ECD4EC11 +12173911121739310040152622171F05031F0F17111F366B070330176B0D113810D43CEC +32D43CEC3211123911123911123939305901343633321736333216151406232227062322 +263714163332332635343726232206250615141736353437161514073233323635342623 +220164CF915244445291CFCF915244445291CF98755306053F3E05055375015E3232325A +3E3F0506537575530502EC92CD2020CD9291CE2121CE90537558717057017632384D4C38 +384C4D7B577071587553537600050054015A0660047A0003001D00380052006D00000133 +112300220706070E0115141617161716323736373E0135342627262F01321716171E0115 +140607060706222726272E013534363736373604220706070E0115141617161716323736 +373E0135342627262F01321716171E0115140607060706222726272E0135343637363736 +030F9696021D522329181D1E1F1C1D24235223241D1D1E201B1D244C4B3A432E32353532 +31403D933A432E3235353231403DFD1F522329181D1E1F1C1D24235223241D1D1E201B1D +244C4B3A432E3235353231403D933A432E3235353231403D047AFCE0025A0F121A1F4629 +284A1B1C100F0F101C1E4728294B1A1C10A51A1E2E327F49467F32311B1A1A1E2E327F46 +497F32311B1A960F121A1F4629284A1B1C100F0F101C1E4728294B1A1C10A51A1E2E327F +49467F32311B1A1A1E2E327F46497F32311B1A000003000A018D06AA044A00370051006B +0000013E013736373633321716171E0115140607060706222726272E0127210E01070607 +06222726272E013534363736373633321716171E011724220706070E0115141617161716 +323736373E0135342627262724220706070E0115141617161716323736373E0135342627 +262703F707233631403D484B3A432E3235353231403D933A432E352506FEC50627323140 +3D933A432E3235353231403D484B3A432E36230702B95223241D1D1E1E1D1D2423522324 +1D1D1E1E1D1D24FBF7522329181D1E1F1C1D24235223241D1D1E201B1D2403351F5B3531 +1B1A1A1E2E327F49467F32311B1A1A1E2E3460181D5D32311B1A1A1E2E327F46497F3231 +1B1A1A1E2E355B1F7F0F101C1F462928471E1C100F0F101C1E472829461F1C100F0F121A +1F4629284A1B1C100F0F101C1E4728294B1A1C10000200D201E6060E04520005000B0000 +0135250715170125051105250578FCCCDCDCFE8E017203CAFC36FE8E02D78A654DBA4D01 +5E8278FE84788200000201B1005605030596001500290000013736353427262735373523 +15171506070615141F022103263534373637273521150716171615140703735F7652244E +3AC83A4E2452765FA3FEECA47B61542C580240582C54617B0111B4DF34625C2931A2253D +3D25A231295C6234DFB4BB013AEA60BE5C4B1944FAFA44194B5CBE60EA000000000200AF +FEFF052D05DA000700150000002620061016203605261000200010070607112311260479 +EAFEBBE7E70145EAFCDFA9015001DB0153A988B4B4B50443E4E4FEB9D8D8E89D01DD014C +FEB4FE229C7E12FD7B02861100010159FEFF052C05DA001A000021112311213521111633 +32361026200727362000100706071121150349B5FEF001102834A1E8E8FEBB7E75A801DB +0150A789B3010FFEFF01017C01B207D80147E46A74A6FEB4FE209A7E12FEF87C000200B0 +FEFF052C05DA00130017000013090211323315222311222311222335323311370902B002 +3E023EFE21878888875B5A8888888856014CFEB4FEB4039F023BFDC5FDE9FEF47CFEFF01 +017C010CA8016F014AFEB600000100B0FEFF052C05DA001F000001371711331137170121 +15210107271121152111231121352111072701213521011D7BFDB3FC7BFEE40189FE7E01 +157AFD0110FEF0B4FEF00110FD7A0115FE7E018904F97AFD0164FE9DFC7AFEE47CFEEB7B +FDFDAE7CFEFF01017C0252FD7B01157C00030078FF0F065405250009000D001700000115 +210901213521090103112311012117372115230901230654FED3FE3FFE3FFED301A2014C +014CF1ACFE770104D6D601048EFEB4FEB48E014A7CFE4101BF7CFEB6014A03DBFDA6025A +FD74D5D57CFEB6014A0000000002018EFF2C04C205AF0007001C00002414163236342622 +02261037363711331125170D010725111617161006022675A67575A63ECF68485DAC0125 +56FE99016756FEDB594668CFDDA67575A676FDD9CE012367471603CEFECEAD94D1D294AE +FED2164567FEDDCE00010144FEFF045805DA001E00002111231121352111262726351025 +36373617060706151017161706271521150348B4FEF001109C664E01295DAE483BCB6274 +FA57554A6E0110FEFF01017C01334C9875A1012EB93A1007173B859DC4FECDA2380F2513 +FF7C0000000300560008065E046D000F001D0033000013151417162037363D0106070620 +27260020070615141716203736353427371E011511140607062120272E01351134363736 +2120BAABCE024ECEAB2E38F0FD6CED3F039DFDB2CEABABCE024ECEABAB2C73707070E6FE +C2FEC7E873707070E6013E0139021D9360566868566092221C787820020B685663605668 +685660635659398851FEA24E8839737339884E015E51883973000000000400560008065E +05CB000D001D002D00450000002007061514171620373635342701151417162037363D01 +06070620272603151417162037363D01060706202726011E01151901140607062120272E +013519013436373621200481FDB2CEABABCE024ECEABABFB6BABCE024ECEAB2E38F0FD6C +ED3F2AABCE024ECEAB2E38F0FD6CED3F049773707070E6FEC2FEC7E873707070E6013E01 +3905676856636056686856606356FE7C9360566868566092221C787820FEC19360566868 +566092221C787820035A398851FEA2FEA24E8839737339884E015E015E51883973000000 +000500560008065E046D000B00210031003F005500000106070621202726271621200020 +171617161506070607062027262726373637363703151417162037363D01060706202726 +0020070615141716203736353427371E011511140607062120272E013511343637362120 +05781F38B8FEF1FEEFB63B1EEC01340131FDC00220B63A1F1C021A1F3AB8FDE0B63B1E1D +01011B2039D9ABCE024ECEAB2E38F0FD6CED3F039DFDB2CEABABCE024ECEABAB2C737070 +70E6FEC2FEC7E873707070E6013E013901641B1C5D5D1E1A6302A35D1D1C190E0E161C1D +5D5D1E1B180C0F181C1DFED59360566868566092221C787820020B685663605668685660 +635659398851FEA24E8839737339884E015E518839730000000700560008065E05CB0015 +0021002D003B004B005B0073000000201716171615060706070620272627263736373637 +010607062120272627162120170607062120272627162120022007061514171620373635 +342701151417162037363D0106070620272603151417162037363D01060706202726011E +01151901140607062120272E01351901343637362120024B0220B63A1F1C021A1F3AB8FD +E0B63B1E1D01011B203903E51F38B8FEF1FEEFB63B1EEC01340131ED1F38B8FEF1FEEFB6 +3B1EEC013401310AFDB2CEABABCE024ECEABABFB6BABCE024ECEAB2E38F0FD6CED3F2AAB +CE024ECEAB2E38F0FD6CED3F049773707070E6FEC2FEC7E873707070E6013E013905035D +1D1C190E0E161C1D5D5D1E1B180C0F181C1DFE1C1B1C5D5D1E1A63FC1B1C5D5D1E1A6304 +656856636056686856606356FE7C9360566868566092221C787820FEC193605668685660 +92221C787820035A398851FEA2FEA24E8839737339884E015E015E5188397300000300AF +FFE3052D06BE000B0020002C000001222635343633321615140613161716151400232200 +103736370301270901150103323635342623220615141602EE40615E43465B5E17B587A9 +FEB0EFECFEADA988B401FEF90101630163FEF75AA4E7E7A4A1EAEA017D5B443E58554142 +5D02BB117E9DEEEFFEB4014C01DE9C7E120167FEFAC7015DFEA3C70105FAF7E4A4A3D8D8 +A3A4E400000300160185066C05140050006A00850000012723222726070614070E010706 +232227262726272634373E01373633321F01163332373E01373626272627262F01262726 +272634373E01373632161716171614070E01070617161F01213217161721172516333237 +3E013736342726272E012322070E01070614171E0101262322070E010706141716171E01 +3332373E013736342726272603CDD673AE1C2A0302090D30241C2B2424241A1517120C0D +31231D2A23095A615A250E16250303070D0C164E543132191C0F130C0D34202348481A1F +0D120C091E030522296D52018BE84F6511FD62D5FC0D15141810151D05050C0A0F112C11 +1414151A07060D0A1E013012171C0C151E04050C0A0F0F2B141711151C05060D0A0F0B01 +85CD06080D082917202E0D0B0F0E1915241D4A1E202E0D0B03252703051C07082E131313 +44221414181B1E26421D20310A0B1E181E1C24431E171D070D161A6D5220294BCF790905 +071810111F17120D101006061711112016121C02B5090507190F112115120E0D12050719 +0E122016120E0900000300560121064504B500150064007A0000123236373E0135342627 +2E012206070E01151416171601050623222725070E011514161514060706070622272627 +2E01353436373637363B0132373E02342E0127262B01222726272E013534363736373632 +1716171E011514061514161F012536333217242206070E01151416171E013236373E0135 +34262726FD2E26101110101110262E261011101011100301026D2C6D51DBFE916A965011 +1C1C1628214E21221C1A1E1C1C162821096156661B261818261B625A35352128161C1C1E +1A1C22214E2128161C1C1150966A016FDB516D2CFAE62E26101110101110262E26101110 +10111001710E0D0E1F13121F0E0D0E0E0D0E1F12131F0E0D016CFC3F57922C3E0E0E0826 +19233C1A15100D0D0E17154123223C1A15100D22091A2810281A09220D10151A3C222341 +15170E0D0D10151A3C231926080E0E3E2C92573F7E0E0D0E1F13121F0E0D0E0E0D0E1F12 +131F0E0D00><0003001600C0066C044F004F006A00850000013307210607062321070607 +061514161716140706070E0122272E01272634373637363F01363736373E01272E012726 +23220F01062322272E01272634373E0137363332171E01171614171637363B0125060706 +070614171E0117163332363736373634272E012726232201363736373634272E01272623 +22060706070614171E011716333203CDD6D5029E11654FE8FE755264321D21090C120D1F +1A48482320370A0C130C1F19095A544E160C0D0703031D1E082B5A613231232B1C24300D +0C1212342424242B1C24300D0902062719B173FDB9150F09100D060717181117152A0F0F +0A0D0607161A1117140106150F09100D06071C131216152A0F0F0A0C05071E1214140E04 +4FCF4B2920526423140F061E17213E261C1E181E0B0A331E22392A1B1E18042422441313 +132E0807160B032714140B0E2D2020442120320E0F0B0D2E20172C050E090654080E0818 +13250F11150806120E0D1216230E12150805FD3A090D081813260F10190505120D0E1213 +280C111905050009004900F4069804E8005A0078009600B400D200FE012D013401480000 +01363726272627262B012227262726272634373637363736333217161716171615140716 +1F012536333217161514070D011615140706232227250706071615140706070607062322 +27262726272635343736373637363B013237360032373637363736353427262726272622 +070607060706151417161716171232373637363736353427262726272622070607060706 +151417161716171222272627262726353437363736373632171617161716151407060706 +070222272627262726353437363736373632171617161716151407060706070527262726 +27263534373635342726272627262207060706070615141716171617163B013217161716 +173736032D012623220705060706070607062B0122070607060706151417161716171632 +373637363736353427263534373625051633323725261407060706222726272634373637 +363217161702490B07070B0919515C432D2D25282213121210281B2F2A302E2C2B22280D +12062F8E58015DE25C8839081EFE0201FE1E083C855CE2FEA3588E2F061213222429302A +332729222A0D121210251C312A3043525B17FEEB1C0B0C090A030303030A090C0B1C0B0C +090A030303030A090C0B1C0B0C090A030303030A090C0B1C0B0C090A030303030A090C32 +321515111309090909131115153215151113090909091311151532151511130909090913 +1115153215151113090909091311150186259527160B100B060D0D1B14261F4A1F201B18 +0F0D0D0D1B14261F25435C641E121707231CBD01ED025F2A5D4FD9FE9194121117141C68 +5843251F26141B0D0D0D0F181B201F4A1F26141B0D0D060B100C0193011FD94F5D2AFDDD +D9050609091609090605050609091609090602D9080D0E07060A20100D221C2926602622 +271A121010101F24222F271D12093C258B5A530D111F0CCFCF0C250D0B535A8B253C0918 +172F272A1D200E1010111C222535222D2824231B13102009FED204050807070408070506 +0807050404050807070407080506080705023B0405080707040708050608070504040508 +07070408070506080705FD8207080E10111216151211100E080707080E10111215161211 +100E08023807080E10111215161211100E080707080E10111216151211100E085C0F3D07 +0406080E0A180E17211C1B1913100C0C0E1613201C21201C1B1913100C220A0E11081811 +FEF2C2F72F5692431E1E0F0E0A220C1013191B1C20211C2013160E0C0C1013191B1C2117 +0D190A1402025B75562FE12B16090906050506090916090906050506090000030056FFE3 +065E05F00061007500870000010E0115141617161716203736373E013534262726272623 +220706070E01151416171526023534123736373633321716171612151402070607062027 +2E0235343637263534371233321716151407062322070607061514171615140706070623 +221736373637363716171607060706070623222726011617161514070607062322273637 +3635340213030C151314186F011B7A776264615F665F7A788A867C776264614D2C6B726E +756C8D8C9C9A8E867370736E756E8B8DFEC78A2A4424201C046CB5A9510610242209362A +1F443F10121622352712104B1828321917032D0612020A131B23201303031B019E3E1610 +150D1E0A1C0A3A111D1E0137061E1B1C3212130B3333316364ED888DE869603433333163 +64ED8D87BC3E986D01109C9D0110776E3C3B3B38726FFEECA198FEF077703A3B3B124256 +302F4D1F100FCDEF01900814383A44402A1F7C736120161B1E092D46281C370C222A3D38 +192608150F3021321D1C0106042016110C312D3524260C0A12363A42340000060056FFE3 +065E05F0000D001A00280042005C00770000012E01272506151416171617161713262726 +22070607133633321713050E010725363736373E01353424321716171E01151406070607 +06222726272E0135343637363713220706070E01151416171E01203736373E0135342627 +26272627321716171612151402070607062027262726023534123736373601A93F270501 +2A08262420311408F3242D2B682B2D24463337383288012A022A3FFE920E0E31202426FE +E73E1A1B1616161616161B1A3E1A1B1616161616161B39867C77626461626360F201117A +776264615F665F7A788A9A8E867370736E756E8B8DFECB8E867370736E756C8D8C017749 +5F0FFD1E22325B242016090101A322141313142201830909FE0BFD076749830406162024 +5B3221760B0C1517351F1E3517150C0B0B0C1517351E1F3517150C021533316364ED8D85 +F163616633316364ED888DE869603433643B38726FFEECA198FEF077703A3B3B38726F01 +149C9D0110776E3C3B0000010078002C064105AA002D0000133537273317362502033317 +331523173315231721321716140706232107331523073315230723121324270723379424 +40649292011E35AB7B739456649A5D980108463020203046FEF8985D9A645694737BAB35 +FEE29292644002D72805DAC90D0A0114016A96508250C61A112C111AC650825096016001 +1E0B0CC9DA000005008400DC063004F9000D001400220029002D00000901210136373637 +363217161716170111011615140501210106070607062227262726270111012635340111 +211103F601B8FB5801B807081D242352232B16082F01AEFE5203FE9FFE4604A8FE460607 +1D24235223241D0731FE5301AD03FDF205AC0368012CFED409081C100F0F131909AAFEDE +028DFEDD1112136BFED3012D08071C100F0F101C07AA0122FD730121131312FDDF041DFB +E300000401B30000047C05EF005000D000E300F30000013437363F013637363317262726 +2726353437363332171617161F0116173637363736373635342726353437363332171617 +16151407031415141F0116151407171607061D012135342726272635342726012623220F +01060F01060F0127262F01262726272623220706151417161F0216171615140706232227 +2627262F0126272623220706071617161716171617140706071514171617161716151407 +26272627263F0136272627062322272627262322171617161F0116150721273437363D01 +273635342726353435133435342701071617161716171633323736353427262726070607 +16171633323736353427262F0101B3251711340E2D0202041A291E1422171B2410123011 +4A4B260F0E1207050A050C0501011F1B29371D0D010A01090202040103011603FDDF0509 +25301B1202870327230405010A2305163619092B112D4C1A14110E1108071D0D135F885F +5E04140504161206032F7334072A1E130C0705030831280D082E3201150F013617183609 +031C7C2F06070E02070207070F1C280E0F1811310904020A07040A5C010101AB010C0D02 +0309050C02FDFE11233A37110401110A0B04020C05066C800F0353260F0B1008040C080B +7802EB3731071F5D181701012F7E5B233C241C1B20050C1A6FBB5F251510402C1F10863A +060B0A060624221E3B1A1276521512FED40709181D162458242B7C1989138D89B6122135 +44582650593B02B639446F1E36C21D060F0B0D62276E95320F0C0B060B1F381A33FE6546 +5D0C08130401140607345426031F160A070C162620120C2F3530243F2D200231401B1126 +1106051009336C0F0A17174B0F0B0A051903050D430422503116D6090B9FB73A30333C08 +443A3558492E290607014404071326FE5B142B2D313A0D0C11100909151709057112142F +413809120A0A12110B06670000000008002B00A9066A056C00060039004C0070007B007F +0089008D00001316373635342701222726232207062B0122270705132635343F01363736 +373637363332173736333217161507171633323733161514072335062536373637012726 +232207060F01161514073601222F01071407363716151407062322071615140732373633 +321716333237363534270637161514071533363534270536370F01061514171617363534 +05073726D81B140F1003702F6C301D26964A2163532559FEF881060E451C195D7B404322 +24343E992D3B251E0DBECC2C283606E1593EF86DFCBC211E0805014E24111F58EC362214 +2F2C2803AB3419F273533F580F2455813E182214734A3F4B28602E2F34892B32194B322E +9E303EFCB0530DA25A70301F2B17FEA0315E0701F003271F1F2010FEC70E0510083B6373 +010C12201A2FE85E227C592F1A0D34AB3238191DDB871D1E83B4CF956C25E9256F1D0F01 +741E0EE9356F413D275924090161109D85A354022A0D161210270127472B2D0D0B0C0625 +5F6B6A7E071D728D7D6A4C60D0867CEF5C67C34B1A402F1E14032E383596632824000007 +00B6009A05CB05AF00130019002000280032004C00530000013E0135342F010623140706 +2314071716333236013F010F021325011325011305013332353427010036353427010701 +1633013E0137363332161514070E01070E0107062322263534373E011707011633322705 +25080C023B171E36364916ED08070A1AFC80990D92990D830109027DB8FD67FD841101BD +020D023F02FDC201A43606FDFB710205110FFD6E0D1F100D0A0E110506130E0E1D110E09 +0E100405142D7F023E0B0A39010140081C0A0508EE174836371C183B020B02F998920D98 +92017A12FD83FD68B8027C010914FDF3380A0C023EFCB636210F11020671FDFA0602EA0D +150504110D0A0E101F0D0E1405040E100B0D101EF40EFDC20242000700350196068E043E +00110017001E0025002B003F0046000000342627262F0114071614071615373637360535 +270715170337210901212701213635342721003427211521243436373E013216171E0114 +06070E01222627261707213635342705A30707050AD215333315D20A0507FBA65E5E5EAF +AF03520258FDA8FCAEAF014902B41923FD06034A32FD5602AAFC6C070807121412070807 +0708071214120708C55002FA231902DE18150907067E211438903814217E0607094BD870 +70D8700168C8FEACFEACC8012C19171B19FEC47018A03C28230F0E10100E0F2328230F0E +10100E0F6964191B1719000700B6009A05CB05AF00130019002000280032004C00530000 +012E0123220F011615321716153217373635342601271F02270727030125030127170136 +35342B012E012322070117013635012E0127263534363332171E01171E01171615140623 +22272E01130136232207010525091A0A0708ED164936361E173B020CFC6F920D99920DA8 +C611027C0299B8FD83120E023E023F022536210F11FDFB71020506FD170E140504100E09 +0E111D0E0E130605110E0A0D101F12020C01390A0BFDC20509090B023B181C37364817EE +08050A1CFD180D92980D92D5C60109027CB8FD68FD83EC7F023E0C0A38883606FDFA7102 +06110FFD6D0E1E100D0B100E0405140E0D1F100E0A0D11040515010F020C4202FDC20002 +0058017B060E045B002500470000121027323320250115252E012726220706070E011416 +1716171632373E013725150124212223121407323332052505060706222726272E013436 +37363736321716170525042322238A32080701250217026BFDBF050E120D1E0D0E0B0A0C +0E080B0E0D1E0D120E050241FD95FDE9FEDB070899170405B1020F01B7FE9C161B1A3E1A +1B1616161616161B1A3E1A1B160164FE49FDF1B1050402490144606EFEED531C08100806 +06060A091D201F070A0606060810081C53FEED6E01478A596EB918150C0B0B0C1517353E +3517150C0B0B0C1518B96E0000000001008A01AD060E0429002800000125150124232223 +363427323332250115252627262726220706070E0115141617161716323736373603E802 +26FD95FDECF8070632320607F80214026BFDDA070F0E12112A11120E0F0F0F0F0E12112A +11120E0F02C51C21FEED6E60E0606EFEED211D110F0E080808080E0F231514230F0E0808 +08080E0F00000001013300C60557050A001C000001321716333237001336333216151407 +0001062322272627263534373601C5271428110D0E0119EF3E87201621FE7EFEB6174748 +0D222E34462B028E40781401C20116480C090E29FE30FDFC24060F8A99272A2718000001 +00ED00B205C5050C001E0000013217163332370037363332171615140700070623222726 +2726353437363301C5271428110D0E0119EF65607F1A0B17FD7D7B2A9832371739484660 +30031A407814019DAF4A080316121BFD1EDE4C1A0C8DB28631202C000000000101030094 +05B10541000B000009010709012709013709011703E701C98EFE38FE378E01C9FE378E01 +C901C98E02EAFE378D01C9FE378D01C901C98DFE3801C98D0000000100AF003F06050596 +000B0000090B04910174FEC9FE8CFE8CFEC90174FE8C013701740174013702EAFE8CFEC9 +0174FE8C0137017401740137FE8C0175FEC9000100F1FFEE059C05DC0042000001321716 +173633321716151407060316171615140F01161514070623222726270603062322272635 +34373637121302272635343736333217363736333217161736373604F80A1011121D110F +1A1016D0C4518F0C1E2004161A0C1A147880ACDE204C2404330B051BCCE47A2809121310 +0F17090D121C200A36588ECC1805DC10101A331B11151B17E2FEF1C6EF140C1719160C14 +1E10121A9AD0E0FE8A362A1C3D501F0E2A013E010A01229D230A0E1A1A1D110C101EA898 +B8CC18000000000100FC0000060805EA0052000001321716173617161716151407060316 +1716151407060706070623222706070623222726270207062B0122272627062322272635 +3437262726353437363726032635343736333217363736333217161712373605541D1B14 +081A0A200E0E10F1EB9494111E1425020E1820241E080E241B171A5979E6581115021211 +0F021412191D241E0909121882C47A63161710161A30010B17112B0D6A96E7F90C05EA1E +163A01050C0E0D152810DBFEC7CCA21218311A110211233A18120B1B1C5FBFFEFB871A24 +1F0D0C1E23111B2D030D1818151FB0D4BD010B3D1C1A3120270E19321AC4BA0108DA0C00 +0000000300700000064405D5000B00170023000001112111211121112111211125211121 +11211121112111210321152111231121352111330436FE48FE5601AA01B801AAFEA601BE +FE42FDA8FE4201BE0258C801BEFE42C8FE4201BEC803C601ABFE55FE48FE5601AA01B850 +FDA8FE4201BE025801BFFD79C8FE4201BEC801BF0000000100700000064405D5000B0000 +01211121112111211121112104220222FDDEFE70FDDE0222019003B2FE70FDDE02220190 +0223000200700000064405D50003000F00000133352337211521112311213521113302E3 +EEEEEF0272FD8EF0FD8E0272F00273EE01F0FD8E0272F0027300000200700000064405D5 +0003000F0000012111212521112111211121112111210293018EFE72018F0222FDDEFE70 +FDDE022201900223018E01FE70FDDE02220190022300000101520000056205D5000B0000 +01211521112311213521113303D20190FE70F0FE700190F00445F0FCAB0355F001900002 +010C0000056C05D5000F001B000001331711211121271123271121112117071123112115 +21113311213504409696FED4FE8E969696012C017296C8DCFED4012CDC012C04A996FE8E +FD5F96020B960172012C96FA012CFED4DCFD5F02A1DC0003013E0000057605D5000B0017 +002300000111211121112111211121112721112111211121112111210321152111231121 +35211133040EFE98FEE8011801680118C80118FEE8FDF8FEE801180208A00118FEE8C8FE +E80118C8046D0118FEE8FE84FD5F02A1017C50FDE4FD5F02A1021C0118FE48DCFD5F02A1 +DC0118000000000100700000064405D5004B000001232206070607061507111716171617 +1E013B013534262726272623272107060706070E011D0133323637363736353711272627 +26272E012B011514161716171633172137363736373E0135030F7B64B349422C24321919 +242A4445B3687B4C47435F521B51047C3636525F43494A7B68B345442A24321919242C42 +46B6647B4C47435F521B51FB843636525F43494A02A04B4942604F1B54047C38374F5C46 +474B7A64B647422B25321919252B4249B4647A4B47465C4F1B54FB8438374F6042484C7B +64B647422B25321919252B4249B464000000000800BBFFE505F905F3000B001100140017 +001A001D0020002300000113210313210B012113032117031321130B0107331F01370307 +33052317012707133723035AE001BFE0E0FE41E0E0FE41E0E001BF3AA6A6014CA6A6A66C +D8AD6D6C6C6DD9FE7AD86CFEE76D6C6C6DD905F3FE7CFE7DFE7DFE7C01840183018364FE +E1FEE00120011F011FBB64BBBBFE7DBC63BC011FBCBC0184BB0000010054FFE3066005F0 +00430000013534272635343736321716151407061D013332373633321716140706232227 +262B011514171615140706222726353437363D0123220706232227263437363332171633 +03284C2C36387838362C4C20BA7A467664303030306476467ABA204C2C36387838362C4C +20BA7A467664303030306476467ABA031C20BA7A467664303030306476467ABA204C2C36 +387838362C4C20BB794676643031313064764679BB204C2C36387838362C4C0000000001 +0056FFE7065E05F00083000001262723060706070E0123222627262726343736373E0133 +32161716171617333637363735262726272E0135343637363736321716171E0115140607 +06070607151617161733363736373E0133321617161716140706070E0123222627262726 +27230607060715161716171E0115140607060706222726272E0135343637363736373526 +0302100AD4020A141D20522E2D52201F121111121F20522D2E5220220F0903D40A101115 +10142A1E212222212028285C282820212222212325170D1511100AD403090F2220522E2D +52201F121111121F20522D2E52201D140A02D40A1011150D172523212222212028285C28 +2820212222211E2A1410150293111510142A1E212222212028285C282820212222212325 +170D1511100AD5020A141D20522E2D52201F121111121F20522D2E5220220F0903D50A10 +11150D172523212222212028285C282820212222211E2A14101511100AD403090F222052 +2E2D52201F121111121F20522D2E52201D140A02D40A00010053FFE3066105F000830000 +01342627262726272E0135343637363736321716171E0115140607060706070E01153236 +37363736373E0133321617161716140706070E0123222627262726272E01231416171617 +16171E0115140607060706222726272E0135343637363736373E0135220607060706070E +0123222627262726343736373E0133321617161716171E0103282C240E10180A28282A26 +203A2E72303624262A2A260A1A081429274465210D0C120A2764383A6226241815151824 +26623A3864270A120C0D2961402F210D10190A27292A2624362F722F3624262A29270A19 +100D27293F63270E0C120A2664383A6226211B1616182426623A3D5F260A120614286203 +1C4363220E0C120A2664383A6226201C1616182426623A3D5F260A12061428613F2F210D +10190A27292A2624362F722F3624262A29270A19100D27294465210D0C120A2764383A62 +2624181515182426623A3864270A120C0D29614029270E10180A28282A26203A2E723036 +24262A2A260A1A0814282800000000010054FFE5066005F2013000000132171617161716 +151407060736373633321716171617161514070E01070623222726272627112126272627 +263534363736373633321716171617161514070607363736333217161716171615140706 +070607062322272627161716151406070607062322272627262726353437363736372111 +363736373633321716171617161514070607060706232227262716171615140706070607 +062227262726272635343736370607062322272627262726353437363736373633321617 +161711211617161716151407060706070623222726272627263534373637060706232227 +262726272634373637363736333217161726272635343736373637363332171617161716 +15140706070607211106070607062322272E012726353437363736373633321716172627 +263534373637363736035A1E171B12140B0A0A0B140B14191C1E181A13140B0A0A0B2819 +191D1E1719140E0A0126110E130B0B1613141A191C1D191A14130B0B0B08091419191C20 +171914130C0A0A0C1316171A1D1C19191409080B1613111D191D181D1A14130B0B0B0B13 +0E11FEDB090E111D171E1D181A13140B0A0A0B14121B171E191C140B130B0B0B0B14121B +173C171B12140B0B0B0B130B14181D1E171B12140B0A0A0B14131A181D1B34140E09FEDB +110D16090A0A0B1416171A1B1E191914130C0A0A080A16171A1C1D191A14120C0A0A0C12 +111D191D1C1A19140A080A0A0C13131A191E1B1A1914140B0A0A0C130D1101250A0E1419 +171E1D181A280A0B0B0A14131B171E1D18140B140B0A0A0B14131A1705F20B0C12141A19 +1D1C191A140A080A0A0C12141A191D1F1719280B0A0A0C130D11FEDB0A0E1419171E1D32 +14140B0A0A0B14131A181E1C19140B140B0A0A0B14121B181D1E181B1216090A0A0B140B +14191C1A3613110D0B0B0A14141A181D1E1719140E0AFEDA120D100E0B0B0B13141A191C +1D191A14120C0B0B0809131A191C1D1A1914130C0A0A0C1314191A1D1C191A1309080B0B +0C12141A191D1C191A14130B0B16130D120125090E1716181E1C191A1316090A0A0B1412 +1B171E1D19130C16090A0A0B14121B173C171B13110D0B0B0A140B14181D1E171B12140B +0B0B0B14131A181D1E171A130E0A0125110D130C0A0A0B28191A1C191D1A14120C0A0A08 +0A141A191C1D191A14120C0B000000010057FFE3065F05F5001B00001332373637361235 +1412171617163322070607060215340227262726579B8D867370736E756C8D8C9C9C8C8D +6C756E737073868D02EC3B38726F0114A19DFEF0776E3C3B3B3C6E77FEF09DA101146F72 +383B00020057FFE3065F05F5001B0037000001321716171E011534363736373633222726 +272E01351406070607062132373637361235141217161716332207060706021534022726 +27260165655C574A494B474D465B5B66665B5B464D474B494A575CFE8D9B8D867370736E +756C8D8C9C9C8C8D6C756E737073868D02EC26254A48B36966B14D4827262627484DB166 +69B3484A25263B38726F0114A19DFEF0776E3C3B3B3C6E77FEF09DA101146F72383B0002 +002FFFEC068505F300090013000013250901050113250513010321010309010301212F02 +0D011E011E020DFEA625FE0AFE0A2501D1BFFD9501F4BF01F501F5BF01F4FD9503A58301 +CBFE3583FE61FDE6CACA021A03ECFDB3FE94FDB4016CFE94024C016C000000020056FFE3 +065E05F00009002400000103210103090103012103321716171612151402070607062027 +2627260235341237363736035AACFDD301C2AC01C301C3AC01C2FDD3AC9A8E867370736E +756E8B8DFECB8E867370736E756C8D8C05EAFDEEFEB8FDEF0148FEB80211014802183B38 +726FFEECA198FEF077703A3B3B38726F01149C9D0110776E3C3B00020030FFED068405F2 +001D00270000002207060706070615141716171617163237363736373635342726272627 +03132101130901130121039D863939303017181817303039398639393030171818173030 +397CBF026BFE0CBFFE0BFE0BBFFE0C026B03E4181A2D32383B42413B38322D1A18181A2D +32383B41423B38322D1A0226FDB3FE94FDB4016BFE95024C016C0003002FFFEC068505F3 +000900270031000013250901050113250513003217161716171615140706070607062227 +262726272635343736373637130321010309010301212F020D011E011E020DFEA625FE0A +FE0A25018E863939303017181817303039398639393030171818173030397CBFFD9501F4 +BF01F501F5BF01F4FD9503A58301CBFE3583FE61FDE6CACA021A01DE181A2D32383B4241 +3B38322D1A18181A2D32383B41423B38322D1A0226FDB3FE94FDB4016BFE95024C016C00 +000000030030FFED068405F200090013001D000001132107132707132721130321010309 +0103012103132101130901130121035A600135FA5FFAFA5FFA0135608FFE2F0178900178 +0178900178FE2F8FBF026BFE0CBFFE0BFE0BBFFE0C026B0449FED9B6FEDAB6B60126B601 +FBFE47FEEFFE470111FEEF01B90111028EFDB3FE94FDB4016CFE94024C016C0000000003 +0030FFED068405F200090013001D0000011733071727073727331B012101130901130121 +37032105032505032521035A267C6426646426647C26BF026BFE0CBFFE0BFE0BBFFE0C02 +6BBF73FE8A012F75012F012F75012FFE8A034976497649497649031FFDB3FE94FDB4016C +FE94024C016CFBFE9DDBFE9DDBDB0163DB0000060030FFED068405F2000200050008000B +000E001800002501352501370103272521070B01171113210113090113012101E10178FD +A00178E8017A90E80260FE2F8F018F8FBF026BFE0CBFFE0BFE0BBFFE0C026B990111F3C7 +FEEF4BFDFB01B94BC7C60280FE47C60353FDB3FE94FDB4016CFE94024C016C0000000002 +00320018068605B7000D0017000009011327250127130121131713210103210503010503 +01210686FE3EACA2FEB2FE6AA2ACFE3E022DACA29B019CFD2786FE4F015E86015F015F86 +015EFE4F0371FEB8FDEF34F3FED93402110148021234FE220168FE64FFFE6400FFFF019C +00FF000100840000063005D5001100000111211125130D01032511211105032D011302BA +01400196A0FE6A0196A0FE6AFEC0FE6AA00195FE6BA0040001D5FE2AEBFEEBEAEBFEEBEB +FE2A01D6EB0115EBEA0115000000000200980000061C05D5000500170000010717333727 +05013701113311011709010701112311012702E37878EE7878FE99FE2E7801D2F001D278 +FE2E01D278FE2EF0FE2E7803B9CED0D0CECE010DD0FEF2021BFDE5010ED0FEF3FEF2D001 +0DFDE6021AFEF3D00000000100700000064405D500170000090107011123110127012135 +21013701113311011701211503D401B947FE4664FE464701BAFD8F0272FE454701BA6401 +BA47FE45027202B8FE464701BBFD8E0271FE464701BA6401BB47FE460271FD8F01BA47FE +456400010040FFE3064C05F0000F0000011309010D0109010B0109012D01090103464F01 +D4FE9D0246FDBA0163FE2C4F4FFE2C0163FDBA0246FE9D01D405F0FDB90164FE2C504FFE +2C0164FDB90247FE9C01D44F5001D4FE9C0000090054FFE3066005F0000200050008000B +000E00110014001700270000010503052505010325031303012513250525011305130313 +111325030D0113250B0105132D01030504F5FEDD770244FEDBFEE1019A7BFEE1017777FE +65012377FDBC0125011FFE667B011F0177779F0184A40187FE79A4FE7C9F9FFE7CA4FE79 +0187A4018404857BFEE0017878FE65012377FDBC0125011FFE667B011F017777019CFEDD +780245FEDBFEE00306FE79A4FE7CA09FFE7CA4FE790187A401849FA00184A40000000001 +00BBFFE305F905F0000B00000113250901250B0105090105035A70022FFE4101BFFDD170 +70FDD101BFFE41022F05F0FDBCC1FE7DFE7DC1FDBB0245C101830183C10000010054FFE3 +066005F0000F0000011309010D0109010B0109012D010901035A7101B2FEEF01F4FE0C01 +11FE4E7171FE4E0111FE0C01F4FEEF01B205F0FE0B0112FE4E7271FE4E0112FE0B01F5FE +EE01B2717201B2FEEE0000010054FFE3066005F0000F0000011325030D0113250B010513 +2D010305035AAA017989016CFE9489FE87AAAAFE8789FE94016C89017905F0FE9489FE87 +ABAAFE8789FE94016C890179AAAB0179890000010054FFE3066005F00017000001132503 +25030D01132513250B01051305132D0103050305035A74010F470163EF0156FEAAEFFE9D +47FEF17474FEF147FE9DEFFEAA0156EF016347010F05F0FEA9EFFE9D47FEF17374FEF147 +FE9DEFFEA90157EF016347010F7473010F470163EF00000100700000064405D5002F0000 +011133111317030117012517052115210507250107011307031123110327130127010527 +252135212537050137010337031E78AA6FAA013A55FEC5019C2EFE6301BEFE4601992EFE +64013B55FEC7A96FAA78AA6FAAFEC655013AFE652E019AFE4501BEFE632E0199FEC85501 +39A96F041701BEFE4501992EFE66013A55FEC5AB6FAB78AA6FACFEC4550139FE672E0198 +FE4601BCFE662E019BFEC555013AAA6FAA78AB6FA9013955FEC701992E00000100A7FFE3 +060D05F0009B0000013534272635343736321716151407061D0117161737363736373633 +32171617161514070607062B012223220F0116151407171633323B013217161716151407 +0607062322272627262F01060F011514171615140706222726353437363D012627262707 +06070607062322272627263534373637363B013233323F0126353437272623222B012227 +2627263534373637363332171617161F0136373603274B2C36387838362C4C101F180A71 +4127433C341615471E14050E573E4C0C0B0A83640C04050C66820B0A0C4C3E570E05141E +471516343C4327436E0E1B19104C2C36387838362C4D09081D170E6E4327433C34161547 +1E14050E573E4C0C0B0A84640C05040C64830A0B0C4C3E570E05141E471516343C432745 +6C0B1A1D0803940C8077464E6430313130644E46797E0C060E1706447A49272307183423 +2C16184732243A0713141514073B24324718162C233418072327497E3F081909060F7F79 +464E6430313130644E46797F0F03030D15083F7E492723071834232C16184732243A0714 +161413073A24324718162C23341807232749823B06190B030000000200A7FFE3060D05F0 +001900B5000000220706070E0115141617161716323736373E0135342627262F01353427 +2635343736321716151407061D011716173736373637363332171617161514070607062B +012223220F0116151407171633323B0132171617161514070607062322272627262F0106 +0F011514171615140706222726353437363D012627262707060706070623222726272635 +34373637363B013233323F0126353437272623222B012227262726353437363736333217 +1617161F01363736036F2A11120E0F0F0F0F0E12112A11120E0F0F0F0F0E12594B2C3638 +7838362C4C101F180A714127433C341615471E14050E573E4C0C0B0A83640C04050C6682 +0B0A0C4C3E570E05141E471516343C4327436E0E1B19104C2C36387838362C4D09081D17 +0E6E4327433C341615471E14050E573E4C0C0B0A84640C05040C64830A0B0C4C3E570E05 +141E471516343C4327456C0B1A1D08035008080E0F231514230F0E080808080E0F231415 +230F0E084C0C8077464E6430313130644E46797E0C060E1706447A492723071834232C16 +184732243A0713141514073B24324718162C233418072327497E3F081909060F7F79464E +6430313130644E46797F0F03030D15083F7E492723071834232C16184732243A07141614 +13073A24324718162C23341807232749823B06190B03000100A1FFE3061205F000680000 +0117323F0127262307222627263437363736321617161F01353427263437363217161407 +061D013736373E013217161514070E012327220F01171633373216171614070607062227 +2627262F01151417161407062227263437363D010706070E012227263534373E0101B59F +51212F2F1F56965AA2120B192A6E144E992B4738306A403652A85236406A302F5031934E +14B10B12A2549E502330301F56965AA2120B192C6C154F40572B4738306A403652AC4E36 +406A30334C2F954E14B20C13A102C40A141C1B12075D482C602C4820065963A2211C3737 +995CCA344F4F34D0568F41371C1CA7675506339A272C485E0A141B1C12075D482C602C4C +1C06263363A2211C3737995CCA344F4F37CD568F41371C1EA5665606349A262C485E0004 +00A1FFE3061205F0000E009900B100C90000013637363534272622070615141716131716 +333237363332171617161514070607062322272627262F01151417161514070623222726 +353437363D010706070607062322272627263534373637363332171633323F0127262322 +07062322272627263534373637363332171617161F013534272635343736321716151407 +061D01373637363736333217161716151407060706232227262322070506232227262322 +070607061514171617163332373637362516171617163332373637363534272627262322 +07062322035A1C38251E3154311E2538A8123565202512105A465C120B192B6D14163842 +532F3F4A125640365254584E3640561247423151423816146E2A190B125C465A10122520 +653512123365212612105A465A140C1A2A6E14163842532F3F4A1256403652A852364056 +1247423151423816146D2B190B125C465A101226216533FE9844411E1D090A353C3B0705 +0D153E0A0C1F2B46192401DC482419462B1F0C0A3E150D05073B3C350A091D1E4103D363 +4A3251441A2B2B1A4451324AFEB40B2003012835482C27392C4A1E0626306689290A1454 +7C5C606A344F4F3767605C7C54140A278B672F260620482C39272C4934280103200B0B1F +030128334A2C26392D48200626306689290A14547C5C606A344F4F346A605C7C54140A27 +8B672F26061E4A2C39272C48352801031F801204012222271815211724150419283A554A +4A553A2819041524172115182722220104000002006E0000064605AD0019007700000022 +0706070E0115141617161716323736373E013534262726273736373633321716171E0115 +140607060706222716171E0115140607060706222726272E01270E010706070622272627 +2E0135343637363706222726272E013534363736373633321716172E0135343637363736 +33321716171E0115140603A2903D3E3334333334333E3D903D3E3334333334333E783E25 +38353637302D292C282D25382F5B1B30212D282C292D3032792F3825301D050525292D30 +32792F38252D282E27262417572F3A232D282C292D30323B3E2F3B29081B2C292D30323B +3E2F38252D281B040B1A1C30367B49467B36301C1A1A1C30367B46497B36301C13351017 +17142B27673E3A632E2619150517222E633B3D67272B1415151926314E252657272B1415 +1519262E633A3E6B23221504151B242E633A3E67272B141515192C0E423A3E67272B1415 +1519262E633B3D3F00000006006E0000064605AD005C0076008D00A600BD00D60000250E +0107060706222726272E0135343637363706222726272E01353436373637363332171617 +2E0135343637363736321716171E011514060736373633321716171E0115140607060706 +222716171E0115140607060706222726272E010232171617373635342627262726220706 +070E0115141F01363713262726272627070E011514161716171632373637363703263534 +36372726272623220706070E01151416171617163301171617161716323736373E013534 +262F0106070607061337363736373E0135342627262726232207060F011E011514035A05 +1D3025382F7932302D292C282D21301B5B2F38252D282C29302D3C313A33253E071B282D +25382F7C2F38252D281B073E2538353637302D292C282D25382F5B1B30212D282C292D30 +32792F3825301D4D903D08070E0D191B19201E481E20191B1903180708432320422F1E1B +67181C191B19201E481E20191804D6032F2A780807231F241E2019181C191B1920150402 +3823231819201E481E20191B191C18671B1E2F4220F550501520191B191C1819201E241F +230708782A2FF8254E3126191515142B27673D3B632E2217051519262E633A3E67272E11 +171710350D3F3D3B632E2619151519262E633B3D3F0D35101717142B27673E3A632E2619 +150517222E633B3D67272B1415151926314E03381A0304474714243E1B180E0D0D0E181B +3E24140C820403FD63060E1E2E1E2C62174224233E1B180E0D0D0E18180901BA16174972 +304304030D0D0E18183E27233E1B180E09FEC24C4D18180E0D0D0E181B3E23244217622C +1E2E1E0E01240A0A090E181B3E23273E18180E0D0D030443307249170000000A0054FFE3 +066005F00029005400BD00E60111013A0165017F01A901D3000001262707062322262726 +2726343736373E0133321F013637272623220607060706141716171E0133323725060717 +1633323637363736342726272E0123220F01161737363332161716171615140706070E01 +232223012E013437363736373633321617343637363736321716171E01153E0133321716 +171617161514060732161716171615140706070E01231E011514070E0107062322262714 +0607060706222726272E01350E012322272E012726343637222627262726343736373E01 +0126270706070E010726272E012726273E0137363F0126270706070E011516171E023332 +363736370116173736373E0135262726272E0123220607060F0116173736373E01371617 +1617161716170E010706070306071716171E011706070E010706072E0127262F01060717 +16171E0133323E0137363734262726270136372726272E01232206070607060714161716 +1F0136372726272E012736373637363736371E0117161704220706070E01151416171617 +16323736373E01353426272627130607171615140607060706222726272E0135343F0126 +27070615141617161716323736373E01353427031617373635342627262726220706070E +0115141F013637272635343637363736321716171E01151407023A0602E411090A1A0A08 +06040406080A180C0911E40206CE24141C3416140C0A0A0C1416341C1424031A02067777 +121E3416140C0A0A0C1416341E1209E5060278790710160A0806040406080C180C0705FB +CA25221110202128292D2B552422212028285C282820212226532B2D2928211C14112027 +374D22230F101012201C5632252011104128292D2E522422212028285C2828202122284F +2D2C2A284110112223325022230F10101220224D01C114108E0B0607190E0D0B0C130504 +01010909060EB40E0BB8210E1515010A0A2A341D2032150E1301EB0F0A6B6B0D1615010A +0A1515341D2032160D057514114B4B050A170E0D0B0C0A09050401010B080503A60B0EB4 +0E06070B01010405130C0B0D0E1709060B8E111369130E1532201D342A0A0A0115150E21 +FD8912133D3D0D1632201D3415150A0A0115160D09CD0B0E605F050A0901010405090A0C +0B0D111608050301775223241D1D1E1E1D1D24235223241D1D1E1E1D1D240316151B020A +090A0B0C1A0C0B0A090A021B1615380A1516151A193C191A1516150A6317152021151615 +1A193C191A151615033E16160E0F0A090A0B0C1A0C0B0A0B0801029A16161C020A0A0A0A +0C1A0C0C0A080A021A1614380A1416161A183C1A1A1416160A64181421211616141A1A3C +181A161614023E17150E0E0A080A0C0C0C0E0C0A0A0C0801222555562A29202110102124 +3352201F121111121F205233261F1010211C2D282D255A2620222325282E322428201E26 +26532B2C292941101021243352201F121111121F205233271E1010412929585424222223 +2528602428202220FE1C0B0DB40D06080A01010405120C0C0D0D1809060A8E111369130E +14331F1D1B192B1415140E21027812133D3D0D15331F1D1B1916151415150D09CD0C0E5F +60050B080101040509090C0C0D1016090502FE1D13118E0A0608190D0D0C0C1205040101 +0909060DB40E0AB9210E1415142B191B1D1F33140E1301EB0F0B6B6B0D1515141516191B +1D1F33150D057513124B4A050B170D0D0C0C0909050401010A090503DF0F101C1F462928 +471E1C100F0F101C1E472829461F1C10FE240602E411080B190A0905050505090A170D08 +11E40206CD25131D3316140C0B0B0C1416331D1325031A02067777121E3316140C0B0B0C +1416331E1209E506027879070F160A0905050505090C170C070500040056FFE3065E05F0 +00190033004E005E000000220706070E0115141617161716323736373E0135342627262F +01321716171E0115140607060706222726272E0134363736373613321716171612151402 +0706070620272627260235341237363736170325130D0103251B0105032D01130503977A +33352B2B2B2B2B2B35337A33352B2B2B2B2B2B357057444D36393F3D3B394A48AA444D36 +393F3D3B394A48539A8E867370736E756E8B8DFECB8E867370736E756C8D8C9C9DFE7FA3 +FE7D0183A301819D9D0181A30183FE7DA3FE7F04131618282E683C3B682E281816161828 +2E683B3C682E2818861F22363896544E963B391F1F1F22363896A2963B391F1F016D3B38 +726FFEECA198FEF077703A3B3B38726F01149C9D0110776E3C3B08FE7DA3FE7F9E9DFE7F +A3FE7D0183A301819D9E0181A300000700A1FFE3061205F000080093009C00A700B200C1 +00D000000111220706151417161317163332373633321716171615140706070623222726 +27262F01151417161514070623222726353437363D010706070607062322272627263534 +373637363332171633323F01272623220706232227262726353437363736333217161716 +1F013534272635343736321716151407061D013736373637363332171617161514070607 +062322272623220F01113237363534272613252627262322070607060105161716333237 +3637361325061514171617163332373633320D0136353427262726232207062322035A2A +311E2538A8123565202512105A465C120B192B6D14163842532F3F4A125640365254584E +3640561247423151423816146E2A190B125C465A10122520653512123365212612105A46 +5A140C1A2A6E14163842532F3F4A1256403652A8523640561247423151423816146D2B19 +0B125C465A1012262165339E2A311E2538AE017E153E0A0C1F2B461924FE24FE82153E0A +0C1F2B46192448FE820D05073B3C350A091D1E4101D8017E0D05073B3C350A091D1E4103 +D301B92B1A4451324AFEB40B2003012835482C27392C4A1E0626306689290A14547C5C60 +6A344F4F3767605C7C54140A278B672F260620482C39272C4934280103200B0B1F030128 +334A2C26392D48200626306689290A14547C5C606A344F4F346A605C7C54140A278B672F +26061E4A2C39272C48352801031FF5FE472B1A4451324A01C1DD24150419283A55FECDDD +24150419283A550133DD172115182722220104FBDD172115182722220104000100B60000 +05FE05D50041000001270727372737173533151711273717353315371707113735331537 +170717072707173717071707271523352711170727152335072737110715233507273727 +371702E2F0C23C86C43CC478F0C23C8678863CC2F078C43CC4863CC2F0F0C23C86C43CC4 +78F0C23C8678863CC2F078C43CC4863CC202EB8A70684D7268729BE08B01166F684DE3E3 +4D686FFEEB8AE09B7268724D68708A8B70684D7268729BE08BFEEA6F684DE3E34D686F01 +168BE09B7268724D68700007009C0000061805D5001D002100250029002D003100350000 +013311251125170D0307251125112311051105272D033705110507151735250715370507 +1737250717370507153F01151735031E78012301093CFEF60124FEDD01093CFEF7FEDD78 +FEDDFEF73C0109FEDD0124FEF63C01090123ABAB0123ABABFDF7ACABAB019AAAABABFDF6 +ABAB78AB05D5FECCA9FEB09A689AA8A899689AFEAFA9FECD0133A901519A6899A8A89A68 +9A0150A927C563C56363C56368636262636362626763C663C6C663C60000000100820004 +063005D50041000001331537170711251133153717071707270D01371707170727152311 +251117072715233507273711051123350727372737172D01072737273717353311051127 +3717031E788C64F0010CC8583C5B8C64F0FEF6010BF0648C593C5CC8FEF9F0648C788C64 +F0FEF7C85B3C588C64F0010BFEF7F0648C5C3C59C8010DF0648C05D56851AD8BFECE9B01 +15A234683551AD8A999B8BAD51346835A1011598FED08BAD51686551AD8B013599FEEBA1 +35683351AE8B9A998BAD51356833A1FEEB9B01368BAD51000000000100A1FFE5061305F0 +008700000116151407060706232227262707173637363332171617161514070607062322 +27262726353437271116171E01140E01222E013436373637110716151407060706232227 +26272635343736373632171617372706070623222E013437363736333217161716151407 +171126272E013436373633321E0114060706071137263534373637363332171605F81B1B +1B2E2F35362E1311F1F111132E36352F2E1B1B1B1C2D2F3533312F1B1A04F115132E3636 +5C6C5C36362E1315F1041A1B2F2F35362E2E1B1B1B1B2E2F6A2F1311F1F110143133365C +361B1B2E2F3533312F1B1A04F115132E36362E2F35365C36362E1315F1041A1A302E3635 +2F33046D362E352F2E1B1B1B0B0F8B8C0F0B1B1B1B2E2F35342F2F1B1B1B1A2F2E361615 +8CFEE9070B1B5C6C5C36365C6C5C1B0B0701178C1516362E2F1A1B1B1A2F2E36352F2E1B +1B1B0B0F8C8B0F0B1B365C6C2E2F1A1B1B1A2F2E3616158C0117070B1B5C6C5C1B1B365C +6C5C1B0B07FEE98C1516362E2D1C1B1B1E0000090061FFE5065305D70007000B000F0013 +0017002E0045005C00730000001406222634363209013709023709022709022701132627 +262726353437363736321716171615140706070603161716171615140706070622272627 +263534373637360306070607062322272627263437363736333217161716053637363736 +333217161716140706070623222726272603F0587C58587C01A7FE7D550183FD0DFE7F55 +0181029EFE7D550183FDB7FE7F550181B9390F17040102030F1772170F03020104170F39 +390F17040102030F1772170F03020104170F9F3C1825441D52451E4120313120411E4552 +1D44251801EC3C1825441D52451E4120313120411E45521D442518032C7C58587C58FD2E +018455FE7C024A018155FE7E012DFE7C550184FD0DFE7E550181012E3C1825441D52451E +4120313120411E45521D442518FE143C1825441D52451E4120313120411E45521D442518 +0114390F17040102030F1772170F03020104170F39390F17040102030F1772170F030201 +04170F00000000090061FFE5065305D70007000B000F00130017002B003F005300670000 +001406222634363209011709021709020709020701021407060706222726272634373637 +363217161712140706070622272627263437363736321716170032171617161407060706 +2227262726343736372432171617161407060706222726272634373637043B84BA8484BA +FD69012955FED702A1012955FED7FD0A012955FED7034B012955FED7081315272E6E2E27 +15131315272E6E2E2715131315272E6E2E2715131315272E6E2E2715FD19703137232828 +2337317031372328282337044D7031372328282337317031372328282337034BBA8484BA +84FD38012955FED7034C012955FED7017EFED7550129FD5EFED755012903497031372328 +282337317031372328282337FBB370313723282823373170313723282823370259131527 +2E6E2E2715131315272E6E2E2715131315272E6E2E2715131315272E6E2E271500000009 +0054FFE5066005F2000C001800250032003F004B0057005F006B0000011407062B012535 +25333217160534363B0105150523222726011615140F0101270137363332012635343F01 +01170107062322033633321F01010701272635340106222F010137011716151400321716 +1D010323033534371214062226343632022227263D0113331315140706601C1C280AFE60 +01A00A261E1CF9F438280A01A0FE600A261E1C052A1D1B07FEB7470102071C292AFBD51D +1B07014947FEFE071C29281F1D2A281D07010247FEB7071B04651E521C07FEFE47014907 +1BFD96521E1D3264321DDD587C58587C15521E1D3264321D02EC2A1E1C3264321E1E2828 +3C3264321C1E024D1D2A271D07FEFE460149071CFB9B1D2A271D07010246FEB7071C0465 +1E1C07FEB7460102071B292AFBD61E1C07014946FEFE071B292A050D1D1C280AFE61019F +0A271DFD557C58587C58FC631D1C280A019FFE610A271D00000000010054FFE5066005F2 +006C00000132171615140703013637363332171615140706070125363332171614070623 +222725011617161514070623222726270113161514070622272635343713010607062322 +272635343736370105062322272634373633321705012627263534373633161716170103 +2635343736035A2B2F23054E011C15023432421F1E340A19FE7B01DF190C4C222E2E244A +0725FE28018A1C02341E1F422B3B0A12FEE94E05232F562F23054EFEE9120A3432421F1E +34021C018AFE2825074A242E2E224C0C1901DFFE7B190A341E1F4224420215011C4E0523 +2E05F22F234A071FFE2201891D01351F1E43372F0912FEE94E04222E582E24064DFEE516 +013531431E1F3509190185FE221F074A232F2F234A0E1E01D8FE7B1909351F1E434C1A01 +16011B4D06242E582E22044E011712093531431E1F1421011DFE7701D81E0E4A232F0002 +0066FFEC06C605E8000D002300E7BA0000001300032BBA001F000600032BB8001F10411B +001600000026000000360000004600000056000000660000007600000086000000960000 +00A6000000B6000000C6000000D60000000D5D410500E5000000F5000000025D410500EA +000600FA000600025D411B00190006002900060039000600490006005900060069000600 +790006008900060099000600A9000600B9000600C9000600D90006000D5DB80025DC00BA +0003001000032BB8000310BA0017000A00032BB8001710B8001010B8000ED0B8000E2FB8 +001710B80019D0B800192FB8001710B8001BD0B8001010B80022D0303113100021200011 +3402242322040201062320001134122433321736333204121510002122B9018F011C011C +018FB6FEB8ADADFEB8B602DC1919FEC4FE3FCE0171BE19191919C10171CDFE3FFEC21902 +EAFEE4FE700190011CB30147B1B1FEB9FC510201BF013FC60172C60202C6FE90C8FEC1FE +41000002007AFF9C06B205D40003000B0037BA0003000500032BB8000310BA000A000000 +032BB8000A10B8000DDC00BA0000000A00032BB8000010BA0007000100032BB800071030 +3125112111172311211533112105FBFAD2116405D464FA2C53052EFAD25305D464FA2C00 +00000002007A000006B206380003000B0037BA0003000600032BB8000310BA000B000000 +032BB8000B10B8000DDC00BA0000000500032BB8000010BA000A000100032BB8000A1030 +3125112111251521113335211105FBFAD20581FA2C6405D453052EFAD2116405D464FA2C +00000002007AFF9C06B205D4000300090037BA0003000400032BB8000310BA0008000000 +032BB8000810B8000BDC00BA0000000800032BB8000010BA0006000100032BB800061030 +312511211107112117112105FBFAD25305D464FA4A53052EFAD25305D482FA4A00000002 +007A000006B20638000300090037BA0003000500032BB8000310BA0009000000032BB800 +0910B8000BDC00BA0000000400032BB8000010BA0008000100032BB80008103031251121 +1105211137211105FBFAD20581FA2C8205B653052EFAD25305D464FA4A00000400AAFFFB +068205D300030007000B000F000B00B800092FB8000F2F303113090EAA01530153FEAD01 +DF01540152FEAEFD1501530153FEADFEAD01530153FEAD02E90153FEADFEAD01530152FE +AEFEAB02EA0155FEABFEAEFE220153FEADFEAD00000000010305FE1403AF061400030000 +0111231103AFAA0614F800080000000102B0FE140404061400030000011121110404FEAC +0614F800080000010206FE1404AE0614000300000111211104AEFD580614F80008000001 +00AE0328021C05D50006001BBA0000000100032B00B800032FBA0006000000032BB80006 +10303101211113330333021CFE92A481529B0328016E013FFEC100010078032801E605D5 +00060027BA0001000000032BB800011000B800032FBA0001000200032BB8000110B80002 +10B80005D030311321110323132378016EA481529B05D5FE92FEC1013F00FFFF00AE0328 +03D605D510260FE4000010070FE401BA0000FFFF0078032803A005D510260FE500001007 +0FE501BA00000002013EFF42062D06CF00440048000001321716171E0115140607060706 +23220706070E01141617161716323736373E013511212226353424332135371521072311 +140607060706222726272E01353436373637360111231102DD1F1A1B1615171715161B1A +1A1C131310111013101410185F3E492D3734FECDDEFD0104D701338D01548CC8503E4350 +4EB144472D322E312F26443901A8BE01D00B0C1514381F1E3814150C0B09081010272E2A +0E1206091B212D388144015FEEB8BEE8C832FA7BFBDB6AB740462725191A2E3372404D64 +372B1D1801340256FDAA0002019EFFDD051605F00015002F000001321716171E01151407 +06072627263534363736373612321716171E0115140607060706222726272E0135343637 +3637035AA0792A2C262788B28286AE88272625315D7E7C34352C2D2B2B2D2C35347C3435 +2C2D2B2B2D2C3505F0280E2B2661347C6483CECE83667A346126241528FC4616182A2E6A +3D3C6A2E2A181616182A2E6A3C3D6A2E2A180002014EFFDD056605F00021003B00000126 +252635343637363736321716171E0115343637363736321716171E011514070402321716 +171E0115140607060706222726272E01353436373637035A7BFEF788272625312D6C2E2F +2625282825262F2E6C2D3125262788FEF6B87C34352C2D2B2B2D2C35347C34352C2D2B2B +2D2C3502ADC4A153833461262415141414252560363660252514141415242661348452A1 +FEC516182A2E6A3D3C6A2E2A181616182A2E6A3C3D6A2E2A18000001006E00AB06460528 +002100002526252635343637363736321716171E0115343637363736321716171E011514 +0704035A89FE5FC237373545419A414436353939353644419A4145353737C2FE5FABEFF2 +71B24B8A36341E1C1C1D3534894E4E8934351D1C1C1E34368A4BB46FF20000010158FFFE +05D405D600230000010603062322262726272635343736373E0133222627262726353437 +36373E013332171205D4EEF272B24A8A36341E1C1C1C3634884E4E8834361C1C1C1E3436 +8A4AB470F202EA8AFE60C236383446404E4C424436343A38363644404E4C4244363638C2 +FE5E0002007E002A05CB059E000D00690000013635342726232207061514171617060736 +333217161514050615143332373633321514212227263510213217363726272623220706 +15141615140706232227263534373633321F013437363332171615140716333237363534 +2726353437363332171615140706232203970A1D18361E16132B4A6F152E5E567C3B2DFE +CB9969741033363FFEC5CCBAC6010566514D1065E6454A533F282A1A241420190E735D76 +68324B292A4E4C3133103C564C2A2D152A151F38412A148F5F78410467302C3F2921120F +1B3A22398C4A4F43513E5EDA763A4D632A845A9C99A3FE01658D974330AA334E33352526 +1D17141C2D194B745C4B2539531F203E42574E3D1121241F3F122415381C284621357B5E +3F00000200A0015A06130484000900510000010607060736373635340120353437262726 +2B0116070607163332371615142322272335333637363726272627263534330615141716 +1732171617263534373633321316333235342726353433321514070601DF3F2D421B4F3E +3D01FEFE8B763036262504024B606419432B2808916B1424271E654D3F0F24233D48620C +8F42043D292B381F3D2E6DD8391865620C0B5042F7B0033E0E2433671D3A383404FE21FE +4542371C145750660F472B1E314BAB488E503C021212120E1146661D2326542719131430 +502B41271DFE939A3C1E12111B3C51AD624600010191FEE4052F06270020000001222726 +2726272635343736373637363315220706020706151417161716171633052FBAA9A18A86 +464444408C84A7A9BA6D6860A628282828534E656372FEE447438985A7A4BBBDA99D8F86 +46472F433EFEFA9B9CB8B19C9B837D44430000010191FEE4052F06270022000001303532 +373637363736353427260227262330353217161716171615140706070607060191726365 +4E5328282828A660686DBAA9A7848C40444446868AA1A9FEE42F43447D839B9CB1B89C9B +01063E432F4746868F9DA9BDBBA4A7858943470000000001021DFEF104980611000C0000 +01060210121721260235341237049886828385FEB0969594970611E6FE3EFE32FE3BE5EB +01C6E0DF01C4EC0000000001021DFEF104980611000D0000013021161215140207213612 +1002021D015097949596FEB08583820611ECFE3CDFE0FE3AEBE501C501CE01C200000001 +01B9FE1404DB06140005000001210901210104DBFEDCFE0201FE0124FE00FE1404000400 +FC00000101DBFE1404FC061400050000090221090101DB01FFFE02012201FEFE02FE1404 +000400FC00FC0000000000010123FE14057B061400050000012109012101057BFE3CFD6C +029401C4FD60FE1404000400FC0000010139FE1405910614000500000902210901013902 +A0FD6001C40294FD6CFE1404000400FC00FC0000000000010155FE14053F061400050000 +012109012101053FFE14FE0201FE01ECFE00FE1404000400FC0000010177FE1405600614 +000500000902210901017701FFFE0201EA01FEFE02FE1404000400FC00FC000000000001 +02C4FE1304480614000700000517070111011707035AEE6AFEE6011A6AEE95EE6A011A05 +CD011A6AEE000001026CFE1303F00614000700000127370111012737035AEE6A011AFEE6 +6AEE04BCEE6AFEE6FA33FEE66AEE00010166FEB2051306140024000005152322263D0134 +262B01353332363D0134363B01152322061D011406071E011D011416330513D4F9A96C8E +3D3D8F6BA9F9D4448D565B6E6F5A568DBE9094DDEF97748F7395F0DD938F588DF89D8E19 +1B8E9CF88D58000101A1FEB2054E061400240000053332363D013436372E013D0134262B +01353332161D0114163B01152322061D0114062B0101A1448D565A6F6E5B568D44D4F9A9 +6B8F3D3D8E6CA9F9D4BE588DF89C8E1B198E9DF88D588F93DDF095738F7497EFDD940002 +0098FFEC069405E8000D0018001B00BA000E000300032BB8000E10BA000A001300032BB8 +000A103031011000212000113412243332041201213523112305153711230694FE3FFEC2 +FEC4FE3FCE0171BEC10171CDFBD30269DAA1FEFFECD902EAFEC1FE4101BF013FC60172C6 +C6FE90FD5C8E031A2F982BFD820000020098FFEC069405E8000D002A001B00BA00250003 +00032BB8002510BA000A001400032BB8000A103031011000212000113412243332041205 +3E0135342623220607153E0133321615140607060407152135213E010694FE3FFEC2FEC4 +FE3FCE0171BEC10171CDFDE63E30C5A33D916173843B4E5F2F3E21FEFA5202A4FE4C7DB2 +02EAFEC1FE4101BF013FC60172C6C6FE90E946693A7D971F24AB3A264E3F28553F22F14D +818E74A7000000020098FFEC069405E8000D00360099BA0000000E00032B410500EA000E +00FA000E00025D411B0019000E0029000E0039000E0049000E0059000E0069000E007900 +0E0089000E0099000E00A9000E00B9000E00C9000E00D9000E000D5DB80031DC00BA002E +000300032BB8002E10BA000A001100032BB8000A10BA0021002700032BB8002110BA0018 +001E00032BB8001810BA0015001E0018111239BA002A0027002111123930310110002120 +00113412243332041225342623220607153E013332161514062B01153332161514062322 +2627151E013332363534262732360694FE3FFEC2FEC4FE3FCE0171BEC10171CDFE7CC1A1 +3D885F6B79345B5C585A918C636B7375447D5B588840C3D8760D046802EAFEC1FE4101BF +013FC60172C6C6FE9018738914189720153F3C3A3C8C4E474C4F1C30A81B179C8D5D8103 +720000030098FFEC069405E8000D0010001B001B00BA0014000300032BB8001410BA000A +001B00032BB8000A10303101100021200011341224333204120121090115211533353335 +2311230694FE3FFEC2FEC4FE3FCE0171BEC10171CDFD16FEF3010DFE4301BDB68D8DCE02 +EAFEC1FE4101BF013FC60172C6C6FE90FEBC017EFE838ED3D38D0248000000020098FFEC +069405E8000D002B008BBA0000002100032B410500EA002100FA002100025D411B001900 +21002900210039002100490021005900210069002100790021008900210099002100A900 +2100B9002100C9002100D90021000D5D00BA001E000300032BB8001E10BA000A002B0003 +2BB8000A10BA0011001700032BB8001110BA0028002400032BB8002810BA001A00170011 +11123930310110002120001134122433320412053E0133321615140623222627151E0133 +323635342623220637352135210694FE3FFEC2FEC4FE3FCE0171BEC10171CDFBEF596532 +6876766844775F588540BED5CFB01F3F030191FDC302EAFEC1FE4101BF013FC60172C6C6 +FE90F32514615655611B2FAC1814AC9895B00A01AB8E00030098FFEC069405E8000D0019 +003200F1B800332FB800342FB8003310B80006D0B800062FB8003410B8000EDCB80014DC +410500EA001400FA001400025D411B001900140029001400390014004900140059001400 +69001400790014008900140099001400A9001400B9001400C9001400D90014000D5DB800 +0610B8001DDC411B0016001D0026001D0036001D0046001D0056001D0066001D0076001D +0086001D0096001D00A6001D00B6001D00C6001D00D6001D000D5D410500E5001D00F500 +1D00025D00BA0020000300032BB8002010BA000A001A00032BB8000A10BA001700110003 +2BB8001710BA002C002600032BB8002C10BA002F0026002C111239303101100021200011 +34122433320412011406232226353436333216032202151416333236353426232206373E +0133321617352E010694FE3FFEC2FEC4FE3FCE0171BEC10171CDFDA25C4E4E5C5C4E4E5C +71C8EAC0B9A0C2BAA04E8009057F772F61544A6A02EAFEC1FE4101BF013FC60172C6C6FE +90FE8F5A62625A5B6262022AFEF5DAE7FDB39194B13D0B509416239C181300020098FFEC +069405E8000D0014001B00BA0011000300032BB8001110BA000A001400032BB8000A1030 +310110002120001134122433320412252101330135210694FE3FFEC2FEC4FE3FCE0171BE +C10171CDFBC201CFFEAFC1016CFD5502EAFEC1FE4101BF013FC60172C6C6FE9076FCE603 +5D4B00040098FFEC069405E8000D00190031003D018BBA001A000600032BB8001A10BA00 +0E001400032BB8000E10BA0000002000032BB8000010410500EA001400FA001400025D41 +1B0019001400290014003900140049001400590014006900140079001400890014009900 +1400A9001400B9001400C9001400D90014000D5D411B0016001A0026001A0036001A0046 +001A0056001A0066001A0076001A0086001A0096001A00A6001A00B6001A00C6001A00D6 +001A000D5D410500E5001A00F5001A00025D410500EA002000FA002000025D411B001900 +20002900200039002000490020005900200069002000790020008900200099002000A900 +2000B9002000C9002000D90020000D5DBA002F00060000111239BA003B0014000E111239 +B8003B2F410500EA003B00FA003B00025D411B0019003B0029003B0039003B0049003B00 +59003B0069003B0079003B0089003B0099003B00A9003B00B9003B00C9003B00D9003B00 +0D5DB80035DC00BA001D000300032BB8001D10BA000A002900032BB8000A10BA00170011 +00032BB8001710BA0032003800032BB80032103031011000212000113412243332041201 +1406232226353436333216051416333236353426273E01353426232206151416170E0101 +32161514062322263534360694FE3FFEC2FEC4FE3FCE0171BEC10171CDFDB95F54545F5F +54545FFDE2BEADADBE760F0469B69D9DB76A040F76016B4B52524B4D515102EAFEC1FE41 +01BF013FC60172C6C6FE90FE6C48515049495050498A97978A5C83030176537488887453 +7601038301C4423E3D42423D3E4200030098FFEC069405E8000D0026003200E9B800332F +B800342FB80000DCB80011DC410500EA001100FA001100025D411B001900110029001100 +39001100490011005900110069001100790011008900110099001100A9001100B9001100 +C9001100D90011000D5DB8003310B80027D0B800272FB8002DDC411B0016002D0026002D +0036002D0046002D0056002D0066002D0076002D0086002D0096002D00A6002D00B6002D +00C6002D00D6002D000D5D410500E5002D00F5002D00025D00BA000E000300032BB8000E +10BA000A001400032BB8000A10BA001A002000032BB8001A10BA002A003000032BB8002A +10BA00230020001A11123930310110002120001134122433320412013212353426232206 +151416333236070E0123222627151E010334363332161514062322260694FE3FFEC2FEC4 +FE3FCE0171BEC10171CDFCD1C8E9BFB9A0C2BA9F4F800A057F772E61554A6B3C5C4E4E5B +5B4E4E5C02EAFEC1FE4101BF013FC60172C6C6FE90FD4B010ADAE8FDB49194AF3C0D4F93 +15249C181302845B62625B5B626200040098FFEC069405E8000D00180024003000D3BA00 +25001100032BB8002510BA0019001F00032BB8001910BA0000002B00032BB80000104105 +00EA001F00FA001F00025D411B0019001F0029001F0039001F0049001F0059001F006900 +1F0079001F0089001F0099001F00A9001F00B9001F00C9001F00D9001F000D5D410500EA +002B00FA002B00025D411B0019002B0029002B0039002B0049002B0059002B0069002B00 +79002B0089002B0099002B00A9002B00B9002B00C9002B00D9002B000D5D00BA000F0003 +00032BB8000F10BA000A002E00032BB8000A10B8000310B80028DC303101100021200011 +341224333204120121352311230715371123011406232226353436333216051416333236 +3534262322060694FE3FFEC2FEC4FE3FCE0171BEC10171CDFB2401C29F75BCAC9E038946 +3F404545403F46FE70888382888882838802EAFEC1FE4101BF013FC60172C6C6FE90FDA9 +8002CA2A8927FDC20124A29797A2A39797A3D0E4E4D0D1E4E40000030009FF9606AB063D +00190024003E000000200706070602151412171617162037363736123534022726270121 +110535253311211521122017161716121514020706070620272627260235341237363703 +F4FECC86846D6F6D6D6F6D8486013486846D6F6D6D6F6D84FDA50108FEE1011EA10108FD +508D015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F6FFEF79C97FE +F76F6F363939366F6F0109979C01096F6F36FB7D038F39933AFBDD8805A8403E7D7EFED4 +B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E0000030009FF9606AB063D00190036 +005000000020070607060215141217161716203736373612353402272627012115213536 +00373E0135342623220607353E01333216151406070E0102201716171612151402070607 +0620272627260235341237363703F4FECC86846D6F6D6D6F6D8486013486846D6F6D6D6F +6D84FE290234FD0A5C013D294E3D856B50A56061AA46BADD374914D7BA015C98967C7E7B +7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F6FFEF79C97FEF76F6F363939366F6F +0109979C01096F6F36FB8088885F01412E58783B5F783535A32728BA9B49845A17DE0456 +403E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E00000000030009FF96 +06AB063D00190042005C0000002007060706021514121716171620373637361235340227 +2627031E0115140623222627351E013332363534262B013533323635342623220607353E +01333216151406002017161716121514020706070620272627260235341237363703F4FE +CC86846D6F6D6D6F6D8486013486846D6F6D6D6F6D84747483F3E04C9F5544A057989F94 +848B91787E827A42985C52AB47BAD572FE3D015C98967C7E7B7B7E7C9698FEA498967C7E +7B7B7E7C9605D939366F6FFEF79C97FEF76F6F363939366F6F0109979C01096F6F36FD79 +199C74B0C21E1D9C272878726A77855F5A5C621D1E90171CA78E64880309403E7D7EFED4 +B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E0000040009FF9606AB063D0019001C +002700410000002007060706021514121716171620373637361235340227262707012103 +331133152311231121350020171617161215140207060706202726272602353412373637 +03F4FECC86846D6F6D6D6F6D8486013486846D6F6D6D6F6D84DEFE6801982ACBAAAAA1FD +E5012B015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F6FFEF79C97 +FEF76F6F363939366F6F0109979C01096F6F36EDFD82030BFCF586FEE6011A9C03F2403E +7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E0000030009FF9606AB063D +001900370051000000200706070602151412171617162037363736123534022726270521 +1521113E0133321615140623222627351E01333236353426232206071220171617161215 +14020706070620272627260235341237363703F4FECC86846D6F6D6D6F6D848601348684 +6D6F6D6D6F6D84FDA3027AFE1A234723C8E9F0DA4B9C534C95568AA2A28A4181438F015C +98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F6FFEF79C97FEF76F6F36 +3939366F6F0109979C01096F6F366388FEDB0C0CDBBBC1D61A19A32925927D7C921D1E03 +58403E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E000000040009FF96 +06AB063D00190025003E0058000000200706070602151412171617162037363736123534 +0227262701220615141633323635342613152E01232206073E0133321615140623220211 +1000333216002017161716121514020706070620272627260235341237363703F4FECC86 +846D6F6D6D6F6D8486013486846D6F6D6D6F6D84FED66C80806C6D7F7FD43D7C3CA0A90C +2F8E56B4D1DAB5CFDB010DE23D7CFE5E015C98967C7E7B7B7E7C9698FEA498967C7E7B7B +7E7C9605D939366F6FFEF79C97FEF76F6F363939366F6F0109979C01096F6F36FD8B9582 +80969680829501FA931D1ED8DA454BDBBCB8DE013E012D011D014F180100403E7D7EFED4 +B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E0000030009FF9606AB063D00190020 +003A00000020070607060215141217161716203736373612353402272627052115012301 +21122017161716121514020706070620272627260235341237363703F4FECC86846D6F6D +6D6F6D8486013486846D6F6D6D6F6D84FD5C0300FE4EA80198FDC2D6015C98967C7E7B7B +7E7C9698FEA498967C7E7B7B7E7C9605D939366F6FFEF79C97FEF76F6F363939366F6F01 +09979C01096F6F366045FB9A04230185403E7D7EFED4B1ACFED47E7D3E40403E7D7E012C +ACB1012C7E7D3E00000000050009FF9606AB063D0019002300390045005F000000200706 +0706021514121716171620373637361235340227262701220614163236353426252E0135 +34362016151406071E011514062026353436131416333236353426232206122017161716 +121514020706070620272627260235341237363703F4FECC86846D6F6D6D6F6D84860134 +86846D6F6D6D6F6D84FEDF738484E68584FEEA6874CC0164CC74687583D4FE74D4843974 +6968767668697430015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F +6FFEF79C97FEF76F6F363939366F6F0109979C01096F6F36FD2C7BD87B7C6B6C7B451A8E +678FA6A68F678E1A1B9E73ADBABAAD739E011A5C68685C5D686801D0403E7D7EFED4B1AC +FED47E7D3E40403E7D7E012CACB1012C7E7D3E00000000040009FF9606AB063D00190032 +003E00580000002007060706021514121716171620373637361235340227262701351E01 +333236370E01232226353436333212111000232226133236353426232206151416022017 +161716121514020706070620272627260235341237363703F4FECC86846D6F6D6D6F6D84 +86013486846D6F6D6D6F6D84FDA73D7C3CA0A90C2E8F56B5CFDAB4D0DAFEF4E23D7DFE6D +7F7F6D6C80804A015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9605D939366F6F +FEF79C97FEF76F6F363939366F6F0109979C01096F6F36FB0E931D1ED7DB444AD9BCB8DE +FEC3FED1FEE6FEAF18021295828195958182950395403E7D7EFED4B1ACFED47E7D3E4040 +3E7D7E012CACB1012C7E7D3E000000050009FF9606AB063D001D002B0038004300610000 +002007060706070615141716171617162037363736373635342726272627072207061514 +163332363534272E01201716111007062002111037013311073537331133152100201716 +171617161514070607060706202726272627263534373637363703F4FECC86846D6F3538 +38356F6D8486013486846D6F353838356F6D841D542C2B565556562B2BEF013251505051 +FECEA050FE338D96958183FE700105015C98967C7E3C3F3F3C7E7C9698FEA498967C7E3C +3F3F3C7E7C9605D939366F6F83869C9786836F6F363939366F6F8386979C86836F6F36CB +7B7AF7F5F6F6F5F37E7B809F9EFED1FED39F9F013E012D012CA1FC67038F259326FBDD88 +05A8403E7D7E9597B1AC97957E7D3E40403E7D7E9597ACB197957E7D3E0000020009FF96 +06AB063D000A002400000115213521112305152511022017161716121514020706070620 +2726272602353412373637021F02B0FEF8A1FEE2011F7B015C98967C7E7B7B7E7C9698FE +A498967C7E7B7B7E7C96011D888804233A9339FC710520403E7D7EFED4B1ACFED47E7D3E +40403E7D7E012CACB1012C7E7D3E00020009FF9606AB063D001C00360000013E01373E01 +35342623220607153E013332161514060706000715213500201716171612151402070607 +0620272627260235341237363702A3C3D7144937DDBA46AA6160A94C6B853D4E29FEC35C +02F6FDD5015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C960120C7DE175A84499B +BA2827A33535785F3B78582EFEBF5F8888051D403E7D7EFED4B1ACFED47E7D3E40403E7D +7E012CACB1012C7E7D3E00020009FF9606AB063D002800420000013E0135342623220607 +153E013332161514062B011533321615140623222627151E013332363534260020171617 +16121514020706070620272627260235341237363704066972D7B847A15C5C98427A827E +78918B84949F9857A044559F4CE0F383FE32015C98967C7E7B7B7E7C9698FEA498967C7E +7B7B7E7C9603191B88648EA7191A901E1D625C5A5F85776A727828279C1D1EC2B0749C03 +3D403E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E000000030009FF96 +06AB063D0002000D00270000011121090115211133113335231124201716171612151402 +07060706202726272602353412373637039CFE68016EFE0F021BA1AAAAFE6F015C98967C +7E7B7B7E7C9698FEA498967C7E7B7B7E7C9604B3FD82030BFD0B9CFEE6011A86030BFD40 +3E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E00020009FF9606AB063D +001D0037000001113E0133321615140623222627151E0133323635342623220607112135 +0020171617161215140207060706202726272602353412373637021D4381418AA2A28A56 +9948539C4BDAF0E9C823472301E6FE15015C98967C7E7B7B7E7C9698FEA498967C7E7B7B +7E7C96053DFDA81E1D927C7D922727A3191AD6C1BBDB0C0C0125880100403E7D7EFED4B1 +ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E000000030009FF9606AB063D000B0024 +003E0000013216151406232226353436012E01232200111012333236353426232206073E +0133321617002017161716121514020706070620272627260235341237363703506D7F7F +6D6C808001AD437C3DE2FEF3DBCFB5DAD1B4568E2F0CA9A03C7C3DFE1B015C98967C7E7B +7B7E7C9698FEA498967C7E7B7B7E7C96032B958280969680829501FA1818FEAFFEE5FED3 +FEC2DEB8BCDB4B45DAD81E1D01AB403E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1 +012C7E7D3E0000020009FF9606AB063D0006002000000115210133013524201716171612 +1514020706070620272627260235341237363701D6023EFE68A801B2FDD6015C98967C7E +7B7B7E7C9698FEA498967C7E7B7B7E7C96054088FBDD046645FD403E7D7EFED4B1ACFED4 +7E7D3E40403E7D7E012CACB1012C7E7D3E0000040009FF9606AB063D0009001F002B0045 +000001321615140622263436270E011514162036353426273E0135342620061514163734 +363332161514062322261220171617161215140207060706202726272602353412373637 +0359748485E684842F7484D4018CD483756874CCFE9CCC742D746968767668697430015C +98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9602CC7B6C6B7C7BD87B451B9E73ADBA +BAAD739E1B1A8E678FA6A68F678EE55D68685D5C68680289403E7D7EFED4B1ACFED47E7D +3E40403E7D7E012CACB1012C7E7D3E00000000030009FF9606AB063D00180024003E0000 +251E01333200111002232206151416333236370E01232226270122263534363332161514 +0600201716171612151402070607062027262726023534123736370221437D3DE2010CDA +D0B4DAD1B3568F2E0CA9A03C7C3D01416C80806C6D7F7FFEDD015C98967C7E7B7B7E7C96 +98FEA498967C7E7B7B7E7C96AE18180151011A012F013DDEB8BCD94A44DBD71E1D016795 +828195958182950395403E7D7EFED4B1ACFED47E7D3E40403E7D7E012CACB1012C7E7D3E +000000040009FF9606AB063D000B00150020003A00000132161514062322263534362420 +021110122012111001152135231123071537111220171617161215140207060706202726 +2726023534123736370499656464656464640105FEBEA9A90142AAFB9101C2A181B3B496 +015C98967C7E7B7B7E7C9698FEA498967C7E7B7B7E7C9604D5F5F7F5F6F6F5F7F580FEC3 +FED1FED3FEC2013E012D012FFD0588880423269325FC710520403E7D7EFED4B1ACFED47E +7D3E40403E7D7E012CACB1012C7E7D3E000000010075009A0650046A0008000009012101 +21352101210650FE18FEC00170FBDD0423FE9001400282FE180170F00170000100FC0070 +057404E900060000090137011B012503F7FD05A902B5D446FD4E018B02B4AAFD050134FD +4E470001007500CC065004380006000001053505030901047AFBFB040544021AFDE6023C +32F0320170FE4AFE4A00000100FC001B0574049400060000090127012D0103045AFD4BA9 +02FBFECB02B2460316FD05AA02B4D447FD4E000100750108065003FC0008000013210304 +05040513217502BBA8015C026CFD94FEA4A8FD4502BE013EFC7E7EFC013E000100750075 +061D048F002B000000140607010607062226272E013534363F0121222E02343E02332127 +2E01353436373E01321716170116061D1411FE70101816342C1212131411BAFC801A2C24 +1313242C1A0380BA11141312122C34161810019013029B322E11FE70100B0A1411132C19 +1A2E11BA14222E322E2214BA112E1A192C1311140A0B10FE70130001007500CC06500438 +0006000001213521110901049AFBDB042501B6FE4A025A50018EFE4AFE4A0001007500CC +065004380006000001213521110901049AFBDB042501B6FE4A0214DC0148FE4AFE4A0004 +007500CC0650043800030007000B00120000012311330123113303231133012111213509 +0102B9F0F0FEC07878C83C3C03E9FE6F019101B6FE4A01BA0190FE700190FE700190FE70 +0190EEFE4AFE4A0000000004007500CC065004380008000C001000140000013512170603 +352111032311330123113303231133049AB8FEFEB8FE6F50F0F0FEC07878C83C3C03AE8A +FECE8484FECE8A0258FDA80258FDA80258FDA8025800000100750086067D047E00060000 +090211211121048101FCFE04FBF4040C047EFE04FE04011101D6000200E400C006500444 +000200060000012101130902031E0236FCBC96FE3E056CFA940282010FFEF101C2FE3EFE +3E00000200E400C0065004440002000600000902210902031EFEF20344FD52FE3E056CFA +940282FEF1010F01C2FE3EFE3E00000100E4FFF80650050C0003000009030242FEA2056C +FA940282028AFD76FD760001007500CC0650046200140000131114161716171633213509 +013521202726272E01752423232B2A0C035A01B6FE4AFE4DFE4D2A2B232324028201E030 +5525211312C6FE4AFE4AC6121321255500000001007500A2065004380014000013343637 +36373633213509013521200706070E0115752423232B2A0C035A01B6FE4AFE4DFE4D2A2B +2323240282305525211312C6FE4AFE4AC6121321255530000000000101ECFFF104D90513 +0006000001211121110901035EFE8E0172017BFE85015602580165FD6FFD6F0000000001 +007500CC065004380008000001351205040335211104049001BCFE4490FC71039A9EFECE +8484FECE9E023000000000020075009A061E046A0008000F000025352111213533090103 +150901152111033CFD3902C7FA01E8FE18AA0198FE68FD399AEE01F4EEFE18FE18013EEE +01980198EEFEAC00000000020075009A061E046A0008000F000025352111213533090103 +150901152111033CFD3902C7FA01E8FE18140198FE68FD399AEE01F4EEFE18FE18013EEE +01980198EEFEAC0000000002002B0018065A04B0000B0012000013012137331315012335 +3721010701030721032B012003498A64D8FD269616FD2B03D18A0284AC8AFCB7C401CE01 +F4EEFDFEC8FE32C8260118EE01980198EEFEAC0000000002002B0018065A04B0000B0012 +0000133521273533011503232721012113211713012B02D5169602DAD8648AFCB702B1FC +B7C403498AACFD7C02FAC826C8FE32C8FDFEEE01A4FEACEE019801980000000201150000 +06320498000A0011000021272127112135330117010315090115211103DC4FFDEC640263 +6E01E864FE18820198FE68FD9D9EC802949EFE18C8FE1801B69E019801989EFE0C000002 +0115000006320498000A0011000001330107012335211137211321112115090103DC6E01 +E864FE186EFD9D6402143BFD9D02630198FE680498FE18C8FE189E0294C8FEE8FE0C9E01 +9801980000000002007F006406640498000C001400000103213533011701232735212713 +07211509011521011596035D6E01E832FE186E32FCD532F272032D0198FE68FCD302B001 +4A9EFE1864FE18633B64014AFA9E019801989E0000000002007F006406640498000C0014 +000001033721353733010701233521130721150901152101159632032B326E01E832FE18 +6EFCA3F272032D0198FE68FCD3024C014A643B63FE1864FE189E014AFA9E019801989E00 +00000001013CFFD805C404AF001E000001363736373633321716171E0115140607060706 +2227262726272115090115013C27435A6D6F7E7974695E5A5C585E56716FF774695E3F29 +025801CAFE36036E47435A2E2F2F2B5D59DD807ADA5F58302F2F2B5D3E499E01CA01CA9E +0000000900820142065003C200030007000B000F001E00220026002A002E000001073337 +230733372307333723073337233503211321111617060711210321013327231733272317 +3327231733272302E1A03CA0B4A03CA0B4A03CA0B4A03CA078BB021CBB0187ADC3C3ADFE +79BBFDE4025F3CA03C283CA03C283CA03C283CA03C025AF0F0F0F0F0F0F0F0500118FEE8 +0116BC8282BC0116FEE80168F0F0F0F0F0F0F0000000000300A500700574053E000C0010 +0014000001370615141726232207372737090121012511011104A2AF072A8EA44246AFFE +38FE1D01A9FEE5FE57015501A8017BB04643A38F2A07B0FE39016FFE5801A83A011BFE57 +FEE6000300750162065003A20008000C0010000001351617060735213505210721132721 +1704FE9AB8B89AFE98FDA70258C8FDA8C8C80258C802AAF8BC6464BCF85052C8011AC8C8 +0000000300A7FFC505740494000C00100014000001271633323706151417270727090111 +01032101210469AF4642A48E2A07AFFF38FE9101A8FE583AFEE501A8011B03C1B0072A90 +A24346B0FF39FE1D01A8FEE5FE58015501A8000100A700700574053E002A000025262322 +073E013727262726220607013E0133321F01372635343637010E011514171617013E0137 +06151405748FA54245488018A6A623264E4B1CFE5824572F2C30150513242201A820190E +0F07014404354807702A07473603A6A60E0F1E1C01A82422120821302C305922FE572146 +2627262307FEBB1781474743A300000100750161065003A3002700000106073436372322 +0706070E011521343637363F012726272E01352114161716171633212E0135160650B79B +350EEBEA23241D1C1FFDA825221F2F15152F1F222502581F1C1D24230A01CB0E359B0282 +65BC6581130F101C1B4A283059211F15091B151F225830284A1B1C100F138165BC000001 +00A7FFC605740494002A000001061514172E01270706070615141617012E0135343F0127 +0623222627011E0132373637012E012716333205742907483504A7A70C0E1D1CFE582224 +130923302C30582201A81C4B4E26230701451880484542A504948EA34347478117A8A81F +2627264B1CFE572259302C30150412242201A81C1E0F0E070145033647070003007300AC +0650045800140027003C000001362506030607060706222726272E013534363736243436 +373637363217040504050622272627260126272E01353436373637363217161716171217 +24042FF6012BE37A0C15161B1A3E1A1B161517171516FC5F201B1D2423522301610366FC +82FEB723522329181D039E1B1615171715161B1A3E1A1B16120F78E5FE9401CE625175FE +FD1D14150C0B0B0C1514381F1E38141597524B1A1C100F0E853638830E0F121A1F01230C +1514381E1F3814150C0B0B0C151120FEFE766300000000020096011F063C03E5001A002A +000013363736373637363332171617160504070E012322272627262726252E0135343637 +161706072E0135343696411E201427272B2F35343C33A20191FE6FA2386E32332739151D +171D04062525252562FCFC622525250282263A3F13241213131620664C4C662326131D19 +232F3A26275832335827F17272F12758333258000000000100A20156063203AE00630000 +0014060F01060706222726272E0135343F013635342627262726232122070607060F0106 +070607062321222E02343F0136342F0126343637363736332132171617161F0116171617 +163321323736373E0135342F0126353436373637363217161F011606320606F004090710 +0707060606031B03060605080807FEE60807070605045A050306080608FD9C080E0C0603 +3B3A01740306060607070802640806090503055A040506070708011A070808050606031B +03060606070710070904F006028B120E06F0040503030405060E09040B630B05070F0605 +03040304050508B40A03050403060C0E120675751402E806120E06050403030306030AB4 +0805050403040305060F07050B630B04090E06050403030504F006000000000100A200F1 +06320413006300000014060701060706222726272E0135343F0136353426272627262B01 +22070607060F0106070607062321222E02343F0136342703263436373637363321321716 +1716171316171617163B01323736373E0135342F01263534363736373632171617011606 +320606FE700508071007070606060357030606050808075C080707060504780503060806 +08FD6A080E0C0603494A019C030606060707080296080609050305820405060707085C07 +0808050606035703060606070710070805019006028B120F05FE98050403030405060E09 +0708DB0808070F060503040304050508F00A03050403060C0E120693931402013806120E +06050403030306030AFEFC0805050403040305060F070808DB0807090E06050403030405 +FE98050000000003007500A60650045E0013001D00270000010607060723373637363726 +27262F013316171601233E01372135210E01031E01172135212E0127065088538C4A6C2E +2EA026474726A015476C488E53FDCC6913372AFCD603E56E4B0D0D4B6EFC1B032A2A3713 +02824F558FA96E6DA427363627A431AAA49455FDD53D8548648EB7038F29B78E6448853D +00000001006EFEB202D0062600190000012A012724032600272623220727363736151400 +07060516363702D0096837FE480201016D04035E3A2D8833D5DEFEB0070C011C524514FE +B20B530152AE03F5514F8931D80101CCA1FC4C8FDC48150102000001004FFEB202B10626 +0019000013351E01372427260035341716170726232207060007020506224F144552011C +0C07FEB0DED533882D3A5E0304016D0102FE483768FEB28F02011548DC8F03B4A1CC0101 +D831894F51FC0BAEFEAE530B000000030006FE2303EE0675000300060009000009052109 +012101F901F5FE0BFE0D01F3FE9802D2FE96016AFD2E0675FBDBFBD3042D0331FD08FC8E +0300000200B0FEF2033006140003000B00000111331125211523113315210114C8FED402 +80F0F0FD8005B0F9A6065A6464F9A6640000000200AFFEF2032F06140003000B00000123 +1133131121353311233502CBC8C864FD80F0F005B0F9A606BEF8DE64065A640000000001 +00B7FEF2027B061200050000130133090123B7011AAAFEE6011AAA02820390FC70FC7000 +0000000100A4FEF202680612000500000901230901330268FEE6AA011AFEE6AA0282FC70 +039003900000FFFF00B7FEF203CF06121026104700001007104701540000FFFF00A4FEF2 +03BC061210261048000010071048015400000001005B0000065B05DC0016000009010727 +11231127112311270711231107112311072701038902D2785C78D2786A6A78D2785C7802 +D205DCFD2C785CFD140365D3FBC804B06868FB500438D3FC9B02EC5C7802D40000000001 +0059FFF9065905D500160000050137171133111711331117371133113711331137170103 +2BFD2E785C78D2786A6A78D2785C78FD2E0702D4785C02ECFC9BD30438FB50686804B0FB +C8D30365FD145C78FD2C0001004F006C06840544003D0000013037161716323736373E01 +353427262726272622070607060706073717012301371736373637363736333217161716 +171615140706070607062322272602657740524EBE4A563C424221234040524EBE4A563C +42211707E878FE785AFE7678E90824305954746785806C71575A2F2E2E2F5A5375678580 +6C6A0121783F232121273B42A25D595153403F232121273B4251393FEA78FE76018A78E9 +6058715953342E2E30575971707B7F70715953342E2E2D0000000001004F007C06840554 +003C00000106070623222726272627263534373637363736333217161716171617371701 +2301371726272627262726220706070607061514161716171632373637046E5E6A6C8085 +67745459302E2E305958706C808567745459302408E978FE765AFE7878E8071721423C56 +4ABE4E524040232142423C564ABE4E524001315A2D2E2E34535971707F7B70715957302E +2E345359715860E978FE76018A78EA3F3951423B272121233F405351595DA2423B272121 +233F00030075FFE308DE052400250039004D0000011501273721060706070E0120262726 +272627233533363736373E01201617161716172127372422070607060706072111331121 +262726272627012111231121161716171617163237363736373608DEFE7778E9FEA00B21 +316362EEFEECEE626331210BBDBD0A22316362EE0114EE626331220A0160E978FCE2D65D +5C4C4D26170901AEAA01AE0917264D4C5C013BFE52AAFE520A16264D4C5C5DD65D5C4C4D +261602AF5AFE7778E95A517663626464626376515AAA5B527763626464626377525BE978 +5627274C4D5C373D0165FE9B3D375C4D4C27FDC6FE9B01653B365C4D4C272727274C4D5C +36000001006400CC0B03043800090081B4020906000A10D4D4CC32400940024009300230 +09045D31400A020509010006059C060A0010D4EC113939CC10CC30400C08090900080708 +079C000900070510FC3C0806103C400C03020201030403049C010201070510FC3C080610 +3CB0104B535800B303020809103C103CB4089C090900070510ECB4039C020201070510EC +591335011707211521170764018978E90987F679E97802555A018978E9AAE97800000001 +007500CC0B14043800090081B4020900060A10D4D4CC3240094F024F095F025F09045D31 +400A090602010005069C050A0010D4EC113939CC10CC30400C08090900080708079C0009 +00070510FC3C0806103C400C03020201030403049C010201070510FC3C0806103CB0104B +535800B303020809103C103CB4089C090900070510ECB4039C020201070510EC59011501 +273721352127370B14FE7778E9F6790987E97802AF5AFE7778E9AAE978000001006400CC +0B140438000F00DAB6070A09020F001010D4CC32D4CC32400D5002500F5F0A5F074F0A4F +07065D3100400F0702040A0F00010809040D049C0D1010D4EC111739CC3210CC3230400C +0E0F0F000E0D0E0D9C000F00070510FC3C0806103C400C03020201030403049C01020107 +0510FC3C0806103C400C0B0A0A090B0C0B0C9C09090A070510FC3C0806103C400C060707 +08060506059C080708070510FC3C0806103CB0104B535800B70B0A060703020E0F103C10 +3C103C103CB40E9C0F0F00070510ECB4039C020201070510ECB40B9C0A0A09070510ECB4 +069C070708070510EC591335011707212737011501273721170764018978E90880E97801 +89FE7778E9F780E97802555A018978E9E978FE775AFE7778E9E9780000000001006400CC +0B030438000E000001211521170701350117072115210701900973F7055D78FE77018978 +5D08FBF68D690219785D7801895A0189785D786900000001007500CC0B140438000E0000 +01372721352127370115012737213509E86969F68D08FB5D780189FE77785DF705021969 +69785D78FE775AFE77785D7800000002006400CC0B140438000500150000012137272107 +05211707013501170721273701150127019008586969F7A8690849F8985D78FE77018978 +5D07685D780189FE77780219696969E15D7801895A0189785D5D78FE775AFE7778000001 +006400CC0B030438000D000001211707013501170721113311230A59F723E978FE770189 +78E908DDAAAA022DE97801895A018978E90159FCA4000001007500CC0B140438000D0000 +0111231133112127370115012737011FAAAA08DDE9780189FE7778E9022DFEA7035CFEA7 +E978FE775AFE7778E9000002006400CC0B030438000D0012000001211707013501170721 +3533112311352107170A59F7AF5D78FE770189785D0851AAAAF737696901A15D7801895A +0189785DCDFCA40145D2696900000002007500CC0B140438000D00120000012127370115 +01273721152311331115213727011F08515D780189FE77785DF7AFAAAA08C9696903635D +78FE775AFE77785DCD035CFEBBD2696900000001007500CC0B1404380025000013173717 +371737173717371737173733273701150127372307270727072707270727072707277546 +B4B4B4B4B4B4B4B4B4B4B4B4468BE9780189FE7778E9636EB4B4B4B4B4B4B4B4B4B4B4B4 +4602D746B4B4B4B4B4B4B4B4B4B4B4B446E978FE775AFE7778E96EB4B4B4B4B4B4B4B4B4 +B4B4B4460000FFFF012C0514025806401007175E012C05140000FFFF012C02DD02580409 +1007175E012C02DD0000FFFF012C02DD025806401027175E012C05141007175E012C02DD +0000FFFF012C00A7025801D31007175E012C00A70000FFFF012C00A7025806401027175E +012C05141007175E012C00A70000FFFF012C00A7025804091027175E012C02DD1007175E +012C00A70000FFFF012C00A7025806401027175E012C05141027175E012C02DD1007175E +012C00A70000FFFF0384051404B006401007175E038405140000FFFF012C051404B00640 +1027175E012C05141007175E038405140000FFFF012C02DD04B006401027175E012C02DD +1007175E038405140000FFFF012C02DD04B006401027175E012C05141027175E012C02DD +1007175E038405140000FFFF012C00A704B006401027175E012C00A71007175E03840514 +0000FFFF012C00A704B006401027175E012C05141027175E012C00A71007175E03840514 +0000FFFF012C00A704B006401027175E012C02DD1027175E012C00A71007175E03840514 +0000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E012C00A7 +1007175E038405140000FFFF038402DD04B004091007175E038402DD0000FFFF012C02DD +04B006401027175E012C05141007175E038402DD0000FFFF012C02DD04B004091027175E +012C02DD1007175E038402DD0000FFFF012C02DD04B006401027175E012C05141027175E +012C02DD1007175E038402DD0000FFFF012C00A704B004091027175E012C00A71007175E +038402DD0000FFFF012C00A704B006401027175E012C05141027175E012C00A71007175E +038402DD0000FFFF012C00A704B004091027175E012C02DD1027175E012C00A71007175E +038402DD0000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E +012C00A71007175E038402DD0000FFFF038402DD04B006401027175E038405141007175E +038402DD0000FFFF012C02DD04B006401027175E012C05141027175E038405141007175E +038402DD0000FFFF012C02DD04B006401027175E012C02DD1027175E038405141007175E +038402DD0000FFFF012C02DD04B006401027175E012C05141027175E012C02DD1027175E +038405141007175E038402DD0000FFFF012C00A704B006401027175E012C00A71027175E +038405141007175E038402DD0000FFFF012C00A704B006401027175E012C05141027175E +012C00A71027175E038405141007175E038402DD0000FFFF012C00A704B006401027175E +012C02DD1027175E012C00A71027175E038405141007175E038402DD0000FFFF012C00A7 +04B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E03840514 +1007175E038402DD0000FFFF038400A704B001D31007175E038400A70000FFFF012C00A7 +04B006401027175E012C05141007175E038400A70000FFFF012C00A704B004091027175E +012C02DD1007175E038400A70000FFFF012C00A704B006401027175E012C05141027175E +012C02DD1007175E038400A70000FFFF012C00A704B001D31027175E012C00A71007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E012C00A71007175E +038400A70000FFFF012C00A704B004091027175E012C02DD1027175E012C00A71007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E +012C00A71007175E038400A70000FFFF038400A704B006401027175E038405141007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E038405141007175E +038400A70000FFFF012C00A704B006401027175E012C02DD1027175E038405141007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E +038405141007175E038400A70000FFFF012C00A704B006401027175E012C00A71027175E +038405141007175E038400A70000FFFF012C00A704B006401027175E012C05141027175E +012C00A71027175E038405141007175E038400A70000FFFF012C00A704B006401027175E +012C02DD1027175E012C00A71027175E038405141007175E038400A70000FFFF012C00A7 +04B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E03840514 +1007175E038400A70000FFFF038400A704B004091027175E038402DD1007175E038400A7 +0000FFFF012C00A704B006401027175E012C05141027175E038402DD1007175E038400A7 +0000FFFF012C00A704B004091027175E012C02DD1027175E038402DD1007175E038400A7 +0000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E038402DD +1007175E038400A70000FFFF012C00A704B004091027175E012C00A71027175E038402DD +1007175E038400A70000FFFF012C00A704B006401027175E012C05141027175E012C00A7 +1027175E038402DD1007175E038400A70000FFFF012C00A704B004091027175E012C02DD +1027175E012C00A71027175E038402DD1007175E038400A70000FFFF012C00A704B00640 +1027175E012C05141027175E012C02DD1027175E012C00A71027175E038402DD1007175E +038400A70000FFFF038400A704B006401027175E038405141027175E038402DD1007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E038405141027175E +038402DD1007175E038400A70000FFFF012C00A704B006401027175E012C02DD1027175E +038405141027175E038402DD1007175E038400A70000FFFF012C00A704B006401027175E +012C05141027175E012C02DD1027175E038405141027175E038402DD1007175E038400A7 +0000FFFF012C00A704B006401027175E012C00A71027175E038405141027175E038402DD +1007175E038400A70000FFFF012C00A704B006401027175E012C05141027175E012C00A7 +1027175E038405141027175E038402DD1007175E038400A70000FFFF012C00A704B00640 +1027175E012C02DD1027175E012C00A71027175E038405141027175E038402DD1007175E +038400A70000FFFF012C00A704B006401027175E012C05141027175E012C02DD1027175E +012C00A71027175E038405141027175E038402DD1007175E038400A70000FFFF012CFE70 +0258FF9C1007175E012CFE700000FFFF012CFE70025806401027175E012C05141007175E +012CFE700000FFFF012CFE70025804091027175E012C02DD1007175E012CFE700000FFFF +012CFE70025806401027175E012C05141027175E012C02DD1007175E012CFE700000FFFF +012CFE70025801D31027175E012C00A71007175E012CFE700000FFFF012CFE7002580640 +1027175E012C05141027175E012C00A71007175E012CFE700000FFFF012CFE7002580409 +1027175E012C02DD1027175E012C00A71007175E012CFE700000FFFF012CFE7002580640 +1027175E012C05141027175E012C02DD1027175E012C00A71007175E012CFE700000FFFF +012CFE7004B006401027175E038405141007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E038405141007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C02DD1027175E038405141007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E038405141007175E012CFE700000FFFF +012CFE7004B006401027175E012C00A71027175E038405141007175E012CFE700000FFFF +012CFE7004B006401027175E012C05141027175E012C00A71027175E038405141007175E +012CFE700000FFFF012CFE7004B006401027175E012C02DD1027175E012C00A71027175E +038405141007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E012C00A71027175E038405141007175E012CFE700000FFFF012CFE70 +04B004091027175E038402DD1007175E012CFE700000FFFF012CFE7004B006401027175E +012C05141027175E038402DD1007175E012CFE700000FFFF012CFE7004B004091027175E +012C02DD1027175E038402DD1007175E012CFE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E038402DD1007175E012CFE700000FFFF012CFE70 +04B004091027175E012C00A71027175E038402DD1007175E012CFE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C00A71027175E038402DD1007175E012CFE70 +0000FFFF012CFE7004B004091027175E012C02DD1027175E012C00A71027175E038402DD +1007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD +1027175E012C00A71027175E038402DD1007175E012CFE700000FFFF012CFE7004B00640 +1027175E038405141027175E038402DD1007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E038405141027175E038402DD1007175E012CFE700000FFFF +012CFE7004B006401027175E012C02DD1027175E038405141027175E038402DD1007175E +012CFE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +038405141027175E038402DD1007175E012CFE700000FFFF012CFE7004B006401027175E +012C00A71027175E038405141027175E038402DD1007175E012CFE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C00A71027175E038405141027175E038402DD +1007175E012CFE700000FFFF012CFE7004B006401027175E012C02DD1027175E012C00A7 +1027175E038405141027175E038402DD1007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E012C00A71027175E038405141027175E +038402DD1007175E012CFE700000FFFF012CFE7004B001D31027175E038400A71007175E +012CFE700000FFFF012CFE7004B006401027175E012C05141027175E038400A71007175E +012CFE700000FFFF012CFE7004B004091027175E012C02DD1027175E038400A71007175E +012CFE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +038400A71007175E012CFE700000FFFF012CFE7004B001D31027175E012C00A71027175E +038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E +012C00A71027175E038400A71007175E012CFE700000FFFF012CFE7004B004091027175E +012C02DD1027175E012C00A71027175E038400A71007175E012CFE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E038400A7 +1007175E012CFE700000FFFF012CFE7004B006401027175E038405141027175E038400A7 +1007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E03840514 +1027175E038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C02DD +1027175E038405141027175E038400A71007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E038405141027175E038400A71007175E +012CFE700000FFFF012CFE7004B006401027175E012C00A71027175E038405141027175E +038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E +012C00A71027175E038405141027175E038400A71007175E012CFE700000FFFF012CFE70 +04B006401027175E012C02DD1027175E012C00A71027175E038405141027175E038400A7 +1007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD +1027175E012C00A71027175E038405141027175E038400A71007175E012CFE700000FFFF +012CFE7004B004091027175E038402DD1027175E038400A71007175E012CFE700000FFFF +012CFE7004B006401027175E012C05141027175E038402DD1027175E038400A71007175E +012CFE700000FFFF012CFE7004B004091027175E012C02DD1027175E038402DD1027175E +038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E038402DD1027175E038400A71007175E012CFE700000FFFF012CFE70 +04B004091027175E012C00A71027175E038402DD1027175E038400A71007175E012CFE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E038402DD +1027175E038400A71007175E012CFE700000FFFF012CFE7004B004091027175E012C02DD +1027175E012C00A71027175E038402DD1027175E038400A71007175E012CFE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E +038402DD1027175E038400A71007175E012CFE700000FFFF012CFE7004B006401027175E +038405141027175E038402DD1027175E038400A71007175E012CFE700000FFFF012CFE70 +04B006401027175E012C05141027175E038405141027175E038402DD1027175E038400A7 +1007175E012CFE700000FFFF012CFE7004B006401027175E012C02DD1027175E03840514 +1027175E038402DD1027175E038400A71007175E012CFE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E038405141027175E038402DD1027175E +038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C00A71027175E +038405141027175E038402DD1027175E038400A71007175E012CFE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C00A71027175E038405141027175E038402DD +1027175E038400A71007175E012CFE700000FFFF012CFE7004B006401027175E012C02DD +1027175E012C00A71027175E038405141027175E038402DD1027175E038400A71007175E +012CFE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +012C00A71027175E038405141027175E038402DD1027175E038400A71007175E012CFE70 +0000FFFF0384FE7004B0FF9C1007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD1007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1007175E +0384FE700000FFFF012CFE7004B001D31027175E012C00A71007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C00A71007175E0384FE700000FFFF +012CFE7004B004091027175E012C02DD1027175E012C00A71007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A71007175E +0384FE700000FFFF0384FE7004B006401027175E038405141007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E038405141007175E0384FE700000FFFF +012CFE7004B006401027175E012C02DD1027175E038405141007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E038405141007175E +0384FE700000FFFF012CFE7004B006401027175E012C00A71027175E038405141007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E +038405141007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E +012C00A71027175E038405141007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E012C00A71027175E038405141007175E0384FE70 +0000FFFF0384FE7004B004091027175E038402DD1007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E038402DD1007175E0384FE700000FFFF012CFE70 +04B004091027175E012C02DD1027175E038402DD1007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C02DD1027175E038402DD1007175E0384FE70 +0000FFFF012CFE7004B004091027175E012C00A71027175E038402DD1007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E038402DD +1007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD1027175E012C00A7 +1027175E038402DD1007175E0384FE700000FFFF012CFE7004B006401027175E012C0514 +1027175E012C02DD1027175E012C00A71027175E038402DD1007175E0384FE700000FFFF +0384FE7004B006401027175E038405141027175E038402DD1007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E038405141027175E038402DD1007175E +0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E038405141027175E +038402DD1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E038405141027175E038402DD1007175E0384FE700000FFFF012CFE70 +04B006401027175E012C00A71027175E038405141027175E038402DD1007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E03840514 +1027175E038402DD1007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD +1027175E012C00A71027175E038405141027175E038402DD1007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E +038405141027175E038402DD1007175E0384FE700000FFFF0384FE7004B001D31027175E +038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E +038400A71007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD1027175E +038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E038400A71007175E0384FE700000FFFF012CFE7004B001D31027175E +012C00A71027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C00A71027175E038400A71007175E0384FE700000FFFF012CFE70 +04B004091027175E012C02DD1027175E012C00A71027175E038400A71007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A7 +1027175E038400A71007175E0384FE700000FFFF0384FE7004B006401027175E03840514 +1027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C0514 +1027175E038405141027175E038400A71007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C02DD1027175E038405141027175E038400A71007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E038405141027175E +038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C00A71027175E +038405141027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C00A71027175E038405141027175E038400A71007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C02DD1027175E012C00A71027175E03840514 +1027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C0514 +1027175E012C02DD1027175E012C00A71027175E038405141027175E038400A71007175E +0384FE700000FFFF0384FE7004B004091027175E038402DD1027175E038400A71007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E038402DD1027175E +038400A71007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD1027175E +038402DD1027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E038402DD1027175E038400A71007175E0384FE70 +0000FFFF012CFE7004B004091027175E012C00A71027175E038402DD1027175E038400A7 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C00A7 +1027175E038402DD1027175E038400A71007175E0384FE700000FFFF012CFE7004B00409 +1027175E012C02DD1027175E012C00A71027175E038402DD1027175E038400A71007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +012C00A71027175E038402DD1027175E038400A71007175E0384FE700000FFFF0384FE70 +04B006401027175E038405141027175E038402DD1027175E038400A71007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E038405141027175E038402DD +1027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD +1027175E038405141027175E038402DD1027175E038400A71007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E038405141027175E +038402DD1027175E038400A71007175E0384FE700000FFFF012CFE7004B006401027175E +012C00A71027175E038405141027175E038402DD1027175E038400A71007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E03840514 +1027175E038402DD1027175E038400A71007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C02DD1027175E012C00A71027175E038405141027175E038402DD1027175E +038400A71007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E012C00A71027175E038405141027175E038402DD1027175E038400A7 +1007175E0384FE700000FFFF012CFE7004B0FF9C1027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B004091027175E012C02DD1027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B001D31027175E012C00A71027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C00A7 +1027175E012CFE701007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD +1027175E012C00A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E012C00A71027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E038405141027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E038405141027175E +012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E +038405141027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E038405141027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C00A71027175E038405141027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C00A7 +1027175E038405141027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C02DD1027175E012C00A71027175E038405141027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +012C00A71027175E038405141027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B004091027175E038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E038402DD1027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B004091027175E012C02DD1027175E038402DD1027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD +1027175E038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE7004B00409 +1027175E012C00A71027175E038402DD1027175E012CFE701007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C00A71027175E038402DD1027175E +012CFE701007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD1027175E +012C00A71027175E038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E038402DD +1027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E03840514 +1027175E038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C05141027175E038405141027175E038402DD1027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E038405141027175E +038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E038405141027175E038402DD1027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C00A71027175E03840514 +1027175E038402DD1027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C00A71027175E038405141027175E038402DD1027175E +012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E +012C00A71027175E038405141027175E038402DD1027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A7 +1027175E038405141027175E038402DD1027175E012CFE701007175E0384FE700000FFFF +012CFE7004B001D31027175E038400A71027175E012CFE701007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E038400A71027175E012CFE701007175E +0384FE700000FFFF012CFE7004B004091027175E012C02DD1027175E038400A71027175E +012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E +012C02DD1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B001D31027175E012C00A71027175E038400A71027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B006401027175E012C05141027175E012C00A71027175E038400A7 +1027175E012CFE701007175E0384FE700000FFFF012CFE7004B004091027175E012C02DD +1027175E012C00A71027175E038400A71027175E012CFE701007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E012C02DD1027175E012C00A71027175E +038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +038405141027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E038405141027175E038400A71027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD1027175E03840514 +1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C05141027175E012C02DD1027175E038405141027175E038400A71027175E +012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E012C00A71027175E +038405141027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C00A71027175E038405141027175E038400A7 +1027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E012C02DD +1027175E012C00A71027175E038405141027175E038400A71027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +012C00A71027175E038405141027175E038400A71027175E012CFE701007175E0384FE70 +0000FFFF012CFE7004B004091027175E038402DD1027175E038400A71027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E038402DD +1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B00409 +1027175E012C02DD1027175E038402DD1027175E038400A71027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD1027175E +038402DD1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B004091027175E012C00A71027175E038402DD1027175E038400A71027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C00A7 +1027175E038402DD1027175E038400A71027175E012CFE701007175E0384FE700000FFFF +012CFE7004B004091027175E012C02DD1027175E012C00A71027175E038402DD1027175E +038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E012C00A71027175E038402DD1027175E038400A7 +1027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E03840514 +1027175E038402DD1027175E038400A71027175E012CFE701007175E0384FE700000FFFF +012CFE7004B006401027175E012C05141027175E038405141027175E038402DD1027175E +038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +012C02DD1027175E038405141027175E038402DD1027175E038400A71027175E012CFE70 +1007175E0384FE700000FFFF012CFE7004B006401027175E012C05141027175E012C02DD +1027175E038405141027175E038402DD1027175E038400A71027175E012CFE701007175E +0384FE700000FFFF012CFE7004B006401027175E012C00A71027175E038405141027175E +038402DD1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE70 +04B006401027175E012C05141027175E012C00A71027175E038405141027175E038402DD +1027175E038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B00640 +1027175E012C02DD1027175E012C00A71027175E038405141027175E038402DD1027175E +038400A71027175E012CFE701007175E0384FE700000FFFF012CFE7004B006401027175E +012C05141027175E012C02DD1027175E012C00A71027175E038405141027175E038402DD +1027175E038400A71027175E012CFE701007175E0384FE7000000002006400CC063F0438 +000D00120000012117070135011707213533112311352107170595FC735D78FE77018978 +5D038DAAAAFBFB696901A15D7801895A0189785DCDFCA40145D2696900000002007500CC +06500438000D0012000001212737011501273721152311331115213727011F038D5D7801 +89FE77785DFC73AAAA0405696903635D78FE775AFE77785DCD035CFEBBD2696900000001 +0100000005B405DC001100000901330107271123112711231107112311070100022D5A02 +2D785C78D278D2785C03AE022EFDD2785CFC6E040AD3FB2304DDD3FBF603925C00000001 +0100FFF905B405D5001100000137171133111711331137113311371701230100785C78D2 +78D2785C78FDD35A0227785C0392FBF6D304DDFB23D3040AFC6E5C78FDD20002004F0080 +052706B5001E003D00002522272627262726103736373637011707161716171617161007 +060706070603301707010607060706141716171E01333237363736373634272627262726 +02BD7F706B5F56312E2E31566272018978E960586B5F56312E2E31565F6B7049EA78FEB8 +58453F232121273B42A25D595153403F232121273B425139802E2D5C5A6E6C01006C6E5A +5F2E018978E908242D5C5A6E6CFF006C6E5A5C2D2E0429E8780146214640524EBE4A563C +424221234040524EBE4A563C4221170000000002004F0080052706B5001E003C00002522 +272627262726103736373637363727370116171617161007060706070603060706070607 +061417161716171633323637363736342726272627012702B97B706B5F56312E2E31565F +6B5860E9780189726256312E2E31565F6B70B13F3951423B272121233F405351595DA242 +3B272121233F4558FEB878802E2D5C5A6E6C01006C6E5A5C2D2408E978FE772E5F5A6E6C +FF006C6E5A5C2D2E0429071721423C564ABE4E524040232142423C564ABE4E52404621FE +BA7800020100FEB204DF06140010002E005C40122E1D20131A111B16060D0027240F252B +202F10DCB20020015D4BB00C5458B90020004038593CCC393939CC32DCB40F061F06025D +DCCC3239391139393100400E0F27A925041A1CC025091311B12F10F4CC32C4F4CC3210EC +3930011514161726351134370E011D011007160115232206151114163B01152122263D01 +34262B01353332363D01343633029C4BB03230B049ADAD0243358C55558C35FEF9F9A76C +8E3E3E8E6CA7F90112EF986D07479D04D0A142076B98F0FEEE3E4403F464578EFB308D58 +6494DDEF97748F7395F0DD93000000020100FEB204DF06140010002E0057401212232E1C +19021B162005000A2D262E25292F10DC4BB00A5458B90029FFC03859CC323939CCDCB40F +001F00025D3CDC3CCC3939391139393100400E0219A91B0D2624C01B082D11B12F10F4CC +32C4F4CC3210EC393001103726113534262716151114073E0135012132161D0114163B01 +152322061D011406232135333236351134262B010343ADAD49B03032B04BFDBD0107F9A7 +6C8E3E3E8E6CA7F9FEF9358C55558C350112010E443E0112F0986B0742A1FB309D47076D +9805F193DDF095738F7497EFDD9464588D04D08E5700000400D9FE3205DB05F900020006 +0009000D0000051109023501050901031101150531FCAA0400FAFE0502FBA80356FCAAAA +0502CC0264FECEFDCC01E1A601E10701320132FC9A0468FE1FA6000300D9001E072704E6 +00030006000A0000252311330111090235010727AAAAFE0AFCA20408FAFE05021E04C8FC +3A02C4FE9EFD9C0211A602110000000300D9001E072704E600030006000A000037113311 +090203110115D9AA014C035EFCA2AA05021E04C8FB38010201620162FC3A04C8FDEFA600 +0000000200D9FFC207270542000500080000171109011101370111D903270327FCD99B01 +E23E0580FDB00250FA80024E71FEA202C600000200D9FFC2072705420005000800001711 +09011101270111D903270327FCD99BFE1E3E0580FDB00250FA80024E71015EFD44000001 +00D9FFC20727054200050000171109011101D903270327FCD93E0580FDB00250FA80024E +0000000100D9FFC207270542000800002515090111090115010727FCD9FCD903270327FD +74A2E0024EFDB20580FDB00250DDFE1C0000000100D9FFC2072705420008000013350901 +1109013501D903270327FCD9FCD9028C0462E0FDB2024EFA800250FDB0DD01E400000001 +0006FE2303EE067500030000090301FA01F4FE0CFE0C0675FBDBFBD3042D000100D90000 +05DB0504001300000111331121113311211521112311211123112135020CA8014CA80133 +FECDA8FEB4A8FECD02D7022DFDD3022DFDD3AAFDD3022DFDD3022DAA0000000100D90000 +05DB0504001B000001113311331133113311331133152311231123112311231123112335 +01A8A8B6A8B6A8CFCFA8B6A8B6A8CF02D7022DFDD3022DFDD3022DFDD3AAFDD3022DFDD3 +022DFDD3022DAA0000000003003AFE6B07C605FB0003001D003700000121112100200706 +070602151412171617162037363736123534022726272420041716171615140706070604 +20242726272635343736373603680130FED00138FEC08C89727472727472898C01408C89 +72747272747289FE0D018E01568E8E474646478E8EFEAAFE72FEAA8E8E474646478E8E02 +E8FE9203C83B3A7273FEECA39FFEEC73723A3B3B3A727301149FA3011473723AF4908D8E +ACAAC9C5ACAA8E8D90908D8EAAACC5C9AAAC8E8D00000003003AFE6B07C605FB00190033 +003F00000020070607060215141217161716203736373612353402272627242004171617 +161514070607060420242726272635343736373605112115211123112135211104A0FEC0 +8C89727472727472898C01408C8972747272747289FE0D018E01568E8E474646478E8EFE +AAFE72FEAA8E8E474646478E80027F028DFD73A8FD73028D05423B3A7273FEECA39FFEEC +73723A3B3B3A727301149FA3011473723AF4908D8EACAAC9C5ACAA8E8D90908D8EAAACC5 +C9AAAC8E804BFD73AAFD73028DAA028D00000003003AFE6B07C605FB00190033003F0000 +002007060706021514121716171620373637361235340227262724200417161716151407 +0607060420242726272635343736373617090117090107090127090104A0FEC08C897274 +72727472898C01408C8972747272747289FE0D018E01568E8E474646478E8EFEAAFE72FE +AA8E8E474646478E805D01CD01CE78FE3301CD77FE33FE327801CDFE3305423B3A7273FE +ECA39FFEEC73723A3B3B3A727301149FA3011473723AF4908D8EACAAC9C5ACAA8E8D9090 +8D8EAAACC5C9AAAC8E80E8FE3301CD78FE32FE337701CDFE337801CE01CDFFFF0075FE4D +0A25060E10270CB5066F000010260CB5000010270CB50225000010070CB5044A00000001 +0075FE4D03B6060E001D0000051633323713213521133E01321617072623220703211521 +030E012226270109113B450820FEEE011A19089F98801494103C4508180112FEE621089F +9880149B82AF029AAA020CA5877A8F0F82AFFE0DAAFD4DA5877A8F00000000010075FE4D +03B6060E0025000005163332371321352137213521133E01321617072623220703211521 +07211521030E012226270109113B450816FEF801100CFEE401240F089F98801494113B45 +080E0108FEF00C011CFEDC17089F9880149B82AF01CEAAF0A80140A5877A8F0F82AFFED9 +A8F0AAFE19A5877A8F0000010075FE4D03B6060E001D0000012623220703251505030E01 +222627371633323713053525133E013216170322113B45081A0143FEB51F089F98801494 +113B45081AFEBE014A1F089F98801404F682AFFDE7B6A0BAFD6DA5877A8F0F82AF0219B6 +A0BA0293A5877A8F000000020075FE4D03B6060E002A0033000001262322070316170726 +27033637170607030E012226273716333237132627263534373637133E01321617011306 +0706151417160322113B45080E99497128501620153E36440F089F98801494113B45080F +5D496E6F53750F089F988014FE01163D2C484B2504F682AFFED62988414821FE2B061170 +2409FEBEA5877A8F0F82AF013118496F9B9C735613013CA4887A8FFC4501D6112E4A6768 +482400010075FE4D042D060E003200000126232207033637363D01072737331707271514 +070607030E01222627371633323713262726353314171617133E01321716170322113B45 +082B402F565D48BD36BC485C6E556D1108A097801494113B4409105B3E776650332C2C08 +A09749361504F682AFFC78102F55721E5D48BDBD485D1E9C6C5611FEA1A5877A8F0F82AF +014D193E779A724E330B0399A58746348F0000020075FE4D03B6060E0003002100000133 +15230313211121133E01321617072623220703211121030E012226273716333201B2D3D3 +10110177FEA51408A2977E1494113946090E0139FEA10D08A9917D149411394702A6FEFD +EA0172023001AEA7857D8C0F82AFFEF5FCBCFEFFAD7F7D8C0F8200020075FE4D03B6060E +0003002B000001331523130607133E0132161707262322070316171610070607030E0122 +262737163332371316333236342601B2D3D363291F1608A2977E1494113947080E63426E +6E56730F08A2977E1494113946091628366F879402A6FE017E020501C3A7857C8D0F82AF +FED6184B7CFED26F5513FEBEA7857D8C0F82AF01B61093D87F0000020075FE4D0470060E +0003002E00000133152301163332371316171632363426232207133E0132161707262322 +0703363332161006232227030E01222627028FD3D3FE7A113B45081B2D3870C6968A71D5 +531806A297801494113B45080F3B44A9CEDC9B57491108A097801402AEFEFDB582AF0233 +3E1C3691D18FA1025DA4887A8F0F82AFFECB13E9FED2DB21FEA3A5877A8F00030075FE4D +03B6060E0003000B00310000013315231636342622061416031633323713262726103736 +37133E0132161707262322070316171610070607030E0122262701ADD3D3CB948AD6908F +A2113B45080F5D496E6F53750F089F98801494113B45080E63426E6E56730F089F988014 +02B0FE7A90D18F91CF90FE2D82AF013118496F0137735613013CA4887A8F0F82AFFED618 +4B7CFED26F5513FEBEA5877A8F0000030075FE4D03B6060E001D00210025000037112113 +3E01321617072623220703211121030E0122262737163332371B01033311212311339801 +3B1008A2977E1494113947080F0118FEC40E08A98F7F1494113947080DC518ACFEC5AB93 +9B02FE0149A7857D8C0F82AFFED0FD02FEDEAD7F7B8E0F82AF01090274FE1601EAFE1600 +00000001FFBEFE4D046D060E002D0000012623220703213216140623353637262721030E +01222627371633323713211707013501170721133E01321716170322113B450819010E64 +8B905F60080860FEEB21089F98801494113B440920FEDCBB60FEC5013B60BB012A1B089F +9849361504F682AFFDEF8FC68C8804656206FD49A5877A8F0F82AF029EBA60013A48013A +60BA022AA58746348F0000010075FE4D03B6060E00230000012623220703371707170727 +030E01222627371633323713072737273717133E013216170322113B4508149D78FCFC78 +B119089F98801494113B4508179F7AFEFC78B217089F98801404F682AFFE589D79FCFC78 +B0FDF4A5877A8F0F82AF01E2A079FCFC78B301D8A5877A8F000000010075FE4D03B6060E +002A000005163332371306070615112311343637133E0132161707262322070316171615 +1123113427030E012226270109113B45082D412034799B790E089F98801494113B45080D +5A3E56797C2D089F9880149B82AF03A61032509CFEC80149CFBC11012BA5877A8F0F82AF +FEE6184561D6FEB70138F332FC4AA5877A8F00010075FE4D03B6060E0027000001262322 +070336190133111005030E01222627371633323713262726351133111417133E01321617 +0322113B45082A9279FEEF11089F98801494113B4508105C3F56797E2B089F98801404F6 +82AFFC8D2601070138FEB7FE8C27FEA2A5877A8F0F82AF014C174661D60149FEC8F53103 +85A5877A8F0000020075FE4D03C006F90017001B0000013E01333216170726232207030E +012322262737163332370115213501E308A24B4C7E1494113947084108A24C4B7E149411 +394708021EFCEF04E2A7857D8C0F82AFFAB0A7857D8C0F82AF0767A0A0000002006BFD62 +03B6060E0017001B0000013E01333216170726232207030E012322262737163332370135 +211501E308A24B4C7E1494113947084108A24C4B7E149411394708FEC9031104E2A7857D +8C0F82AFFAB0A7857D8C0F82AFFDD0A0A000FFFF0119003F059C04C5100600990000FFFF +00D901D305DB046A10260CC6000010070D4F02140124FFFF00D9009F05DB046A10260CC6 +000010270D4F00E7FE5710070D4F03400124000200D9FF0405DB04A60003000A00000515 +013509021501350105DBFAFE0502FBF80408FAFE050246B601D1B60265FE91FE93B601D1 +A601D1000000000200D9FF0405DB04A60003000A00001701150111350115013501D90502 +FAFE0502FAFE04084601D1B6FE2F04ECB6FE2FA6FE2FB6016D00FFFF00D9FF0405DD04A6 +10270D4F042FFFBB100611850000FFFF00D9FF0405DB04A610260D4FFEBB100611860000 +0000FFFF00D9FF0405DB052710270D4F010F01E1100611850000FFFF00D9FF0405DB0527 +10270D4F031D01E1100611860000FFFF00D9FF0405DD061210270D4F042F02CC10061185 +0000FFFF00D9FF0405DB060D10270D4FFFFE02C7100611860000000300D9FEF105DC054E +001C003A0041000025150E01232227262726272623220607353E013332171E0117163332 +3613150E01232227262726272623220607353E0133321716171617163332361309011501 +350105DC6AB2626E920A0606109A5E58AC6268B4606E940A0C0E9C5E56A8686AB2626E92 +0A0606109A5E58AC6268B4606E940A04080E9C5E56A867FC4003C0FAFE050250B34E453B +040302063D4C54B34E453B0504063D4B019BB250443A040402063C4C52B24E443A040204 +063C4A035EFEEBFEEEB20170AA016F000000000300D9FEF105DC054E001C003A00410000 +25150E01232227262726272623220607353E013332171E01171633323613150E01232227 +262726272623220607353E0133321716171617163332360135011501350105DC6AB2626E +920A0606109A5E58AC6268B4606E940A0C0E9C5E56A8686AB2626E920A0606109A5E58AC +6268B4606E940A04080E9C5E56A8FB650502FAFE03C050B34E453B040302063D4C54B34E +453B0504063D4B019BB250443A040402063C4C52B24E443A040204063C4A035EB0FE91AA +FE90B2011200000200D9FF0805DB04A8000B001200000117072115210727372135210902 +1501350103AC965A01F3FDB285965AFE0D024E02B4FC4003C0FAFE050201A250A8AAF850 +A8AA034EFEEBFEEEB20170AA016F000200D9FF0805DB04A8000B00120000011707211521 +0727372135210135011501350103AC965A01F3FDB285965AFE0D024EFDB20502FAFE03C0 +01A250A8AAF850A8AA034EB0FE91AAFE90B201120000000200D9FE5F05DC054E0036003D +000001150E01232227071633323637150E0123222F010727372623220607353E01333217 +3726232206073536373617161F01371707163332361309011501350105DC6AB262445334 +8C5756A8686AB2626D93085CA459402F58AC6268B4604553338B5658AC62685A6D4D9270 +075BA459403156A867FC4003C0FAFE05020196B250441783344B55B34E453B03E940E210 +4C54B34E451782344C52B24E222A080E2C03E940E30F4A035EFEEBFEEEB20170AA016F00 +0000000200D9FE5F05DC054E0035003C000001150E01232227071633323637150E012322 +2F010727372623220607353E01333217372623220607353E0133321F0137170716333236 +0135011501350105DC6AB2624453348C5756A8686AB2626D93085CA459402F58AC6268B4 +604553338B5658AC6268B4606E94075BA459403156A8FB650502FAFE03C00196B2504417 +83344B55B34E453B03E940E2104C54B34E451782344C52B24E443A03E940E30F4A035EB0 +FE91AAFE90B201120000000400D9FD8405DB06540006000A001100150000011501350115 +0501213521012D01350115090121352105DBFAFE0502FC8B0375FAFE0502FAFE0375FC8B +0502FAFE0502FAFE050203FBB0012FAA0130B2D2FD5CAAFB5ED5D2B2FED0AAFED1037EAA +0000000400D9FD8405DB06540006000A001100150000132D013501150111352115111501 +3501150501352115D90375FC8B0502FAFE0502FAFE0502FC8BFE73050203FBD5D2B2FED0 +AAFED1FEE1AAAAFC08B0012FAA0130B2D201F9AAAA00000300D9FF0205DC054E001A001E +0025000001150E0123222F0126272623220607353E0133321F0216333236012115210902 +1501350105DC6AB3616E921006109A5E58AC6268B4606E940E169C5E56A8FB660502FAFE +0501FC4003C0FAFE05020196B250443A0802063C4B53B24E443A060A3C4AFE6CAA059CFE +EBFEEEB20170AA016F00000300D9FF0205DC054E001A001E0025000001150E0123222F01 +26272623220607353E0133321F0216333236012115210335011501350105DC6AB3616E92 +1006109A5E58AC6268B4606E940E169C5E56A8FB660502FAFE010502FAFE03C00196B250 +443A0802063C4B53B24E443A060A3C4AFE6CAA059CB0FE91AAFE90B20112000300D9FE12 +05DB060C001A00210028000001150E0123222F0126272623220607353E0133321F021633 +323613150135011505012D013501150105DB69B3616E9211060F9B5E58AC6269B3616E93 +0F169B5E56A967FAFE0502FC8BFE730375FC8B0502FAFE02BEB24F453B0702063D4C53B2 +4E453B06093D4B014AB0012FAA0130B2D2FA3AD5D2B2FED0AAFED1000000000300D9FE12 +05DB060C001A00210028000001150E0123222F0126272623220607353E0133321F021633 +3236012D01350115090115013501150505DB69B3616E9211060F9B5E58AC6269B3616E93 +0F169B5E56A9FB650375FC8B0502FAFE0502FAFE0502FC8B02BEB24F453B0702063D4C53 +B24E453B06093D4B014AD5D2B2FED0AAFED1FBBFB0012FAA0130B2D20000000400D9FE2C +05DB05D70006000A001100150000132D0135011501113521151115013501150501352115 +D90375FC8B0502FAFE0502FAFE0502FC8BFE7305020125D5D2B2FED0AAFED1FEE1AAAA04 +28B0012FAA0130B2D2F9D9AAAA00000400D9FE2C05DB05D70006000A0011001500000115 +013501150501213521012D01350115090121352105DBFAFE0502FC8B0375FAFE0502FAFE +0375FC8B0502FAFE0502FAFE05020125B0012FAA0130B2D2FD5CAA037ED5D2B2FED0AAFE +D1FB5EAA0000000400D9FE3605DB05EE0006000A00110015000001150135011505090135 +09012D01350115011135011505DBFAFE0502FC8B0375FAFE0502FAFE0375FC8B0502FAFE +05020395B0012FAA0130B2D2FD56012FB0FED1FDA6D5D2B2FED0AAFED1FED0B0012FB000 +0000000400D9FE3605DB05EE0006000A001100150000132D013501150111350115111501 +3501150509013501D90375FC8B0502FAFE0502FAFE0502FC8B0375FAFE05020395D5D2B2 +FED0AAFED1FEDBB0012FB0FD27B0012FAA0130B2D2FD4B012FB0FED10000000200D9FF84 +05DB05260003000A0000090135011115013501150105DBFAFE0502FAFE0502FBF80470FE +2FB601D1FB14B601D1A601D1B6FE93000000000200D9FF8405DB05260003000A00001335 +0115090235011501D90502FAFE0408FBF80502FAFE0470B6FE2FB6FD9B016F016DB6FE2F +A6FE2F000000FFFF00D9FF8405DD052610270D4F042FFEEE1006119D0000FFFF00D9FF84 +05DB052610270D4FFFFEFEE41006119E0000000300DAFFB605DC057B0003000A000E0000 +133521151115013501150901352115DA0502FAFE0502FC40FEBE050204D1AAAAFB95B001 +6FAA0170B2FEEE0239AAAA000000000300DAFFB605DC057B0003000A000E000001213521 +0902350115090121352105DCFAFE0502FAFE03C0FC400502FAFE0502FAFE050204D1AAFA +EB01150112B2FE90AAFE9103FEAA000300D9FFC005DB05CD00030007000E000013350115 +0135011511150135011501D90502FAFE0502FAFE0502FC4003ABB20170B2FD4EB20170B2 +FC97B0016FAA0170B2FEEE000000000300D9FFC005DB05CD00030007000E000009013501 +11013509033501150105DBFAFE0502FAFE0502FAFE03C0FC400502FAFE03AB0170B2FE90 +FE0C0170B2FE90FD5501150112B2FE90AAFE91000000000200D9001105DC0528001B0022 +0000012E012322070E01070623222627351E013332373E01373633321617031501350115 +0105DC68A8565E9C0E0C0A946E60B46862AC585E9A100C0A926E62B26A01FAFE0502FC40 +03CA544C3E0604043C464EB2544C3E0604043C464EFC45B0016FAA0170B2FEEE00000002 +00D9003005DC0528001B00220000012E012322070E01070623222627351E013332373E01 +37363332161709023501150105DC68A8565E9C0E0C0A946E60B46862AC585E9A100C0A92 +6E62B26AFAFD03C1FC3F0502FAFE03CA544C3E0604043C464EB2544C3E0604043C464EFC +6401150112B2FE90AAFE91000000000400D9FE9805DC05D4000300070024002B00001735 +211501352115132E012322070E01070623222627351E0133323736373637363332161703 +150135011501D90502FAFE05020168A8565E9C0E0C0A946E60B46862AC585E9A1006060A +926E62B26A01FAFE0502FC403EAAAAFED6AAAA05DC564A3C0604063A444EB4544C3C0602 +04043A444EFC44B0016FAA0170B2FEEE0000000400D9FE9805DC05D4000300070024002B +00001735211501352115132E012322070E01070623222627351E01333237363736373633 +321617090235011501D90502FAFE05020168A8565E9C0E0C0A946E60B46862AC585E9A10 +06060A926E62B26AFAFD03C0FC400502FAFE3EAAAAFED6AAAA05DC564A3C0604063A444E +B4544C3C060204043A444EFC4401150112B2FE90AAFE91000000000300D9006605DB04CE +000300200024000013211521010607060706072135213637363736321716171617211521 +262726272601211521D90502FAFE0282432E381E1B04FE64010A0C1E365759CE5956371C +0E010AFE65041B1E382DFD3B0502FAFE025AAA027B041E2B44445CA839335B323333325B +303CA85B45432C1EFCE9AA000000000200D9FFCF05DB05570003000E0000372115210100 +050401150025352401D90502FAFE0502FE81FE2401DC017FFE81FC7D0383017F79AA04B1 +FEE55D5DFEE5D7017884A67C0180000200D9FFCF05DB05570003000F0000251521351135 +0005301504013500252405DBFAFE017F0383FC7DFE81017F01DCFE2479AAAA0407D7FE80 +7CA684FE88D7011B5D5D000200D9FED705DB0557000B0016000001170721152107273721 +3521010005040115002535240103AC965A01F3FDB285965AFE0D024E02B4FE81FE2401DC +017FFE81FC7D0383017F017150A8AAF850A8AA0407FEE55D5DFEE5D7017884A67C018000 +0000000200D9FED705DB0557000B00160000011707211521072737213521013500051504 +013500252403AC965A01F3FDB285965AFE0D024EFDB2017F0383FC7DFE81017F01DCFE24 +017150A8AAF850A8AA0407D7FE807CA684FE88D7011B5D5D0000000300D9FF0905DB054B +000300070012000037352115013521151100050405150025352401D90502FAFE0502FECD +FDD80221013AFEA3FC5B03A5015D33AAAAFED6AAAA056BFEE52D51F7D7017458A6560176 +0000000300D9FF0905DB054B000300070012000037352115013521150135000515040135 +242524D90502FAFE0502FAFE015D03A5FC5BFEA3013A0221FDD833AAAAFED6AAAA056BD7 +FE8A56A658FE8CD7F7512D000000000200D9FE7105DB054B0013001E0000052135213721 +352137170721152107211521072701000504051500253524010226FEB301DA60FDC602C8 +898A290150FE225F023DFD368A8A03DEFECDFDD80221013AFEA3FC5B03A5015DEAAA73AA +A47331AA73AAA574058FFEE52D51F7D7017458A6560176000000000200D9FE7105DB054B +0013001E0000052135213721352137170721152107211521072701350005150401352425 +240226FEB301DA60FDC602C8898A290150FE225F023DFD368A8AFEDC015D03A5FC5BFEA3 +013A0221FDD8EAAA73AAA47331AA73AAA574058FD7FE8A56A658FE8CD7F7512D00000003 +00D9FE7905DB058B001D003A0045000005150E01232227262726272623220607353E0137 +3617161716171633323613150E01232227262726272623220607353E0133321716171617 +163E01130005040515002535240105DB69B3616E920A07060F9B5E58AC6269B26260A10B +05060F9B5E56A96769B3616E920A07060F9B5E58AC6269B3616E930A05070FAA9EB067FE +CDFDD80221013AFEA3FC5B03A5015D28B34E453B040302063D4C54B34E390C0641050202 +063D4B019AB24F453B040302063D4C53B24E453B04020306430C4503ECFEE52D51F7D701 +7458A6560176000300D9FE7905DB058B001D003A0045000005150E012322272627262726 +23220607353E01373617161716171633323613150E01232227262726272623220607353E +0133321716171617163E01013500051504013524252405DB69B3616E920A07060F9B5E58 +AC6269B26260A10B05060F9B5E56A96769B3616E920A07060F9B5E58AC6269B3616E930A +05070FAA9EB0FB65015D03A5FC5BFEA3013A0221FDD828B34E453B040302063D4C54B34E +390C0641050202063D4B019AB24F453B040302063D4C53B24E453B04020306430C4503EC +D7FE8A56A658FE8CD7F7512D0000000200D9FDED05DC058B00360041000001150E012322 +27071633323637150E0123222F010727372623220607353E013332173726232206073536 +373617161F0137170716333236130005040515002535240105DC6AB2624453348C5756A8 +686AB2626D93085CA459402F58AC6268B4604553338B5658AC62685A6D4D9270075BA459 +403156A867FECDFDD80221013AFEA3FC5B03A5015D0124B250441783344B55B34E453B03 +E940E2104C54B34E451782344C52B24E222A080E2C03E940E30F4A03E6FEE52D51F7D701 +7458A6560176000200D9FDED05DC058B00360041000001150E0123222707163332363715 +0E0123222F010727372623220607353E013332173726232206073536373617161F013717 +0716333236013500051504013524252405DC6AB2624453348C5756A8686AB2626D93085C +A459402F58AC6268B4604553338B5658AC62685A6D4D9270075BA459403156A8FB65015D +03A5FC5BFEA3013A0221FDD80124B250441783344B55B34E453B03E940E2104C54B34E45 +1782344C52B24E222A080E2C03E940E30F4A03E6D7FE8A56A658FE8CD7F7512D00000003 +00D9FEA105DB04AE00030007000E000037011501110115090315013501D90502FAFE0502 +FAFE0502FC4003C0FAFE0502C3FE90B2017001F4FE90B2017002ABFEEBFEEEB20170AA01 +6F00000300D9FEA105DB04AE00030007000E000025150135011501351135011501350105 +DBFAFE0502FAFE0502FAFE03C0C3B2FE90B202B2B2FE90B20369B0FE91AAFE90B2011200 +0000000200B5FFC9059F04B30006000D00000117011701171113270902272103C25DFD23 +BF02DD5C65C1FD23FEB402DDC202CF044E5CFD23BF02DD5D0178FD96C2FD23014C02DDC1 +000000020120FFC9060A04B30006000D00000121113701370901112107090202FDFE885C +02DDBFFD23FE8002CFC202DDFEB4FD23044EFE885DFD23BF02DDFDF202CFC1FD23FEB402 +DD00000200B50034059F051E0006000D000001070107010721053709023711053A5CFD23 +BF02DD5D0178FD96C2FD23014C02DDC102115D02DDBFFD235C65C102DD014CFD23C2FD31 +0000000201200034060A051E0006000D00002527012701271103170902172102FD5D02DD +BFFD235C65C102DD014CFD23C2FD31995C02DDBFFD235DFE88026AC202DDFEB4FD23C100 +00000002003700860650047E000900130000250901112111090111210115213509013521 +15010233FE0401FC022101FCFE04FDDF0285FD17FEF6010A02E9010A8601FC01FCFEEF01 +11FE04FE04011101F58383FEF6FEF68383010A000000000100370086063F047E00060000 +250901112111210233FE0401FC040CFBF48601FC01FCFEEFFE2A0001015E000005560608 +00060000090221112111015E01FC01FCFEEFFE2A040C01FCFE04FBF4040C0001015EFFCD +055605D50006000001211121112101015E011101D60111FE0401C9040CFBF4FE04000001 +00B5FFC9059F04B300060000012709022721059FC1FD23FEB402DDC202CF01E4C2FD2301 +4C02DDC1000000010120FFC9060A04B300060000011121070902012002CFC202DDFEB4FD +2301E402CFC1FD23FEB402DD0000000100B50034059F051E0006000025370902371102D0 +C2FD23014C02DDC134C102DD014CFD23C2FD31000000000101200034060A051E00060000 +0117090217210120C102DD014CFD23C2FD310303C202DDFEB4FD23C10000000100370086 +0650047E00090000250901112111090111210233FE0401FC022101FCFE04FDDF8601FC01 +FCFEEF0111FE04FE0401110000000001015EFFCD05560608000900000121090121112109 +012104450111FE04FE040111FEEF01FC01FCFEEF01C9FE0401FC024301FCFE0400000001 +0075FFF9065202D7000B000005230137171121352111371704C85AFE7878E8FC2F047BEA +7807018A78EA011CAAFE3AEA780000010075022D0652050B000B00000901072711213521 +1107270104C8018A78EAFB8503D1E8780188050BFE7678EAFE3AAA011CEA78018A000001 +0048FFF9062502D7000B000005013717112115211137170101D2FE7678EA047BFC2FE878 +FE7807018A78EA01C6AAFEE4EA78FE76000000010048022D0625050B000B000001330107 +271121152111072701D25A018878E803D1FB85EA78050BFE7678EAFEE4AA01C6EA780002 +00BAFF0406D505240003000700001711211125211121BA061BFA570537FAC9FC0620F9E0 +72029E000000000200BAFF0406D505240003000700001711211101211121BA061BFA5705 +37FAC9FC0620F9E00310029E0000000200BAFF0406D50524000200060000052101031121 +11012C0537FAC972061B8A053CFA520620F9E0000000000200BAFF0406D5052400020006 +0000051121031121110663FAC972061B8A053CFA520620F9E00000020006FF0406210524 +00020006000005090503130276FD8AFCF3030D030EFCF26402780278FD880310FCF0FCF0 +000000020006FF040621052400020006000013011109039E0275FCF3030D030EFCF20214 +FD8804F0FD880310FCF0FCF0000000020006FF0406210524000200060000130901210902 +9E02750276FA7D030D030EFCF20214FD8802780310FCF0FCF00000020006FF0406210524 +000200060000132109049E04EBFD8AFCF3030D030EFCF202140278FD880310FCF0FCF000 +0000000C00BAFF0406D5052400050009000D00110015001B001F00230029002D00310037 +000005152335333513152335131523350115233523152335011523352335231523352315 +2335011533152335131523351315233513152315233506D5E37172727272FEA5CCEACA04 +4D7271EACCEACAFEA472E472727272E4727216E6747201B6CCCC01B4CACAFC2474747474 +05ACE6727474747474FAC67274E601B6CCCC01B4CACA01D07472E600000000010024FFCA +06D0062300040000130902212403560356FEBAFBE003B6026DFD93FC140000020024FFCA +06D00623000400090000130121090521AA0113037A0113FD30FCAA03560356FEBAFBE003 +8BFCB1034F020CFE1F026DFD93FC1400000000020096FF46066605FC0005000B00000902 +110901031109011101010802760276FD8AFD8A7202E802E8FD180135FE95016B02D8016B +FE95FCE6035C01ADFE53FCA4FE5300010096FF46066605FC000500003711090111019602 +E802E8FD18F3035C01ADFE53FCA4FE53000000010022FFB906D905890005000005090121 +090101D0FE5201AE035B01AEFE524702E802E8FD18FD1800000000010070FE0008840628 +000B00001610012420050010010420257002050103020401030205FDFBFEFDFDFCFEFD40 +04A8012A9696FED6FB58FED696960001004DFFA006A7064D00040000090311043A026CFD +94FC14064CFCAAFCAA01460420000002004DFFA006A7064D000400090000090111090511 +040EFCB2034E020CFE20026CFD94FC1405C6FEEEFC86FEEC02D00356FCAAFCAA01460420 +00000001000A0000046A05D5001500001333112115211521152111211521112335333523 +3533C9CA015BFEA5015BFEA502D7FC5FBFBFBFBF05D5FE07909090FE7EAA022C90909000 +00000001000A0000022A0614001300000133152311231123353335233533113311331523 +0179B1B1B8B7B7B7B7B8B1B102BC90FDD4022C9090900238FDC8900000000001FFD70000 +046A05D5002300001333111617163332373637330E012322271121152111262726232207 +0607233E01333217C9CA0201110D261212027D02665B141302D7FC5F0605110D26121202 +7D02665B191605D5FD2C010109252452869404FE2FAA0302040309252452869406000002 +000A0000048D05D50010001D00001321321716151407062B011123112335331715333236 +3534262B01152115C901C8FB80818180FBFECABFBFCAFE8D9A9A8DFE015B05D57172DBDD +7171FDA803CF9090D192878692D090000000000200C9FE66055405D5001B002400C7401A +110E0F0B050603190900241E16050A211904193F1C0A1C14042510FCEC32FCC4EC111739 +1139393910CC393931004026090807030A0611030403051104040342140B0A0E9511B004 +0604001D03041D950924951581042FF4ECD4EC12391239123910F4EC113939304B535807 +1005ED071005ED1117395922B2402601015D40427A1B0105000501050206030704150015 +011402160317042500250125022603270626072608260920263601360246014602680575 +047505771B88068807980698071F5D005D011E01171323032E012B011114163B01152322 +261901212016151406011133323635342623038D417B3ECDD9BF4A8B78DC6E863F4DE3CD +01C80100FC83FD89FE9295959202BC16907EFE68017F9662FDF1C296AAF4010E056DD6D8 +8DBA024FFDEE8783838500040048FFA2049C04BC00230028003000370000011123350E01 +232227072737263534363B013726272623220607353E01333217371707160F0133353407 +01163332363D0101130607061514042DB83FBC88875C67606E3BFDFB299E0B0D549760B6 +5465BE5AE778945FA839BB3538AFFEBC3E6399B9FDC5E5633356027FFD81AA66613C7D4E +85567BBDC0BF0C0C452E2EAA272772B34FCB732B411218BAFE782ED9B429FEE201150C1E +337B200000000001FFE8FF4203120693001C0000011133133303331523031514163B0115 +23222726270323131123353311017731C0AAC0A0D1AA4B73BDBDD5510D0A66AAD6878705 +9EFEC20233FDCD8FFE0F6F894E9A500C10FED60272021D8F013EFFFF00C9FEBF060405D5 +100604360000000100BAFEE5051C06140019000021231134262322061511231133113637 +363332171615113311230464B87C7C95ACB9B942595A75C16363B8B8029E9F9EBEA4FD87 +0614FD9E6532327778E8FDF5FE4C000100C9FEBF056A05D5000E00002533112311230111 +2311331101210104C1A9C545FD33CACA029E0104FD1BAAFE15014102CFFD3105D5FD8902 +77FD48000000000100BAFEE5049C0614000E0000133311013309013311231123011123BA +B90225EBFDAE01CC9FB838FDC7B90614FC6901E3FDF4FE45FE4C011B0223FDDD00000001 +005CFEBF05E805D5000B0000132115012111231121350121730495FC500490C9FB3D03B0 +FC6705D59AFB6FFE1501419A049100010058FEE504930460000B00001321150121112311 +2135012171036AFD4C036CB8FC7D02B4FD650460A8FCDBFE52011BA8032500020073FFE3 +057705F10010001C002D40181A95000E149505088C0E9101AF031704001C0211190B101D +10FCECD4EC323231002FECE4F4C4EC10C4EE300135331123350E01232000111000213216 +01101233321211100223220204B3C4C44DECA5FEF2FEAC0154010EA5ECFCDFEACCCDEBEB +CDCCEA04EDE8FA2BE7848001AB015C015C01AB80FD78FEE3FEBB0145011D011D0145FEBB +0000000100C9FE66061F05D50014000013210901211110062B0135333236351101230111 +23C9012D017D017F012DCDE34D44866EFE7FCBFE7FC405D5FC0803F8FA93FEF2F4AA96C2 +04B7FC000400FAE10000000200100000056805D50002000A000025012101230133132113 +3302BC0112FDDB0185E5FDC7D28802A188D5C702E7FC5205D5FE81017F0000020073FFE3 +057705F1000F001800002515231133153E01332000100021222600100223220210122001 +37C4C44DECA5010E0154FEACFEF2A5EC0321EACCCDEBEB019AE7E805D5E78480FE55FD48 +FE5580016B023A0145FEBBFDC6FEBB0000000001003D000005E0047B0014000001300123 +01330901363332161D01233534262207060424FEB7FAFE5CC3015E011454DE83B9B25172 +2915036DFC930460FC5402E6E1BF8672723A542A1500000100440000090605F0001D0000 +09012309012301330901330901363736333217161D0123353426232207060766FEC9FEFE +C5FEC2FEFE8ACC013A0139E3013A0107123C5689885555AA512E2A281C04E1FB1F0510FA +F005D5FB1204EEFB12042647405C5C5B6E83793650281C00000000010056000007B1047B +001B0000013637363332161D01233534262207060703230B012301331B01331305461739 +5B8483B9B25172291806E5D9F1F2D9FEDBB8E6E5D9E60388563D60BF8672723A542A1914 +FC930396FC6A0460FC96036AFC96000200680000047F04B30013001B0000131025363332 +15140F011301330123030735363F0136352623221514C801541412A8B87FAB015EC3FE5C +FAD1A8313CA5D8012DAF034D01323103F8995D40FE2F03ACFBA0023255C4141C506E3434 +A025000100C90000047105D50007000001211123113311210471FD22CACA02DE02C7FD39 +05D5FD9C0000000100C1000003D0046000070000012111231133112103D0FDA9B8B80257 +0204FDFC0460FE33000000020070FFE704D10468000A0027000001221511323736353427 +262732171611100706230722272611103736371506070615141716331110033D415F5F55 +5646368B80898981CBB7C885888865A7423A56564D7003CB91FD52685DDFD0705B9D848D +FED9FEF1A19801999C0113011E926D1CA3174E73BECA736702AF012E000000010000FFE5 +029006140017000021350621222F0116333236351134262B013533321716151101D772FE +F92538013C589CA74D69E7FEB74F52AEC90ABD23CBBE026C99619C6062D4FB8200000003 +0071FFE30475047B0007000F002000000026220614163236080120001000200801262006 +1514173637363332171617363503165E875D5D885DFD5B011101E10112FEEEFE1FFEEF03 +41ACFED9AC1416375C85885933171201615F5E875C5C02680138FEC8FDD9FEC7013901DA +E9E7C9604D47385D5F3242495B0000010062000003330460000B00000111213521112135 +211121350333FD2F0217FE0D01F3FDF60460FBA093017894012D94000000FFFFFFE9FF11 +00EE0367120702740000FD6400000001000A029C036805E0000600000901330901330101 +71FE9985012A012B84FE99029C0344FD4002C0FCBC0000010087FE1004C805F0003A0000 +01152E01232206151417161F011617161514070607171E013B011523222E022F01262726 +272627351E013332363534262F012E01353424333216044873CC5FA5B33C36AB7AE6676C +9273C9877C9346183F224E677B3CDF310E322777807BEC72ADBC879A7BE2CA0117F569DA +05A4C53736807663332E23192F696CB6D9705912776E49AA10274F38CE2D0A2107182FD0 +4546887E6E7C1F182DC0ABC6E4260001005CFE10051F05D50018000001331523222E0227 +25262B0135012135211501161F011E0104B36C932759768C45FED34E717D03B0FC670495 +FC557275FC889FFEBAAA10274F38F33F9A0491AA9AFB6F0D5FCD6E4900000001007BFF7E +045B0460001F000001233736232205030633323633320307233736230E0123221B013307 +243332070438C013196A90FED6451B7380DC5AB72B12BF12133459F596C02A74BF18017A +92D21902AD81ACACFE04AE9AFEE282828401830131032FB1B1B100010032FE1E0472045F +0020000001061607060423202437330616333E0137362637042322371333030633162513 +3303FA0F681A1BFEF4D2FEFFFEDD16BA0B93D96BC019136A08FE7794D31998BC8418729D +011071BF012D78D19E87A1AB96447C01487660D351B0AF037EFD05AE02B002FB00000001 +0050FE1E0448045F00230000010616151404232224273533151E01333236353426371336 +23220703231333072433320703A81FBFFEBED2B0FED80CBB079F9495B7C7245A196891FC +39BE64BD1701528FD41801998DB6B0BDCB9FAC59505B79818384E8B80166AEAEFDE30350 +B0B0B00000000002007FFFEB049406750009002E00000136070407030633203F01060706 +23201B013625130E0123223F013307063332373633321D012335260F011337240303AA1B +F9FED427451B9E018B20C218DBDBACFE9C3939200185045A852EA4020AC40A0A4942CAD1 +61A1750279A407680124340351B41820CAFE28BCD70CA06463018E017FE15A016514157E +885041343547783D251327FE94091BFE830000010069FE1E044B045F0011000001230313 +36232205032313330724333207030434D15268196987FED146C475BF19017E8FD21875FE +1E026602A8AEAEFD8303B0B0B0B0FCD5000000010050FE1D06E5045F001C000001230313 +362322050323133307243332072433320703231336232205030412CB556C196892FEE251 +BE74C01401788DCA17016C93D5179EC189196F91FEEC6BFE1D026702A8AEAEFCD4045FB0 +B0B0B0B0FC51032CAEAEFD58000000020000FFEF049A0687000B00240000013623220503 +063732373637170605042322371B01232227353315163B013733032433320703C9196990 +FED94F155FD47B6B14BF27FECDFEF888D3186A68B6B011C6096A570FB07A017B90D31903 +2BAEAEFDCE860138427E03F74241AF02D60144E6E9FD6B36FE59B1B100000002006C0000 +0734045F0009002200000136232205030633162501243332070323133623220503233704 +23223713330724333203A91C658FFEBB521A728D013B012701698ED4189BBE87186890FE +EC82B81EFE7BABDA2490BF1B01998FD6032CAEB2FE0AAE02B0027DB0B0FC51032CAEAEFC +D4C8C8AF03B0B4B400000001008E00000457045F000F0000212313362322050323133307 +2433320703BEBF88196893FEDC35BE5EC017018189D820032CAEAEFE9102A2B0B0C00002 +0068FE1E047306870009001A000001362322050306333225171323030423223713033313 +072433320703A11F6F90FEC74E1871AF0116AA6DCD5DFE8AACD3189049D3341D01928DD3 +18032CAEAEFDF5AEBF9DFD890291C0AF03E30206FDEFC7B0B0000002005000000739045F +000900220000013623220503063332251323370423223713362306050323133307243332 +07243336070665196F82FEB44E1A6D88014990C11CFE79BBB7185B196679FEB85FBF88C1 +1901908EC8170196B0C21E032CAE9DFDF5AEAEFECEBFBFAF027DAE02ACFCD4045FB0B0B3 +B301B10000000002006DFFEF04C306870009002900000104070306333237363701030607 +06072437133625032623220623223D0133151433323633321713331503A2FDEF14451DB6 +7379780D011F6318C0BBD2FEB71D561902CF26144A5DD57A81C33753B272AC23315803DB +2D83FE1BF53B3D6D02A4FD3BA040400606FB025DA66B0101728195A2773F7CC0FED38600 +0000000100500000045B0687001B00002123133623220503231327102120171523352623 +221513072433360703B6C093186990FEDA73BF992201A0018502BC02ADFE2011017E8EDB +20032CAEAEFCD40438D3017CA17B6141A3FEB974B001B10000000001006800000719045F +001A0000250423223713330306333225133303063332251333032337042322032CFE929C +D218A6BE931A729D010787C08A155C9D011A69C093C119FE808ABEAFAFAF03B0FCD3AEAE +032DFCD3AEAE032DFBA1AFAF00000001006800000472045F000F00002504232237133303 +0633322513330323032CFE929CD2189AC0891A729D011173C09CC2AFAFAF03B0FCD3AEAE +032DFBA100000001008EFE1E06230687001E000001230337042322371304232237133303 +0633323733030633322513033313030602D24C0FFEBD90CA1C42FEEC7FA9165CCE4E1E74 +32D6BE510D427F0117697ACC687FFE1E02184E84C101BA889701D7FEBA8F73FDE35E7902 +DC02AEFD27FC8E000000000100680000070B0687001D0000011303331307243332070323 +13362322050323370423223713330306333203356D54CD400C01728DD5189CBE87186890 +FEE673C017FE9C9CD21898C38A1A729D013203000255FDA179B0B0FC51032CAEAEFCD4AF +AFAF03B0FCD3AE000000000100680000045D068700110000130333130306333225133303 +233704232237ED4DDC37701A689D01075ECA88CA17FE9C9CD21804610226FDD7FCD4AEAE +032DFBA1AFAFAF00000000020066FE1E044A045F0007001C000001362322050324371706 +05061633323717062120021B0133072433360303881B6A8FFED96501FC17BC21FD441B75 +988ECE63C2FEE0FEFAA5328ABD15017891D62D032BAEAEFD394B9E1BEC469BC6BA85B601 +46013403C7B1B101FECB00010068FE1E0713054A001D0000250423223704232237133303 +06333225133303063332251327331303132305DEFE808AC816FE9C9CD218A4C0931A729D +010787C08A155C9D011A6824CB14786DD0AFAFAFAFAF03B0FCD3AEAE032DFCD3AEAE032C +ECFEE7FC69FD840000000003007BFE1E0722045F00090013002D00000136072605030633 +32250136230605030617322513233704232707132303370421221B013307243332072433 +3203039D0E6F9EFEE7471A70AF010A0315197D59FEAF48188D95010395B71AFED6967409 +55CE401AFEFDFEF1D42C74BF180153D4C0180150D7C32D036D6E0201B0FE07ADAE01FAAF +01AFFE06AE01AFFECFAFAF2D3BFE2C01CCC5AF0131032EB1B1B1B1FECD0000010064FE1E +0645068700170000251323031305072313330325130333130325373303231305036D67CE +5136FE5A1DC47BC74F01A64255C2453301A01CCB7BCC50FE5C6FFDAF025201847BED036F +FDF88601BB0253FD7BFEAB78C0FC9F021C7E000100500000045C045F000F000001243332 +0703231336232205032313330196017B8ED5189BC987185E90FED971C19EBF03AFB0B0FC +51032CAEAEFCD4045F0000010068FE1E0472046000100000250423223713330306333237 +1333031323030EFEAF9BD21894BE811A729DF388C99C83DAAFAFAF03B0FCD3AEAE032EFC +40FD7E00000000010068FE1E0468045F0016000025042322371333030633322513330302 +00212335333224031FFE9C99D21898C2891A729D011169C0723BFE2EFEDB5549E70159A5 +A5AF03B0FCD3AEAE032DFC91FE57FED78BDF00010050FE1E045406870013000001031323 +03130333130724333207032313362322017C6A63DA4B974AC43D0F017B8DD5188BBD7418 +6890032CFD1AFDD802150438021CFDB38BB0B0FC51032CAE000000010056FE1E04410460 +001900000132371706230600371333072433320B01231336232205030616025B77CB58D0 +DAFBFEE22492BD1B0128E6D42B82BE7E19A373FEF7651EA1FE9DAD7AB2020133F6041BB1 +B0FECDFD1A02E6AEAEFD19CEDA000002007AFFEE04C50687000700150000010407030633 +20370123030205201B011225033313330396FDFC18431FCD013819018F69642CFDDBFEA9 +2A3E2702C26AD24B7003F027B5FE32D6B202B7FD5CFEE62D015801BA011B430229FDD900 +000000010050FE1E06F60460001C00002113362322050313230313330724333207243332 +07032313362322050302F4731B5F91FEE0605CDA4488C014017590C916016996D1169BC2 +891B7190FEEB74032CAEAEFCF5FDFD01E20460B1B1B1B1B1FC51032CAEAEFCD400000001 +003CFE1E045C045F00200000012313362322050336172011102506273716332011262122 +0723133307241736030422C029156790FEDC5DEA9701B3FDB4E9EB1EDDE501A007FED8B3 +C5C59DC61D012CDFD02A02230108AEB1FD977301FE8DFE5E020151BE7A0114DF6E0428B1 +B20101FECB0000010060FE1E0461045F000E000001041B01330320371333030205022102 +C8FD386094BE8B02052154C05E24FD393701E7FE1E01024303FFFC25F002EBFCD3FEFF1D +FE8B00010046FE1E04600489001E000001161724130423202F0133171633203733020116 +17072427061323122526270152247701AD01FECBF2FEE81006D80806A1016191A115FDF5 +D6B03BFEFFB9CB22C20201149B2C0283ABD8DC01D264ED526D5194FD72FEBEAE3C897AB0 +BEFE9601D7BFCBF800000001005000000700045F001B00002123370423221B0136232205 +032313330724333207030633322513330678C218FE969FCA2C49176B83FEE066C38EBF16 +0120E4D0195A18676901435EBFAFAF013201F9AEAEFCD6045EB1B1B1FD84AEAE032D0001 +0064FE1E045B068700170000013623220503012301131221331523220703243716070323 +03881B697CFEC65D0202F6FE31B32A0170AEEA911B3201789ACB195EC0032CAEAAFD7FFD +6F026604C001438AB9FE78A30102AFFDE0000001007A0000044E05020018000001022120 +1B013303063320371336043735331506373633320703FF2CFDACFED12A23CB261AC50138 +1B4F16FD7C01D002868586F1240132FECE01320108FEF7ADB902329AC2E7D4A4763B3CFC +00000001007CFE1E046D06870013000025132303370423221B0133030633162513033313 +03E36EDC4F02FE909BCD2C8ABE8C178768012A734ED33B84FD9A02256CAF0131032EFCD2 +AD02AF03210235FDCD0000010068FE1E04D2045F00130000252337071323030623223713 +33030633322513330445BD08D46DCB4AC540BF1890C286148F7F01726FC3465F45FDBE02 +1533AF03B0FCB48F9903420000000002007AFFEF06BB0688000700250000010407030633 +2037052313360705030221201B013625110221041715233526072217112520030395FDFE +1A4218AB01471C0317BD731C65FE9F6629FDB9FED62A491C02C402018D015701CA01A9C0 +02015E01332A03D92BBDFE30B2B2B302BDCA0915FD4AFECE01320219C1390152010202BE +4D3F5B0196FEBA27FEE000020071FFE304BB030B000B00170024400A194509120F031215 +451810FCECD4ECEC3100400800B90C06B9128C1810F4ECD4EC3001220615141633323635 +3426273204151404232224353424029699B7B79999B7B799F9012CFED4F9F9FED4012C02 +677D73737D7D73737DA4D3C1C1D3D3C1C1D300030073FFE306A705F00013001F002B0032 +400D2D102114190A9120151900102C10FCEC32F4EC32EC3100400E159520AD1A950F2695 +05910F8C2C10E4F4EC10ECF4EC3013341236243332041612151402060423222426022521 +16171E01333237363736252126272E01232207060706737ED40126A2A20126D47E7ED4FE +DAA2A2FEDAD47E0559FB811B8A4ED677776BD85D1EFB8C047E1A8A4ED677776BD85D1E02 +EA9D011ED17A7AD1FEE29D9EFEE2D17A7AD1011E49CA9153603061E44AF9C99252603061 +E54900050073FFE306A705F000130018001D00220027004940151A22951824241E232910 +1E19190A9123141900102810FCEC32F4EC32EC1112392F3CEC32310040121A14952223AD +1B17950F21259505910F8C2810E4F4EC3210EC32F43CEC32301334123624333204161215 +1402060423222426023716001711290111360013260027112901110600737ED40126A2A2 +0126D47E7ED4FEDAA2A2FEDAD47EDB1A010BB502A4FE26B5010B1A1AFEF5B5FD5C01DAB5 +FEF502EA9D011ED17A7AD1FEE29D9EFEE2D17A7AD1011E49C9FEE6210204FDFB21011A01 +74C9011A21FDFC020421FEE60000000200400000053505D5000A000D0063400D03000C95 +01810609030500090E10DCC4D4C431002F3CF4EC39393040150D110006000611050D050C +110308030811090A09424B5358071001ED071001ED071001ED071001ED40140B090C0D05 +0A090C000607000608030403080D050F0F0F0F5922133521150901230901230137012176 +0495FE280202F0FE76FE75F00202790154FD57052BAA9AFD7FFD460217FDE902BAA301CE +000000><000200430000053805D5000A000D0063400D03000C9507058102000703050E10 +DCC4D4C431002FE432EC39393040150D110006000611050D050C110308030811090A0942 +4B5358071001ED071001ED071001ED071001ED40140B090C0D050A090C00060700060803 +0403080D050F0F0F0F59222515213509013309013301070121050EFB6B01CCFDFEF0018B +018AF0FDFE78FEAB02A99A9AAA027102BAFDE90217FD46A3FE32000000030040000004D5 +05D500020005000F006A40110C0F01950D810A07039509110F070A0C1010DC3CD43CCC31 +002FEC3939F4EC393930401500110F030F01110C040C041107010703110A000A424B5358 +071001ED071001ED071001ED071001ED40140201070A000601070F0305030F040C0B0C04 +0A000F0F0F0F59220121090121013701152135090135211503D9FD570158FEB402A9FEA8 +7701D1FB6B01D1FE2F0495052BFE57FD2801A996FDC1AAA0024C023FAAA0000000030096 +000003E805D500030007000B002340110307000804910B011C00091C08051C040C10DCFC +DCFCDCEC31002FF43C3CCC3230013315230133152301331123031CCCCCFD7ACCCC0144CA +CA05D5FF00FFFF00FFFA2B00FFFF00100000056805D5100603300000FFFF001000000568 +05D5100600390000FFFF00C90000048B05D5100600280000FFFF00830000044505D51006 +0150000000050096FFE304E005F0000B00170023002F003300484010354531031B12152D +300921120F27453410FC3CEC32C4D43CEC32C4EC3100401633B930302A0C00B90C06B912 +91341EB92A18B9248C3410F4ECD4EC10F4ECD4EC1112392FEC3001323635342623220615 +141617222435342433320415140403323635342623220615141617222435342433320415 +14040121152102BB99B9B99999B9B999F9FED4012CF9F9012CFED4F999B9B99999B9B999 +F9FED4012CF9F9012CFED4FD3703A0FC6004733934343838343439A48E83828E8E82838E +FCB83934343838343439A48E83828E8E82838E035CAA0000000100DB0000067D05D50013 +003A400D05090C031C070D021C0010121410DCCC32EC32DCEC32CC323100401007129500 +0D9502AD0004911108950B0F2F3CEC32E432F4EC10EC3230132111211121152111211521 +1121112135211121DB020B018B020BFEBF0141FDF5FE75FDF50141FEBF05D5FD9C0264AA +FB7FAA02C7FD39AA048100000002005C0000055205D50008000B005440100B9502810700 +0A020406000B1C02040C10F4EC32D4C4113931002F3CF4EC304019051100040807080911 +000400071106050605080A11040004424B5358071005ED3C3C071005ED00071004ED0710 +08ED59222123112115090123010501210126CA04ACFE1E022CEFFE50FE7302E6FD1A05D5 +9AFD76FD4F0217D303E70000000300960000037E05D500030007000B002D40130B9508AF +07039500AD0495070100090508040C10DC3CCC32DCCCB43F0D3F01025D31002FECFCEC10 +FCEC300133152301331523113315230280FEFEFE16FEFEFEFE0351CDFE49CD05D5CD0000 +0002005C0000055205D50008000B005440100006810B95050B060401070A1C06040C10F4 +EC32D4C4113931002FECF43C304019021107030800080911070307001101020102080B11 +030703424B5358071005ED3C3C071005ED00071004ED071008ED59220133090115211133 +012511210463EFFDD401E2FB54CA018DFE7302E605D5FD4FFD769A05D5FDE9D3FC190000 +00030073FFE306A705F00013001F002B0037401120951B1B14262D1026190A9114190010 +2C10FCECF4ECEC1112392FEC3100400C1A21950F1B209505910F8C2C10E4F4EC3210EC32 +301334123624333204161215140206042322242602371417161716171106070E01011136 +373E01353427262726737ED40126A2A20126D47E7ED4FEDAA2A2FEDAD47ED52E5DD83D40 +B5814E5C02AAB5814E5C2E5DD83D02EA9D011ED17A7AD1FEE29D9EFEE2D17A7AD1011E9E +7D71E4611B0C04B3218853E101DDFB4D218952E17D7C70E5611B000000030073FF9506A7 +063F00190023002D006E401E162E1517001A0A080D1C1D27260714061A24092F1024190D +911A1900102E10FCECF4ECECC4111217391239391112393911393100401E0709051F162E +1C1D2726041F14120A171F29150A2995121F950591128C2E10E4F4EC10EC2FC411123939 +123912173912391112393930133412362433321737170716121514020604232227072737 +2602371417012623220E02053427011633323E02737ED40126A2E3C4A0829D6E807ED4FE +DAA2E4C4A0819C6E7FD59102EA8FA777D69C5C048A91FD1590A777D69C5C02EA9D011ED1 +7A76C469C26AFEDE9D9EFEE2D17A77C56BC16A01219EE6AE03985E60A5E17CE5AFFC675E +60A5E100000400960000019405D500030007000B000F002E401707950403950008950B0C +9500AF0F0D0905010C0804001010DC3C3C3CCC32323231002FE4ECDCEC10ECDCEC301333 +152315331523153315231533152396FEFEFEFEFEFEFEFE05D5CDDFCDE1CDE1CD00010029 +000004E105D50007003A400907AF0105090701050810DCDCC4CC31002F3CE43040140311 +000704021101000100030711060411050605070110ED10ED32320710ED0810ED09012309 +0123013302B2022EE8FE5DFEBAE6037FE802BAFD46021EFDE205D500000100C90000053B +05D5000B002D400D0D04031C06021C0B071C0A040C10FCFCDCFCDCFCFC3100400A009105 +0795020BAD09052F3CF43CEC10E430013311211123112111231121029DCA01D4CAFD22CA +01D405D5FD9CFC8F02C7FD39037100000001003E0000053C05D5000E0081400C031C0404 +07000F0709000D0F10D4C4DCC4C41112392FEC31B480007F0D025D004006060300AF0C09 +2F3CEC3232304BB042505840140A110908090C110D0E0D06110708070111000E00050710 +EC0710EC0710EC0710EC400F0801090C070B0C070A000E0D06000A0F0F0F400A05110808 +0702110E000E070010ED070010ED5913330111331101330901230901230182DA0113CA01 +0BD8FE200200D8FE5CFE58DA021605D5FE64019CFE73018DFD33FCF8027BFD85031D0000 +00040096000003A205D500030007000B000F003140140F07950C04AF0800950B03050104 +000D090C081010DC3CCC32DC3CCC32B43F053F01025D31002F3CEC32F43CEC3230253315 +2311331523013315231133152302A4FEFEFEFEFDF2FEFEFEFECDCD05D5CDFBC5CD05D5CD +0002005C0000050805D500020009003740100295058108950401050906001C05040A10F4 +ECD4C4113931002FECF4EC3040090011080111070807424B5358071005ED0410ED592209 +012101211121150121012602E6FD1A03E2FB5404ACFC9C0364014403E7FAD505D59AFB6F +0003009602680492036800030007000B0024401103070B0004080D011C00051C04091C08 +0C10DCECDCECDCECCC31002F3C3CCC32323001331123013311230133112303C6CCCCFE68 +CCCCFE68CCCC0368FF000100FF000100FF000000FFFF00C90000048B05D5100603370000 +0001006F0000039605D5000B002140100B039500AF040895070501031C0008092FCC32FC +CC3231002FEC32F4EC32301321152111211521352111216F0326FED3012EFCDA012DFED2 +05D5AAFB7FAAAA04810000000001006FFFE2073105F0002F002C40160F2195141C910927 +952C048C30311E2A24190C12063010CC32DCECDC32CC310010E432EC32F43CEC32300106 +070621222735163320001110002122073536332017161736373621321715262320001110 +0021323715062320272603D02534C2FEAC80726D7901000110FEF0FF00796D7280014FC7 +34252534C2015480726D79FF00FEF001100100796D7280FEB2C834012A4236D021AF2A01 +3A01270128013829AF20CF36414236CF20AF29FEC8FED8FED9FEC62AAF21D0360002006F +000005CC05D5001B001F005040130B07090D051C101F04111D011C161A1814002010D43C +3CCC32EC3232D43C3CFC3C3CCC3231004014091C189506021B000A1D17950D11140400AF +0F132F3CE432DC3C3CEC323210DC3C3CEC32323001331121113311211521112115211123 +112111231121352111213521171121110191CA0192CA0115FEEB0115FEEBCAFE6ECAFEDE +0122FEDE0122CA019205D5FE780188FE78AAFE8EAAFE790187FE790187AA0172AAAAFE8E +01720000FFFF00C90000053305D5100603AC0000000100CC0000048805D50007001C400E +049501AF0595000602051C00040810FCECC43231002FECFCEC303311211521112115CC03 +BCFD0E02F205D5AAFB7FAA00FFFF00C90000019305D51006002C00000001006F000005CC +05D500130037400C07030509011C120E0C100014102F3C3CCC32FC3C3CCC323100400E10 +0595021300060F95090C00AF0B2FE4DC3CEC3210DC3CEC32300133112115211121152111 +23112135211121352102BFCA0243FDBD0243FDBDCAFDB00250FDB0025005D5FE78AAFE8E +AAFE790187AA0172AA000000000200C90000019505D5000300070037400C070004AF0205 +011C0400040810FC4BB0105458B900000040385932EC3231002FECCCCC3001400D300940 +09500960098F099F09065D3733152313331123C9CCCC02CACACDCD05D5FBA6000001009F +FFE305A405D5001C003E4018150F951A049507911A8C1D141915121907170C190600101D +10FCC4ECD4C4ECD4EC310010E4F4EC10ECC43040060411080908424B5358071001ED5922 +133412370121352115010E0115141633323635342733161514042120249FBEBB0210FCA4 +04ACFD78B7C5DBC9E2D5CBBFE1FEBBFEB9FECEFEB901C39F010477014EAAAAFE6574E477 +96A48888B3CEE0A1CEE6F90000040063FFE304AD05F0000B00170023002F0039400E3145 +031B12152D0921120F27453010FC3CEC32D43CEC32EC3100401000B90C06B91291301EB9 +2A18B9248C3010F4ECD4EC10F4ECD4EC3001323635342623220615141617222435342433 +3204151404033236353426232206151416172224353424333204151404028899B7B79999 +B7B799F9FED4012CF9F9012CFED4F999B7B79999B7B799F9FED4012CF9F9012CFED4041A +5049495050494950A4A59897A6A69798A5FD115049495050494950A4A69798A5A59897A6 +00020073FFE306A705F0001300270028400B29101E190A91141900102810FCECF4ECEC31 +00400A19950F239505910F8C2810E4F4EC10EC3013341236243332041612151402060423 +2224260237141E0233323E0235342E0223220E02737ED40126A2A20126D47E7ED4FEDAA2 +A2FEDAD47ED55C9CD67777D69C5C5C9CD67777D69C5C02EA9D011ED17A7AD1FEE29D9EFE +E2D17A7AD1011E9E7DE1A56060A5E17D7CE1A56060A5E10000020073FF9106A705F00016 +002C005040180F0E0A1F20211E0D100617230E2E1023190A91171900102D10FCECF4ECEC +C411121739123939310040160F12201F211E100D06281C0F1C951228950591128C2D10E4 +F4EC10ECC011121739123930133412362433320416121514020717072706232224260237 +141E023332370137013635342E0223220E02737ED40126A2A20126D47E7F6CA281A7C4E5 +A2FEDAD47ED55C9CD677A790FE9183016A905C9CD67777D69C5C02EA9E011ED07A7AD0FE +E29E9EFEE069C76BCA777AD0011E9E7CE2A4605E01BE6AFE49AEE57CE2A46060A4E20000 +000100C90000053B05D5000B002D400D0D04061C04081C0B021C00040C10FCFCDCFCDCFC +FC3100400A0501910A0B089503AD0A2FF4EC3210E43230131133112111331121112311C9 +CA02DECAFE2CCA02640371FD3902C7FC8FFD9C0264000000000300C9000001C705D50003 +0007000B002840140B9508AF07039500AD0495070D0901050004080C10DC3C3CDC3C3CCC +31002FECFCEC10FCEC30133315231133152311331523C9FEFEFEFEFEFE0351CDFE49CD05 +D5CD0000000500960000056805D500030007000B000F0013003F401B1301950210AF0F0B +9508AD0C0695050F0307020609080D110C101410DC3CCC32DCCCDC3CCC32B63F073F033F +09035D31002F3CEC32FCEC10FC3CEC323001233533112335330133152301331523113315 +230568FEFEFEFEFD18FEFEFE16FEFEFEFE0508CDFA2BCD0284CDFE49CD05D5CD00030073 +FFE306A705F000130027002B003E40102D100A292828141E190A91141900102C10FCECF4 +EC11392FCC10ECB22F29015D3100400E2BCE28AD19950F239505910F8C2C10E4F4EC10EC +F4EC3001133412362433320416121514020604232224260237141E0233323E0235342E02 +23220E0205331523737ED40126A2A20126D47E7ED4FEDAA2A2FEDAD47ED55C9CD67777D6 +9C5C5C9CD67777D69C5C01C6FEFE02EA9D011ED17A7AD1FEE29D9EFEE2D17A7AD1011E9E +7DE1A56060A5E17D7CE1A56060A5E115CD00000000020073FFE406A7063E0016002C0050 +401809080D252423260A07061721082E1021190D91171900102D10FCECF4ECECC4111217 +391239393100401608050A072326242506281C081C951228950591128C2D10E4F4EC10EC +C411121739123930133412362433321737170716121514020604232224260237141E0233 +323E023534270127012623220E02737ED40126A2E4C4A0839E6E7F7ED4FEDAA2A2FEDAD4 +7ED55C9CD67777D69C5C90FE9482017090A777D69C5C02EA9E011ED07A76C469C26BFEE0 +9E9EFEE2D07A7AD0011E9E7CE2A46060A4E27CE5AEFE3F6C01C65E60A4E2000000030073 +FFE3057205EF00250031003D004740103826190E181E08192C3230131900103E10FCECF4 +3CEC32DC3CEC32310040172F951B3B950B350B1B2904161016952310950391238C3E10E4 +F4EC10EC111217392FEC2FEC301310002120171E01151406232226353437200011100021 +263534363332161514060706212000051416333236353426232206133426232206151416 +333236730186015301935F20149985849918FEFEFEFB01050102189984859914205FFE6D +FEADFE7A03992C1C1C2C2C1C1C2C902C1C1C2C2C1C1C2C02E90167019F73274F3B819191 +814836FEBEFEE2FEE2FEBE3648819191813B4F2773019F7A3C31313C3B323203873C3131 +3C3B2F2F00010064000005C005D5000B00234011050895020BAD00AF070305021C0A080B +0C10D43CC4FC3CC431002FE4F43CEC323001331121152111231121352102ADCA0249FDB7 +CAFDB7024905D5FD6AAAFD6B0295AA00FFFF003D0000053B05D51006003B000000030073 +FFE3057205EF0033003F004B0066401919181B95151E1E1F4634190E252B0819363A301F +141900104C10FCEC32F43CEC32DC3CEC3211392F3CFC3CCC3100401F171C1418951E1A3D +952849950B1A430B283705231023953110950391318C4C10E4F4EC10EC111217392FEC2F +EC2F3CEC32CCC4301310002120171E011514062322263534372207061533113315331523 +15231123141716332635343633321615140E010706212000051416333236353426232206 +133426232206151416333236730186015301935F20149985849918EE977FBBCCE9E9CCBB +7F97EE189984859914413B85FEEFFEADFE7A03992C1C1C2C2C1C1C2C902C1C1C2C2C1C1C +2C02E90167019F73274F3B819191814836A28ADF00FFFFAAFF00FFDF8AA2364881919181 +3B4F4F1734019F7A3C31313C3B323203873C31313C3B2F2F000100C90000048B05D50013 +003B401C0E0B95040295008110950804AD12050E950B080901110F031C00041410FCEC32 +D4C4C4DC3CEC3231002FEC32ECF4EC10EE3230B21F1401015D1321152111211133153315 +231523112111211521C903B0FD1A0111CCEAEACCFEEF02F8FC3E05D5AAFE4600FFFFAAFF +00FFFDE3AA000000FFFF00100000056805D5100603290000000100C90000053B05D50007 +001F40100602810495000904051C00041C01040810FCECD4ECEC31002FECF43C30290111 +3311211133053BFB8ECA02DECA05D5FAD5052B00000100C90000047905D50008003D400C +42070395048101050104040910FC3CD431002FF4EC32304B535840120811020201071103 +03020811020011010201050710EC10EC0710EC0810EC5921230901352115210101B1E801 +DFFE2103B0FD3801DF02C0026BAAAAFD9A00000000010073000005DB05D50023004F4016 +2510060F19070E0224102118192019130A021C141D012F3C3CEC3232D43CEC32EC10D43C +EC32EC310040120003951D0A200106810F1512951C0B19140F2F3C3CD43CEC3210F43C3C +D43CEC323001113311323635331000231132001123342623112311220615231000331122 +001133141602C2CA8AF2D3FE87D6D60179D3F28ACA8AF2D30179D6D6FE87D3F2042B01AA +FE56E2C8FEEEFEBAFEDBFEBAFEEEC8E2FE5601AAE2C801120146012501460112C8E20000 +000100C9000003F605D50006002E400B0300AF060103041C00040710FC4BB0105458B900 +0000403859ECC4C431002FECC430B40211030304070110ED13210123011123C9011A0213 +DEFE75C405D5FD2D021DFAE100010073000005DB05D5002B0066401A162D101D10191C11 +0C2C100107261906272018140C1C232B030B2F3C3C3CEC323232D43CEC32C4EC10D43CEC +32ECC43100401A182BB91501012A030D0A951403100B06811D202395192A27221D2F3C3C +D43CEC3210F43C3CD43CEC321112392F3CEC323001352135220011331416331133113236 +35331000231521152115320011233426231123112206152310003335010101C1D6FE87D3 +F28ACA8AF2D3FE87D601BFFE41D60179D3F28ACA8AF2D30179D60295AA3E01460112C8E2 +01AAFE56E2C8FEEEFEBA3EAA3DFEBAFEEEC8E2FE5601AAE2C8011201463D000000010036 +042D03E905D500070017400906020401000504010810DCCCDCCC31002FCCC43230012111 +331121113303E9FC4D8F02958F042D01A8FEFB01050000000003008FFE6E03AC045E0017 +001B0024000001331136373637150E012322263534363F013E01373E0135132335330311 +070E011514171601F3BF202059615EC167B8DF485A582F27080606C5CBCBC52D39334224 +02CDFC53080D2343BC3938C29F4C8956562F3519153C34010EFEFABE01AE2D355E315937 +1F000000FFFF00D9009F05DB033210260CC6000010070D4F0213FE57000100B0033A0258 +061400050000132115231123B001A8F0B806148FFDB50000000100C7033A026F06140005 +000001233521112301B6EF01A8B905858FFD2600000100B0FEF2025801CC000500000533 +152111330168F0FE58B87F8F02DA0000000100C7FEF2026F01CC00050000012135331133 +026FFE58EFB9FEF28F024B00FFFF0093000003B005F01006054D0000FFFF00AAFEBC0682 +05D5102717D80000FC36100617D80000FFFF00AAFEBC068205D5102717DF0000FC361006 +17DF0000FFFF00AAFEBC068205D5102717DB0000FC36100617DD0000FFFF00AAFEBC0682 +05D5102717DD0000FC36100617DE0000FFFF00AAFEBC068205D5102717D80000FC361006 +17DD0000FFFF00AAFEBC068205D5102617D80000100717DD0000FC36FFFF00AAFEBC0682 +05D5102717DD0000FC36100617DF0000FFFF00AAFEBC068205D5102617DD0000100717DF +0000FC36FFFF00AAFEBC068205D5102717D80000FC36100617DC0000FFFF00AAFEBC0683 +05D5102617D80000100717D90000FC36FFFF00AAFEBC068205D5102617DF0000100717D8 +0000FC36FFFF00AAFEBC068205D5102717DF0000FC36100617D80000FFFF00AAFEBC0682 +05D5102617D80000100717DA0000FC36FFFF00AAFEBC068205D5102717D80000FC361006 +17DA0000FFFF00AAFEBC068205D5102617DF0000100717DE0000FC36FFFF00AAFEBC0682 +05D5102617DB0000100717DF0000FC36FFFF00AAFEBC068305D5102717DB0000FC361006 +17D90000FFFF00AAFEBC068205D5102717DC0000FC36100617DE0000FFFF00AAFEBC0683 +05D5102717D90000FC36100617DF0000FFFF00AAFEBC068205D5102617DC0000100717DF +0000FC36FFFF00AAFEBC068205D5102717DB0000FC36100617DA0000FFFF00AAFEBC0682 +05D5102617DE0000100717DA0000FC36FFFF00AAFEBC068205D5102617DE0000100717DF +0000FC36FFFF00AAFEBC068205D5102717DB0000FC36100617DF0000FFFF00AAFEBC0682 +05D5102717DB0000FC36100617D80000FFFF00AAFEBC068205D5102617DE0000100717D8 +0000FC36FFFF00AAFEBC068205D5102617DE0000100717DB0000FC36FFFF00AAFEBC0683 +05D5102617D90000100717DC0000FC36FFFF00AAFEBC068205D5102617DD0000100717DD +0000FC36FFFF00AAFEBC068205D5102617DA0000100717DA0000FC36FFFF00AAFEBC0683 +05D5102617D90000100717DE0000FC36FFFF00AAFEBC068205D5102617DB0000100717DC +0000FC36FFFF00AAFEBC068205D5102717DE0000FC36100617D80000FFFF00AAFEBC0682 +05D5102617DB0000100717D80000FC36FFFF00AAFEBC068205D5102717DF0000FC361006 +17DA0000FFFF00AAFEBC068205D5102617DF0000100717DA0000FC36FFFF00AAFEBC0682 +05D5102617DC0000100717DA0000FC36FFFF00AAFEBC068305D5102617DA0000100717D9 +0000FC36FFFF00AAFEBC068205D5102617DD0000100717DE0000FC36FFFF00AAFEBC0682 +05D5102717DD0000FC36100617DB0000FFFF00AAFEBC068305D5102617DE0000100717D9 +0000FC36FFFF00AAFEBC068205D5102617DC0000100717DB0000FC36FFFF00AAFEBC0683 +05D5102617D90000100717D80000FC36FFFF00AAFEBC068205D5102617D80000100717DC +0000FC36FFFF00AAFEBC068305D5102617D90000100717DF0000FC36FFFF00AAFEBC0682 +05D5102617DF0000100717DC0000FC36FFFF00AAFEBC068305D5102717DD0000FC361006 +17D90000FFFF00AAFEBC068205D5102617DD0000100717DC0000FC36FFFF00AAFEBC0683 +05D5102617D90000100717DA0000FC36FFFF00AAFEBC068205D5102617DA0000100717DC +0000FC36FFFF00AAFEBC068205D5102617DB0000100717DB0000FC36FFFF00AAFEBC0682 +05D5102617DE0000100717DE0000FC36FFFF00AAFEBC068205D5102617DC0000100717DE +0000FC36FFFF00AAFEBC068305D5102617DB0000100717D90000FC36FFFF00AAFEBC0682 +05D5102617DB0000100717DA0000FC36FFFF00AAFEBC068205D5102617DA0000100717DE +0000FC36FFFF00AAFEBC068205D5102617DC0000100717DC0000FC36FFFF00AAFEBC0683 +05D5102617D90000100717D90000FC36FFFF00AAFEBC068205D5102617DC0000100717DD +0000FC36FFFF00AAFEBC068305D5102617DD0000100717D90000FC36FFFF00AAFEBC0683 +05D5102617DC0000100717D90000FC36FFFF00AAFEBC068205D5102617DB0000100717DE +0000FC36FFFF00AAFEBC068205D5102617DD0000100717DA0000FC36FFFF00AAFEBC0682 +05D5102717DD0000FC36100617DA0000FFFF00C9000004EC05D5120600250000FFFF00C9 +0000048D05D5120600330000000200460000040A05D50008001300002511232206151416 +33052122243534243B0111330340FE8D9A9A8D01C8FE38FBFEFF0101FBFECAA602319287 +8692A6E3DBDDE20258000000FFFF00C9000005B005D5120600270000FFFFFFFA000004E9 +05D51206003700000001FFFA000004E905D500070000290135211133112104E9FB110212 +CB0212AA052BFAD5FFFF0073FFE3058B05F01206002A0000FFFF00C90000056A05D51206 +002E00000001FFD50000047605D5000A000021231101210901210111330476CAFD62FEFC +02E5FCE6010A02CDCA0277FD8902B8031DFD3102CF000000FFFF0000FFE3034F05D51206 +175F0000FFFF0073FFE3052705F0120600260000FFFF0073FFE3052705F0120601480000 +FFFF005C0000051F05D51206003D0000FFFF00C90000042305D5120600290000FFFF00C9 +0000042305D512060BC90000FFFF00C90000061F05D5120600300000FFFF00C900000533 +05D5120600310000FFFF00C90000046A05D51206002F0000FFFF0087FFE304A205F01206 +00360000FFFF00C90000055405D51206003500000002003B000004C605D50013001B0000 +012E01270333131E013B0111331121202635343601112322061016330202417B3ECDD9BF +4A8B78DCCAFE38FF00FC830277FE92959592031916907E0198FE8196620277FA2BD6D88D +BAFDB1021287FEFA85000000FFFF00100000056805D5120602070000FFFF001000000568 +05D5120600390000FFFF00C90000053B05D51206002B0000FFFF00A3FFE305BB05F01206 +0BD80000000100C90000041805F2000F000001152E012322061511231110363332160418 +5BC2688F71CAD3F760BE0598EC515195CBFC1203EE011AEA2C000000FFFF0044000007A6 +05D51206003A0000FFFF003D0000053B05D51206003B0000FFFFFFFC000004E705D51206 +003C000000030091000004B405D500080011002000000111212206151416330111212206 +1514163305212226353436372E01353424332103EAFEBCA39D9DA30144FED59491919401 +F5FDFCE7FA807C95A50110FB0218030C0223878B8C85FD9A01C26F727170A6C0B189A214 +20CB98C8DA000000FFFF00100000056805D5120600240000FFFF00100000056805D51206 +11EA0000FFFF00C90000048B05D5120600280000000100830000044505D5000B00002901 +352111213521112135210445FC5002E6FD3902C7FD0803C2AA01BAAA021DAA00FFFF00C9 +0000019305D51206002C0000FFFF0073FFE305D905F0120600320000FFFF00B2FFE30529 +05D5120600380000000100B20000052905F20011002A40090A1C0838111C00411210FC4B +B0105458B90000FFC03859ECFCEC3100B50D95049109002F3CF4EC303311100021200019 +01231134262322061511B20121011B011A0121CBAEC2C3AE03A40124012AFED6FEDCFC5C +038BF0D3D3F0FC75FFFF0008000003A905D512060BD90000000200730000055A05D50008 +0011001F4009001C0A3204190E101210FCECF4EC3100B60095098107950B2FECF4EC3001 +23200011100021331311212000111000210490F4FECBFEE1011F0135F4CAFE61FE50FE68 +019601B2052FFEE9FED4FED2FEE8052FFA2B016A0182018001690000000100AF000001B7 +013E0003000013211121AF0108FEF8013EFEC20000010092FEC001B7013E000600001321 +1103231323AF0108A481A487013EFEC2FEC00140FFFF00AF00000416013E102712C6025F +0000100612C60000FFFF00AFFEC00416013E102712C7025F0000100612C6000000020092 +FEC001B704230006000A00001321110323132311211121AF0108A481A4870108FEF8013E +FEC2FEC001400423FEC20000000200AF000001B704230003000700001321112111211121 +AF0108FEF80108FEF8013EFEC20423FEC2000000000200AF0000040502D6000300070000 +012111210121152102FD0108FEF8FDB20356FCAA013EFEC202D6A800000200AF01600405 +03A20003000700001321152115211521AF0356FCAA0356FCAA03A2A8F0AA0000FFFF0072 +FFE3048D05F0100601690000FFFF0064FFE303BC047B1006016A0000FFFF00C9000002C6 +05D5100601580000FFFF00A60000026E04601006022B000000010076FFE308FA05290034 +003E401530312C1C001817040A0F2A241E0600361C1C13453510FCECCCCC1739D4CC10EC +D4CC3100400C24950A2A1E95040F8C30173510CC32F432EC32DCEC300114070623222726 +272623220F01062322272635343736373306070615102132373637363332171617163320 +11342726273316171608FA6674EA5B6E61607A787A7BC26E5BEA746643476FF97E5B5501 +008A5D4C4B669D9B644B4A5D8A0100555B7EFA6F464301FEF28B9E444040444480449D8B +F2C6E2EC986BF3E2B6FEBA36333336363333360146B6E2F36B98ECE200010098FFE307A1 +03C50021003C401321001F08020E0D04091D1204022310080B452210FCECCCCC1739D4CC +10ECD4CC3100400C17A9071D12A904098C210D2210CC32F432EC32DCEC30011211102122 +2422042320111013330215103332373637363217161716333211340307178AFE7254FED5 +F0FED452FE728AC692D03E49781564FC641479493FD09203C5FEE3FEEBFE50E2E201B101 +14011DFEB9FAFEFB385B0C37370C5B380107F80147000000FFFF003C0000077205D51026 +03BE00001007002C05DF0000FFFF003E0000068A047B102603DE0000100700F305110000 +00020073FFE307D005F00009001C00000020001110002000111017211133112311210200 +2120001110002120000401FE48FEFF010101B80103D20130CACAFED00EFE98FEC5FEC6FE +880178013A013B0168054CFEB8FEE5FEE6FEB80148011A011BC50296FA2B0295FEE8FE66 +01A50161016201A5FE67000000020070FFE305FB047B000A001C00000122061514163332 +361026013311331123112306002322001110002012027293ACAC9395ABAC0168D5B8B8D0 +09FEF9F1F0FEEE011201E0F903DFE9C7C8E8E70192E7FEC201BFFBA00209F8FED2013901 +1301140138FEE400000200D3000007BF05D5000F00120000013301230321032313211123 +1133112109012104A1E50239D288FD5F88D5FCFE3ACACA0207016FFEEE022505D5FA2B01 +7FFE810295FD6B05D5FD6A01CFFD1900000200C1FFE30604047B0022002D000001112335 +0E012322263534372111231133112136332135342623220607353E013332160122061514 +163332363D010604B83FBC88ACCB2FFEF8B8B801D26A950102A79760B65465BE5AF3F0FE +91DFAC816F99B9027FFD81AA6661C1A27350FDF70460FE4122127F8B2E2EAA2727FCFEB4 +667B6273D9B4290000020065FEBF080805D50012001A0000373637121901211521113311 +231123211123110121151003060721AC862661064FFD29AAAACAFCA8AA0402FE1B701728 +0294AA3F7801340226011AAAFB7FFE150141FEBF01EB0481D4FE0DFEB5442B000002006B +FEE506E7046000120019000037363736113521152111331123112321112311012115100F +0121B05B28620552FDA39393B9FD2D930366FE7D761D0216932855D301A9D493FCC6FE52 +011BFEE501AE033A8CFE64DC36000000000100540000081105D500110000333536373611 +35211521112311211510030654DD3A57064FFD29CAFE1B6662AA30A3F60264FEAAFAD505 +2BB8FDCAFEF8FD000001004C000006D104600012000033353637361135211521112B0111 +21151007064CBB33440553FDA301B8FE7B585E991D7DA601D0B793FC3303CD6FFE50C2CF +000100C9000008F605D5000F000013210901211521112B01110123011123C9012D017D01 +7F0404FD29C505FE84CBFE7FC405D5FC0803F8AAFAD50512FC0D0400FAE10000000100BA +000007AC0460000E0000132109012115211123110123011123BA010D013E013F0368FDA3 +B9FECBB8FECAB90460FD1202EE93FC3303B0FD2702D9FC50FFFF0073FFE305D905F01027 +007901E20000100600320000FFFF0071FFE30475047B10260052000010070079012EFF84 +00040073FFE3066505F0000300070013001D000001331523253315231220272610373620 +17161007002000111000200011100231D3D301A4D3D3F1FD4CD0CFCFD002B4D0CFCFFEC8 +FE1AFEE0012001E601200346FEFEFEFD9BD2D202C4D3D2D2D3FD3CD20497FEB8FEE5FEE6 +FEB80148011A011B00040071FFE30543047B000300070012001E00000133152325331523 +032206101633323635342627200011100021200011100001BCD3D30168D3D34AC3E3E1C5 +C2E3E3C201200149FEB7FEE0FEDFFEB8014802CAFEFEFE0213E7FE6EE7E8C8C7E99CFEC8 +FEECFEEDFEC701390113011401380000FFFF0073FFE30A6A05F010261323000010270079 +067200001007007901E20000FFFF0071FFE307B6047B10270079046FFF8410270079012E +FF84100613240000000F003AFE5706CE05F100030007000B000F00130017001F0027002F +0037003F0047004F00800084000025331523253315230133152325331523013315232533 +152300220614163236341222061416323634242206141632363412220614163236342422 +061416323634002206141632363424220614163236341332161514062B01161514062027 +26270607062026353437222322263534363B012635343620171617363736201615140701 +331523044A8686FDEF868602118686FDEF8686FEF8868604228686FC7BB46969B468A0B4 +6969B46801A9B46969B468A1B46969B468FD86B46969B468FE90B46969B46801A9B46969 +B468478DABAB8D0434ABFEE355160F0F1555FEE2A93302018FABAB8F0232A9011E55150F +0F1655011DAB34FDAF8686169C9C9C05499C9C9CFE469C9C9C015D8EF78E8EF702E48FF6 +8E8EF68F8FF68E8EF6FE398EF78E8EF78E8EF78E8EF7FE378EF78E8EF78E8EF78E8EF703 +52CCACA9CC5C85ABCB641B1D1D1966CAAC855CCAABACCC5A85ABCC651A1D1D1A65CCAB85 +5AFED29C0001FFFAFE66061005D5001D0000032115211121321716151110062B01353332 +3736351134262321112311210604EFFDEE01A1BA716DCCE44C3E8638377C7CFE88CBFDEE +05D5AAFE467772EEFECEFEF2F4AA4B4BC201229F9EFD39052B0000000001003CFE560548 +0460001F000013211521113320171615111407062B013533323736351134272623211123 +11213C0431FE42FA010746525251B5C1AC6E2126262C8BFEFCB5FE42046093FEAA4751E5 +FEF2D561609C3037930108A62429FE1903CD00000001FFFA000004E905D5000F00000321 +15211114163B01152322261901210604EFFDEE6D863F4DE3CDFDEE05D5AAFCD7C296AAF4 +010E03290001003C0000046D04600011000013211521111417163B011523222726351121 +3C0431FE4623236D586EB65053FE45046093FDBE912E309C6062D40237000000000100AF +000004B305D50017002A400B19081C06130C001C0F041810FCEC32CCD4ECCC3100400A02 +950BAD15951281070E2F3CF4ECF4EC300115213216151123113426232111231134363321 +15232206017A01A1BADEC97C7CFE88CBA3B50109E0694D043FCEE9EEFE66018A9F9EFD39 +043FD6C09C610000FFFF00BA000004640614100602280000000200D60000031D05580003 +000700001333152301113311D6AAAA01BF88055888FB300558FAA800000200D60000031D +05580003000700001333152301113311D6AAAA01BF88042488FC640558FAA800000200D6 +0000031D05580003000700001333152301113311D6AAAA01BF8802F088FD980558FAA800 +000200D60000031D05580003000700001333152301113311D6AAAA01BF8801BC88FECC05 +58FAA800000200D60000031D05580003000700003733152301331123D6AAAA01BF888888 +880558FAA8000000000200D60000031D055800030007000001331523012311330273AAAA +FEEB8888055888FB30055800000200D60000031D05580003000700000133152301231133 +0273AAAAFEEB8888042488FC64055800000200D60000031D055800030007000001331523 +012311330273AAAAFEEB888802F088FD98055800000200D60000031D0558000300070000 +01331523012311330273AAAAFEEB888801BC88FECC055800000200D60000031D05580003 +0007000025331523212311330273AAAAFEEB888888880558000100D60000031D05580005 +0000212311211521015E880247FE410558880000000100D60000031D0558000700002123 +113311211521015E888801BFFE410558FECC8800000100D60000031D0558000700002123 +113311211521015E888801BFFE410558FD988800000100D60000031D0558000700002123 +113311211521015E888801BFFE410558FC648800000100D60000031D0558000500002521 +15211133015E01BFFDB988888805580000010066029C028E05E400090000013317072711 +23110727015E39F74C926B934C05E4DD4383FD5502AB8343000100660298028E05E00009 +000001232737171133113717019739F84C936B924C0298DD438302ABFD558343000200C3 +029C014305E000030009000013331523113311072327C38080800D660D032A8E0344FE91 +C8C80000000200C3029C014305E00003000900000123353311231137331701438080800C +660E05528EFCBC016FC8C800FFFF00C3000001430344100712FF0000FD64000000010089 +000002CD05D4001000003335200221352002213520131607161312CF014A14FEB6014A1E +FEA2021D0E09AEB8060AA30227A301C5A2FE8CE5636DFEFBFE5A00000001008900000291 +0460001000003335200221352002213520131607161712BB012C14FED4012C1EFED401E1 +0E08848F060A99017C99011A98FEE89E4D59BCFEB80000000001007301CB034A05F00009 +000001101707023510211520012D75A48B02D7FDE303CDFEFED22E0131D40220DB000000 +000100730056034A047B0009000001101707023510211520012D75A48B02D7FDE30258FE +FED22E0131D40220BD000000000100C9FE66053B05D5001300001333112111331110062B +01353332363511211123C9CA02DECACDE34D3F866EFD22CA05D5FD9C0264FA93FEF2F4AA +96C2025FFD390000000100BAFE5604640614001C00000134262322061511231133113E01 +333216151114062B0135333237363503AC7C7C95ACB9B942B375C1C6A3B546316E212602 +9E9F9EBEA4FD870614FD9E6564EFE8FD48D6C09C303892000001FFFAFE4C068D05D5002A +000003211521152115013217161716151407062122272627351617163332373635342726 +2B01350121112311210604EFFDEE035EFE6569816355519898FEE8738182826A7F7E89C0 +63645C5DA5AE0181FD9ECBFDEE05D5AACB9AFE16382B6C688ADC7A79131324C33119194B +4B8F86494A9801EAFC4A052B00010037FE4C0534059E0030000001321716171615140421 +22272627351E0133323736353427262B013501211114163B011523222635112335331133 +11211503416981635551FED0FEE85E63646A54C86DBE63645C5BA7AE01AEFD6A4B73BDBD +D5A28787B9036501DC382B6C688ADDF2121325C331324B4B8F844B4AA601F3FDA4894E9A +9FD202608F013EFEC2A80000FFFF00A4FFE3047B05F010060152000000010085FE6703C8 +047C003100000126272635343736333216171526272623220706151417163B0115232207 +0615141716333237363715060706232224353436018B703C3C7271C44CAA626150514781 +3B464443749B9489484E545597615155475A545550EEFEFE8A01AC2056557BBA68681A26 +B62D14153E486D6D4645984D55858855551C1C38BE251312F0E58FC1000100BA0000037E +04600009000013211521112115211123BA02C4FDF601D5FE2BBA046094FED394FDF50000 +FFFF006FFFE303C7047B10060056000000030010000009EE05D5000F0012001500000133 +09013301230321032B0103210323090121090121024AE501D001D1E50239D288FD5F8803 +D288FD5F88D502ACFEEE02250373FEEE022505D5FB3E04C2FA2B017FFE81017FFE81050E +FD1902E7FD1900000004007BFFE30727047B000A00350040004D00000122061514163332 +363D01251123350E012322271523350E01232226353436332135342623220607353E0133 +32173017353E013332160122061514163332363D010116173633213534262322070602BE +DFAC816F99B903B2B83FBC886E51B83FBC88ACCBFDFB0102A79760B65465BE5AF3781265 +BE5AF3F0FE91DFAC816F99B9FD88350179C70102A797605B410233667B6273D9B4294CFD +81AA6661270AAA6661C1A2BDC0127F8B2E2EAA27277E14442727FCFEB4667B6273D9B429 +01686EA63C127F8B1710000000030010FFE3092D05F00013001600200000013313363736 +212000111000212027262721032309012100200011100020001110024AE5B82D70BC013B +013A0178FE88FEC6FEC5BC502EFD6788D502ACFEEE02250388FE48FEFD010301B8010105 +D5FE1EAE7DD2FE5BFE9EFE9FFE5BD25872FE81050EFD190325FEB8FEE5FEE6FEB8014801 +1A011B000003007BFFE3077B047B0022002D00380000013200111000202726270E022322 +26353436332135342623220607353E01332017360122061514163332363D010122061016 +3332363534260579F00112FEEEFE1F88372112608CB2B1CCFDFB0102A79760B65465BE5A +012A718AFE4FDFAC816F99B9020494ACAB9593ACAC047BFEC8FEECFEEDFEC79D3E524587 +61C1A2BDC0127F8B2E2EAA2727BDBDFDB8667B6273D9B42901ACE7FE6EE7E8C8C7E90000 +00020010FFE3087105D50002001600000901210133011621323635113311100021200327 +21032302BCFEEE0225FE7BE501BC4A0101C2AECBFEDFFEE6FE737625FD5F88D5050EFD19 +03AEFB72C0D3F0038BFC5CFEDCFED6013468FE810002007BFFE3071F047B002800330000 +250E01232226353436332135342623220607353E01333216111514163332363511331123 +350E0123200122061514163332363D01039348A2B2B1CBFDFB0102A79760B65465BE5AF3 +F07C7C95ADB8B843B175FEE5FEDBDFAC816F99B9DF8D6FC1A2BDC0127F8B2E2EAA2727FC +FF00BE9F9FBEA4027BFBA0AC66630250667B6273D9B4290000020010000007B405D50002 +000D0000090121130321032301330901330102BCFEEE0225C788FD5F88D5023AE501DC01 +D7D2FDC7050EFD19FDD9017FFE8105D5FB1F04E1FA2B00000002007BFFE3064E047B000A +002800000122061514163332363D0111350E01232226353436332135342623220607353E +01333216190101330102BEDFAC816F99B93FBC88ACCBFDFB0102A79760B65465BE5AF3F0 +015EC3FE5C0233667B6273D9B429FDCDAA6661C1A2BDC0127F8B2E2EAA2727FCFF00FE35 +03ACFBA000030010000007B405D500020012001500000901290215210323032103230133 +0133013301231702BCFEEE0225027E0167FE5992E588FD5F88D5023AE50167E90163D2FD +8B6935050EFD19A8FE81017FFE8105D5FC5203AEFBAA8B000003007BFFE3064E047B0022 +002D0030000021350E01232226353436332135342623220607353E013332171617331333 +03331523030122061514163332363D0121231103753FBC88ACCBFDFB0102A79760B65465 +BE5AF378670FC69AC39B9BD1D3FE14DFAC816F99B901478FAA6661C1A2BDC0127F8B2E2E +AA27277E6DCD019DFE6390FDCD0233667B6273D9B429FE8100020010FE56079B05D50002 +00180000090121010607062B0135333237363F0103210323013309013302BCFEEE022501 +6C4B4D4A7CD8AB4C2A2B321388FD5F88D5023AE501CF01CBD2050EFD19FD71C63F3DAA24 +258532017FFE8105D5FB4004C00000000002007BFE56064E047B00270032000021350E01 +232226353436332135342623220607353E0133321619010133010E012B01353332363F01 +0122061514163332363D0103753FBC88ACCBFDFB0102A79760B65465BE5AF3F0015EC3FE +144E947C936C4C54331AFEF4DFAC816F99B9AA6661C1A2BDC0127F8B2E2EAA2727FCFF00 +FE77036AFB38C87A9A4886420233667B6273D9B429000000FFFF0073FFE3052705F01006 +03930000FFFF007FFFE303F5047B1006031900000001000A0000056A05D5001200001333 +1533152311012109012101112311233533C9CABFBF029E0104FD1B031AFEF6FD33CABFBF +05D5B9AAFEEC0277FD48FCE302CFFD310472AA000001000E000004A40614001200001333 +1521152111013309012301112311233533C2B90122FEDE0225EBFDAE026BF0FDC7B9B4B4 +0614ACA4FDB901E3FDF4FDAC0223FDDD04C4A400000100C90000056605D5000900001311 +3311371121152111C9CAFC02D7FC5F02AD0328FD5E4DFD2AAA02FA00000100C100000263 +0614000700001311331137112311C1B8EAB8029C0378FD0549FC9E02E500000000010053 +0000049C05D5000D00001333153315231121152111233533FBCAA8A802D7FC5FA8A805D5 +E0A4FC59AA0451A400010078000002F20614000B00000133113315231123112335330159 +B8E1E1B8E1E10614FEE2A4FBAE0452A40003000AFFE3066A05F00015001D002500000120 +171613331523020706212027260323353312373604200706072126271321161716203736 +033B013ABCA117817E0BB0BCFEC6FEC5BCB10B7E8117A2BC0217FE48816A1403B413697F +FC460A778101B8817605F0D2B5FEE390FEBEC4D3D2C4014390011DB5D2A4A486D6D686FE +14FA97A4A49700000003000AFFE3058E047B0015001E0027000001321716173315230607 +06232227262723353336373617220706072126272613211617163332373602CDF0896F15 +C4C00A7E89F0F1887E0AC2C6156F88F194563F110273113F56ABFD83084C569593564D04 +7B9C7ECD90F4909D9D90F490CD7E9C9C735583815575FE25AB6773746700000000030073 +FFE30A6A05F0001A0024002E000001201716173637362120001110002120272627060706 +212000100004200011100020001110002000111000200011100327013ABC2F23232EBD01 +3B013A0178FE88FEC6FEC5BD2E24232EBCFEC6FEC5FE8701790217FE48FEFD010301B801 +010390FE48FEFD010301B8010105F0D2353D3D35D2FE5BFE9EFE9FFE5BD2343D3C34D301 +A402C401A5A4FEB8FEE5FEE6FEB80148011A011B0148FEB8FEE5FEE6FEB80148011A011B +00030071FFE307B6047B000A001500310000012206101633323635342621220610163332 +363534262732171617363736333200111000232227262706070623220011100005B494AC +AB9593ACACFC2C94ACAB9593ACAC93F0891512121589F1F00112FEEEF0F1891512121589 +F0F1FEEF011103DFE7FE6EE7E8C8C7E9E7FE6EE7E8C8C7E99C9C181B1B189CFEC8FEECFE +EDFEC79D181B1B189D01390113011401380000000002000A0000048D05D50008001D0000 +0111333236353426232521321716151407062B01153315231123112335330193FE8D9A9A +8DFE3801C8FB80818180FBFEEDEDCABFBF052FFDCF92878692A67172DBDD7171C490FEFC +010490000002FFFBFE5604A4047B0007001F000000102620061016200135331133153E01 +3332001002232226271133152315233503E5A7FEDCA7A70124FCBDBFB93AB17BCC00FFFF +CC7BB13AFEFEB901640196E7E7FE6AE7FE679004ECAA6461FEBCFDF0FEBC6164FECC908E +8E000000000200320000059905D5001E00270000012132041514042B0111231123220706 +151617161707262726353437363B0332363534262B0101D501C8FB0101FEFFFBFECA2D5C +303A011A173C444F45467F61A221CAFE8D9A9A8DFE05D5E3DBDDE2FDA80258181F3D3527 +20189417494B7D934C3B92878692000000020032FE5605C2047B000C0031000001171633 +323736353426200615032623220706151417161707262726353437363332171133153E01 +3332001002232227112302910A9D928E5853A7FEDCA7B933292C343A1B173C444F45467F +54673537B93AB17BCC00FFFFCAC0A8B901320BAA7370CFCBE7E7CBFEE319181B41342820 +189417494B7D86593B1702A6AA6461FEBCFDF0FEBCA2FDD100020073FE9405D905F00009 +002300000020001110002000111001273727070623200011100021200011100207173717 +071723270403FE48FEFD010301B80101FE8048AA6B33120FFEC5FE870179013B013A0178 +D1C645D748BC62F40E054CFEB8FEE5FEE6FEB80148011A011BFA907D6274030101A50161 +016201A5FE5BFE9EFEFCFE8E584B7C7D6C6B0F0000020071FE560519047B0017001F0000 +250E01232202100033321617353311331523152335233521001016203610262003A23AB1 +7CCBFF00FFCB7CB13AB8BFBFB8FF00FFFD8DA70124A8A8FEDCA864610144021001446164 +AAFB14908E8E900386FE6AE7E70196E70002000A0000048D05D50007001B000001113332 +361026230133153315231533320410042B011123112335330193FE8D9A998EFE38CAEDED +FEFB0101FEFFFBFECABFBF0427FDD192010C9101AE2D904BE1FE48E2FEAE051890000000 +0002FFFBFE5604A406140007001F00000010262006101620251123112335333533153315 +23113E01333200100223222603E5A7FEDCA7A70124FE35B9BFBFB9FEFE3AB17BCC00FFFF +CC7BB101640196E7E7FE6AE72BFDAE069B90939390FEC56461FEBCFDF0FEBC610002000A +0000048D05D50007001B0000011133323610262301331133320410042B01153315231523 +352335330193FE8D9A998EFE38CAFEFB0101FEFFFBFEEDEDCABFBF0427FDD192010C9101 +AEFEF8E1FE48E25E906464900002FFFBFE5604A406140007001F00000010262006101620 +0135331133113E013332001002232226271133152315233503E5A7FEDCA7A70124FCBDBF +B93AB17BCC00FFFFCC7BB13AFEFEB901640196E7E7FE6AE7FE679006A0FDA26461FEBCFD +F0FEBC6164FECC908E8E00000001000B000003AC05D50005000021231121352103ACCAFD +2903A1052BAA0000000200C1FE560179047B0000000400000107331123011D5CB8B8047B +1BF9F600000100C9FE56060E05F0001A0000011114163315222619013426232206151123 +11331536373633321205196F86F1CD9A99B3D7CACA5166659EE3E9037DFE85C296AAF401 +0E017DD7D5FFDEFB08077FF1874342FEC1000000000100BAFE56051A047B001900000111 +141633152226271134262322061511231133153E0133321604644A6CCAA3017C7C95ACB9 +B942B375C1C602A4FEE78E619CC1D501089F9EBEA4FBDD060AAE6564EF000000FFFF00F0 +000001C304231206001D0000000200A001490262030B0003000700000121352135213521 +0262FE3E01C2FE3E01C20149969696000001013501E1020005D50005003A400B03008106 +0403010300000610FC4BB00B5458B90000FFC03859EC3939310010E4CC400920035F03B0 +03EF03045D3001B6000720075007035D0133110323030135CB14A21505D5FD71FE9B0165 +000100C503AA016F05D500030037400A0184008104000502040410FC4BB012544BB01354 +5B58B90002FFC03859EC310010F4EC3001400D40055005600570059005A005065D011123 +11016FAA05D5FDD5022B0000FFFF00AF000004B305D5120603BB00000002004DFE560354 +06140006001F000001262322071433371133113315231114163B01152322263530032320 +37363332019217374D015C50B8FAFA3D783146BF99023CFEE80101F5350312844B39FA02 +08FCFEA0FD707C749CCCCA0286BDF600000100C9FEBF05DD05D5000D0000132101113311 +3311231121011123C901100296C4AAAAFEF0FD6AC405D5FB1F04E1FAD5FE15014104E1FB +1F000000000100BAFEE504F7047B00170000011133112311231134262322061511231133 +153E0133321604649393B87C7C95ACB9B942B375C1C602A4FDEFFE52011B029E9F9EBEA4 +FD870460AE6564EF00010004FFE3063905F0002800002511213521110604232427262707 +27372635100021320417152E01232007060301170116171621323604C3FEB6021275FEE6 +A0FEA2C68D28542B7001018B015E9201076F70FC8BFEEF8A870304C62BFB16196A8A0111 +6BA8D50191A6FD7F535501CC92E21C7F251E1F016E01994846D75F609997FED901957FFE +5FCF77992500000000030004FE560510047B00260030003A000025100221222627351E01 +3332363D010E012322272627072737263510123332161735331137170F01051617163332 +36353427262726232206151417045AFEFEFA61AC51519E52B5B439B27CCE7E431F682174 +07FCCE7CB239B89521B6B9FD9F142E529495A50D1332529694A5028BFEE2FEE91D1EB32C +2ABDBF5B63629D5370236327383E0104013A6263AAFEB132633D3DCB573C6EDCC7157E62 +416EDCC81D1C0000000200040000056A05D5001300160000133311012101172517050121 +0107112311072737252715C9CA029E0104FD1B7902522BFDED0237FEF6FE0BD8CA9A2BC5 +01386E05D5FD890277FD487AC57FB0FDC701F748FE51016C337F41686E93000000020004 +0000049E06140013001600001333110133011725170501230107112335072737252715BA +B90225EBFDAE5E01EE21FE4601B8F0FE85BEB99521B60122690614FC6901E3FDF45BA462 +93FE58016C3FFED3F032633C6165880000030004000005F805D500130016001900001321 +012511331137170711210105112311072737250311251311C90110015E0138C49A2BC5FE +F0FEA6FEC4C49A2BC501C0FC01EAF805D5FD6B67022EFE13337F41FCA5028E69FDDB01E4 +337F419501DBFDD116FE2C02260000000002000400000510047B00170020000001112311 +051123110727371133153E013336171617371707272627262322061D010464B8FDC7B995 +21B6B942B375C1634A139121ACBA09333E7C95AC02A4FD5C026ABEFE54016F32633D0283 +AE6564017859993163393075404FBEA45F000000000300040000058B05D5001D0024002B +0000011E01171323032E012B01112311072737112120171617371707161514062D012627 +26272311153316373637038D417B3ECDD9BF4A8B78DCCA9A2BC501C801007E491FE92BFF +0183FD890219122C4A93FEFE934A3D0B02BC16907EFE68017F9662FD890301337F420246 +6B3E644E7F550D0E8DBAF2B33F284201FE16280144386300000100040000034A047B0019 +0000012E012322061D012517051123110727371133153E0133321617034A1F492C9CA701 +B221FE2DB99521B6B93ABA85132E1C03B41211CBBE3490629CFE54016F32633C0284AE66 +6305050000010004FFE3051005F0002F000001152E012322061514161F01251705161716 +15140421222627351E013332363534262F01052725262726353424333216044873CC5FA5 +B377A65402222BFE9957366CFEDDFEE76AEF807BEC72ADBC879A61FDE22B01654D306501 +17F569DA05A4C53736807663651F11B57F7727386CB6D9E0302FD04546887E6E7C1F13B4 +7F77222E60ABC6E42600000000010004FFE30427047B002F000001152E01232206151416 +1F0125170516171617140623222627351E013332363534262F0105272526272635343633 +3216038B4EA85A898962942D01BA21FED64B2C5201F7D85AC36C66C661828C65AB24FE4F +2101233E264CE0CE66B4043FAE282854544049210A93636320284C899CB62323BE353559 +514B5025089063611B264A839EAC1E000001FF970000059F05D500140000010E011D0123 +3534363B0111211133112311211123012D84769CC0D6CA02DECACAFD22CA0530015E6931 +46B5A3FD9C0264FA2B02C7FD390000000002007F029C041F05E000130017000013331521 +3533153315231123112111231123353317152135E88001CE7F6A6A7FFE328069698001CE +05E07D7D7D5CFD95018EFE72026B5C5C7E7E000000030047028C04E4051E000600270031 +0000012E01232206070515211E0133323637150E01232226270E01232226353436333216 +173E0133321624220614163332363534046F0167576075080211FDEB088073437E3E3F83 +436598332D845898ACAC9858852A31925A8EA7FD04BA6D6C5E5D6C040E556461591E326A +701D1D6118183D3D3E3CAF9A9BAE3E3C3C3EA34C81E28182706F0000FFFF00BA00000698 +0460120603DC000000010077000003D105D5000900000111231121352111213503D1CAFD +B00250FD7005D5FA2B02C9AA01B8AA00000200460000040A05D500080013000001232206 +1514163B0113112311232224353424330340FE8D9A9A8DFECACAFEFBFEFF0101FB052F92 +86879202D7FA2B0258E2DDDBE3000000000100C90000061F05D5000C0000331133110133 +01113311210901C9C40181CB0181C5FED3FE81FE8305D5FAE10400FC00051FFA2B03F8FC +08000000000100C900000193076D0003000013331123C9CACA076DF89300000000010044 +0000095505D5000F00002501330901330123090123090123013303F4013EE3013A0139CD +FE89FEFEC5FEC2E3FEC6FEC7CD0177FEC50510FB1204EEFA2B0510FAF004EEFB1205D500 +000100C204D00295055800030011B601A9020403010410C4D4310010D4EC300121352102 +95FE2D01D304D0880001008D039C02BD0558000400001327013315D649020828039C7301 +498800000001006E026802E405580005000001230127013302E410FE026802274F04D0FD +9857029900010060013402F205580005000001230127013302F20EFDF27602355D04D0FC +644403E00001005A000002F905580005000001230127013302F90BFDE87C023B6404D0FB +303705210001008D039C02BD0558000400001301152301D601E728FDF80558FECC880149 +FFFF00C2039C029504241007134E0000FECC0000FFFF008D026802BD04241007134F0000 +FECC0000FFFF006E013402E40424100713500000FECC0000FFFF0060000002F204241007 +13510000FECC00000001006E026802E405580005000001152301370102E44FFDD96801FE +02F088029957FD98FFFF008D026802BD0424100713530000FECC0000FFFF00C202680295 +02F01007134E0000FD980000FFFF008D013402BD02F01007134F0000FD980000FFFF006E +000002E402F0100713500000FD98000000010060013402F2055800050000011523013701 +02F25DFDCB76020E01BC8803E044FC64FFFF006E013402E40424100713580000FECC0000 +FFFF008D013402BD02F0100713530000FD980000FFFF00C20134029501BC1007134E0000 +FC640000FFFF008D000002BD01BC1007134F0000FC6400000001005A000002F905580005 +000025152301370102F964FDC57C02188888052137FB3000FFFF0060000002F204241007 +135D0000FECC0000FFFF006E000002E402F0100713580000FD980000FFFF008D000002BD +01BC100713530000FC640000FFFF00C20000029500881107134E0000FB300007B1000400 +103C3000000100D60000015E05580003000EB502010008030410D4EC3100C4C433113311 +D6880558FAA80000000E00960000073A05DC00030007000B000F00130017001B001F0023 +0027002B002F0033003701DAB72F243028372C343810DC3CDC3C3C3C3CB61F232B20331C +27DC3C3C3C3CDC3CB6140C1B13081018DC3CDC3C3C3C3CB603070F0417000BDC3C3C3C3C +DC3CB039CCB0584B5258B038104BB00A626620B0005458B133303C3C5920B0405458400A +33302F2C37342B2827243C3C3C3C3C3C3C3C3C3C5920B0805458B323202F2C3C3C3C3C59 +20B0C05458B337342F2C3C3C3C3C5920B801005458B52B28272437343C3C3C3C3C3C5920 +B801405458B337341F1C3C3C3C3C5920B801805458B11F1C3C3C5920B801C05458B72B28 +333037342F2C3C3C3C3C3C3C3C3C59B8100062B80280634B236120B0005458B117143C3C +5920B0015458400A0F0C1B18171413100B083C3C3C3C3C3C3C3C3C3C5920B0025458B307 +0413103C3C3C3C5920B0035458B313101B183C3C3C3C5920B0045458B51B180B080F0C3C +3C3C3C3C3C5920B0055458B303001B183C3C3C3C5920B0065458B103003C3C5920B00754 +58B7131017141B180F0C3C3C3C3C3C3C3C3C59B0095458B11B183C3C591BB60F0C2B2827 +2438103C3C3C3C3C3CB7070403000B0817143C3C3C3C3C3C3C3C593100B7040D18203429 +0C282F3CDC3C3C3C3C3CB5051419213035DC3C3C3C3C3CB50015101C312CDC3C3C3C3C3C +B709250108111D242DDC3C3C3C3C3CDC3C30011133110311331101352115013521150111 +331115352115011133110111331103113311013521150135211501113311153521150111 +331106D6646464FD760226FDDA0226FD76640226FD7664FE70646464FD760226FDDA0226 +FD76640226FD76640325024EFDB2FD44024EFDB2050F6464FA8864640325024EFDB26964 +64FDAD024EFDB202BC024EFDB2FD44024EFDB2050F6464FA8864640325024EFDB2696464 +FDAD024EFDB20000000E00960000073A05DC00030007000B000F00130017001B001F0023 +0027002B002F0033003701E0B72F243028372C343810DC3CDC3C3C3C3CB61F232B20331C +27DC3C3C3C3CDC3CB6140C1B13081018DC3CDC3C3C3C3CB603070F0417000BDC3C3C3C3C +DC3CB039CCB058004B015258B03810004B01B00A626620B0005458B133303C3C5920B040 +5458400A33302F2C37342B2827243C3C3C3C3C3C3C3C3C3C5920B0805458B323202F2C3C +3C3C3C5920B0C05458B337342F2C3C3C3C3C5920B801005458B52B28272437343C3C3C3C +3C3C5920B801405458B337341F1C3C3C3C3C5920B801805458B11F1C3C3C5920B801C054 +58B72B28333037342F2C3C3C3C3C3C3C3C3C59B8100062B8028063004B01236120B00054 +58B117143C3C5920B0015458400A0F0C1B18171413100B083C3C3C3C3C3C3C3C3C3C5920 +B0025458B3070413103C3C3C3C5920B0035458B313101B183C3C3C3C5920B0045458B51B +180B080F0C3C3C3C3C3C3C5920B0055458B303001B183C3C3C3C5920B0065458B103003C +3C5920B0075458B7131017141B180F0C3C3C3C3C3C3C3C3C59B0095458B11B183C3C591B +B60F0C2B28272438103C3C3C3C3C3CB7070403000B0817143C3C3C3C3C3C3C3C593100B7 +040D182034290C282F3CDC3C3C3C3C3CB5051419213035DC3C3C3C3C3CB50015101C312C +DC3C3C3C3C3CB709250108111D242DDC3C3C3C3C3CDC3C30011133110311331101352115 +013521150111331115352115011133110111331103113311013521150135211501113311 +153521150111331106D6646464FD760226FDDA0226FD76640226FD7664FE70646464FD76 +0226FDDA0226FD76640226FD7664031B0258FDA8FD440258FDA805196464FA886464031B +0258FDA85F6464FDA30258FDA802BC0258FDA8FD440258FDA805196464FA886464031B02 +58FDA85F6464FDA30258FDA8000E00960000073A05DC00030007000B000F00130017001B +001F00230027002B002F0033003701DAB72F243028372C343810DC3CDC3C3C3C3CB61F23 +2B20331C27DC3C3C3C3CDC3CB6140C1B13081018DC3CDC3C3C3C3CB603070F0417000BDC +3C3C3C3CDC3CB039CCB0584C5258B038104CB00A626620B0005458B133303C3C5920B040 +5458400A33302F2C37342B2827243C3C3C3C3C3C3C3C3C3C5920B0805458B323202F2C3C +3C3C3C5920B0C05458B337342F2C3C3C3C3C5920B801005458B52B28272437343C3C3C3C +3C3C5920B801405458B337341F1C3C3C3C3C5920B801805458B11F1C3C3C5920B801C054 +58B72B28333037342F2C3C3C3C3C3C3C3C3C59B8100062B80280634C236120B0005458B1 +17143C3C5920B0015458400A0F0C1B18171413100B083C3C3C3C3C3C3C3C3C3C5920B002 +5458B3070413103C3C3C3C5920B0035458B313101B183C3C3C3C5920B0045458B51B180B +080F0C3C3C3C3C3C3C5920B0055458B303001B183C3C3C3C5920B0065458B103003C3C59 +20B0075458B7131017141B180F0C3C3C3C3C3C3C3C3C59B0095458B11B183C3C591BB60F +0C2B28272438103C3C3C3C3C3CB7070403000B0817143C3C3C3C3C3C3C3C593100B7040D +182034290C282F3CDC3C3C3C3C3CB5051419213035DC3C3C3C3C3CB50015101C312CDC3C +3C3C3C3CB709250108111D242DDC3C3C3C3C3CDC3C300111331103113311013521150135 +211501113311153521150111331101113311031133110135211501352115011133111535 +21150111331106D6646464FD760226FDDA0226FD76640226FD7664FE70646464FD760226 +FDDA0226FD76640226FD76640325024EFDB2FD44024EFDB2050F6464FA8864640325024E +FDB2696464FDAD024EFDB202BC024EFDB2FD44024EFDB2050F6464FA8864640325024EFD +B2696464FDAD024EFDB20000000E00960000073A05DC00030007000B000F00130017001B +001F00230027002B002F0033003701E0B72F243028372C343810DC3CDC3C3C3C3CB61F23 +2B20331C27DC3C3C3C3CDC3CB6140C1B13081018DC3CDC3C3C3C3CB603070F0417000BDC +3C3C3C3CDC3CB039CCB058004C015258B03810004C01B00A626620B0005458B133303C3C +5920B0405458400A33302F2C37342B2827243C3C3C3C3C3C3C3C3C3C5920B0805458B323 +202F2C3C3C3C3C5920B0C05458B337342F2C3C3C3C3C5920B801005458B52B2827243734 +3C3C3C3C3C3C5920B801405458B337341F1C3C3C3C3C5920B801805458B11F1C3C3C5920 +B801C05458B72B28333037342F2C3C3C3C3C3C3C3C3C59B8100062B8028063004C012361 +20B0005458B117143C3C5920B0015458400A0F0C1B18171413100B083C3C3C3C3C3C3C3C +3C3C5920B0025458B3070413103C3C3C3C5920B0035458B313101B183C3C3C3C5920B004 +5458B51B180B080F0C3C3C3C3C3C3C5920B0055458B303001B183C3C3C3C5920B0065458 +B103003C3C5920B0075458B7131017141B180F0C3C3C3C3C3C3C3C3C59B0095458B11B18 +3C3C591BB60F0C2B28272438103C3C3C3C3C3CB7070403000B0817143C3C3C3C3C3C3C3C +593100B7040D182034290C282F3CDC3C3C3C3C3CB5051419213035DC3C3C3C3C3CB50015 +101C312CDC3C3C3C3C3CB709250108111D242DDC3C3C3C3C3CDC3C300111331103113311 +013521150135211501113311153521150111331101113311031133110135211501352115 +01113311153521150111331106D6646464FD760226FDDA0226FD76640226FD7664FE7064 +6464FD760226FDDA0226FD76640226FD7664031B0258FDA8FD440258FDA805196464FA88 +6464031B0258FDA85F6464FDA30258FDA802BC0258FDA8FD440258FDA805196464FA8864 +64031B0258FDA85F6464FDA30258FDA80001006EFFE20436069F00150000011510212011 +351333031510212011353400113316000436FE26FE123BC84501300126FE0FB50101EF02 +A19FFDE002258B0109FEDA73FE80018AB8F901CB0117C5FE070000000002006EFFE20436 +069F0009001C000001122504031510212011371510201135102533362726273314160733 +04038201FEC9FEE10101260130B4FC3801931434868502CFDA25140160029D01670B08FE +8255FE4E01AD4B37FD9F02613C01EB3DA35152B26C9EF3450002006EFFE2048F06B30009 +002100000110212011151021201137151021201135102533362736232215231021200306 +070403D2FEB7FEA30158014EBDFDF1FDEE02075E4D0201BEB6A40157015C010C44012602 +7E01A4FE625FFE5D01A36157FDB3023E7801D64E5C619AC40164FEC18B5572000002006E +000006AF06B30024002E0000253637001135102132173621201115100524113534372623 +2011151001170417212627240701102120031502052413010D637FFE7F01B9A99F650102 +01D9FE27FE342754ADFEFB01A8D4018738FEF80DEEFEFEF50485FEE5FEEF01010113011A +01C34E21015601C66D01F8B3A9FDFF95FDD21E1C0230775F8C9AFEB763FE2DFEB11853D6 +524040B4045D018FFE9E9AFE771D1C016C0000000001006FFFE2043606B3001700000110 +21201B013303102104190134212211172303102120110436FE16FE22015AD06E0127013A +FEE4F353BE4501B201B701C8FE1A01E9012FFED1FEB701014F0373D1FEFFF6010D0189FE +8B0000000001006EFFE2043606B300210000010607161D01102011133303102120113534 +27233533360334230407172327102120040E05CDFAFC3835C43C01270130BEAA5BDC02FC +FEFE0325BA1901B301BB051BE67C73FE7DFE1702010117FEE0FEA8014E73CD28A950010A +DA01F59A9E0190000003006EFFE204CB06B3000900130026000001120722151417363733 +011021201115102120113715102120113534372635102120111407330402D102D6D16866 +C012013DFEC8FEB40147013DC2FDFFFDFD65BE018501910C0B014804DE013601DFB45F63 +09FDE50188FE764BFE5D019B584BFDB80252468DBC94CE018EFE2E25407F00000002006E +FFE206A006B3000900220000011021201901102120112502032124131110232203111021 +20190110213617363320130349FEF3FEED0113010D03570ACCFEF7010817F2D713FE2EFE +3C01CEE09573F701830204920182FE7EFD93FE5C01A49BFE46FEFAE501DB01D20179FEED +FD2DFDBD0243026D022102E6DAFDE9000001006E0000040306B300150000011003231219 +0110232019011013230219011021201104039BC6A4F5FEDBAEC8A401E301B202FBFE8AFE +7B014301B8019A017EFE82FE66FE53FEB201790182019A021EFDE2000001006EFFE20436 +06A0001C000001022120111333031005201135102B013533243534272116151007161104 +3601FE1DFE1C2BCE3C01270130D2DCAF00FFC8011864FAFA01C6FE1C01F30124FEDCFEAE +01014478013CA881AF95B9BE6DFEF76463FE9E000001006E000008EB06B3003500002536 +37240335102132173620173633201115140323123D011223201511231110230615112311 +26212211151205361704012126252401015D7391FE3D3001B7E87064019E6D5EF301AEDF +CDFC01FDFF00ACE3DBAD02FEFEF82D01B27A90020E0112FED028FE99FE9BFEC9A67843F3 +01F07B01EBB2BBBBB2FE1F67BAFE6401B1C83F0147E7FE5A01A6010002FEFE5A01A6E7FE +B779FE41E9170B23FE76A55C5BFEC3000002006EFFE2043406B30009001E000001112627 +201115100520131021201135102532173534212206072310212011038B6AB8FEC3013A01 +25A9FE32FE0801E39E9DFEE1997B01B201BA01D401DA01CB3F01FEC1B8FE95010162FDFE +0202AE01EE144AD1F9716C017CFE630000020082FFE2044A06A00009001B000001102122 +071110212011371510212019011029011521221D013633200396FEC9A8810135012BB4FE +15FE2301510203FE02ACA98C01E902AB014F4BFE26FEAD0144FEFAFE1801E803540182A0 +E6C755000001006E0000068606B300200000011001230019011021221901231110212219 +0110012300190110213217363320110686FEC7E0015EFEF2E5B7FEF7EB013ED7FEDA01BE +DE7882B901C902C2FE66FED8016E015501CF0183FECFFD8202870128FE7DFE31FEA7FE96 +0128019B01CF0221F0F0FDDF0001006EFFE20436069F001E000001151021201137330710 +2120113534272335332435342435331404151407040436FE30FE0814C71D01350121B0DA +730103FE5DDC017BEB00FF01D725FE3001DFB3B3FEC1013F43C02DB63FB0A372F4968EDD +B2796E000001006EFFE2043506C100160000011025201133102120190105041510253504 +27362535250435FE18FE21BE01210133FDD70160FDB801A20102FE5D03C601D1FE0F0202 +4AFE56014F043B3278D6FE7D02B202CEB05FA7820001006E000006E106B3002600000115 +100123001135102520031123111021201115100123001135102524211524033720173633 +2006E1FEA2EF0187FEFBFEF501C0FEE6FEFC0194EBFE990114011502CAFCF2B07D010A8A +75DB01DC031E7CFE62FEFC016201498C014B02FED7FEA2015E0128FEB495FEC1FE9D0104 +019E7701A3FCFBA001FED834F4F4000000010082FFE2044A069F00150000011510212019 +01331110212011353402273733071400044AFE15FE23B4012B0135FC032DC84A01080207 +2DFE080252046BFB6DFE7601494BB3012699D9E67CFEFB000001006EFFE2056A06B30024 +0000010E0107111612333212351134022723132313353315330412071116002120003511 +341237020A84580201D1F1EFCDD6921214BB149312012EFA0101FED7FEA9FEBCFEC7C0DC +05A72FF0B3FEBD98FE880177990140B6014402FCED0311E7445CFE75B1FEB6D1FE2601D6 +D50149B0018D520000010032FFE1042706B3001E00000112212003133303102120190126 +2322171123112627233533321736332011042701FE34FE080438CB440142011D01C2A702 +9F149E938C9B4B69A9017101EEFDF301EA012EFED7FEB2013503B4A954FE68014D8C139F +9F9FFE860002006EFFE2068C06B300090037000001102120111510212011010600212322 +24353314163B01323635102B013537363510212207160715102504113510213217363320 +111007160346FEEFFEF701160104033201FEBFFEE964CFFEB5CBDA824AC6E87F7C3BD4FE +F7856F2603FE49FE2B01C3FD6D8CBB01AAECD8049E0176FE8F8BFE790187FDC0D4FEDED9 +813B80CD890101970289DF013580709472FDD7010102298B0210C7C1FE5AFEE5927B0000 +0001006EFFE20435069F0019000001102120113733071021201901022706071323271005 +321311330435FE16FE2337CC450121013FD0B4C40142B33C016DACDDA901CAFE1801E8DD +DCFEB7014E022401B20201A8FEFFF9015E02FEAA01A200000001006EFFE206BB06B3002A +0000373637001135102132131233201315060323123D0110212019012311102013150201 +161704172126272405FC789AFE6001C6D98978E601C60102AFE5D6FEFDFEF1ABFDEC0201 +01B3765301A538FEF803FDFEEFFEDFB16222011C02204B01F7FEF90107FE2061EBFE9401 +60F2540154FE72FEC00140018DFEA86DFE4DFEAD041270E1526345BE0001006EFFE20434 +06A1001D0000011021201113330310212019010607041135343733061D01100536371133 +0434FE19FE211EC2220121013FA590FE3590E1B30103A49BA801C5FE1D01BB0109FEF3FE +E9014401F8630B0201B928A9C9D49428FEDA010A740239000002006EFFE2043406B30008 +0023000001112623201115022013102120113512211617113423221D0123353421352017 +36332011038C73C7FEDB010260A8FE31FE090101E388B2AEB3A4FEE7011C3655D4014B01 +CB01C649FE9072FE8A015DFE03020C9001F9023D01607CB43A3AB49FC0C0FEB300010083 +0000044A06B3001D0000013734232211153617040315100323123D011005260711231102 +21201307028F10A5C4AE9B01CC01C0DCE8FEDA81B9B3010155017F0114050E9275FEE6E1 +680401FE3D5EFEFDFEA70175B0AE0113010162FC7C04F001C3FEED9200010070FFE204C3 +06A00023000001160704111000200019010213330215111A0117321235262B0135332435 +342437331404049102F80128FEE5FE02FEC60272D78601CCABAFAC03C0D25A0109FEE301 +B801240507AE8C7BFEF6FEE2FEB801480170019D012C013DFE8BF4FE63FEF6FEF201010F +ADFBA365C281918C548800000002006EFFE20434069F0009001500000111262320111510 +2120131021201135102136171133038C7ACAFEE4011C0144A8FE1EFE1C01E4A694A80207 +01A350FE7059FE71016CFDF4023E4A023D0152024900000000020083FFE2044906B3001A +002400001310213217362115201D01233534232215113637201315102120133310212011 +351021220783015BC86025011EFEE7A4A7AFA68A01E102FE09FE3002B301170149FECF89 +A60579013ABDBD9FA04444A09BFE9E5501FE1177FDDB021BFE8501718B0152600002006E +FFE2045C06A0001C00230000012313331523131021201137330710212011032320111021 +332733173301143B01032322045BDC3F9E7E57FE16FE233BCC48012001355EE1FE67019C +5E25C822F2FCEEF2D5448EF50569FEB98DFE35FE1801E8DDDCFEB7014E01C5012D0136A8 +A8FECFA50147000000020082FFE2044A069F000900150000011021060711102120113715 +102120190133113633040396FECBA28801390126B4FE26FE12B5A18901E9028D017F0355 +FE58FE7601806E5FFDD102250498FDBC4E01000000010032FFE203EF06B3002100000107 +2503041706042135203635262527132537053736342623073533321716140F0103EF4DFE +A79101500101FEC9FE990117C901FEFA73E9FE795001816A134A40829C844A4B1E7003CC +8AABFEEB6EDCC6E6A07F7AC028450195BE8CC5C21C5338139B4B4AB237C400000001006E +FFE2043506B30026000001102120113310212011342B0135333235342723353332353421 +233533201114073316151607160435FE18FE21BE0121012D9AE7D39A9AE7E783FE6D7676 +02519702A90183960199FE490204FE9C0117C5B3847901B599B89EFEAA9B614E8794476E +00010078FFE2043E06B2002600000520190133111021201134272335333237262B013533 +3635342135201106072316170607161510025CFE1CB50130012783A9A96C050583A6A66F +FED701E7019F02B60101ADC01E01E6044DFBB3FEBA0112E002B1837DB701B4819EFED2A5 +53677E805C56E1FE4E0000000002006EFF5F043606B30009001F00000110052419011021 +201103323723001901102120190110011633152225230427012C0124012AFED7FEDBBED9 +3F02FEEA01E201E4FEE52EEFC3FEE715FEEEC502D2FE687878019801A201A0FE60FBA953 +0104015E01A6023BFDC1FE5EFEA2FEF84FBEE6E7010000000001006EFFE2043406B5001C +000001102120113533151021201135102B01353324110515231125100504110434FE17FE +25BD01250128F2E47B015BFDB5C103C6FEBA01460196FE4C01D4D3DDFED601149B011AA4 +AD017817EC018F15FDBF9E71FED200000001006EFFE2044206B400200000011021201135 +3315102120111021233533240306230411331421323733150205040435FE18FE21BF0120 +012DFEEAC06E016702A783FE21BE0121DE7D9A0BFED5012901C8FE1A01E68E8EFEBA0146 +0160AD1E01BB6F020177C6C5E1FE80A69C0000000003006EFFE204C106B30007000F0025 +000001212411102120350110290111342120011510212011102523241110212011153315 +231133150384FEC7FEE101260132FDA8011F0139FECEFEDA0309FE16FE23010C01FEF501 +E401E38C8C8C02DE02FECEFED4F40362FEBA019FE3FBDA78FE6C01CC01216A60013F01DB +FE7E97BEFECAB8000002006EFFE2073606B4000A002C0000011134262320131110052013 +1021222715102135203511102122061D0110211520033510243720173621041215067C81 +A6FEDE0201200127BAFE18809AFE2F0114FEDA8B990125FE1D010118CB01048976010101 +27BA03B50103C39AFED9FE6BFED4010184FDDD879EFE6CA0F4037801279BC2FDFEAFA501 +F6FD0101FA01D8D801FEC4BF0003007AFE1E06F906870007000F00230000010407030633 +322517243713362322050106051323030423221B01362503331324333203039AFDFC1B42 +177072012EAB02040E481B8F5AFEE3026420FD445DCF5DFEC7A9D32A482402BC4FBE4801 +35C1D42A03FC4EA8FE2CAEAEAE466801FAAE49FDA1E028FDF40291AF013201F9FA3A0228 +FD6A6EFECD00000000020064FE28061306D30030003C0000011600151407041102052403 +343727262706111005041723262506072736372411102526353717071416333236352600 +35011023220706070615120536036B0101561E016F15FE72FE62143C466A55DD0165022B +05DB0BFE7C96747A6B9CFE9E013A2426A01897616F8001FEAC02A5EF1F1B3658430A0101 +E506D3A4FEE6AB664514FE2CFE20141401EB668E0A095C37FEB9FEACADC0DEA85903986C +672FE9014201BF384D75B138798E90898B7A011BD4FB0E015205341A5DA1FE950A0A0000 +00020064FFE203AC061E0003001600001321152101102120113733071033321126243533 +161716D90256FDAA02D3FE5BFE5D27B023F2EF01FE7ABB02BEBF061E94FC20FE3801BED0 +D0FECE013CDDFEDDAD7E7E0000030064FFE203AC063800030007001A0000013315232533 +152301102120113733071033321126243533161716024CCBCBFE79CBCB02E7FE5BFE5D27 +B023F2EF01FE7ABB02BEBF0638CACACAFC3CFE3801BED0D0FECE013CDDFEDDAD7E7E0000 +00040064FFE203AC070000120016001A001E000001102120113733071033321126243533 +16171601211521013315232533152303ACFE5BFE5D27B023F2EF01FE7ABB02BEBFFD0D02 +56FDAA0189CBCBFE79CBCB01AAFE3801BED0D0FECE013CDDFEDDAD7E7E02B6940225CACA +CA00000000020064FFE203AC068C00030016000001330123011021201137330710333211 +262435331617160227EBFEFEAD0249FE5BFE5D27B023F2EF01FE7ABB02BEBF068CFEF8FC +26FE3801BED0D0FECE013CDDFEDDAD7E7E00000000030064FFE203AC071800030007001A +00000133032307211521011021201137330710333211262435331617160227B9E4999602 +56FDAA02DFFE5BFE5D27B023F2EF01FE7ABB02BEBF0718FEF87994FCA7FE3801BED0D0FE +CE013CDDFEDDAD7E7E00000000020064FFE203AC06790008001B00000102200333163332 +3713102120113733071033321126243533161716032813FDB4137619AAAC17FAFE5BFE5D +27B023F2EF01FE7ABB02BEBF0679FEE1011F9696FB31FE3801BED0D0FECE013CDDFEDDAD +7E7E000000020064FE1D0398061E0003001B000001211521011021201137330710333219 +011023221517232710212011010B0256FDAA028DFE74FE5821AC19F4E2E1C837AC31016E +018B061E94FA37FE5C01A6E1E3FEE8011603130102F4E2DC0186FE6F00020064FE1D0398 +06640003001B000001330123011021201137330710333219011023221517232710212011 +026DEBFEFEAD01EFFE74FE5821AC19F4E2E1C837AC31016E018B0664FEF8FA65FE5C01A6 +E1E3FEE8011603130102F4E2DC0186FE6F00000000030064FE1D0398073600030007001F +000001330323072115210110212011373307103332190110232215172327102120110259 +D7E4B7960256FDAA0299FE74FE5821AC19F4E2E1C837AC31016E018B0736FEF87994FAA0 +FE5C01A6E1E3FEE8011603130102F4E2DC0186FE6F00000000020064FE1D039806790008 +002000000102200333163332371310212011373307103332190110232215172327102120 +11035013FDB4137619AAAC17BEFE74FE5821AC19F4E2E1C837AC31016E018B0679FEE101 +1F9696F948FE5C01A6E1E3FEE8011603130102F4E2DC0186FE6F000000020064000003C0 +061E0011001500000110032312111023221110132302111021200121152103C099AD92F6 +FE91AE9701B201AAFD370256FDAA0244FEF4FEC8012101230190FE6DFEE0FEDF013A010D +021901BE9400000000020064000003C00664001100150000011003231211102322111013 +2302111021200133012303C099AD92F6FE91AE9701B201AAFEB7E1FEFEA30244FEF4FEC9 +012001230190FE6DFEE0FEDF013A010D02190204FEF8000000020064000003C006790011 +001A000001100323121110232211101323021110212003022003331633323703C099AD92 +F6FE91AE9701B201AA7013FDB4137619AAAC170244FEF4FEC9012001230190FE6DFEE0FE +DF013A010D02190219FEE1011F96960000020064FFFF05E6061E001C0020000001120123 +001102232211152335102322110201230011102132173633200121152105E601FEEDD101 +2F01D2E7A6E7D3010138D1FEE60188E15762DC0185FC140256FDAA0256FED1FED9011601 +40017EFEA7D2D20159FE82FECDFEDC01240133020AE3E301BE94000000030064FFFF05E6 +0638001C0020002400000112012300110223221115233510232211020123001110213217 +363320013315232533152305E601FEEDD1012F01D2E7A6E7D3010138D1FEE60188E15762 +DC0185FD9BCBCBFE79CBCB0256FED1FED901160140017EFEA7D2D20159FE82FECDFEDC01 +240133020AE3E301D8CACACA00040064FFFF05E60728001C002000240028000001120123 +001102232211152335102322110201230011102132173633200121152101331523253315 +2305E601FEEDD1012F01D2E7A6E7D3010138D1FEE60188E15762DC0185FC120256FDAA01 +89CBCBFE79CBCB0256FED1FED901160140017EFEA7D2D20159FE82FECDFEDC0124013302 +0AE3E30137940225CACACA0000020064FFFF05E60664001C002000000112012300110223 +2211152335102322110201230011102132173633200133012305E601FEEDD1012F01D2E7 +A6E7D3010138D1FEE60188E15762DC0185FD76E1FEFEA30256FED1FED901160140017EFE +A7D2D20159FE82FECDFEDC01240133020AE3E30204FEF80000020064FFFF05E60679001C +002500000112012300110223221115233510232211020123001110213217363320010220 +03331633323705E601FEEDD1012F01D2E7A6E7D3010138D1FEE60188E15762DC0185FE8B +13FDB4137619AAAC170256FED1FED901160140017EFEA7D2D20159FE82FECDFEDC012401 +33020AE3E30219FEE1011F96960000000002003CFE1D03E8061E00030024000013211521 +0110212011373307102132190126232215032303342B0127371733321736332011ED0256 +FDAA02FBFE6EFE4719B2170105E802877B0A8F0A9C526D1457508F4F43A00130061E94FA +36FE5D01A3E3E3FEE901170367B68CFEA5015B7828A132BBBBFEC7000003003CFE1D03E8 +063800030007002800000133152325331523011021201137330710213219012623221503 +2303342B01273717333217363320110274CBCBFE79CBCB02FBFE6EFE4719B2170105E802 +877B0A8F0A9C526D1457508F4F43A001300638CACACAFA52FE5D01A3E3E3FEE901170367 +B68CFEA5015B7828A132BBBBFEC700000004003CFE1D03E8073C00030007000B002C0000 +1321152101331523253315230110212011373307102132190126232215032303342B0127 +371733321736332011EB0256FDAA0189CBCBFE79CBCB02FBFE6EFE4719B2170105E80287 +7B0A8F0A9C526D1457508F4F43A0013005AB940225CACACAF94EFE5D01A3E3E3FEE90117 +0367B68CFEA5015B7828A132BBBBFEC70002003CFE1D03E8066400030024000001330323 +0110212011373307102132190126232215032303342B01273717333217363320110259D7 +F8A30253FE6EFE4719B2170105E802877B0A8F0A9C526D1457508F4F43A001300664FEF8 +FA64FE5D01A3E3E3FEE901170367B68CFEA5015B7828A132BBBBFEC70002003CFE1D03E8 +067900080029000001022003331633323701102120113733071021321901262322150323 +03342B0127371733321736332011034613FDB4137619AAAC170118FE6EFE4719B2170105 +E802877B0A8F0A9C526D1457508F4F43A001300679FEE1011F9696F947FE5D01A3E3E3FE +E901170367B68CFEA5015B7828A132BBBBFEC7000002003CFE1D03E806D1000600270000 +011323270723130110212011373307102132190126232215032303342B01273717333217 +363320110258F58BB4B48BF50224FE6EFE4719B2170105E802877B0A8F0A9C526D145750 +8F4F43A0013006D1FE88F5F50178F8EFFE5D01A3E3E3FEE901170367B68CFEA5015B7828 +A132BBBBFEC7000000020071FFE304750614000900200000012206101620363534260110 +372E01353436332115212215141633320010002000027293ACAB0128ACACFD6BC34F41C2 +9E01FCFE28BC7592ED0115FEEDFE20FEEF03DFE7FE6EE7E8C7C8E9FE50014D9B2F8D317C +9493894934FEC8FDDAFEC601390000000001002F000005AA061400240048401326000709 +05080C21180D1E08110C2110144C2510FC3CC432C4FC3CC4103CFC3CC4C4C43100401109 +0D11A912021A87001897061F12BC0B0F2F3CE63232FE3CEE3210EE32323001152322061D +01211521112311211123112335333534363B0115232207061D01213534363305AAB0634D +012FFED1B9FE07B9B0B0AEBDAEB063272601F9AEBD0614995068638FFC2F03D1FC2F03D1 +8F4EBBAB99282868634EBBAB0002002F0000044A061400150019005240111B4600170816 +0F1404080803160A064C1A10FC3CC432C4FC3CC410FE3CEC310040120803A90010870E18 +BE16B10E970900BC05012F3CE632EEFEEE10EE10EE3230400BFF1BA01B901B801B101B05 +015D01112311211123112335333534363B01152322061D0101331523044AB9FE07B9B0B0 +ADB3B9B0634D01F9B9B90460FBA003D1FC2F03D18F4EB7AF9950686301B2E9000001002F +0000044A061400150037400F17460108040A0C08081004120E4C1610FC3CC4C4FC3CC410 +FEEC3100400D0F0BA909048700971109BC0D022F3CE632FEEE10EE323001211123112122 +061D01211521112311233533353436024A0200B9FEB7634D012FFED1B9B0B0AE0614F9EC +057B5068638FFC2F03D18F4EBBAB00000002002F000006FC06140029002D005A40182F46 +172B082A101B15081A2A09001F0608241E0922264C2E10FC3CC432C4FC3CC410C432FC3C +C410FC3CEC310040171B1F23A924110187002DBE2AB1100097160724BC191D212F3C3CE4 +3232E432F4EC10EC3210EC3232300115232207061D01213534373637363B01152322061D +01211123112111231121112311233533353436330533152302F8B063272601F9571C274E +83AEB0634D02B2B9FE07B9FE07B9B0B0AEBD03F9B9B9061499282868634EBB551C132799 +506863FBA003D1FC2F03D1FC2F03D18F4EBBAB02E90000000001002F000006FC06140026 +004E401628460D0810161814081009001C0608211B091F234C2710FC3CC432C4FC3CC410 +C4FC3CC410FCEC31004012181C20A9211102870C2697150721BC0F1A1E2F3C3CE43232F4 +3CEC3210EC3232300115232207061D012135343633211123112122061D01211521112311 +211123112335333534363302F8B063272601F9AEBD0200B9FEB7634D012FFED1B9FE07B9 +B0B0AEBD061499282868634EBBABF9EC057B5068638FFC2F03D1FC2F03D18F4EBBAB0000 +0001002F0000054C0614002D000001353427262B0122070615112311233533353437363B +013217161D01211521111417163B01152322272635112335031824256522632726B9B0B0 +5757BD1EBD5755017BFE85252673BDBDD5515187046063682828282868FB3D03D18F4EBB +55565653BD4E8FFDA08927279A504FD202608F000001006FFFE306B205F0005900000115 +26272623220706151417161F011E01151407062322272627351617163332373635342726 +2F01262726353437363332172635343736373217161D01211521111417163B0115232227 +26351123353335342726072207061514035156495446753F3B3131943FC3A67B7CD8605C +616C66636361824646322DB140AB4C4C6670B5484D055C5BA28C625E017BFE85252673BD +BDD551518787303644453634043FAE2B11142A2757402524210E2B98899C5B5B111223BE +351A1B2D2C514B28232A0F244A4B82A64E560B1D1F875F5D01605C884C8FFDA08927279A +504FD202608F4E412B32013130403D00000100ABFFE308E30614004B0000011615112335 +0E012322263511331114163332363511342721222726353437363B011523221514332127 +26353437363B0115232215141F01210314163332363511331123350E012322263511044F +09B843B175C1C8B87C7C95AD05FE53985B505A777259598383016C17360937D1ECDE600E +3B01DD017C7C95ADB8B843B175C1C803B62521FC90AC6663F0E70166FEA19F9FBEA40191 +241C5E5391834257AF7B8A38834B1F157AAF2B292091FD61A09EBEA4027BFBA0AC6663F0 +E701FC00000100AEFFE308E30614003A000001212615141F0116151123350E0123222635 +11331114163332363511342F01263736332111211521111416333237363511331123350E +01232226350539FEF3600E4D31B843B175C1C8B87C7C95AD104D4D2037D101D302F2FD0E +7C7C985357B8B843B175C1C80565012C2722BC784DFC90AC6663F0E702A6FD619F9FBEA4 +01913F27BCBB477AFE4CAAFE0B9F9F5F62A1013BFCE0AC6663F0E700000100AEFE5608E3 +06140035000001212615141F0116151123350E012322263511331114163332363511342F +012637363321113E013332161511231134262322061511230539FEF3600E4D31B843B175 +C1C8B87C7C95AD104D4D2037D101D442B375C1C6B87C7C95ACB90565012C2722BC784DFC +90AC6663F0E702A6FD619F9FBEA401913F27BCBB477AFD9E6564EFE8FD5C029E9F9EBEA4 +FBDD0000000200AEFE5608E306140035003C000001212227263736373633211121111416 +3332363511331123350E0123222635112311331521110E01232226351133111416333237 +36351901212215143303A0FEDB955E53030357737601DD01997C7C95ADB8B843B175C1C8 +E1D5FE7343B175C1C8B87C7C955756FEDB838303B65E5391834257FE4CFD619F9FBEA402 +7BFBA0AC6663F0E701FCFB42A202566663F0E70166FEA19F9F5F5FA4027B01057B8A0000 +000100AEFE560B9B06140048000001212615141F0116151123350E012322263511331114 +163332363511342F012637363321113637363332161D011417163332363511331123350E +01232227263D0134262322061511230539FEF3600E4D31B843B175C1C8B87C7C95AD104D +4D2037D101D463255A6BC1C63E386E8CADB8B843B16CAF62647C7C78ACB90565012C2722 +BC784DFC90AC6663F0E702A6FD619F9FBEA401913F27BCBB477AFD9E811632EFE8E39758 +4FBEA4027BFBA0AC6663787BE4E49F9EBEA4FBDDFFFF0088005B014204601226052F0000 +10070514FE4E01A000010156050003C8061F000C000001331E0133323637330E01202601 +56760B615756600D760A9EFEDE9E061F4B4B4A4C8F909000FFFF004A005B02A204601226 +0543000010070517FEE701A00001004E0000047E046000160000011332373637363D0133 +11140E0523213521030163C1FA5C400802BA0E2640658BBE78FE6A0114C10460FC51A371 +F23C76F7FED674B6B0816F4527B103AF000100AE0000062D046000200000090123010E03 +15112311343E02370133013E0335113315140E0304B3017AEDFD4B2D625B3BB849777A3F +FE87ED02B42D625C3BB8324E68600169FE97029706385E9758FEF4010A64AD774E16016A +FD6906385E9758010CF6569A6E5A370000010058000005BD046000070000012311231121 +352105BDCABAFC1F056503D1FC2F03D18F000000000200BA0000061D0460001300170000 +01112311342E0423213521321E0425112311061DB90D273E6E8C69FD2B02D585C4945E3B +18FB6BB901EDFE1301ED6A8B74402C0F8F1A3F5D91B245FD5402600000010058000005BB +0460001B00001321321E03140E0323213521323E03342E0323215802D796E88F5C23235C +8FE796FD2802D871A8663D16163D66A871FD28046041699599B0999569418E2A486E759A +756E482A00010058000005BB06140008000021012111331121150102A0025CFB5CBB04A8 +FDA403D10243FE4C84FC2400000200BA0000061D046000080013000029011121321E0215 +01112111342E0423061DFA9D0335A3D38335FB5703EF0C2133576D4F04603C90D9A701BD +FCBE01855E82683D290F000000010058000005BB0460000F000001112311342E02232135 +21321E0205BBBA1D548C74FCC80337A2D382350214FDEC021486A16D298F3C90D9000000 +00010060FFF805BA0460001E000001112311342E03232111140623222735163332363511 +233521321E0205BABA0A24407050FE247B98354E4126472EAD03438FC16C2C025EFDA202 +604A63653A25FDCDD6D0108F0E72A302338F438AB4000000000100D9022D05DB05040007 +0000012135211133112105DBFAFE022DA8022D022DAA022DFDD30000FFFF005800000553 +059610260521A1001206053F00000000FFFF004E000005530596102605194E001206053F +00000000FFFF0058000005530596102705190258FC0F1026052197001206053F00000000 +FFFF0058000005530596102705190258FC0F10270521FB4C00001206053F0000FFFF00BA +FEBB049F04601026051700001206052600000000FFFF00BAFE75049F0460102605180000 +1206052600000000FFFF00BAFEBB049F04601026051400001206052600000000FFFF0058 +0000044804601027051CFEE10000120605270000FFFF0058FFF6031104601027051CFE21 +0000120605280000FFFF00580000041704601027051CFE810000120605290000FFFF00BA +0000048004601026051C00001206052A00000000FFFF00590000021E04601027051CFE0B +00001007052B00AA00000000FFFF0059000002E704601027051CFE0B00001006052C7A00 +FFFF00B9FFE304BF046B1026051C00001206052E00000000FFFF005901A2021C04601027 +051CFE0B00A01007052F00DA00000000FFFF0058FE56039204601027051CFEB100001206 +05300000FFFF0058000003CA04601027051CFEB10000120605310000FFFF0058000003F0 +05D51027051CFE510000120605320000FFFF0058000004B504701026051C300012060534 +00000000FFFF00580000027804601027051CFE210000100605360000FFFF00B9FFE304BF +04601026051C00001206053700000000FFFF00BAFE56046404601026051C710012060539 +00000000FFFF00BA0000048E04601026051C7C001206053A00000000FFFF005800000405 +04601027051CFE5100001206053C0000FFFF00BAFE560511045F1026051CD1001206053D +00000000FFFF0058000003CA04601027051CFEB100001206053E0000FFFF005800000553 +0460102705190239FC0D1206053F0000FFFF0014FFF8048804601026051C000012060540 +00000000FFFF00BA00000174059610270521FBB400001206052B0000FFFF005800000448 +05961027051FFF710000120605270000FFFF0058000003CA05961027051FFF4100001206 +05310000FFFF00BA0000048E05961026051F00001206053A000000000001005800000491 +0614002C0000090123010E0415112335343E053703231133113307013E0435113315140E +050372011DD9FE601C2338211AB8141F322D412C1FBAB8BB7802019E1B2338221AB8141F +322E402C019BFE6502580E1535416E45FEF4B9518A6051322C150D010D0242FE4C03FDAB +0D1536416E45010CB952896151312C15FFFF0082FE0C06EB029D102717250339FF061006 +058E0000FFFF0082FE0C07EF029D102717250339FF06100617270000FFFFFFECFE0C0187 +02581026172800001007172500E0FF06FFFFFFECFE0C027E02581027172500E0FF061006 +17290000FFFF0082FE0C06EB029D1027172402BCFF061006058E0000FFFF0082FE0C07EF +029D1027172402BCFF06100617270000FFFFFFECFE0C01F3025810261728000010071724 +0063FF06FFFFFFECFE0C027E0258102617290000100717240063FF06FFFF0082FE0C06EB +029D1027172602BCFF061006058E0000FFFF0082FE0C07EF029D1027172602BCFF061006 +17270000FFFFFFECFE0C01F30258102617280000100717260063FF06FFFFFFECFE0C027E +0258102617290000100717260063FF06FFFF0082FFEC06EB041A10271725033903841006 +058E0000FFFF0082FFEC07EF041A1027172503390384100617270000FFFFFFEC00000187 +04E21026172800001007172500E0044CFFFFFFEC0000027E04E210261729000010071725 +00E0044CFFFF0082FFEC06EB041A1027172602BC03841006058E0000FFFF0082FFEC07EF +041A1026172700001007172602BC0384FFFFFFEC000001F304E210261728000010071726 +0063044CFFFFFFEC0000027E04E2102617290000100717260063044CFFFF0082FFEC06EB +04991027054B0184FDA81006058E0000FFFF0082FFEC07EF04991026172700001007054B +0184FDA8FFFFFFEC0000023005611026172800001007054BFF2BFE70FFFFFFEC0000027E +05611026172900001007054BFF2BFE70FFFF0082FFA4079E060E102717230578047E1006 +05BA0000FFFF0082FFA5085C05461026172A000010071723057803B6FFFFFFEC0000033F +060E1026172B000010071723012C047EFFFFFFEC0000042005781026172C000010071723 +013E03E8FFFF0082FFA4079E060E1027172605780578100605BA0000FFFF0082FFA5085C +05461026172A000010071726057804B0FFFFFFEC0000033F060E1026172B000010071726 +012C0578FFFFFFEC0000042005781026172C000010071726013E04E2FFFF009DFE0C0528 +036610271725030700AF1006055A0000FFFF009DFE0C053E03661027172502A3007D1006 +14990000FFFFFFECFE3E045C032F1026149A000010071725020DFF38FFFFFFECFE3E053E +032F1026149B000010071725020DFF38FFFF009DFE0C0528036610271722028A00191006 +055A0000FFFF009DFE0C053E0366102717220226FFE7100614990000FFFFFFECFF38045C +032F1026149A0000100717220190FF38FFFFFFECFF38053E032F1026149B000010071722 +0190FF38FFFF009DFE0C0528036610271724029600961006055A0000FFFF009DFE0C053E +03661027172402190032100614990000FFFFFFECFE3E045C032F1026149A000010071724 +0190FF38FFFFFFECFE3E053E032F1026149B0000100717240190FF38FFFF009DFE0C0528 +036610271726029600AF1006055A0000FFFF009DFE0C053E036610271726021900321006 +14990000FFFFFFECFE3E045C032F1026149A0000100717260190FF38FFFFFFECFE3E053E +032F1026149B0000100717260190FF38FFFF007DFED4031B035210271722012BFED41006 +055C0000FFFF007DFED40447035210271722012BFED4100614A10000FFFF007DFFDA031B +04B01027172200FA041A1006055C0000FFFF007DFFDA044704B01027172200FA041A1006 +14A10000FFFF007DFFDA031B05AA1027172300FA041A1006055C0000FFFF007DFFDA0447 +05AA1027172300FA041A100614A10000FFFF007DFFDA031B05F71027054BFFC2FF061006 +055C0000FFFF007DFFDA044705F71027054BFFC2FF06100614A10000FFFFFFABFE0C0384 +04B01027172301F403201006055E0000FFFFFFABFE0C047E04B0102614A5000010071723 +01F40320FFFFFFABFE0C03C1052F1026055E00001007054B00BCFE3EFFFFFFABFE0C047E +052F102614A500001007054B00BCFE3EFFFF0082FFA707290614100605C2000000010082 +FFA707D90614003700002506070623222724113437330615141716333237363736353427 +01263534373637011505060706151417011617163B0115232227262F0106059C63A9CDB7 +C080FEB63FB841CB6897B8C29E231036FECA320A236402E9FDAD4715061F024716262B40 +5884413A67216119784C3C492662010B8A5C5E887E42225041371A2E4542017C3D512321 +772A0136BAFA1E280B192025FD3F1A0E10B8182B29784000FFFFFFEC000003CF06141006 +14D20000FFFFFFEC0000047F0614100614D30000FFFF0082FFA70729072B1027174503CA +0000100605C20000FFFF0082FFA707D9072B1026142500001007174503CA0000FFFFFFEC +000003CF072B1026142600001006174570000000FFFFFFEC0000047F072B102614270000 +1006174570000000FFFF0082FDA80729072B1027172502D5FEA2100605C80000FFFF0082 +FDA807D9072B1026142900001007172502D5FEA2FFFFFFECFDDA03CF072B1026142A0000 +100717250145FED4FFFFFFECFDDA047F072B1026142B0000100717250145FED4FFFF0082 +FFA70729073A10271722038406A4100605C80000FFFF0082FFA707D9073A102614290000 +10071722038406A4FFFFFFEC000003CF073A1026142A000010071722004B06A4FFFFFFEC +0000047F073A1026142B000010071722004B06A4FFFF0093FEB5054802EE100605D30000 +00010093FE0C062B0245002600000116171617163B011523222706070607062322272411 +343733061514171633323736373635342704E02C1B1632353C4B824722035978EF615C74 +71FEB82FB831C95A4C584FC24B216502456160503C40B8306D93C7421B256B0163AF8D89 +B3EC35181430E36570B8CB00FFFF0093FEB5054805161027054B00C8FE25100605D30000 +FFFF0093FE0C062B041C1026143500001007054B00C8FD2BFFFFFFEC0000023005611026 +172800001007054BFF2BFE70FFFFFFEC0000027E05611026172900001007054BFF2BFE70 +FFFF0090FFBD051B03E5100605D7000000030090FE0D052202AB0009002B00360000253E +013534232207061525343733061514163330333510373620171615060721152116140706 +202635222726051417163332353426270602B13FD0544B4A26FDDF10B80E7C141F8A7401 +04442E155E0128FEA8A32E45FEF4F23A71C1022126504554BF2A139F1BA434708D48751A +4A363E2845263701097F6B6343727962B86CE24263DEDF1F352F69438D7034970C070000 +FFFFFFECFFBD03BD03E5100614E20000FFFFFFECFE0D03C402AB100614E30000FFFF0090 +FFC905C706D610271723027105461006056B0000FFFF0090FFC906D206D6102614D10000 +1007172302710546FFFFFFEC000003CF076C102614D2000010071723004B05DCFFFFFFEC +0000047F076C102614D3000010071723004B05DCFFFFFFABFE0C034004D5102605700000 +10070577FFA4FDD8FFFFFFABFE0C043604D5102614E5000010070577FFA4FDD8FFFFFFAB +FE0C034004721027057FFFF4FE3E100605700000FFFFFFABFE0C043604721027057FFFF4 +FE3E100614E50000FFFFFFABFE0C0340053410260570000010070590FFECFE1BFFFFFFAB +FE0C04360534102614E5000010070590FFECFE1BFFFFFFABFE0C034004B0102605700000 +10071723012C0320FFFFFFABFE0C043604B0102614E5000010071723012C0320FFFF0082 +FCFE05C0034A10260571000010071725028AFDF8FFFF0082FCFE06BF020210271725028A +FDF8100614E70000FFFFFFECFE0C018702581027172500E0FF06100617280000FFFFFFEC +FE0C027E02581026172900001007172500E0FF060001FFEC000001870258000D00002506 +2B0135333237363D01331514012B489D5A23632C31B85656B82C316AD9D9BB000001FFEC +0000027E02580014000025062B0135333237363D0133151417163B01152322012B4D985A +23632C31B8312C63376E965656B82C316AD9D96A312CB800FFFF0082FEF305C0034A1006 +05710000FFFF0082FEF006BF0202100614E70000FFFFFFECFED401F302581026144E0000 +100717220063FED4FFFFFFECFED4027E02581026144F0000100717220063FED40001FC70 +06040000076E000700001122040735362433E7FE3DE6EC01C5DF06D467697E7775000000 +0001000006040390076E000700001135320417152624DF01C5ECE6FE3D06D49A75777E69 +670000000001FD2A060D0000072700130000112F01262726232207060723363736333217 +161704901C4F2C2465354605A2047170C85B3F3857064802370B120A243047874A490E0D +2000000000010000060D02D6072700130000111F01161716333237363733060706232227 +262704901C4F2C2461394704A2047170C85B3F385706EC02370B120A242C4B874A490E0D +20000000FFFF000804BA0250069A10070573FF2C00000000FFFFFFEC0000026C069A1026 +0568000010070573FF2C0000FFFF000804BA025006FD10070574FF2C0000000000010069 +0000022C016B000D0000011417163B0115232227263D01330121312C634B828E5C57B801 +4C37312CB85C578D2B000000FFFF0008FE160250FFF610070575FF2C00000000FFFF0008 +04BA025005AA10070576FF2C00000000FFFFFFEC0000026C05AA10270576FF2C00001006 +05680000FFFF000804B9025006FD10070577FF2C00000000FFFFFFEC0000026C06FD1027 +0577FF2C0000100605680000FFFF0008FEE80250FFD810070578FF2C00000000FFFFFFEC +FEE8026C00B810270578FF2C0000100605680000FFFFFFF404CB026406F410070579FF2C +00000000FFFFFFEC0000026C06F410270579FF2C0000100605680000FFFF001804E1023C +07061007057AFF2C00000000FFFFFFEC0000026C07061026056800001007057AFF2C0000 +FFFF00A30055031E03DE1006054E0000FFFFFFB50000028507831027057BFF1D01C21006 +05540000FFFFFFB50000028507831026148500001007057BFF1D01C2FFFF006C000001C3 +08391027057CFF1D01C2100605540000FFFF006C0000028408391026148500001007057C +FF1D01C2FFFFFFABFE0C034004B51027057CFFC2FE3E100605700000FFFFFFABFE0C0436 +04B5102614E500001007057CFFC2FE3EFFFF006CFE0C01C306141027057DFF1D00001006 +05540000FFFF006CFE0C028406141026148500001007057DFF1D0000FFFF0082FEF305C0 +04B51027057CFFF4FE3E100605710000FFFF0082FEF006BF03BB102614E700001007057C +0058FD44FFFFFFEC000001D104E71026144E00001007057CFF2BFE70FFFFFFEC0000027E +04E71026144F00001007057CFF2BFE70FFFF00C1000001790614100605540000000100C1 +000002840614000D0000131133111417163B011523222726C1B8312C634B829A50570173 +04A1FB6B6A312CB85C650000FFFF0082FEA206EB029D102717210339FEA21006058E0000 +FFFF0082FEA207EF029D102617270000100717210339FEA2FFFFFFECFED4018702581026 +172800001007172100E0FED4FFFFFFECFED4027E02581026172900001007172100E0FED4 +FFFF008BFFC603A0041A1027172200FA03841006056F0000FFFF00910000045E041A1026 +14E1000010071722015E0384FFFF0082FFEC06EB03201027172202BC028A1006058E0000 +FFFF0082FFEC07EF03201026172700001007172202BC028AFFFFFFEC000001F303E81026 +172800001007172200630352FFFFFFEC0000027E03E81026172900001007172200630352 +FFFF0082FFEC06EB041A1027172302BC028A1006058E0000FFFF0082FFEC07EF041A1026 +172700001007172302BC028AFFFFFFEC000001F304E21026172800001007172300630352 +FFFFFFEC0000027E04E21026172900001007172300630352FFFF009DFE0C052803661027 +1721030700191006055A0000FFFF009DFE0C053E03661026149900001007172102BCFFCE +FFFFFFECFED4045C032F1026149A000010071721020DFED4FFFFFFECFED4053E032F1026 +149B000010071721020DFED4FFFF009DFE0C052803661006055A00000001009DFE0C053E +036600300000253315232227262726270607061514171621323715062320272635103736 +3722232207060735243320171522071716171605162831817B523D06497262E04A810149 +C1D496FAFE5EA983D460890C0D5F8C875F0110C10126C86D1F1D33485AB8B896649C0FB2 +1850B7FC895EA476B863C296E00102DF6534131329B83F369A0D5BA05F7700000001FFEC +0000045C032F001E0000012627262726073536373217041715060706070607062B013533 +32373637360353536755B452A1484BA2AC0102DC4C58A64A914BDDA38064E38168745E02 +1D171713100706B807012334629A18346242811B50B8483A6A5600000001FFEC0000053E +032F002900000104171506071617163B0115232227262706070607062B01353332373637 +363726272627260735363732027E0102DC4149321E68724256D17E283B4529914BDDA380 +64E38168745E65536755B452A1484BA2030C34629A14294E2277B8A836612E25811B50B8 +483A6A5623171713100706B807010000FFFF009DFE0C052804B010271721023F041A1006 +055A0000FFFF009DFE0C053E04B010261499000010071721023F041AFFFFFFEC0000045C +044C1026149A000010071721020D03B6FFFFFFEC0000053E044C1026149B000010071721 +020D03B6FFFF007DFFDA031B03521006055C00000001007DFFDA04470352002000000126 +2733161716151417163B011523222706070623222735163332373637363534022239B6E3 +615251532C634B8293615DCF2E2D666773542122AC2308023F7A994E89867549532CB882 +80210726B82A0931701B2A44FFFF007DFFDA031B04B0102717210145041A1006055C0000 +FFFF007DFFDA044704B0102614A10000100717210145041AFFFFFFABFE0C036202261006 +055E00000001FFABFE0C047E022600180000013316171617163B01152322270207042135 +203736373635340278B81E030A492A654B82823244FBFEE4FEBE0130CBDA230A0226701E +674D2CB83EFEEA8597B8808AD03A487EFFFFFFABFE0C036203B610271721027103201006 +055E0000FFFFFFABFE0C047E03B6102614A500001007172102710320FFFF0082FE0C091A +02EE10060560000000010082FE0C0A4702EE004600002516373635331417163332190133 +111417163B01152322270607062322272627060706272627060706070623262724113437 +33061716171633323736373627262F01331716171605B84E3025B813406E8EB8532C634B +829176446D252049308A11315F40388927185485C15078806DFEED69B86C0101935F5162 +5F795E4001011040B824101C3AB5027C5FCACD32A901180126FEAA61532CB8605B190919 +467B9F1E1402023CB36BAA3E1A011C470148F6B4CEDCB3261825309E6C8E7D3DEA9C4A3C +7E0000000001FFECFFE3060A02EE00310000250607062B0135333237363D013315141716 +333237363533141716373619013311140706070623222726270607062322272601802B38 +4C63824B632C50B82C2B686D2C25B813406E8EB85C4B6625233F3789123060444162483C +8A3B212EB82C5064C09C4064637A67C2CD32AA010201160126FEAAC7715C1809193D849C +211831280001FFECFFE3073702EE00380000050623222726270607062322272627060706 +2B0135333237363D0133151417163332373635331417163736190133111417163B011523 +22270604FD25233F3789123060444162483C292B384C63824B632C50B82C2B686D2C25B8 +13406E8EB8532C634B829176441409193D849C21183128493B212EB82C5064C09C406463 +7A67C2CD32AA010201160126FEAA61532CB8605AFFFF0082FE0C091A04B01027172304E2 +0320100605600000FFFF0082FE0C0A4704B0102614A900001007172304E20320FFFFFFEC +FFE3060A04B0102614AA000010071723028A0320FFFFFFECFFE3073704B0102614AB0000 +10071723028A0320FFFF0082FE0C091302E510060562000000020082FE0C09E102E5003C +004900002901222726351407060706232627241134373306171617163332373637363534 +2733061716173637363736171617161514071617163B011523222F010603220706073332 +3736272627260675FEEC26342D4A58EE5078806DFEED69B86C0101935F51665BA22B2127 +AB010E0A28737B6C9348587D61BA2A0E103D524B8283671CCC124C7EA891BBED81BB0102 +89251E1A10BA809B4D1A011C470148F6B4CEDCB3261825448A6C7F938A0F372832926C5E +462201022547E94D460C0B2CB85C1A760237516CC23F5B46871305000002FFEC00000632 +02E5000C002E000001060733323736353427262322032122272627062B0135333237363D +0133151416173637363736333217161514070603D0A891BBED81BA8B25304CBAFEEC4A4E +412766AC824B632C50B8122D655F99904B55736BBAB8CA01E66CC23F5B46871305FDC92A +233D8AB82C5064724E1850328654893F212744ECA96D78000002FFEC0000070402E5002B +00380000290122272627062B0135333237363D0133151416173637363736333217161514 +071617163B011523222F0106030607333237363534272623220398FEE84A4E412766AC82 +4B632C50B8122D655F99904B55716DBE2A0E103D524B8283671CCCE0A891BBED81BA8B25 +304C2A233D8AB82C5064724E1850328654893F212744EC4B480C0B2CB85C1A7601E66CC2 +3F5B468713050000FFFF0082FE0C091303B61027172104FB0320100605620000FFFF0082 +FE0C09E103B6102614B100001007172104FB0320FFFFFFEC0000063203B6102614B20000 +1007172101DB0320FFFFFFEC0000070403B6102614B300001007172101DB0320FFFF0090 +000006DC061410060564000000020090000007AC0614001C002900002902352111331112 +2536333217161514071617163B011523222F0106253332373627262726232207060440FD +BFFE91016FB8D901145C447569BC2A0E103D524B8283671CCCFDE9BBED81BB0102892530 +507AB1B8055CFB0E013F63212745EB4D460C0B2CB85C1A76B83F5D448713055178000000 +0002FFEC000005D40614000C001F00002533323736272627262322070613290135211133 +1112253633321716151407060239BBED81BB0102892530507AB175FDC1FEF5010BB8D901 +145C447569BAB8CAB83F5D448713055178FE92B8055CFB0E013F63212745EBA96D780000 +0002FFEC000006A40614001C002900002902352111331112253633321716151407161716 +3B011523222F0106253332373627262726232207060338FDBFFEF5010BB8D901145C4475 +69BC2A0E103D524B8283671CCCFDE9BBED81BB0102892530507AB1B8055CFB0E013F6321 +2745EB4D460C0B2CB85C1A76B83F5D448713055178000000FFFF0090000006DC06141027 +172103CF0352100605640000FFFF0090000007AC0614102614B900001007172103CF0352 +FFFFFFEC000005D40614102614BA00001007172102C70352FFFFFFEC000006A406141026 +14BB00001007172102C70352FFFF0075FE0C04B2042A10060566000000020075FE0C04B2 +030E0025002B000001062120272610372E013534373632171615140706071E013B011523 +2027061514171621323701363422151404B298FEFFFE5B9D62BB3D447D61EE5F7F5B395E +44B655A090FEF6F89A296B015FC1D4FD3188CEFE6F63BC7A01928C32742A6E4030304070 +5B432B20404DB8E4816B6C3D9E760296386033240001FFEC000003F8042A001A00003732 +372627263510373633152206141716333237251505042B01353CA1CA4B3458CC7DFBDABA +3E5346374A0120FE5CFEA2B258B85D183B648C01087D4DA989FF34462181B8C5A4B80000 +0002FFEC000003F0030E0024002F000025062B0135333237363726272635343736373632 +1716171615140706071617163B01152322022207061514173635342701EEC2CA76606B42 +3B2861441E0D106859EE5968100D1E4D581F443D706076CAA8341E2F67672FB9B9B82824 +27555A272D1F2937342D2D3437291F2D27624D1F2C28B80255070C201D5F5F1D200C0000 +FFFF0075FE0C04B2054610271721017704B0100605660000FFFF0075FE0C04B2044C1026 +14C100001007172101A903B6FFFFFFEC000003F80546102614C2000010071721017704B0 +FFFFFFEC000003F0044C102614C300001007172101A303B6FFFF0082FFA4079E05141027 +172105F5047E100605BA0000FFFF0082FFA5085C044C1026172A00001007172105F503B6 +FFFFFFEC0000033F05141026172B00001007172101A9047EFFFFFFEC00000420047E1026 +172C00001007172101BB03E8FFFF006BFE48059B0514102717220352047E1006058F0000 +FFFF006BFE0C06C004011026172D0000100717220384036BFFFFFFEC0000033F05141026 +172E000010071722012C047EFFFFFFEC00000420047E1026172F000010071722013803E8 +FFFF0090FFC905C706141006056B000000020090FFC906D2061400210044000001150607 +061514171617161514070623222735163332373635342726272637363736130627262724 +353437330615161716333237363736190133111417163B01152322270603D04A26500E0C +446658524E4238442D3C393C4E4A122001024B558A6D91CF65FEDB10B80E02A0797F8C59 +98624CB8312C634B8299719E046F52040E1E211C12100A0F586E2A270B580A1A1A1E2611 +101C2F2E51262BFB701402021E58C34B353A2C5C2E231B2F5E49010103B1FB6B6A312CB8 +7B7E00000001FFEC000003CF0614001F0000290135213237363534270126353437363701 +15050607061514170116151407060136FEB60136942D1036FECA320A1F6802E9FDAD4715 +061F0113663E5FB86824244542017C3D512321762B0136BAFA1E280B192025FEB67B7B71 +659B00000001FFEC0000047F061400290000290135213237363534270126353437363701 +1505060706151417011617163B0115232227262F010607060136FEB60136942D1036FECA +320A1F6802E9FDAD4715061F024716262B405884413A67216115115FB86824244542017C +3D512321762B0136BAFA1E280B192025FD3F1A0E10B8182B29782E1B9B000000FFFF0090 +FEC8051806141006056C000000010090FEC806230614002400002536351133111417163B +01152322270607060506232227240326373306151417163332373604124EB8312C634B82 +704E121B79FEFB86556345FEDC010140B841A62849506DAE497EE3046AFB6B6A312CB833 +2E28B14222165B01128A5C73737E4210223500000001FFEC000001AF0614000D00000114 +07062B01353332373635113301AF57509A824B632C31B80173B2655CB82C316A04950000 +0001FFEC000002BA06140014000025062B013533323736351133111417163B0115232201 +534D98824B632C31B8312C634B82965656B82C316A0495FB6B6A312CB8000000FFFF008C +FE14045E02F31006056D00000002008CFE14056702740011003500002516333237363534 +272627262322070615140506070623222726070615112311343736332635343736333217 +16171617163B0115232202627A4C2C153506153B282E3B1B4401C00F0D854BB58D324B19 +C878415A02A24D5C4854AA280F2826694B82AFA82A16393A181458110C1B444428A90E08 +504E1C4E1A4FFE93016DAD66372A2894843F2447B3432C2CB80000000002FFECFFCD03A6 +026D0015002400002506232227062B013533323637363736333217161514251633323736 +35342726232206070603646193CE6E4068A07434570514875E51B05B61FDF03E9F2F1334 +282A582E5B0F0734666230B84731B550385459B88F4E451235454B26293C61250002FFEC +FFCE04B4026D001D002F00002506232227062B0135333237363736373633321716171617 +163B011523222516333237363534272627262322070607060364618BD66E4068A074342C +2B0514875E5D4842C1110533246B4B82B0FE143E9F2F133405163A27273529320F073466 +6230B8242331B550381D53A7313E2CB8C445123545131258110C1B2161250000FFFF0093 +FEB5054803B610271721023F0320100605D30000FFFF0093FE0C062B02BC102614350000 +10071721023F0226FFFFFFEC0000018703E81027172100E00352100617280000FFFFFFEC +0000027E03E81027172100E00352100617290000FFFF008BFFC603A002DE1006056F0000 +000200910000045E02EE000A002400000126272627060706171E01132627331617161716 +3B0115232227262706232227263534373602A40F0D0D0C78526F0201B4740403B8012415 +381F704B825A492D268A9C383ABDCC6D011A2A3132650C43593E293201D1272BA8AE654F +2CB8331F3B490F309AAD7F440003FFECFFBD03BD03E5000F002D003D0000013637363534 +272623220706151417160732372627263534373617262735161704171615140706232227 +062B01350116151407060716333237363534272601942422270F163A4117160715F9344E +2D0F122D304D2C51DFD80100400E284AAF839A78928902AB06620C1134317D1107211D01 +0D1B40483D2E25342D2B231E2576880B69454E4054535A09270EB944B3D1DE3439673D71 +6C29B80144252793690E132E431E213C4E4500000003FFECFE0D03C402AB000A0024002F +000025333237363534232207060712373633321716151407211521161514070623222726 +03233505141716333235342726230153132A6072544B4A26B809816C818A452EA30158FE +A8A32E458A816C8109AF0167264A4B5472602AB84C5A34708D4875010A7E6B6343726F6C +B86C6F7243636B800108B8B875488D70345A4C00FFFFFFABFE0C03400286100605700000 +0002FFABFE0C04360286000B002C00002534272627260706070617160533152327060706 +07062135203736372627262726272637363736171617161716028D10182E3C3C430D1140 +2F0173F6C24619815369C0FE93016984A83E762D8B336A0A020A1BB03B41574585160DB8 +4C39501E291A1D384B362804B8019C81532F56B84C5F940307182B59871F399B4B190102 +315C8D53FFFF0082FEF305C0034A10060571000000010082FEF006BF0202003700000536 +352627262726353437363736333217163B01152322272623220706151417161716070607 +062322272411343733061514171633323736044C5B012F1D1E2C3450541E2A7557802023 +2372706C1A240F2D414903037180DA5968BC75FEDB3FB841A642B35B4D952133212B0D08 +283B3D413C5E0E0686C4B8ACA5061123301B1E7B6256601F0C29670106995A5A99724E1F +080F0000FFFF0082FE0C05C0034A1027172201F4FE0C100605710000FFFF0082FE0C06BF +02021027172201F4FE0C100614E70000FFFFFFECFED401F302581026144E000010071722 +0063FED4FFFFFFECFED4027E02581026144F0000100717220063FED4FFFFFF2EFFEC03BF +06ED102614F200001007057BFE96012CFFFFFF2EFFEC04DA06ED102614F300001007057B +FE96012CFFFFFFE5FFEC03BF07A3102614F200001007057CFE96012CFFFFFFE5FFEC04DA +07A3102614F300001007057CFE96012CFFFF0017FE0C03BF0614102614F200001007057D +FEC80000FFFF0017FE0C04DA0614102614F300001007057DFEC8000000010054FFEC03BF +061400160000011007060706232227351633323701330136373635113303BF8B374DCD9E +6A4B60557188FE16B301AA1D1425B80342FED4DA5547B420B820980446FC412B3789B802 +AE00000000010054FFEC04DA0614001F0000010607062322273516333237013301363736 +351133111417163B01152322272603343B49CAA16A4B60557188FE16B301AA1D1425B822 +40506981AF6611013C5B41B420B820980446FC412B3789B802AEFC44AE549EB8F3290000 +0003001EFF540816074C00030007002A0000090415333527353436373637363F01363736 +35342623220607153E013332161514060F010E011D01041A03FCFC04FC040396CB060606 +0813172C585C2224DFB867C15E61B34F6C8333395A5A38074CFC04FC0403FCFDAEFEFE93 +7B343C15191A1F2B565A40454C9FC23839BC43466E59315E35595682659A000000020064 +FFE305AA05D50007000A00003701330107032103012103640230E60230A8C1FD8CC10106 +01EAF52305B2FA4E4001F6FE0A02AA027C000000000300C8FFED041405E80009001A0024 +000001113332373635342623013633321716151407161514070621222713113332373635 +342623017C34FB773DC6AFFEDE8864F0A2CED7D675ABFEBD6484B46FEA5E2CECBF027EFE +23723B425995035713617BD0D57A7AE48E70A4130534FE15803D38688E00000000010064 +FFE303EA05F300050000050901170901036BFCF903077FFD7802881D030803087FFD77FD +77000000000200C80000040605D2000C0015000033113320171617161007060421373E02 +10272E0127C85A01849E9F190A2738FEB9FEC25ACEC7410A12BCFE05D2787AF965FE627F +B8ADC00863D4017A5CAC910A000100C80000039C05F2000D000001112311050725110507 +25110507017CB402D41CFDFC02201CFDFC02201C0136FECA05F275B253FEC358B253FEC3 +58B20000000100C80000039C05F20009000001112311050725110507017CB402D41CFDFC +02201C032AFCD605F275B253FEC358B200010096000002EE05D5000B0000132115231133 +152135331123960258D2D2FDA8D2D205D5B4FB93B4B4046D000300C80000043805D50003 +0007000B0000132111210111211105211121C80370FC9002BCFDF80208FDF8020805D5FA +2B034501DCFE24B4FE23000000050096FFE306A405F3000F00140019001E002300000002 +0604202426021012362420041612013600372103112116000121110600011121260006A4 +7AD0FEE0FEC6FEE0D07A7AD00120013A0120D07AFD53C201121EFE0EB4FE0E1E0112FED0 +01F2C2FEEE028801F21EFEEE024EFEE0D07B7BD00120013A0120D07B7BD0FEE0FD161E01 +12C3FE0D01F3C3FEEE028901F31EFEEE0130FE0DC3011200000100C80000017C05D50003 +000013331123C8B4B405D5FA2B000000000100C8FFE3048305F3000A0000212311331101 +1709010701017CB4B4028878FD2E02D978FD7105D5FDE1023D86FD81FD7B860243000000 +000100C8000003FB05D50006000009011701231133017C01FF80FD4D80B40133020080FD +4D05D500000100C800000AD405F3000C0000133309031709031123C87402830238022F02 +2E80FD52FDD2FDD1FDB3B405D5FDE50239FDD1022F80FD53022EFDD201EEFB4C000100C8 +0000067705F300080000133309011709011123C8740283023880FD52FDB3B405D5FDE502 +3980FD5301EEFB4C000500C80000069C05D500030007000B000F00130000011121110121 +1121011121112111211113112111017C01DCFD7005D4FA2C034401DCFB9401DCB401DC02 +90FE2401DC0345FA2B0521FE2401DCFE2401DCFD6FFE2401DC00000000030096FFE306A4 +05F3000F001F002700000036342E02220E02141E02323600020604202426021012362420 +041612040622263436321605925E5E9FDEF0DE9F5E5E9FDEF0DE01B17AD0FEE0FEC6FEE0 +D07A7AD00120013A0120D07AFD855274525274520194DFF0DF9E5F5F9EDFF0DF9E5F5F01 +58FEE0D07B7BD00120013A0120D07B7BD0FEE0D75252745252000000000100C8000004E7 +05E8001100000124353424212207112311363320001110050328010BFED3FEE23537B4A2 +7E0162019DFE81025966D4B6F906FAC405D414FEBFFEECFEB0920000000100C80000062A +05D5000B0000331133090133112311090111C8B401FD01FDB4B4FE03FE0305D5FDA2025E +FA2B04BDFDA1025FFB430000000200960000041A05F2000B001D000000342E01220E0114 +1E01323E010607060711231126272E01343E01321E010366487D927D48487D927DFC7A66 +4048B44741687879CFF4CF7903E7937C48487C937D48484CD13B250EFD8902770E253CD0 +F4CF7979CF000000000200C80000044405E9000C00170000080115140207062311231136 +33032037363534272623220702D8016CC8FF768BB4A27E6C010985868B72BE2B2E05E9FE +E5FC8DFED14E24FE5C05D514FC6F7372969E6C580400000000010096FFF202F205EA0009 +00000903070903170194015EFEA2011F80FE63015EFEA2019D80044CFEA2FEA2FEE28001 +9E015E015E019E8000010064000004FC05F200070000010725112311253704FC38FE32B4 +FE2238048AAC95FB8D04AD99AC000000000100C8000004B405EA00070000011701112311 +3311043480FCC8B4B405EA80FCC9FDCD05D5FD5C00010096FFE304E705F2000B00000901 +370901170901070901270251FE45960192019396FE4501BB96FE6DFE6E9602ED02A362FD +A1025F62FD5DFD58620264FD9C62000000030096000004E205D50013001B002400002123 +352627261037363735331516171610070607190136373610272601110607061514171603 +16B4AA81A1A180ABB4AA81A1A180AB604B6D6D4BFEEC604B6D6D4BCC1A80A101C7A2801A +CBCB1A80A1FE39A2801A0387FD30174C6C01326C4BFD4802D0174C6E98966E4C00010064 +0000061605F2000B00002123110137011133110117010397B4FD818001FFB401FF80FD81 +02F4027E80FE0001E3FE1D020080FD8200030096FFF103A205B90007001D002500000034 +262206141632240620261037363726272610362016100706071617160234262206141632 +02EE7BAE7B7BAE012FE5FEBEE5722F37372F72E50142E5722F37372F72B47BAE7B7BAE01 +20AE7B7BAE7B31E5E50142732F1B1C2E720143E5E5FEBE732F1B1C2E7201C3AE7B7BAE7B +000200C8000003E005E8000A001300003311363332041514042119013236353426232207 +C88481F30120FEA9FEF3CAE6B4A22A3005D513E6BDD0DCFD67034E7F76639C06000200C8 +FFED03E005D5000A00140000133311200415140423222713301116333236353426C8B401 +0D0157FEE0F38184B4302AA2B4E605D5FD67DCD0BDE6130287FE12069C63767F00010064 +000002BC05D50007000013352111331123116401A4B4B40291B40290FA2B029100020064 +000005AA05F20006000E000009012301370901000622263436321605AAFDD0E6FDD0A801 +FB01FBFE9152745252745205B2FA4E05B240FADA0526FDD85252745252000000FFFF00C8 +0000017C05D512061503000000010064FFE305AA05D50006000037013301070901640230 +E60230A8FE05FE052305B2FA4E400526FADA0000FFFF0096FFE304E705F2120615110000 +000100640000062705F3000800001309010701112311016402C802FB7AFDDBB4FE10032C +02C7FD3B8401FEFB58049DFE0F000000000300AA01E0068202A800030007000B00000121 +3521053521152901352101FDFEAD015303320153FDBDFEAE015201E0C8C8C8C8C8000000 +FFFF00AA013D0682040B10270F61000003431007151D0000FF5D0000FFFF00AA01370682 +040E1027151D0000FF5810070F62000003460000FFFF00AA013A0682040A1027151D0000 +016210070F610000013A0000FFFF00AA013B0682040A10270F620000013C1007151D0000 +01620000FFFF00AA013D0682040A1027151D0000FF5D1007151D000001620000FFFF00AA +00000682054810270F610000048010260F61000010270F610000018010070F6100000300 +FFFF00AA00000682054810270F610000048010270F610000018010270F61000003001006 +0F620000FFFF00AA0000068205481027151D0000FE2010270F610000048010270F610000 +018010070F61000003000000FFFF00AA00000682054810270F610000048010260F610000 +10270F610000030010070F6200000180FFFF00AA00000682054810270F62000001801027 +0F610000048010270F610000030010060F620000FFFF00AA00000682054810270F620000 +018010270F610000030010270F61000004801007151D0000FE200000FFFF00AA00000682 +054810260F6100001026151D00A010270F610000048010070F61000003000000FFFF00AA +00000682054810260F6200001026151D00A010270F610000048010070F61000003000000 +FFFF00AA0000068205481027151D0000FE2010270F610000030010270F61000004801006 +151D00A0FFFF00AA00000682054810270F620000030010270F610000018010260F610000 +10070F6100000480FFFF00AA00000682054810270F620000030010270F61000004801027 +0F610000018010060F620000FFFF00AA0000068205481027151D0000FE2010270F610000 +018010270F610000048010070F62000003000000FFFF00AA00000682054810260F610000 +10270F620000018010270F620000030010070F6100000480FFFF00AA0000068205481026 +0F62000010270F610000048010270F620000030010070F6200000180FFFF00AA00000682 +05481027151D0000FE2010270F620000018010270F620000030010070F61000004800000 +FFFF00AA0000068205481026151D00A010270F610000048010270F620000030010060F61 +00000000FFFF00AA0000068205481026151D00A010260F62000010270F61000004801007 +0F62000003000000FFFF00AA0000068205481026151D00A010270F610000048010270F62 +000003001007151D0000FE20FFFF00AA0000068205481027151D0000012010270F610000 +018010260F61000010070F6100000480FFFF00AA00000682054810260F62000010270F61 +0000048010270F61000001801007151D00000120FFFF00AA0000068205481027151D0000 +FE201027151D0000012010270F610000018010070F61000004800000FFFF00AA00000682 +054810270F620000018010270F610000048010260F6100001007151D00000120FFFF00AA +0000068205481027151D0000012010260F62000010270F610000048010070F6200000180 +FFFF00AA0000068205481027151D000001201027151D0000FE2010270F61000004801007 +0F62000001800000FFFF00AA0000068205481027151D0000012010270F61000004801026 +151D00A010060F6100000000FFFF00AA0000068205481027151D0000012010270F610000 +04801026151D00A010060F6200000000FFFF00AA0000068205481027151D000001201026 +151D00A010270F61000004801007151D0000FE20FFFF00AA00000682054810270F620000 +048010270F610000030010270F610000018010060F610000FFFF00AA0000068205481027 +0F620000048010260F62000010270F610000030010070F6100000180FFFF00AA00000682 +054810270F620000048010270F610000030010270F61000001801007151D0000FE200000 +FFFF00AA00000682054810270F620000048010270F620000018010270F61000003001006 +0F610000FFFF00AA00000682054810270F620000048010260F62000010270F6100000300 +10070F6200000180FFFF00AA00000682054810270F62000004801027151D0000FE201027 +0F610000030010070F62000001800000FFFF00AA00000682054810270F62000004801027 +0F61000003001026151D00A010060F6100000000FFFF00AA00000682054810270F620000 +048010270F61000003001026151D00A010060F6200000000FFFF00AA0000068205481027 +0F62000004801026151D00A010270F61000003001007151D0000FE20FFFF00AA00000682 +054810270F620000048010260F61000010270F610000018010070F6200000300FFFF00AA +00000682054810270F620000048010260F62000010270F610000018010070F6200000300 +FFFF00AA00000682054810270F620000048010270F620000030010270F61000001801007 +151D0000FE200000FFFF00AA00000682054810270F620000048010270F62000003001027 +0F620000018010060F610000FFFF00AA00000682054810270F620000048010270F620000 +018010270F620000030010060F620000FFFF00AA00000682054810270F62000004801027 +0F620000030010270F62000001801007151D0000FE200000FFFF00AA0000068205481027 +0F620000048010260F61000010270F62000003001006151D00A00000FFFF00AA00000682 +054810270F620000048010270F620000030010260F6200001006151D00A00000FFFF00AA +00000682054810270F62000004801027151D0000FE2010270F62000003001006151D00A0 +FFFF00AA00000682054810270F620000048010260F61000010270F61000001801007151D +00000120FFFF00AA00000682054810270F62000004801027151D0000012010270F610000 +018010060F620000FFFF00AA00000682054810270F620000048010270F61000001801027 +151D000001201007151D0000FE200000FFFF00AA00000682054810270F62000004801027 +151D0000012010260F61000010070F6200000180FFFF00AA00000682054810270F620000 +048010270F620000018010260F6200001007151D00000120FFFF00AA0000068205481027 +0F620000048010270F62000001801027151D0000FE201007151D000001200000FFFF00AA +00000682054810270F620000048010260F6100001026151D00A01007151D000001200000 +FFFF00AA00000682054810270F620000048010260F6200001026151D00A01007151D0000 +01200000FFFF00AA00000682054810270F62000004801027151D0000FE201026151D00A0 +1007151D00000120FFFF00AA0000068205481027151D000002A010260F61000010270F61 +0000018010070F6100000300FFFF00AA0000068205481027151D000002A010270F610000 +018010270F610000030010060F620000FFFF00AA0000068205481027151D000002A01027 +151D0000FE2010270F610000018010070F61000003000000FFFF00AA0000068205481027 +151D000002A010260F61000010270F610000030010070F6200000180FFFF00AA00000682 +05481027151D000002A010270F620000018010270F610000030010060F620000FFFF00AA +0000068205481027151D000002A010270F620000018010270F61000003001007151D0000 +FE200000FFFF00AA0000068205481027151D000002A010260F6100001026151D00A01007 +0F61000003000000FFFF00AA0000068205481027151D000002A010260F6200001026151D +00A010070F61000003000000FFFF00AA0000068205481027151D000002A01027151D0000 +FE2010270F61000003001006151D00A0FFFF00AA0000068205481027151D000002A01027 +0F620000030010270F610000018010060F610000FFFF00AA0000068205481027151D0000 +02A010270F620000030010270F610000018010060F620000FFFF00AA0000068205481027 +151D000002A01027151D0000FE2010270F610000018010070F62000003000000FFFF00AA +0000068205481027151D000002A010260F61000010270F620000018010070F6200000300 +FFFF00AA0000068205481027151D000002A010260F62000010270F620000030010070F62 +00000180FFFF00AA0000068205481027151D000002A01027151D0000FE2010270F620000 +018010070F62000003000000FFFF00AA0000068205481027151D000002A01026151D00A0 +10270F620000030010060F6100000000FFFF00AA0000068205481027151D000002A01026 +151D00A010260F62000010070F62000003000000FFFF00AA0000068205481027151D0000 +02A01026151D00A010270F62000003001007151D0000FE20FFFF00AA0000068205481027 +151D000002A01027151D0000012010270F610000018010060F610000FFFF00AA00000682 +05481027151D000002A010260F62000010270F61000001801007151D00000120FFFF00AA +0000068205481027151D000002A01027151D0000FE201027151D0000012010070F610000 +01800000FFFF00AA0000068205481027151D000002A010270F620000018010260F610000 +1007151D00000120FFFF00AA0000068205481027151D000002A01027151D000001201026 +0F62000010070F6200000180FFFF00AA0000068205481027151D000002A01027151D0000 +01201027151D0000FE2010070F62000001800000FFFF00AA0000068205481027151D0000 +02A01027151D000001201026151D00A010060F6100000000FFFF00AA0000068205481027 +151D000002A01027151D000001201026151D00A010060F6200000000FFFF00AA00000682 +05481027151D000002A01027151D000001201026151D00A01007151D0000FE2000030010 +000005DC05D50003000B000E0000012301330901210321032301170121035DCD01EECDFE +57023AFE5C98FD809977023918FEE302380571FAF30571FA2B01A1FE5F05D5C7FCF70000 +000600C90000054E05D5000C0014001C00200025002A0000132132161514071611140423 +210111333237112623031133363311262321113311013635342F0136353427C90266E7FA +C0FEFEF0FBFD860190EA3B33333BEAD624202024FDFECA0223D0D03ED0D005D5C0B1E55D +61FEE1C8DA02E9FD7B070277070288FDDC03021F02FAF3050DFB1144DCE5449636CDC436 +000400E80000063305D50008001000140019000013212000111000290101133332371126 +23211133030136111027E8020301B20196FE68FE50FDFD01920163F5A0A0F6FE70CA0102 +C6DFDF05D5FE97FE80FE7EFE960571FAF341048B41FAF3050DFB6B95017B01719C000000 +000200C90000055305D50003000F000001113311252115211121152111211521012DCAFE +D20478FD1A02C7FD3902F8FB760571FAF3050D6464FDE464FD736400000200C9000004EC +05D50009000D00001321152111211521112101231133C90423FD6F0251FDAFFE6E012ECA +CA05D564FDE464FD0F0571FAF300000000020073FFE3058B05F0001D0026000001262423 +220711163332373637112135211106042320001110002132041701110607061110171605 +647FFEFC85BB8787BB917F6556FE52021275FEE6A0FEA2FE75018B015E9201076FFC521C +1AA9A91A04E4614740FB3B40261F3501E764FD7F53550199016D016E01994846FB630449 +161AAFFEBAFEBBAF1A00000000020066000002BE05D50003000F00000111331125211523 +1133152135331123012ECAFE6E02586464FDA864640571FAF3050D6464FAF36464050D00 +0002FF96FE66025905D50008001400000111140736373635112521111006232135333236 +35012D56903F4FFED40190CDE3FEED3F866E0571FAB1F2640A4A5EEA050964FA93FEF2F4 +6496C200000200C90000063005D5000A000E0000132111013309012301112113113311C9 +019003039FFCA3039299FCC2FE7064CA05D5FD890277FD40FCEB02CFFD310571FAF3050D +000200C90000053205D500050009000013211121152113113311C9019202D7FB9764CA05 +D5FA8F64056FFAF3050D0000000300C40000076F05D5000C001000140000211101230103 +21112109012111011133112111331105DDFE5F47FE6201FE6E01D8017D017F01D7FED2CA +FA1DCA0571FBAE0452FA8F05D5FC0803F8FA2B0571FAF3050DFAF3050D00000000050072 +FFE305DA05F0000F0017001F002700280000012017161110070621202726111037361316 +20371126200703110607061017160111363736102726010326013ABCBEBDBDFEC6FEC5BC +BDBDBC476C01106C6CFEF06C6924209B9B2002DE231F9B9B1FFBCC05F0D2D5FEA0FEA1D4 +D3D3D201610162D3D2FA8E323204D73232FB6A04541D25B6FD9CB5250435FBAF1D24B602 +63B624FDF400000000020088FFDC049F05F60011002D0000010610161F011E0110073610 +262F012E0134251526200614161F011E01100420253516203634262F012E011024200150 +6488AC6FA9A826819CAA6EB09E030FCEFEC5A26D946ECAC9FEE0FE13FEFBDF0167A97A8A +6FCAB7011601C6052D5BFECA9A291A27B9FEFF4E580164BB271B279DE3B4707589E96924 +1B32EBFE58EE667C9592FD86201A2FCF018CF4000002FFFA000005B005D50003000B0000 +0111331125211521112111210272C8FCC005B6FDEEFE70FDEC0571FAF3050D6464FA8F05 +71000000000300B1FFE305F305D50006000D001D00000111100724190121111005261311 +2521111416203635112111100020001104C6580121FB8601215901FED301918001208001 +91FEACFD66FEAC0571FC99FEE6826201610340FCC0FE9E607C011F036764FC35F0D3D3F0 +03CBFC5CFEDCFED6012A012400020010000005B705D50003000A00001301330125210901 +3301219A01EEC8FE12FEAE019001D901DA64FDC7FECC0571FAF3050D64FB1704E9FA2B00 +00030044000008AE05D50003000700140000090133012101330125210901210901330121 +09012104C40144C8FEBCFB350144C8FEBCFEBB0190013A01390190013A013964FE89FE7C +FEC5FEC2FE800571FAF3050DFAF3050D64FB1204EEFB1204EEFA2B0510FAF0000002006C +0000060605D50003000F0000090133012521090133090121090123010128035CC6FCA4FE +7E01B80176018462FE4C023AFE48FE8AFE7C6401B60571FAF3050D64FDCE0232FD84FCA7 +0232FDCE027B00000002FFFC000005AC05D50008000E0000090133011121110121090123 +011133036E01DA64FDF0FE70FDF0019401AAFE32C801CCCA031302C2FCF2FD3902C7030E +FCF202AAFD56FD9D0004007BFFE30493047B00070022002C003300000134262716151133 +131121350E012322263534363B0135342623220607353E013320040135232207113E0137 +3605110E01151416042F963D4B8864FEB03FBC56ACCBFDFBD0759760B65477DF38011E01 +1AFEB0D0362F5E623B3AFE6743887A027FD386185D88FD59021BFD81AA6661C1A2BDC048 +7F8B2E2E742727FCFE8B5504FDED044E4847DC01FD12678B7774000000040094FFE30513 +0614000F001A001E002D00001321113E0133320010022322262715210134272627113637 +3637360111331101262322070607061514171633323794014E3AB17CCB00FFFFCB7CB13A +FEB204125E2C3C3C2C39160FFC528601F9191AA54B4D160E5F5DA51A190614FDA26461FE +BCFDF0FEBC6164A802749F823D20FC7A203D4F734B03DAFAAC0554FE64036968744A9E9F +8282030000020071FFE303E7047B001A0023000025150E01232200111000213216171526 +272623220711163332373625110607061514171603E74DA55DFDFED6012D010655A24C45 +6D474A58484351524856FE182C247B7A248F642424013E010E0112013A2323641F180F14 +FC571310131B03531A2580EAE683260000040094FFE305130614000F001A001E002D0000 +0111211121350E0123220210003332160114171617161711060706011133110111163332 +3736353427262726232203C5014EFEB23AB17CCBFF00FFCB7CB1FD760F16392C3C3C2C5E +032886FD81191AA55D5F0E164D4BA51A03B6025EF9ECA8646101440210014461FE5A9E4B +734F3D200386203D82029DFAAC0554FE64FC360382829F9E4A7468690003006FFFE3047D +047B00130018001F000001211316333237150E0123200011100033320015010611121713 +212E01232207047DFD8B014B5FD9C36DC36BFEF4FEC70129FCE50104FD23C601C5690201 +03CCA94A400204FE62155D752D290138010A01130143FEDAF701717AFEDBFEF38E01D3BE +E71100000002002F000003E30614000A0020000001060706151133113437362515232207 +061D0121152111211123353335343736330211672E3987161201F1AE943A390184FE7CFE +B1B0B05758BC05AE0A2D3891FBB6044A5F44388B643937926464FC1A03E66464BC545600 +00040071FE5604F0047B00070023002E003D0000011114071636190101351E013332363D +010E01232202100033321617352111100021222603141716171106070607060116333237 +3637363534272623220704066A05EBFC6C519E52B5B43AB17CCBFF00FFCB7CB13A014EFE +D6FECD72CA795E2C3C3C2C39160F012F191AA54B4D160E5F5DA51A1903FAFC3DCA8C059D +0112036FFA97802C2ABDBF7164610144021001446164A8FC2DFEE2FEE91D03779F823D20 +0386203D4F734BFDC2036968744A9E9F82820300000300BA000005480614000A000E0022 +00000116171615113311342726011133112721113E013332161511211134262322061511 +2104000908478C5335FCC287EB014F49C681D4DBFEAC6B6B8095FEB1040D0B0C68BEFD94 +0240C15B3A01B6FAB4054C64FD9E6564EFE8FD5C02D09F9EBEA4FD55000300E600000235 +061400030007000B0000011133110333152307211121014A87A0B9B94B014FFEB103FCFC +6803980218E9CBFBA00000000003FFD7FE56020C061400080016001A0000051123111407 +36373607233533323736351121111407060333152301A8872547283DF4DD316C2425014F +515261B8B8160412FBD0B5540F3048F46430319904ACFB8CD6606007BEE90000000200BA +000005320614000A000E0000132111013309012301112113113311BA014F02848CFD4802 +D196FD6DFEB164870614FC6901E3FDF6FDAA0223FDDD05B0FAB4054C000200E6000002EA +0614000800140000011417161726351123132227263511211114163315014A3D28472587 +F5B65251014F4C690194A048300F54B50430FA5A6060D6047EFB4A9C5E640000000400BA +00000887047B002200260031003C00001321153E01333216173E01333216151121113426 +232206151121113426232206151121131133112516171615113311342726251617161511 +3311342726BA014F49C681D49D1B54DE81D4DBFEAC6B6B8095FEAC6B6B8095FEB1648702 +5B0908478C533502E30908478C53350460AE6564AC4A8076EFE8FD5C02D09F9EBEABFD5C +02D09F9EBEA4FD5503FCFC680398110B0C68BEFD940240C15B3A130B0C68BEFD940240C1 +5B3A0000000300BA00000548047B000A000E002200000116171615113311342726251133 +112721153E0133321615112111342623220615112104000908478C5335FCC287EB014F49 +C681D4DBFEAC6B6B8095FEB1040D0B0C68BEFD940240C15B3A02FC68039864AE6564EFE8 +FD5C02D09F9EBEA4FD55000000040071FFE30475047B000B0013001B0023000001320011 +10002322001110001316323711262207031106070610171601113637361027260273F001 +12FEEEF0F1FEEF01118233783333783364342770702701DA3328707028047BFEC8FEECFE +EDFEC70139011301140138FBD70B0B03BA0B0BFC6B03701D2D80FE24802D0352FC921C2D +8101DB802D00000000040071FE5604F0047B000F001A001E002D00002511211121153E01 +333200100223222601342726272627113637360111231101112623220706151417161716 +333201BFFEB2014E3AB17CCB00FFFFCB7CB1028A0F16392C3C3C2C5EFCD886027F191AA5 +5D5F0E164D4BA51AA8FDAE0608A86461FEBCFDF0FEBC6101A69E4B734F3D20FC7A203D82 +FD6F0548FAB8019003CA0382829F9E4A7468690000040071FE5604F0047B000F001A001E +002D0000250E012322021000333216173521112101141716171106070607060133112301 +163332373637363534272623220703A23AB17CCBFF00FFCB7CB13A014EFEB2FD3C5E2C3C +3C2C39160F03288686FE07191AA54B4D160E5F5DA51A19A864610144021001446164A8F9 +F803949F823D200386203D4F734BFC320548FC48036968744A9E9F8282030000000200BA +000003DF047B000300150000011133112721153E0133321F01152E01232206151121011E +86EA014E3ABA851B0F341F492C9CA7FEB203FCFC68039864AE66630307851211CBBEFD7A +0002006FFFE303C7047B001D0045000001060706151417161F0116171615140736373635 +3427262F012627263534031E013332363534262F012E0135343633321617152E01232206 +1514161F011E01151406232226270169271C4B26277134A53D4225251D552B2E84339039 +47D353A04F6A714C91348F76E0CE66B44C4A5C5F6F70507833A184F7D85AC36C03F90F17 +3D766630332210333B407B523F101742736C3337270F2A37436F54FCFE37385E554E4F2C +102C9788A6B5201F7A31245958444C230F2F9E90A4C025250002003700000388059E0007 +001B0000252637112311061613112115211114163B011521222635112335331101D02A03 +87015C90017BFE854B73BDFEADD5A287876A557C03FFFC37AD4E0528FEAC64FD55894E64 +9FD2027564015400000300B1FFE505440460000A000E0022000025262726351123111417 +16051123111721350E0123222635112111141633323635112101F90908478C5335034387 +EBFEAC49C681D4DB01546B6B80950154530B0C68BE026CFDC0C15B3A020398FC6864AE65 +64EFE802A4FD309F9EBEA402AB0000000002003D000004B104600003000A000013013301 +25210901330121CD015983FEA3FEF10154015E015E64FE5CFED403FCFC68039864FC5403 +ACFBA00000030056000006F204600003000700140000011333032113330325211B01211B +013301210B012103F1F087F2FC62F087F2FEF90154E6E5014EE6E564FEDBFECAF1F2FEC7 +03FCFC680398FC68039864FC96036AFC96036AFBA00396FC6A0000000002004C0000051C +04600003000F000009013301252109013309012109012301011302AB97FD55FEA2019001 +1D011D7CFEA501E5FE70FEE3FEE37C015B03FCFC68039864FE81017FFE2DFD73017FFE81 +01D300000002003DFE5604C30460000300120000130137012521090133010E012B013533 +323637D201B23EFE95FEE6015E015D015F6CFE1450927C939358512B03FCFBDB99038C64 +FC970369FB38C77B64435900000200580000046204600003000D00000901330125211501 +211521350121035CFD768C028AFC8903F1FD770289FBF60289FD9003FCFC6803986464FC +6864640398000000FFFF00100000056805D5100600240000FFFF00C9000004EC05D51006 +00250000FFFF0073FFE3052705F0100600260000FFFF00C9000005B005D5100600270000 +FFFF00C90000048B05D5100600280000FFFF00C90000042305D5100600290000FFFF0073 +FFE3058B05F01006002A0000FFFF00C90000053B05D51006002B0000FFFF0097000002F6 +05D5100617730000FFFFFF96FE66019305D51006002D0000FFFF00C90000056A05D51006 +002E0000FFFF00C90000046A05D51006002F0000FFFF00C90000061F05D5100600300000 +FFFF00C90000053305D5100600310000FFFF0073FFE305D905F0100600320000FFFF00C9 +0000048D05D5100600330000FFFF0073FEF805D905F0100600340000FFFF00C900000554 +05D5100600350000FFFF0087FFE304A205F0100600360000FFFFFFFA000004E905D51006 +00370000FFFF00B2FFE3052905D5100600380000FFFF00100000056805D5100600390000 +FFFF0044000007A605D51006003A0000FFFF003D0000053B05D51006003B0000FFFFFFFC +000004E705D51006003C0000FFFF005C0000051F05D51006003D0000FFFF007BFFE3042D +047B100600440000FFFF00BAFFE304A40614100600450000FFFF0071FFE303E7047B1006 +00460000FFFF0071FFE3045A0614100600470000FFFF0071FFE3047F047B100600480000 +FFFF002F000002F80614100600490000FFFF0071FE56045A047B1006004A0000FFFF00BA +0000046406141006004B0000FFFF00C10000017906141006004C0000FFFFFFDBFE560179 +06141006004D0000FFFF00BA0000049C06141006004E0000FFFF00C10000023906141006 +17690000FFFF00BA0000071D047B100600500000FFFF00BA00000464047B100600510000 +FFFF0071FFE30475047B100600520000FFFF00BAFE5604A4047B100600530000FFFF0071 +FE56045A047B100600540000FFFF00BA0000034A047B100600550000FFFF006FFFE303C7 +047B100600560000FFFF0037000002F2059E100600570000FFFF00AEFFE30458047B1006 +00580000FFFF003D0000047F0460100600590000FFFF00560000063504601006005A0000 +FFFF003B0000047904601006005B0000FFFF003DFE56047F04601006005C0000FFFF0058 +000003DB04601006005D000000040088FFE3049005F00007000C00140019000000200010 +00200010013611102F01262207111632370106111017019001F6010AFEF6FE0AFEF802D8 +CCCC64337B34347B33FEBACACA05F0FE73FD0DFE73018D02F3FC238F01D401D58F300F0F +FAD90F0F04F790FE2CFE2D900002006B000004AC05D50003000E00000111331137112115 +213521110535250236CA62014AFBDC014AFE9901670571FAF3050D64FA8F6464050D4864 +48000000000200820000049A05F000100028000037210136373610272627171615140E01 +03012433320415140E01070121152137360037361026232207FB0100012960224C6C4462 +035A4582D1FEE70103B5F3011F30515DFEFA0228FBE80102019E3D79876D98C16401CD95 +408F01395C39180470A864BACFFEDD043968F4CC62AC9691FE69646405024162BF0119A8 +950000000003009CFFE3047305F00024002A0030000005222735171633323711262B0135 +333237112623220F013536333204151406071E0115140435363534262719013E01353402 +2BBBD421C4AA654F363FCCD43B323A4C9CD91BE6AFE6010C8D808EA2FED0CC755745641D +4A6A0C441002950A640802200A3C086840D1B27CAA211FC590DDF29255E86C8D240296FE +1A1D7958AC0000000003005A000004B805D5000300060011000001231333090121032111 +331523112111213503A7CB03C8FED4FE5101AF030193ADADFE70FDDF0571FAF304C1FCE3 +03CDFC3364FE5C01A47F000000030094FFE3048C05D500040008001E0000011123113601 +36102703112623220711211521110C0110042122273516333201FFC86701B6CCCC64316A +B4CE034BFE4501110118FED4FEBDB9D0BDDC81039101E0FDFD1BFCE65C01DC60FD4902D7 +0A4B02EF64FE2309F5FE46F93C88640000040071FFE304AA05F000040009001100250000 +2536111027010611101F0116323711262207133217152726200711363332001000232000 +1000037CCACAFE26CDCD643AAB2D28AB3FF4A7A82299FED65E4268F50105FEF0F6FEDFFE +EE01507063010C01185C01CAB3FE91FE239532100803220613028C3C6A0C362DFE6616FE +EFFE2FFEEA018D02DB01A50000020041000004D605D50003000A00000901330125211501 +2101210395FE0DD601F3FBD60495FDE7FE53021AFD170571FAF3050D6464FA8F05710000 +0007008BFFE3048B05F00004000C00240029003100390041000025362726270010171617 +11060713352E01353424201615140607151E011514042024353436131106070605163237 +11262207190116323711262205113637361027260358D10707C3FDBA5F212C2C204C8090 +00FF01BDFE908091A2FEF7FE12FEF7A390C307070135317031317031307230307201062C +20605F21644AEAE44E0227FEF84D1B1201FC121BFDDC0220B180B3D0D0B380B1200123C5 +8FD9E8E8D98FC5FD6C02664EE4EA6106060296070702A2FDD0070702300721FE04121B4D +01084D1B0004006AFFE304A305F0000400090011002500000106111017013611102F0126 +22071116323703222735171620371106232200100033200010000198CACA01DACDCD643A +AB2D28AB3FF4A7A82299012A5E4268F5FEFB0110F601210112FEB0056363FEF4FEE85CFE +36B3016F01DD95321008FCDE0613FD743C6A0C362D019A16011101D10116FE73FD25FE5B +FFFF0087FFE3048F05F0100600130000FFFF00E10000045A05D5100600140000FFFF0096 +0000044A05F0100600150000FFFF009CFFE3047305F0100600160000FFFF0064000004A4 +05D5100600170000FFFF009EFFE3046405D5100600180000FFFF008FFFE3049605F01006 +00190000FFFF00A80000046805D51006001A0000FFFF008BFFE3048B05F01006001B0000 +FFFF0081FFE3048705F01006001C0000FFFF00C1000001790614100605540000FFFF0082 +FEA206EB029D100605550000FFFF009DFE0C05280366100605590000FFFF007DFFDA031B +03521006055C0000FFFFFFABFE0C03400286100605700000FFFFFFABFE0C036203B61006 +055F0000FFFF009DFE0C052803661006055A0000FFFF0090000006DC0614100605640000 +FFFF0082FE0C05C0034A100605720000FFFF0090FFC905C706141006056B0000FFFF0090 +FEC8051806141006056C0000FFFF008CFE14045E02F31006056D0000FFFF0093FEB50548 +03B61006056E0000FFFF0082FE0C091A02EE100605600000FFFF0075FE0C04B2042A1006 +05660000FFFF0082FFA4079E0514100605690000FFFF0082FE0C091302E5100605620000 +FFFF006BFE48059B05141006056A0000FFFFFFABFE0C036202261006055E0000FFFF0082 +FE0C091A04B0100605610000FFFF0082FFEC06EB0320100605570000FFFF0082FFEC06EB +041A100605580000FFFF009DFE0C052804B01006055B0000FFFF007DFFDA031B04B01006 +055D0000FFFF0082FE0C091303B6100605630000FFFF0090000006DC0614100605650000 +FFFF0075FE0C04B20546100605670000FFFF0082FFEC06EB029D1006058E0000FFFF0093 +FEB5054802EE100605D30000FFFF0082FFA4079E03D9100605BA0000FFFF006BFE48059B +03D91006058F0000FFFFFFECFED4028102581226183C00001007172101DAFED4FFFFFFEC +FED404C0032F1226160B0000100717210271FED40003FFECFFBD045303E5000F002D003D +000001363736353427262322070615141716073237262726353437361726273516170417 +16151407062322270623213501161514070607163332373635342726022A2422270F163A +4117160715F9344E2D0F122D304D2C51DBDC01053B0E284AAF839A7892FEE1034106620C +1134317E1007211D010D1B40483D2E25342D2B231E2576880B69454E4054535A09270EB9 +43B4D6D93439673D716C29B8014425278A720E132E431E213C4E45000001FFEC000004C0 +032F001E0000012627262726073536373217041715060706070607062B01353332373637 +3603B7536755B452A1484BA2AC0102DC4C58A64A914BDDA3E4C8E38168745E021D171713 +100706B807012334629A18346242811B50B8483A6A560000FFFFFFECFED402ED02581027 +1722015DFED41206183C00000001FFEC000004C90614001F000029013521323736353427 +012635343736370115050607061514170116151407060230FDBC0230932E1036FECA320A +1F6802E9FDAD4715061F0113663E5FB86824244542017C3D511E26762B0136BAFA1E280B +192025FEB67A7C71659B00000001FFEC000002A90614000D000001140706232135213237 +3635113302A957509AFE840145632C31B80173B2655CB82C316A04950002FFECFFCE043C +026D00150025000025062322270623213521323637363736333217161514251633323736 +3534272623220706070603FA6193CE6E4068FECA010A34570514875E51B05B61FDF03E9F +2F1334282A582D2E2A130734666230B84731B550385459B88F4E451235454B26291E1B64 +25000000FFFFFFEC0000028103E81226183C00001007172101DA03520001FFECFFE3066E +02EE00310000250607062B0135333237363D013315141716333237363533141716373619 +013311140706070623222726270607062322272601E42B384C63E6AF632C50B82C2B686D +2C25B813406E8EB85C4B6625233F3789123060444162483C8A3B212EB82C5064C09C4064 +637A67C2CD32AA010201160126FEAAC7715C1809193D849C21183128FFFFFFEC000003F8 +042A100614C20000FFFFFFEC0000033F05141027172101A9047E1006172B00000002FFEC +0000069602E5000C002E000001060733323736353427262322032122272627062B013533 +3237363D013315141617363736373633321716151407060434A891BBED81BA8B25304CBA +FEEC4A4E412766ACE6AF632C50B8122D655F99904B55736BBAB8CA01E66CC23F5B468713 +05FDC92A233D8AB82C5064724E1850328654893F212744ECA96D7800FFFFFFEC0000033F +0514100614CE0000FFFFFFECFFE3066E04B01226161100001007172302EE0320FFFFFFEC +000002ED03E81226183C000010071722015D0352FFFFFFEC000002ED04E210271723015D +03521206183C0000FFFFFFEC000004C0044C1226160B000010071721027103B6FFFFFFEC +0000069603B610271721023F0320120616140000FFFFFFEC000003F80546100614C60000 +FFFF00C1FED4048D06141027172103E6FED4120616310000FFFF00C1FED406CC06141027 +1721047DFED41206161F0000000300C1FFBD062D06140027003700470000131133111417 +163B0132372627263534373617262735161704171615140706232227062B012227260116 +151407060716333237363534272605363736353427262322070615141716C1B8312C638E +344E2D0F122D304D2C51DFD80100400E284AAF839A7892E39A5057044606620C1134317D +1107211DFEBA2422270F163A4117160715017304A1FB6B6A312C0B69454E4054535A0927 +0EB944B3D1DE3439673D716C295C65013B252793690E132E431E213C4E45A91B40483D2E +25342D2B231E2576000100C1000006CC061400280000131133111417163B013237363736 +372627262726073536373217041715060706070607062B01222726C1B8312C6387E38168 +745E65536755B452A1484BA2AC0102DC4C58A64A914BDDA3DA9A5057017304A1FB6B6A31 +2C483A6A5623171713100706B807012334629A18346242811B505C65000200C100000844 +0614000C0028000025333237362726272623220706251133111417163321113311122536 +3332171615140706290122272604A9BBED81BB01028925305377B1FB90B8312C63012EB8 +D901145C447569BAB8CAFEE4FC5C9A5057B83F5D4487130551780504A1FB6B6A312C055C +FB0E013F63212745EBA96D785C650000FFFF00C1FED404F90614102717220369FED41206 +16310000000100C10000063F061400290000131133111417163321323736353427012635 +343736370115050607061514170116151407062321222726C1B8312C630159932E1036FE +CA320A1F6802E9FDAD4715061F0113663E5FDFFE5C9A5057017304A1FB6B6A312C682424 +4542017C3D511E26762B0136BAFA1E280B192025FEB67A7C71659B5C65000000000200C1 +FFCE06160614000F002F0000251633323736353427262322070607060506232227062B01 +222726351133111417163B0132363736373633321716151404063E9F2F1334282A582D2E +2A130701C46193CE6E4068FA9A5057B8312C639734570514875E51B05B61C7451235454B +26291E1B6425B26662305C65B204A1FB6B6A312C4731B550385459B88F000000FFFF00C1 +0000048D06141027172103E60352120616310000000100C1FFE3087A0614003B00001311 +33111417163B013237363D01331514171633323736353314171637361901331114070607 +06232227262706070623222726270607062B01222726C1B8312C636E632C50B82C2B686D +2C25B813406E8EB85C4B6625233F3789123060444162483C292B384C63DC9A5057017304 +A1FB6B6A312C2C5064C09C4064637A67C2CD32AA010201160126FEAAC7715C1809193D84 +9C21183128493B212E5C6500000100C100000668061400240000131133111417163B0132 +372627263510373633152206141716333237251505042B01222726C1B8312C6373A1CA4B +3458CC7DFBDABA3E5346374A0120FE5CFEA2B2B29A5057017304A1FB6B6A312C5D183B64 +8C01087D4DA989FF34462181B8C5A45C65000000FFFF00C1000005AF0614102717210419 +047E120616320000000200C1000008A20614002B00380000131133111417163B01323736 +3D01331514161736373637363332171615140706290122272627062B0122272601060733 +323736353427262322C1B8312C636E632C50B8122D655F99904B55736BBAB8CAFEE4FEEC +4A4E412766ACDC9A5057057FA891BBED81BA8B25304C017304A1FB6B6A312C2C5064724E +1850328654893F212744ECA96D782A233D8A5C6501256CC23F5B468713050000FFFF00C1 +000005AF061410271722039C047E120616320000FFFF00C1FFE3087A06141027172304FA +0320120616250000FFFF00C1000004F906141027172203690352120616310000FFFF00C1 +000004F906141027172303690352120616310000FFFF00C1000006CC061410271721047D +03B61206161F0000FFFF00C1000008A2061410271721044B0320120616280000FFFF00C1 +0000084406141027172105370352120616200000FFFF00C10000066806141027172103E7 +04B0120616260000000100C10000048D0614001700002506232122272635113311141716 +3B013237363D013315140431489DFEB69A5057B8312C63DC632C31B856565C65B204A1FB +6B6A312C2C316AD9D9BB0000000200C1000005AF0614002A003A00001311331114171633 +213237363736370607062726353437363736333217161716151407060706232122272601 +363534272607060706071417163332C1B8312C63011454974F3C1F0F3F61824E62081796 +4E4C5A42602E174E4A7C6D91FE659A5057040529421F2C3428280137282A48017304A1FB +6B6A312C170C5D30323702024557812D33944524324866338CD08F882C275C6501582B4D +3B331901012A293350261900000200C800460A21053B0003000700000133112301112111 +052E8D8DFB9A0959015F02C3FC2404F5FB0B0000000300C800460A21053B00030007000B +0000371121112711211125113311C809598DF7C103D98D4604F5FB0B8D03DBFC258C02C3 +FD3D0000000400C800460A21053B0016001A001E00220000013437363736333217161716 +1514070607062227262726011121112711211125113311073010111A172624181A111010 +111A184918191210F99809598DF7C103D98D02C324171A121010101C1724251819121010 +111A18FDA804F5FB0B8D03DBFC258C02C3FD3D00000500C800460A21053B0016002C0030 +003400380000012227262726343736373633321716171615140706070601222726272634 +37363736333217161716140706070601112111271121112511331108B124171A12101010 +1C1724251819121010111A18FDC524181A120F0F111B172524181A121010111B17FA0809 +598DF7C103D98D013F10101B18481819131010111B172524181A1110021110111C174818 +1A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D000600C800460A21 +053B0016002B004100450049004D00000122272627263437363736333217161716151407 +060706012227262726343736373632171617161407060706012227262726343736373633 +3217161716140706070601112111271121112511331108B124171A121010101C17242518 +19121010111A18FECE24171A131010111C1748181A121010111B17FED224181A120F0F11 +1B172524181A121010111B17FA0809598DF7C103D98D013F10101B18481819131010111B +172524181A111001090F111B174A171A121010111B1748191A120F010810111C1748181A +121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D00000700C800460A21 +053B0015002B00410057005B005F00630000013437363736321716171614070607062322 +272627260034373637363332171617161514070607062227262724343736373632171617 +161514070607062322272627013437363736333217161716140706070622272627260111 +21112711211125113311062710111A1848181A121010101C17242518191210021210111B +1724251819121010111A1848181A12FDDE10111A1848181A121010101C17242518191202 +0210111B1724251819121010111A1848181A1210F88F09598DF7C103D98D03CC24181A11 +1010101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A +1719131010111B172524181A111010101B025324181A111010101B174A1719131010111B +17FC9F04F5FB0B8D03DBFC258C02C3FD3D000000000800C800460A21053B0015002B0041 +0057006E00720076007A0000013437363736321716171614070607062322272627260034 +373637363332171617161514070607062227262724343736373632171617161514070607 +062322272627013437363736333217161716140706070622272627260534373637363332 +171617161514070607062227262726011121112711211125113311062710111A1848181A +121010101C17242518191210021210111B1724251819121010111A1848181A12FDDE1011 +1A1848181A121010101C172425181912020210111B1724251819121010111A1848181A12 +10FEF710111A172624181A111010111A184918191210F99809598DF7C103D98D03CC2418 +1A111010101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B +174A1719131010111B172524181A111010101B025324181A111010101B174A1719131010 +111B17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02 +C3FD3D00000900C800460A21053B0014002A004000550069007F00830087008B00000122 +272627263437363736321716171614070607060322272627263534373637363217161716 +140706070620222726272634373637363332171617161714070607002227262726273437 +36373632171617161407060702222726272E013736373632171617161407060700222726 +272635343736373633321716171E0107060701112111271121112511331107A424171A13 +1010111C1748181A121010111B172524171A131010111C1748181A121010111B17FECD4A +1819121010111A172624171A11100111111A02554A161A12100111111B1748181A120F0F +111B174A161A12100111111B1748181A120F0F111BFD7E4A1819121010111A172624171A +11100111111AFA1C09598DF7C103D98D035210111C1748181A120F0F111B174A161A1310 +FDEF10111B172524171A131010111C1748181A121010111B1847181A131010111C172424 +181A12020210111B172524181A111010101B1848181913FDDE10111B1847181A13101011 +1C1748181A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC +258C02C3FD3D0000000400C800460A21053B00150019001D002100000134373637363217 +161716151407060706222726272601112111271121112511331102CA10111A174A181912 +1010111A1848191A1110FDFE09598DF7C103D98D02C324171A121010101C172425181912 +1010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D000000000500C800460A21053B0015 +002C00300034003800000134373637363217161716151407060706222726272625343736 +3736333217161716151407060706222726272601112111271121112511331102CC10101B +174A1719131010111B1848181A1110046410111A172624181A111010111A184918191210 +F99809598DF7C103D98D02C324171A121010101C1724251819121010111A182524171A12 +1010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D00000600C8 +00460A21053B0015002C00420046004A004E000001343736373632171617161514070607 +062227262726012227262726343736373633321716171615140706070601222726272634 +37363736333217161716140706070601112111271121112511331102CA10111A174A1819 +121010111A1848191A111005E724171A121010101C1724251819121010111A18FDC52418 +1A120F0F111B172524181A121010111B17FA0809598DF7C103D98D02C324171A12101010 +1C1724251819121010111A18FEA110101B18481819131010111B172524181A1110021110 +111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D000000 +000700C800460A21053B0015002C00410057005B005F0063000001343736373632171617 +161514070607062227262726012227262726343736373633321716171615140706070601 +222726272634373637363217161716140706070601222726272634373637363332171617 +16140706070601112111271121112511331102CA10111A174A1819121010111A1848191A +111005E724171A121010101C1724251819121010111A18FECE24171A131010111C174818 +1A121010111B17FED224181A120F0F111B172524181A121010111B17FA0809598DF7C103 +D98D02C324171A121010101C1724251819121010111A18FEA110101B1848181913101011 +1B172524181A111001090F111B174A171A121010111B1748191A120F010810111C174818 +1A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D000800C800460A21 +053B0015002B00410057006D007100750079000001343736373632171617161514070607 +062227262726013437363736321716171614070607062322272627260034373637363332 +171617161514070607062227262724343736373632171617161514070607062322272627 +0134373637363332171617161407060706222726272601112111271121112511331102CA +10111A174A1819121010111A1848191A1110035D10111A1848181A121010101C17242518 +191210021210111B1724251819121010111A1848181A12FDDE10111A1848181A12101010 +1C172425181912020210111B1724251819121010111A1848181A1210F88F09598DF7C103 +D98D02C324171A121010101C1724251819121010111A18012E24181A111010101B174A17 +19131010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B +172524181A111010101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B +8D03DBFC258C02C3FD3D0000000900C800460A21053B0015002B00410057006D00840088 +008C00900000013437363736321716171615140706070622272627260134373637363217 +161716140706070623222726272600343736373633321716171615140706070622272627 +243437363736321716171615140706070623222726270134373637363332171617161407 +060706222726272605343736373633321716171615140706070622272627260111211127 +1121112511331102CA10111A174A1819121010111A1848191A1110035D10111A1848181A +121010101C17242518191210021210111B1724251819121010111A1848181A12FDDE1011 +1A1848181A121010101C172425181912020210111B1724251819121010111A1848181A12 +10FEF710111A172624181A111010111A184918191210F99809598DF7C103D98D02C32417 +1A121010101C1724251819121010111A18012E24181A111010101B174A1719131010111B +17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A11 +1010101B025324181A111010101B174A1719131010111B17E424171A121010101C172425 +1819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D00><000A00C800460A21053B +0015002A00400056006B007F00950099009D00A100000134373637363217161716151407 +060706222726272625222726272634373637363217161716140706070603222726272635 +343736373632171617161407060706202227262726343736373633321716171617140706 +0700222726272627343736373632171617161407060702222726272E0137363736321716 +17161407060700222726272635343736373633321716171E010706070111211127112111 +2511331102CA10111A174A1819121010111A1848191A111004DA24171A131010111C1748 +181A121010111B172524171A131010111C1748181A121010111B17FECD4A181912101011 +1A172624171A11100111111A02554A161A12100111111B1748181A120F0F111B174A161A +12100111111B1748181A120F0F111BFD7E4A1819121010111A172624171A11100111111A +FA1C09598DF7C103D98D02C324171A121010101C1724251819121010111A18B410111C17 +48181A120F0F111B174A161A1310FDEF10111B172524171A131010111C1748181A121010 +111B1847181A131010111C172424181A12020210111B172524181A111010101B18481819 +13FDDE10111B1847181A131010111C1748181A12020210111B172524181A111010101B18 +48181913FCE304F5FB0B8D03DBFC258C02C3FD3D0000000500C800460A21053B0015002B +002F00330037000000222726272634373637363332171617161514070607012227262726 +3534373637363217161716140706070601112111271121112511331104724A1719131010 +111B172524181A111010111AFDAD24181A111010101B174A1719131010111B17FE6D0959 +8DF7C103D98D014010111A1848181A121010111B172425181912020210111B1724251819 +121010111A1848181A1210FCF404F5FB0B8D03DBFC258C02C3FD3D000000000600C80046 +0A21053B0016001A001E00220038004E0000013437363736333217161716151407060706 +222726272601112111271121112511331104222726272634373637363332171617161514 +07060701222726272635343736373632171617161407060706073010111A172624181A11 +1010111A184918191210F99809598DF7C103D98DFEB74A1719131010111B172524181A11 +1010111AFDAD24181A111010101B174A1719131010111B1702C324171A121010101C1724 +251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A121010 +111B172425181912020210111B1724251819121010111A1848181A121000000700C80046 +0A21053B0016002C003000340038004E0064000001222726272634373637363332171617 +161514070607060122272627263437363736333217161716140706070601112111271121 +112511331104222726272634373637363332171617161514070607012227262726353437 +3637363217161716140706070608B124171A121010101C1724251819121010111A18FDC5 +24181A120F0F111B172524181A121010111B17FA0809598DF7C103D98DFEB74A17191310 +10111B172524181A111010111AFDAD24181A111010101B174A1719131010111B17013F10 +101B18481819131010111B172524181A1110021110111C1748181A121010111B1847181A +1310FCF604F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A121010111B1724251819 +12020210111B1724251819121010111A1848181A1210000800C800460A21053B0016002B +004100450049004D00630079000001222726272634373637363332171617161514070607 +060122272627263437363736321716171614070607060122272627263437363736333217 +161716140706070601112111271121112511331104222726272634373637363332171617 +1615140706070122272627263534373637363217161716140706070608B124171A121010 +101C1724251819121010111A18FECE24171A131010111C1748181A121010111B17FED224 +181A120F0F111B172524181A121010111B17FA0809598DF7C103D98DFEB74A1719131010 +111B172524181A111010111AFDAD24181A111010101B174A1719131010111B17013F1010 +1B18481819131010111B172524181A111001090F111B174A171A121010111B1748191A12 +0F010810111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD +3D1F10111A1848181A121010111B172425181912020210111B1724251819121010111A18 +48181A121000000900C800460A21053B00140029003E00530057005B005F007400890000 +013437363736321716171614070607062227262726003437363736321716171615140706 +070622272627243437363736321716171615140706070622272627013437363736321716 +171614070607062227262726011121112711211125113311042227262726343736373633 +321716171614070607012227262726343736373632171617161407060706062710111A18 +48181A121010101C174918191210021210111B17491819121010111A1848181A12FDDE10 +111A1848181A121010101C1749181912020210111B17491819121010111A1848181A1210 +F88F09598DF7C103D98DFEB7491819131010111B172524181A111010111AFDAD24181A11 +1010101B17491819131010111B1703CC24181A111010101B17491819131010111B17FDE9 +491819131010111B172524181A111010101B17491819131010111B172524181A11101010 +1B025324181A111010101B17491819131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD +3D1F10111A1848181A121010111B1749181912020210111B17491819121010111A184818 +1A1210000000000A00C800460A21053B0015002B00410057006E00720076007A009000A6 +000001343736373632171617161407060706232227262726003437363736333217161716 +151407060706222726272434373637363217161716151407060706232227262701343736 +373633321716171614070607062227262726053437363736333217161716151407060706 +222726272601112111271121112511331104222726272634373637363332171617161514 +07060701222726272635343736373632171617161407060706062710111A1848181A1210 +10101C17242518191210021210111B1724251819121010111A1848181A12FDDE10111A18 +48181A121010101C172425181912020210111B1724251819121010111A1848181A1210FE +F710111A172624181A111010111A184918191210F99809598DF7C103D98DFEB74A171913 +1010111B172524181A111010111AFDAD24181A111010101B174A1719131010111B1703CC +24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A111010 +101B174A1719131010111B172524181A111010101B025324181A111010101B174A171913 +1010111B17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC25 +8C02C3FD3D1F10111A1848181A121010111B172425181912020210111B17242518191210 +10111A1848181A121000000B00C800460A21053B0014002A004000550069007F00830087 +008B00A100B7000001222726272634373637363217161716140706070603222726272635 +343736373632171617161407060706202227262726343736373633321716171617140706 +0700222726272627343736373632171617161407060702222726272E0137363736321716 +17161407060700222726272635343736373633321716171E010706070111211127112111 +251133110422272627263437363736333217161716151407060701222726272635343736 +37363217161716140706070607A424171A131010111C1748181A121010111B172524171A +131010111C1748181A121010111B17FECD4A1819121010111A172624171A11100111111A +02554A161A12100111111B1748181A120F0F111B174A161A12100111111B1748181A120F +0F111BFD7E4A1819121010111A172624171A11100111111AFA1C09598DF7C103D98DFEB7 +4A1719131010111B172524181A111010111AFDAD24181A111010101B174A171913101011 +1B17035210111C1748181A120F0F111B174A161A1310FDEF10111B172524171A13101011 +1C1748181A121010111B1847181A131010111C172424181A12020210111B172524181A11 +1010101B1848181913FDDE10111B1847181A131010111C1748181A12020210111B172524 +181A111010101B1848181913FCE304F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A +121010111B172425181912020210111B1724251819121010111A1848181A121000000006 +00C800460A21053B0015002B004100450049004D00000022272627263437363736333217 +161716151407060725222726272634373637363332171617161407060706012227262726 +3534373637363217161716140706070601112111271121112511331104724A1719131010 +111B172524181A111010111AFEB624171A121010101C1724251819121010111A18FED224 +181A111010101B174A1719131010111B17FE6D09598DF7C103D98D014010111A1848181A +121010111B172425181912F910111A174A181A111010111A184918191210010910111B17 +24251819121010111A1848181A1210FCF404F5FB0B8D03DBFC258C02C3FD3D0000000007 +00C800460A21053B0015002B00410058005C006000640000002227262726343736373633 +321716171615140706072522272627263437363736333217161716140706070601222726 +272635343736373632171617161407060706053437363736333217161716151407060706 +222726272601112111271121112511331104724A1719131010111B172524181A11101011 +1AFEB624171A121010101C1724251819121010111A18FED224181A111010101B174A1719 +131010111B1704D510111A172624181A111010111A184918191210F99809598DF7C103D9 +8D014010111A1848181A121010111B172425181912F910111A174A181A111010111A1849 +18191210010910111B1724251819121010111A1848181A12108F24171A121010101C1724 +251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D00000800C800460A21053B +0015002B00410058006E00720076007A0000002227262726343736373633321716171615 +140706072522272627263437363736333217161716140706070601222726272635343736 +373632171617161407060706012227262726343736373633321716171615140706070601 +22272627263437363736333217161716140706070601112111271121112511331104724A +1719131010111B172524181A111010111AFEB624171A121010101C172425181912101011 +1A18FED224181A111010101B174A1719131010111B17065624171A121010101C17242518 +19121010111A18FDC524181A120F0F111B172524181A121010111B17FA0809598DF7C103 +D98D014010111A1848181A121010111B172425181912F910111A174A181A111010111A18 +4918191210010910111B1724251819121010111A1848181A1210FDED10101B1848181913 +1010111B172524181A1110021110111C1748181A121010111B1847181A1310FCF604F5FB +0B8D03DBFC258C02C3FD3D000000000900C800460A21053B0015002B00410058006D0083 +0087008B008F000000222726272634373637363332171617161514070607252227262726 +343736373633321716171614070607060122272627263534373637363217161716140706 +070601222726272634373637363332171617161514070607060122272627263437363736 +321716171614070607060122272627263437363736333217161716140706070601112111 +271121112511331104724A1719131010111B172524181A111010111AFEB624171A121010 +101C1724251819121010111A18FED224181A111010101B174A1719131010111B17065624 +171A121010101C1724251819121010111A18FECE24171A131010111C1748181A12101011 +1B17FED224181A120F0F111B172524181A121010111B17FA0809598DF7C103D98D014010 +111A1848181A121010111B172425181912F910111A174A181A111010111A184918191210 +010910111B1724251819121010111A1848181A1210FDED10101B18481819131010111B17 +2524181A111001090F111B174A171A121010111B1748191A120F010810111C1748181A12 +1010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D000A00C800460A21053B +0015002B00410057006D00830099009D00A100A500000022272627263437363736333217 +161716151407060725222726272634373637363332171617161407060706012227262726 +353437363736321716171614070607062534373637363217161716140706070623222726 +272600343736373633321716171615140706070622272627243437363736321716171615 +140706070623222726270134373637363332171617161407060706222726272601112111 +271121112511331104724A1719131010111B172524181A111010111AFEB624171A121010 +101C1724251819121010111A18FED224181A111010101B174A1719131010111B1703CC10 +111A1848181A121010101C17242518191210021210111B1724251819121010111A184818 +1A12FDDE10111A1848181A121010101C172425181912020210111B172425181912101011 +1A1848181A1210F88F09598DF7C103D98D014010111A1848181A121010111B1724251819 +12F910111A174A181A111010111A184918191210010910111B1724251819121010111A18 +48181A12107A24181A111010101B174A1719131010111B17FDE94A1719131010111B1725 +24181A111010101B174A1719131010111B172524181A111010101B025324181A11101010 +1B174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D000000000B00C80046 +0A21053B0015002B00410057006D0083009900B000B400B800BC00000022272627263437 +363736333217161716151407060725222726272634373637363332171617161407060706 +012227262726353437363736321716171614070607062534373637363217161716140706 +070623222726272600343736373633321716171615140706070622272627243437363736 +321716171615140706070623222726270134373637363332171617161407060706222726 +272605343736373633321716171615140706070622272627260111211127112111251133 +1104724A1719131010111B172524181A111010111AFEB624171A121010101C1724251819 +121010111A18FED224181A111010101B174A1719131010111B1703CC10111A1848181A12 +1010101C17242518191210021210111B1724251819121010111A1848181A12FDDE10111A +1848181A121010101C172425181912020210111B1724251819121010111A1848181A1210 +FEF710111A172624181A111010111A184918191210F99809598DF7C103D98D014010111A +1848181A121010111B172425181912F910111A174A181A111010111A1849181912100109 +10111B1724251819121010111A1848181A12107A24181A111010101B174A171913101011 +1B17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A +111010101B025324181A111010101B174A1719131010111B17E424171A121010101C1724 +251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D00000C00C800460A21053B +0015002B00410056006C0082009700AB00C100C500C900CD000000222726272634373637 +363332171617161514070607252227262726343736373633321716171614070607060122 +272627263534373637363217161716140706070621222726272634373637363217161716 +140706070603222726272635343736373632171617161407060706202227262726343736 +373633321716171617140706070022272627262734373637363217161716140706070222 +2726272E013736373632171617161407060700222726272635343736373633321716171E +0107060701112111271121112511331104724A1719131010111B172524181A111010111A +FEB624171A121010101C1724251819121010111A18FED224181A111010101B174A171913 +1010111B17054924171A131010111C1748181A121010111B172524171A131010111C1748 +181A121010111B17FECD4A1819121010111A172624171A11100111111A02554A161A1210 +0111111B1748181A120F0F111B174A161A12100111111B1748181A120F0F111BFD7E4A18 +19121010111A172624171A11100111111AFA1C09598DF7C103D98D014010111A1848181A +121010111B172425181912F910111A174A181A111010111A184918191210010910111B17 +24251819121010111A1848181A121010111C1748181A120F0F111B174A161A1310FDEF10 +111B172524171A131010111C1748181A121010111B1847181A131010111C172424181A12 +020210111B172524181A111010101B1848181913FDDE10111B1847181A131010111C1748 +181A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC258C02 +C3FD3D000000000700C800460A21053B00140029003E00530057005B005F000001343736 +373632171617161407060706222726272600343736373632171617161514070607062227 +262724343736373632171617161514070607062227262701343736373632171617161407 +060706222726272601112111271121112511331101C110111A1848181A121010111B1749 +18191210021210101C17491819121010111A1848181A12FDDE10111A1848181A12101011 +1B1749181912020210101C17491819121010111A1848181A1210FCF509598DF7C103D98D +03CC24181A111010101B17491819131010111B17FDE9491819131010111B172524181A11 +1010101B17491819131010111B172524181A111010101B025324181A111010101B174918 +19131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D000000000800C800460A21053B +00150019001D00210036004B006000750000013437363736333217161716140706070622 +272627260111211127112111251133110134373637363217161716140706070622272627 +260034373637363217161716151407060706222726272434373637363217161716151407 +06070622272627013437363736321716171614070607062227262726073010111A172624 +181A111010111A184918191210F99809598DF7C103D98DFC0610111A1848181A12101011 +1B174918191210021210101C17491819121010111A1848181A12FDDE10111A1848181A12 +1010111B1749181912020210101C17491819121010111A1848181A121002C324171A1210 +10101C17491819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D026D24181A1110 +10101B17491819131010111B17FDE9491819131010111B172524181A111010101B174918 +19131010111B172524181A111010101B025324181A111010101B17491819131010111B17 +0000000900C800460A21053B0016002C003000340038004E0064007A0090000001222726 +272634373637363332171617161514070607060122272627263437363736333217161716 +140706070601112111271121112511331101343736373632171617161407060706232227 +262726003437363736333217161716151407060706222726272434373637363217161716 +15140706070623222726270134373637363332171617161407060706222726272608B124 +171A121010101C1724251819121010111A18FDC524181A120F0F111B172524181A121010 +111B17FA0809598DF7C103D98DFC0610111A1848181A121010111B172425181912100212 +10101C1724251819121010111A1848181A12FDDE10111A1848181A121010111B17242518 +1912020210101C1724251819121010111A1848181A1210013F10101B1848181913101011 +1B172524181A1110021110111C1748181A121010111B1847181A1310FCF604F5FB0B8D03 +DBFC258C02C3FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010 +111B172524181A111010101B174A1719131010111B172524181A111010101B025324181A +111010101B174A1719131010111B17000000000A00C800460A21053B0016002B00410045 +0049004D00630079008F00A5000001222726272634373637363332171617161514070607 +060122272627263437363736321716171614070607060122272627263437363736333217 +161716140706070601112111271121112511331101343736373632171617161407060706 +232227262726003437363736333217161716151407060706222726272434373637363217 +161716151407060706232227262701343736373633321716171614070607062227262726 +08B124171A121010101C1724251819121010111A18FECE24171A131010111C1748181A12 +1010111B17FED224181A120F0F111B172524181A121010111B17FA0809598DF7C103D98D +FC0610111A1848181A121010111B17242518191210021210101C1724251819121010111A +1848181A12FDDE10111A1848181A121010111B172425181912020210101C172425181912 +1010111A1848181A1210013F10101B18481819131010111B172524181A111001090F111B +174A171A121010111B1748191A120F010810111C1748181A121010111B1847181A1310FC +F604F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A1719131010111B17FD +E94A1719131010111B172524181A111010101B174A1719131010111B172524181A111010 +101B025324181A111010101B174A1719131010111B17000B00C800460A21053B0015002B +00410057005B005F00630079008F00A500BB000001343736373632171617161407060706 +232227262726003437363736333217161716151407060706222726272434373637363217 +161716151407060706232227262701343736373633321716171614070607062227262726 +011121112711211125113311013437363736321716171614070607062322272627260034 +373637363332171617161514070607062227262724343736373632171617161514070607 +06232227262701343736373633321716171614070607062227262726062710111A184818 +1A121010101C17242518191210021210111B1724251819121010111A1848181A12FDDE10 +111A1848181A121010101C172425181912020210111B1724251819121010111A1848181A +1210F88F09598DF7C103D98DFC0610111A1848181A121010111B17242518191210021210 +101C1724251819121010111A1848181A12FDDE10111A1848181A121010111B1724251819 +12020210101C1724251819121010111A1848181A121003CC24181A111010101B174A1719 +131010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B17 +2524181A111010101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B8D +03DBFC258C02C3FD3D026D24181A111010101B174A1719131010111B17FDE94A17191310 +10111B172524181A111010101B174A1719131010111B172524181A111010101B02532418 +1A111010101B174A1719131010111B170000000C00C800460A21053B0015002B00410057 +006E00720076007A009000A600BC00D20000013437363736321716171614070607062322 +272627260034373637363332171617161514070607062227262724343736373632171617 +161514070607062322272627013437363736333217161716140706070622272627260534 +373637363332171617161514070607062227262726011121112711211125113311013437 +363736321716171614070607062322272627260034373637363332171617161514070607 +062227262724343736373632171617161514070607062322272627013437363736333217 +16171614070607062227262726062710111A1848181A121010101C172425181912100212 +10111B1724251819121010111A1848181A12FDDE10111A1848181A121010101C17242518 +1912020210111B1724251819121010111A1848181A1210FEF710111A172624181A111010 +111A184918191210F99809598DF7C103D98DFC0610111A1848181A121010111B17242518 +191210021210101C1724251819121010111A1848181A12FDDE10111A1848181A12101011 +1B172425181912020210101C1724251819121010111A1848181A121003CC24181A111010 +101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A1719 +131010111B172524181A111010101B025324181A111010101B174A1719131010111B17E4 +24171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D02 +6D24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A1110 +10101B174A1719131010111B172524181A111010101B025324181A111010101B174A1719 +131010111B17000D00C800460A21053B0014002A004000550069007F00830087008B00A1 +00B700CD00E3000001222726272634373637363217161716140706070603222726272635 +343736373632171617161407060706202227262726343736373633321716171617140706 +0700222726272627343736373632171617161407060702222726272E0137363736321716 +17161407060700222726272635343736373633321716171E010706070111211127112111 +251133110134373637363217161716140706070623222726272600343736373633321716 +171615140706070622272627243437363736321716171615140706070623222726270134 +373637363332171617161407060706222726272607A424171A131010111C1748181A1210 +10111B172524171A131010111C1748181A121010111B17FECD4A1819121010111A172624 +171A11100111111A02554A161A12100111111B1748181A120F0F111B174A161A12100111 +111B1748181A120F0F111BFD7E4A1819121010111A172624171A11100111111AFA1C0959 +8DF7C103D98DFC0610111A1848181A121010111B17242518191210021210101C17242518 +19121010111A1848181A12FDDE10111A1848181A121010111B172425181912020210101C +1724251819121010111A1848181A1210035210111C1748181A120F0F111B174A161A1310 +FDEF10111B172524171A131010111C1748181A121010111B1847181A131010111C172424 +181A12020210111B172524181A111010101B1848181913FDDE10111B1847181A13101011 +1C1748181A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC +258C02C3FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B +172524181A111010101B174A1719131010111B172524181A111010101B025324181A1110 +10101B174A1719131010111B1700000800C800460A21053B00030007000B00210037004D +006300790000371121112711211125113311013437363736321716171614070607062322 +272627260034373637363332171617161514070607062227262724343736373632171617 +161514070607062322272627013437363736333217161716140706070622272627260534 +3736373632171617161514070607062227262726C809598DF7C103D98DFC0610111A1848 +181A121010111B17242518191210021210101C1724251819121010111A1848181A12FDDE +10111A1848181A121010111B172425181912020210101C1724251819121010111A184818 +1A1210FEF710111A174A1819121010111A1848191A11104604F5FB0B8D03DBFC258C02C3 +FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B17252418 +1A111010101B174A1719131010111B172524181A111010101B025324181A111010101B17 +4A1719131010111B17E424171A121010101C1724251819121010111A1800000900C80046 +0A21053B0016001A001E00220038004E0064007A00900000013437363736333217161716 +151407060706222726272601112111271121112511331101343736373632171617161407 +060706232227262726003437363736333217161716151407060706222726272434373637 +363217161716151407060706232227262701343736373633321716171614070607062227 +26272605343736373632171617161514070607062227262726073010111A172624181A11 +1010111A184918191210F99809598DF7C103D98DFC0610111A1848181A121010111B1724 +2518191210021210101C1724251819121010111A1848181A12FDDE10111A1848181A1210 +10111B172425181912020210101C1724251819121010111A1848181A1210FEF710111A17 +4A1819121010111A1848191A111002C324171A121010101C1724251819121010111A18FD +A804F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A1719131010111B17FD +E94A1719131010111B172524181A111010101B174A1719131010111B172524181A111010 +101B025324181A111010101B174A1719131010111B17E424171A121010101C1724251819 +121010111A18000A00C800460A21053B0016002C003000340038004E0064007A009000A6 +000001222726272634373637363332171617161514070607060122272627263437363736 +333217161716140706070601112111271121112511331101343736373632171617161407 +060706232227262726003437363736333217161716151407060706222726272434373637 +363217161716151407060706232227262701343736373633321716171614070607062227 +2627260534373637363217161716151407060706222726272608B124171A121010101C17 +24251819121010111A18FDC524181A120F0F111B172524181A121010111B17FA0809598D +F7C103D98DFC0610111A1848181A121010111B17242518191210021210101C1724251819 +121010111A1848181A12FDDE10111A1848181A121010111B172425181912020210101C17 +24251819121010111A1848181A1210FEF710111A174A1819121010111A1848191A111001 +3F10101B18481819131010111B172524181A1110021110111C1748181A121010111B1847 +181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A17191310 +10111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524 +181A111010101B025324181A111010101B174A1719131010111B17E424171A121010101C +1724251819121010111A18000000000B00C800460A21053B0016002B004100450049004D +00630079008F00A500BB0000012227262726343736373633321716171615140706070601 +222726272634373637363217161716140706070601222726272634373637363332171617 +161407060706011121112711211125113311013437363736321716171614070607062322 +272627260034373637363332171617161514070607062227262724343736373632171617 +161514070607062322272627013437363736333217161716140706070622272627260534 +373637363217161716151407060706222726272608B124171A121010101C172425181912 +1010111A18FECE24171A131010111C1748181A121010111B17FED224181A120F0F111B17 +2524181A121010111B17FA0809598DF7C103D98DFC0610111A1848181A121010111B1724 +2518191210021210101C1724251819121010111A1848181A12FDDE10111A1848181A1210 +10111B172425181912020210101C1724251819121010111A1848181A1210FEF710111A17 +4A1819121010111A1848191A1110013F10101B18481819131010111B172524181A111001 +090F111B174A171A121010111B1748191A120F010810111C1748181A121010111B184718 +1A1310FCF604F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A1719131010 +111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B17252418 +1A111010101B025324181A111010101B174A1719131010111B17E424171A121010101C17 +24251819121010111A18000C00C800460A21053B0015002B00410057005B005F00630079 +008F00A500BB00D100000134373637363217161716140706070623222726272600343736 +373633321716171615140706070622272627243437363736321716171615140706070623 +222726270134373637363332171617161407060706222726272601112111271121112511 +331101343736373632171617161407060706232227262726003437363736333217161716 +151407060706222726272434373637363217161716151407060706232227262701343736 +373633321716171614070607062227262726053437363736321716171615140706070622 +27262726062710111A1848181A121010101C17242518191210021210111B172425181912 +1010111A1848181A12FDDE10111A1848181A121010101C172425181912020210111B1724 +251819121010111A1848181A1210F88F09598DF7C103D98DFC0610111A1848181A121010 +111B17242518191210021210101C1724251819121010111A1848181A12FDDE10111A1848 +181A121010111B172425181912020210101C1724251819121010111A1848181A1210FEF7 +10111A174A1819121010111A1848191A111003CC24181A111010101B174A171913101011 +1B17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A +111010101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B8D03DBFC25 +8C02C3FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B17 +2524181A111010101B174A1719131010111B172524181A111010101B025324181A111010 +101B174A1719131010111B17E424171A121010101C1724251819121010111A180000000D +00C800460A21053B0015002B00410057006E00720076007A009000A600BC00D200E80000 +013437363736321716171614070607062322272627260034373637363332171617161514 +070607062227262724343736373632171617161514070607062322272627013437363736 +333217161716140706070622272627260534373637363332171617161514070607062227 +262726011121112711211125113311013437363736321716171614070607062322272627 +260034373637363332171617161514070607062227262724343736373632171617161514 +070607062322272627013437363736333217161716140706070622272627260534373637 +3632171617161514070607062227262726062710111A1848181A121010101C1724251819 +1210021210111B1724251819121010111A1848181A12FDDE10111A1848181A121010101C +172425181912020210111B1724251819121010111A1848181A1210FEF710111A17262418 +1A111010111A184918191210F99809598DF7C103D98DFC0610111A1848181A121010111B +17242518191210021210101C1724251819121010111A1848181A12FDDE10111A1848181A +121010111B172425181912020210101C1724251819121010111A1848181A1210FEF71011 +1A174A1819121010111A1848191A111003CC24181A111010101B174A1719131010111B17 +FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A1110 +10101B025324181A111010101B174A1719131010111B17E424171A121010101C17242518 +19121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A17 +19131010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B +172524181A111010101B025324181A111010101B174A1719131010111B17E424171A1210 +10101C1724251819121010111A18000E00C800460A21053B0014002A004000550069007F +00830087008B00A100B700CD00E300F90000012227262726343736373632171617161407 +060706032227262726353437363736321716171614070607062022272627263437363736 +333217161716171407060700222726272627343736373632171617161407060702222726 +272E013736373632171617161407060700222726272635343736373633321716171E0107 +060701112111271121112511331101343736373632171617161407060706232227262726 +003437363736333217161716151407060706222726272434373637363217161716151407 +060706232227262701343736373633321716171614070607062227262726053437363736 +3217161716151407060706222726272607A424171A131010111C1748181A121010111B17 +2524171A131010111C1748181A121010111B17FECD4A1819121010111A172624171A1110 +0111111A02554A161A12100111111B1748181A120F0F111B174A161A12100111111B1748 +181A120F0F111BFD7E4A1819121010111A172624171A11100111111AFA1C09598DF7C103 +D98DFC0610111A1848181A121010111B17242518191210021210101C1724251819121010 +111A1848181A12FDDE10111A1848181A121010111B172425181912020210101C17242518 +19121010111A1848181A1210FEF710111A174A1819121010111A1848191A111003521011 +1C1748181A120F0F111B174A161A1310FDEF10111B172524171A131010111C1748181A12 +1010111B1847181A131010111C172424181A12020210111B172524181A111010101B1848 +181913FDDE10111B1847181A131010111C1748181A12020210111B172524181A11101010 +1B1848181913FCE304F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A1719 +131010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B17 +2524181A111010101B025324181A111010101B174A1719131010111B17E424171A121010 +101C1724251819121010111A1800000900C800460A21053B00030007000B0022003A0050 +0066007C0092000037112111271121112511331101222726272635343736373633321716 +171614070607060322272627263534373637363332171617161514070607062022272627 +263437363736333217161716171407060701222726272627343736373632171617161407 +06070603222726272E013736373632171617161514070607060022272627263534373637 +3633321716171E01070607C809598DF7C103D98DFD8424171A121010101C172425181912 +1010111A182524171A121010101C1724251819121010111A18FECD4A171A121010111B17 +2524171A11100111101B023024171A12100111111B1748181A121010111B172524171A12 +100111111B1748181A121010111B17FD954A171A121010111B172524171A11100111101B +4604F5FB0B8D03DBFC258C02C3FD3D01F210111B172524181A111010111A174A17191310 +FDEF10111A172624171A121010111B1724251819121010111A1848181A121010111B1724 +25181912020210111B1724251819121010111A1848181A1210FDEE10111A1848181A1210 +10111B17242518191210021210111B1724251819121010111A1848181A12000A00C80046 +0A21053B0016001A001E0022003900510067007D009300A9000001343736373633321716 +171615140706070622272627260111211127112111251133110122272627263534373637 +363332171617161407060706032227262726353437363736333217161716151407060706 +202227262726343736373633321716171617140706070122272627262734373637363217 +161716140706070603222726272E01373637363217161716151407060706002227262726 +35343736373633321716171E01070607073010111A172624181A111010111A1849181912 +10F99809598DF7C103D98DFD8424171A121010101C1724251819121010111A182524171A +121010101C1724251819121010111A18FECD4A171A121010111B172524171A1110011110 +1B023024171A12100111111B1748181A121010111B172524171A12100111111B1748181A +121010111B17FD954A171A121010111B172524171A11100111101B02C324171A12101010 +1C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D01F210111B172524 +181A111010111A174A17191310FDEF10111A172624171A121010111B1724251819121010 +111A1848181A121010111B172425181912020210111B1724251819121010111A1848181A +1210FDEE10111A1848181A121010111B17242518191210021210111B1724251819121010 +111A1848181A12000000000B00C800460A21053B0016002C003000340038004F0067007D +009300A900BF000001222726272634373637363332171617161514070607060122272627 +263437363736333217161716140706070601112111271121112511331101222726272635 +343736373633321716171614070607060322272627263534373637363332171617161514 +070607062022272627263437363736333217161716171407060701222726272627343736 +37363217161716140706070603222726272E013736373632171617161514070607060022 +2726272635343736373633321716171E0107060708B124171A121010101C172425181912 +1010111A18FDC524181A120F0F111B172524181A121010111B17FA0809598DF7C103D98D +FD8424171A121010101C1724251819121010111A182524171A121010101C172425181912 +1010111A18FECD4A171A121010111B172524171A11100111101B023024171A1210011111 +1B1748181A121010111B172524171A12100111111B1748181A121010111B17FD954A171A +121010111B172524171A11100111101B013F10101B18481819131010111B172524181A11 +10021110111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD +3D01F210111B172524181A111010111A174A17191310FDEF10111A172624171A12101011 +1B1724251819121010111A1848181A121010111B172425181912020210111B1724251819 +121010111A1848181A1210FDEE10111A1848181A121010111B1724251819121002121011 +1B1724251819121010111A1848181A120000000C00C800460A21053B0016002B00410045 +0049004D0064007C009200A800BE00D40000012227262726343736373633321716171615 +140706070601222726272634373637363217161716140706070601222726272634373637 +363332171617161407060706011121112711211125113311012227262726353437363736 +333217161716140706070603222726272635343736373633321716171615140706070620 +222726272634373637363332171617161714070607012227262726273437363736321716 +1716140706070603222726272E0137363736321716171615140706070600222726272635 +343736373633321716171E0107060708B124171A121010101C1724251819121010111A18 +FECE24171A131010111C1748181A121010111B17FED224181A120F0F111B172524181A12 +1010111B17FA0809598DF7C103D98DFD8424171A121010101C1724251819121010111A18 +2524171A121010101C1724251819121010111A18FECD4A171A121010111B172524171A11 +100111101B023024171A12100111111B1748181A121010111B172524171A12100111111B +1748181A121010111B17FD954A171A121010111B172524171A11100111101B013F10101B +18481819131010111B172524181A111001090F111B174A171A121010111B1748191A120F +010810111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D +01F210111B172524181A111010111A174A17191310FDEF10111A172624171A121010111B +1724251819121010111A1848181A121010111B172425181912020210111B172425181912 +1010111A1848181A1210FDEE10111A1848181A121010111B17242518191210021210111B +1724251819121010111A1848181A12000000000D00C800460A21053B0015002B00410057 +005B005F0063007A009200A800BE00D400EA000001343736373632171617161407060706 +232227262726003437363736333217161716151407060706222726272434373637363217 +161716151407060706232227262701343736373633321716171614070607062227262726 +011121112711211125113311012227262726353437363736333217161716140706070603 +222726272635343736373633321716171615140706070620222726272634373637363332 +171617161714070607012227262726273437363736321716171614070607060322272627 +2E0137363736321716171615140706070600222726272635343736373633321716171E01 +070607062710111A1848181A121010101C17242518191210021210111B17242518191210 +10111A1848181A12FDDE10111A1848181A121010101C172425181912020210111B172425 +1819121010111A1848181A1210F88F09598DF7C103D98DFD8424171A121010101C172425 +1819121010111A182524171A121010101C1724251819121010111A18FECD4A171A121010 +111B172524171A11100111101B023024171A12100111111B1748181A121010111B172524 +171A12100111111B1748181A121010111B17FD954A171A121010111B172524171A111001 +11101B03CC24181A111010101B174A1719131010111B17FDE94A1719131010111B172524 +181A111010101B174A1719131010111B172524181A111010101B025324181A111010101B +174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D01F210111B172524181A +111010111A174A17191310FDEF10111A172624171A121010111B1724251819121010111A +1848181A121010111B172425181912020210111B1724251819121010111A1848181A1210 +FDEE10111A1848181A121010111B17242518191210021210111B1724251819121010111A +1848181A1200000E00C800460A21053B0015002B00410057006E00720076007A009100A9 +00BF00D500EB010100000134373637363217161716140706070623222726272600343736 +373633321716171615140706070622272627243437363736321716171615140706070623 +222726270134373637363332171617161407060706222726272605343736373633321716 +171615140706070622272627260111211127112111251133110122272627263534373637 +363332171617161407060706032227262726353437363736333217161716151407060706 +202227262726343736373633321716171617140706070122272627262734373637363217 +161716140706070603222726272E01373637363217161716151407060706002227262726 +35343736373633321716171E01070607062710111A1848181A121010101C172425181912 +10021210111B1724251819121010111A1848181A12FDDE10111A1848181A121010101C17 +2425181912020210111B1724251819121010111A1848181A1210FEF710111A172624181A +111010111A184918191210F99809598DF7C103D98DFD8424171A121010101C1724251819 +121010111A182524171A121010101C1724251819121010111A18FECD4A171A121010111B +172524171A11100111101B023024171A12100111111B1748181A121010111B172524171A +12100111111B1748181A121010111B17FD954A171A121010111B172524171A1110011110 +1B03CC24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A +111010101B174A1719131010111B172524181A111010101B025324181A111010101B174A +1719131010111B17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03 +DBFC258C02C3FD3D01F210111B172524181A111010111A174A17191310FDEF10111A1726 +24171A121010111B1724251819121010111A1848181A121010111B172425181912020210 +111B1724251819121010111A1848181A1210FDEE10111A1848181A121010111B17242518 +191210021210111B1724251819121010111A1848181A12000000000F00C800460A21053B +0014002A004000550069007F00830087008B00A100B700CC00E100F6010B000001222726 +272634373637363217161716140706070603222726272635343736373632171617161407 +060706202227262726343736373633321716171617140706070022272627262734373637 +3632171617161407060702222726272E0137363736321716171614070607002227262726 +35343736373633321716171E010706070111211127112111251133110122272627263534 +373637363217161716140706070603222726272635343736373632171617161407060706 +202227262726343736373633321716171E0107060701222726272E013736373632171617 +16140706070603222726272E013736373632171617161407060706002227262726343736 +373633321716171E0107060707A424171A131010111C1748181A121010111B172524171A +131010111C1748181A121010111B17FECC491819121010111A172624171A11100111111A +025549171A12100111111B1748181A120F0F111B1749171A12100111111B1748181A120F +0F111BFD7D491819121010111A172624171A11100111111AFA1C09598DF7C103D98DFD84 +24171A121010101C17491819121010111A182524171A121010101C17491819121010111A +18FECC49171A121010111B172524171A11100111101B023024171A12100111111B174818 +1A121010111B172524171A12100111111B1748181A121010111B17FD9449171A12101011 +1B172524171A11100111101B035210111C1748181A120F0F111B1749171A1310FDEF1011 +1B172524171A131010111C1748181A121010111B1847181A131010111C172424181A1202 +0210111B172524181A111010101B1848181913FDDE10111B1847181A131010111C174818 +1A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC258C02C3 +FD3D01F210111B172524181A111010111A174918191310FDEF10111A172624171A121010 +111B17491819121010111A1848181A121010111B1749181912020210111B174918191210 +10111A1848181A1210FDEE10111A1848181A121010111B174918191210021210111B1749 +1819121010111A1848181A120000000200C8FE1405BD076D000300070000011521350121 +112101E102C3FC2404F5FB0B03078D8D0466F6A70000000300C8FE1405BD076D00030007 +000B0000132111213721112113211521C804F5FB0B8D03DBFC258C02C3FD3D076DF6A78D +083FFC278D00000400C8FE1405BD076D0016001A001E0022000001321716171615140706 +07062322272627263437363736012111213721112113211521034524171A121010101C17 +24251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D010510111A172624181A +111010111A1849181912100668F6A78D083FFC278D00000500C8FE1405BD076D0015002B +002F00330037000005343736373632171617161407060706232227262726013437363736 +3217161716151407060706222726272601211121372111211321152101C110101B184818 +19131010111B172524181A1110021110111C1748181A121010111B1847181A1310FCF604 +F5FB0B8D03DBFC258C02C3FD3D7C24171A121010101C17491819121010111A18023B2418 +1A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D000000000600C8FE14 +05BD076D0016002B004100450049004D0000053437363736321716171615140706070623 +222726272601343736373632171617161407060706222726272601343736373632171617 +16151407060706222726272601211121372111211321152101C110101B18481819131010 +111B172524181A111001090F111B174A171A121010111B1748191A120F010810111C1748 +181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D7C24171A121010 +101C1724251819121010111A18013224171A131010111C1748181A121010111B17012E24 +181A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D0000000700C8FE14 +05BD076D0015002B00410057005B005F0063000001321716171614070607062227262726 +353437363736003217161716151407060706232227262726343736371232171617161407 +060706232227262726353437363701321716171615140706070622272627263437363736 +012111213721112113211521044E24181A111010101B174A1719131010111B17FDE94A17 +19131010111B172524181A111010101B174A1719131010111B172524181A111010101B02 +5324181A111010101B174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D02 +0E10111A1848181A121010101C17242518191210FDEE10111B1724251819121010111A18 +48181A12022210111A1848181A121010101C172425181912FDFE10111B17242518191210 +10111A1848181A12100771F6A78D083FFC278D000000000800C8FE1405BD076D0015002B +00410057006E00720076007A000001321716171614070607062227262726353437363736 +003217161716151407060706232227262726343736371232171617161407060706232227 +262726353437363701321716171615140706070622272627263437363736033217161716 +1514070607062322272627263437363736012111213721112113211521044E24181A1110 +10101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A17 +19131010111B172524181A111010101B025324181A111010101B174A1719131010111B17 +E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D +020E10111A1848181A121010101C17242518191210FDEE10111B1724251819121010111A +1848181A12022210111A1848181A121010101C172425181912FDFE10111B172425181912 +1010111A1848181A1210010910111A172624181A111010111A1849181912100668F6A78D +083FFC278D00000900C8FE1405BD076D0014002A004000550069007F00830087008B0000 +253437363736321716171614070607062227262726253437363736333217161716140706 +070622272627261034373637363217161716151407060706072227262700343736373637 +321716171614070607062227262724343736373E01171617161407060706222726270034 +3736373633321716171615140706070E0127262701211121372111211321152103D41011 +1C1748181A120F0F111B174A161A1310FDEF10111B172524171A131010111C1748181A12 +1010111B1847181A131010111C172424181A12020210111B172524181A111010101B1848 +181913FDDE10111B1847181A131010111C1748181A12020210111B172524181A11101010 +1B1848181913FCE304F5FB0B8D03DBFC258C02C3FD3D9124171A131010111C1748181A12 +1010111B172524171A131010111C1748181A121010111B1701334A1819121010111A1726 +24171A11100111111AFDAB4A161A12100111111B1748181A120F0F111B174A161A121001 +11111B1748181A120F0F111B02824A1819121010111A172624171A11100111111A05E4F6 +A78D083FFC278D000000000400C8FE1405BD076D00150019001D00210000013217161716 +14070607062322272627263437363736012111213721112113211521034524171A121010 +101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D056B10111A174A +1819121010111A1848191A11100202F6A78D083FFC278D000000000500C8FE1405BD076D +0015002C0030003400380000013217161716140706070623222726272634373637361332 +171617161514070607062322272627263437363736012111213721112113211521034524 +171A121010101C1724251819121010111A182524171A121010101C172425181912101011 +1A18FDA804F5FB0B8D03DBFC258C02C3FD3D056910101B174A1719131010111B1848181A +1110FB9C10111A172624181A111010111A1849181912100668F6A78D083FFC278D000006 +00C8FE1405BD076D0015002C00420046004A004E00000132171617161407060706232227 +262726343736373601343736373632171617161514070607062322272627260134373637 +3632171617161514070607062227262726012111213721112113211521034524171A1210 +10101C1724251819121010111A18FEA110101B18481819131010111B172524181A111002 +1110111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D05 +6B10111A174A1819121010111A1848191A1110FA1924171A121010101C17242518191210 +10111A18023B24181A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D00 +0000000700C8FE1405BD076D0015002C00410057005B005F006300000132171617161407 +060706232227262726343736373601343736373632171617161514070607062322272627 +260134373637363217161716140706070622272627260134373637363217161716151407 +0607062227262726012111213721112113211521034524171A121010101C172425181912 +1010111A18FEA110101B18481819131010111B172524181A111001090F111B174A171A12 +1010111B1748191A120F010810111C1748181A121010111B1847181A1310FCF604F5FB0B +8D03DBFC258C02C3FD3D056B10111A174A1819121010111A1848191A1110FA1924171A12 +1010101C1724251819121010111A18013224171A131010111C1748181A121010111B1701 +2E24181A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D000800C8FE14 +05BD076D0015002B00410057006D00710075007900000132171617161407060706232227 +262726343736373601321716171614070607062227262726353437363736003217161716 +151407060706232227262726343736371232171617161407060706232227262726353437 +363701321716171615140706070622272627263437363736012111213721112113211521 +034524171A121010101C1724251819121010111A18012E24181A111010101B174A171913 +1010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B1725 +24181A111010101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B8D03 +DBFC258C02C3FD3D056B10111A174A1819121010111A1848191A1110FCA310111A184818 +1A121010101C17242518191210FDEE10111B1724251819121010111A1848181A12022210 +111A1848181A121010101C172425181912FDFE10111B1724251819121010111A1848181A +12100771F6A78D083FFC278D0000000900C8FE1405BD076D0015002B00410057006D0084 +0088008C0090000001321716171614070607062322272627263437363736013217161716 +140706070622272627263534373637360032171617161514070607062322272627263437 +363712321716171614070607062322272627263534373637013217161716151407060706 +222726272634373637360332171617161514070607062322272627263437363736012111 +213721112113211521034524171A121010101C1724251819121010111A18012E24181A11 +1010101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A +1719131010111B172524181A111010101B025324181A111010101B174A1719131010111B +17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD +3D056B10111A174A1819121010111A1848191A1110FCA310111A1848181A121010101C17 +242518191210FDEE10111B1724251819121010111A1848181A12022210111A1848181A12 +1010101C172425181912FDFE10111B1724251819121010111A1848181A1210010910111A +172624181A111010111A1849181912100668F6A78D083FFC278D000A00C8FE1405BD076D +0015002A00400056006B007F00950099009D00A100000132171617161407060706232227 +262726343736373613343736373632171617161407060706222726272625343736373633 +321716171614070607062227262726103437363736321716171615140706070607222726 +2700343736373637321716171614070607062227262724343736373E0117161716140706 +07062227262700343736373633321716171615140706070E012726270121112137211121 +13211521034524171A121010101C1724251819121010111A18B410111C1748181A120F0F +111B174A161A1310FDEF10111B172524171A131010111C1748181A121010111B1847181A +131010111C172424181A12020210111B172524181A111010101B1848181913FDDE10111B +1847181A131010111C1748181A12020210111B172524181A111010101B1848181913FCE3 +04F5FB0B8D03DBFC258C02C3FD3D056B10111A174A1819121010111A1848191A1110FB26 +24171A131010111C1748181A121010111B172524171A131010111C1748181A121010111B +1701334A1819121010111A172624171A11100111111AFDAB4A161A12100111111B174818 +1A120F0F111B174A161A12100111111B1748181A120F0F111B02824A1819121010111A17 +2624171A11100111111A05E4F6A78D083FFC278D0000000500C8FE1405BD076D0015002B +002F00330037000000343736373632171617161514070607062322272627013437363736 +3332171617161407060706222726272601211121372111211321152101C210111A184818 +1A121010111B172425181912020210111B1724251819121010111A1848181A1210FCF404 +F5FB0B8D03DBFC258C02C3FD3D03C34A1719131010111B172524181A111010111A025324 +181A111010101B174A1719131010111B170193F6A78D083FFC278D000000000600C8FE14 +05BD076D0016001A001E00220038004E0000013217161716151407060706232227262726 +343736373601211121372111211321152102343736373632171617161514070607062322 +27262701343736373633321716171614070607062227262726034524171A121010101C17 +24251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A1210 +10111B172425181912020210111B1724251819121010111A1848181A1210010510111A17 +2624181A111010111A1849181912100668F6A78D083FFC278D01494A1719131010111B17 +2524181A111010111A025324181A111010101B174A1719131010111B1700000700C8FE14 +05BD076D0016002C003000340038004E0064000005343736373632171617161514070607 +062322272627260134373637363217161716151407060706222726272601211121372111 +211321152102343736373632171617161514070607062322272627013437363736333217 +1617161407060706222726272601C110101B18481819131010111B172524181A11100211 +10111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D1F10 +111A1848181A121010111B172425181912020210111B1724251819121010111A1848181A +12107C24171A121010101C1724251819121010111A18023B24181A120F0F111B17252418 +1A121010111B1705F8F6A78D083FFC278D01494A1719131010111B172524181A11101011 +1A025324181A111010101B174A1719131010111B1700000800C8FE1405BD076D0016002B +004100450049004D00630079000005343736373632171617161514070607062322272627 +260134373637363217161716140706070622272627260134373637363217161716151407 +060706222726272601211121372111211321152102343736373632171617161514070607 +0623222726270134373637363332171617161407060706222726272601C110101B184818 +19131010111B172524181A111001090F111B174A171A121010111B1748191A120F010810 +111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D1F1011 +1A1848181A121010111B172425181912020210111B1724251819121010111A1848181A12 +107C24171A121010101C1724251819121010111A18013224171A131010111C1748181A12 +1010111B17012E24181A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D +01494A1719131010111B172524181A111010111A025324181A111010101B174A17191310 +10111B170000000900C8FE1405BD076D0015002B00410057005B005F00630079008F0000 +013217161716140706070622272627263534373637360032171617161514070607062322 +272627263437363712321716171614070607062322272627263534373637013217161716 +151407060706222726272634373637360121112137211121132115210234373637363217 +161716151407060706232227262701343736373633321716171614070607062227262726 +044E24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A11 +1010101B174A1719131010111B172524181A111010101B025324181A111010101B174A17 +19131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A121010111B +172425181912020210111B1724251819121010111A1848181A1210020E10111A1848181A +121010101C17242518191210FDEE10111B1724251819121010111A1848181A1202221011 +1A1848181A121010101C172425181912FDFE10111B1724251819121010111A1848181A12 +100771F6A78D083FFC278D01494A1719131010111B172524181A111010111A025324181A +111010101B174A1719131010111B17000000000A00C8FE1405BD076D0015002B00410057 +006E00720076007A009000A6000001321716171614070607062227262726353437363736 +003217161716151407060706232227262726343736371232171617161407060706232227 +262726353437363701321716171615140706070622272627263437363736033217161716 +151407060706232227262726343736373601211121372111211321152102343736373632 +171617161514070607062322272627013437363736333217161716140706070622272627 +26044E24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A +111010101B174A1719131010111B172524181A111010101B025324181A111010101B174A +1719131010111B17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03 +DBFC258C02C3FD3D1F10111A1848181A121010111B172425181912020210111B17242518 +19121010111A1848181A1210020E10111A1848181A121010101C17242518191210FDEE10 +111B1724251819121010111A1848181A12022210111A1848181A121010101C1724251819 +12FDFE10111B1724251819121010111A1848181A1210010910111A172624181A11101011 +1A1849181912100668F6A78D083FFC278D01494A1719131010111B172524181A11101011 +1A025324181A111010101B174A1719131010111B1700000B00C8FE1405BD076D0014002A +004000550069007F00830087008B00A100B7000025343736373632171617161407060706 +222726272625343736373633321716171614070607062227262726103437363736321716 +171615140706070607222726270034373637363732171617161407060706222726272434 +3736373E011716171614070607062227262700343736373633321716171615140706070E +012726270121112137211121132115210234373637363217161716151407060706232227 +26270134373637363332171617161407060706222726272603D410111C1748181A120F0F +111B174A161A1310FDEF10111B172524171A131010111C1748181A121010111B1847181A +131010111C172424181A12020210111B172524181A111010101B1848181913FDDE10111B +1847181A131010111C1748181A12020210111B172524181A111010101B1848181913FCE3 +04F5FB0B8D03DBFC258C02C3FD3D1F10111A1848181A121010111B172425181912020210 +111B1724251819121010111A1848181A12109124171A131010111C1748181A121010111B +172524171A131010111C1748181A121010111B1701334A1819121010111A172624171A11 +100111111AFDAB4A161A12100111111B1748181A120F0F111B174A161A12100111111B17 +48181A120F0F111B02824A1819121010111A172624171A11100111111A05E4F6A78D083F +FC278D01494A1719131010111B172524181A111010111A025324181A111010101B174A17 +19131010111B17000000000600C8FE1405BD076D0015002B004100450049004D00000034 +373637363217161716151407060706232227262713343736373632171617161514070607 +062227262726013437363736333217161716140706070622272627260121112137211121 +1321152101C210111A1848181A121010111B172425181912F910111A174A181A11101011 +1A184918191210010910111B1724251819121010111A1848181A1210FCF404F5FB0B8D03 +DBFC258C02C3FD3D03C34A1719131010111B172524181A111010111A014A24171A121010 +101C1724251819121010111A18012E24181A111010101B174A1719131010111B170193F6 +A78D083FFC278D000000000700C8FE1405BD076D0015002B00410058005C006000640000 +003437363736321716171615140706070623222726271334373637363217161716151407 +060706222726272601343736373633321716171614070607062227262726033217161716 +151407060706232227262726343736373601211121372111211321152101C210111A1848 +181A121010111B172425181912F910111A174A181A111010111A18491819121001091011 +1B1724251819121010111A1848181A12108F24171A121010101C1724251819121010111A +18FDA804F5FB0B8D03DBFC258C02C3FD3D03C34A1719131010111B172524181A11101011 +1A014A24171A121010101C1724251819121010111A18012E24181A111010101B174A1719 +131010111B17FB2B10111A172624181A111010111A1849181912100668F6A78D083FFC27 +8D00000800C8FE1405BD076D0015002B00410058006E00720076007A0000003437363736 +321716171615140706070623222726271334373637363217161716151407060706222726 +272601343736373633321716171614070607062227262726013437363736321716171615 +140706070623222726272601343736373632171617161514070607062227262726012111 +21372111211321152101C210111A1848181A121010111B172425181912F910111A174A18 +1A111010111A184918191210010910111B1724251819121010111A1848181A1210FDED10 +101B18481819131010111B172524181A1110021110111C1748181A121010111B1847181A +1310FCF604F5FB0B8D03DBFC258C02C3FD3D03C34A1719131010111B172524181A111010 +111A014A24171A121010101C1724251819121010111A18012E24181A111010101B174A17 +19131010111B17F9AA24171A121010101C1724251819121010111A18023B24181A120F0F +111B172524181A121010111B1705F8F6A78D083FFC278D000000000900C8FE1405BD076D +0015002B00410058006D00830087008B008F000000343736373632171617161514070607 +062322272627133437363736321716171615140706070622272627260134373637363332 +171617161407060706222726272601343736373632171617161514070607062322272627 +260134373637363217161716140706070622272627260134373637363217161716151407 +060706222726272601211121372111211321152101C210111A1848181A121010111B1724 +25181912F910111A174A181A111010111A184918191210010910111B1724251819121010 +111A1848181A1210FDED10101B18481819131010111B172524181A111001090F111B174A +171A121010111B1748191A120F010810111C1748181A121010111B1847181A1310FCF604 +F5FB0B8D03DBFC258C02C3FD3D03C34A1719131010111B172524181A111010111A014A24 +171A121010101C1724251819121010111A18012E24181A111010101B174A171913101011 +1B17F9AA24171A121010101C1724251819121010111A18013224171A131010111C174818 +1A121010111B17012E24181A120F0F111B172524181A121010111B1705F8F6A78D083FFC +278D000A00C8FE1405BD076D0015002B00410057006D00830099009D00A100A500000034 +373637363217161716151407060706232227262713343736373632171617161514070607 +062227262726013437363736333217161716140706070622272627261332171617161407 +060706222726272635343736373600321716171615140706070623222726272634373637 +123217161716140706070623222726272635343736370132171617161514070607062227 +262726343736373601211121372111211321152101C210111A1848181A121010111B1724 +25181912F910111A174A181A111010111A184918191210010910111B1724251819121010 +111A1848181A12107A24181A111010101B174A1719131010111B17FDE94A171913101011 +1B172524181A111010101B174A1719131010111B172524181A111010101B025324181A11 +1010101B174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D03C34A171913 +1010111B172524181A111010111A014A24171A121010101C1724251819121010111A1801 +2E24181A111010101B174A1719131010111B17FC3410111A1848181A121010101C172425 +18191210FDEE10111B1724251819121010111A1848181A12022210111A1848181A121010 +101C172425181912FDFE10111B1724251819121010111A1848181A12100771F6A78D083F +FC278D000000000B00C8FE1405BD076D0015002B00410057006D0083009900B000B400B8 +00BC00000034373637363217161716151407060706232227262713343736373632171617 +161514070607062227262726013437363736333217161716140706070622272627261332 +171617161407060706222726272635343736373600321716171615140706070623222726 +272634373637123217161716140706070623222726272635343736370132171617161514 +070607062227262726343736373603321716171615140706070623222726272634373637 +3601211121372111211321152101C210111A1848181A121010111B172425181912F91011 +1A174A181A111010111A184918191210010910111B1724251819121010111A1848181A12 +107A24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A11 +1010101B174A1719131010111B172524181A111010101B025324181A111010101B174A17 +19131010111B17E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DB +FC258C02C3FD3D03C34A1719131010111B172524181A111010111A014A24171A12101010 +1C1724251819121010111A18012E24181A111010101B174A1719131010111B17FC341011 +1A1848181A121010101C17242518191210FDEE10111B1724251819121010111A1848181A +12022210111A1848181A121010101C172425181912FDFE10111B1724251819121010111A +1848181A1210010910111A172624181A111010111A1849181912100668F6A78D083FFC27 +8D00000C00C8FE1405BD076D0015002B00410056006C0082009700AB00C100C500C900CD +000000343736373632171617161514070607062322272627133437363736321716171615 +140706070622272627260134373637363332171617161407060706222726272611343736 +373632171617161407060706222726272625343736373633321716171614070607062227 +262726103437363736321716171615140706070607222726270034373637363732171617 +1614070607062227262724343736373E0117161716140706070622272627003437363736 +33321716171615140706070E0127262701211121372111211321152101C210111A184818 +1A121010111B172425181912F910111A174A181A111010111A184918191210010910111B +1724251819121010111A1848181A121010111C1748181A120F0F111B174A161A1310FDEF +10111B172524171A131010111C1748181A121010111B1847181A131010111C172424181A +12020210111B172524181A111010101B1848181913FDDE10111B1847181A131010111C17 +48181A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC258C +02C3FD3D03C34A1719131010111B172524181A111010111A014A24171A121010101C1724 +251819121010111A18012E24181A111010101B174A1719131010111B17FAB724171A1310 +10111C1748181A121010111B172524171A131010111C1748181A121010111B1701334A18 +19121010111A172624171A11100111111AFDAB4A161A12100111111B1748181A120F0F11 +1B174A161A12100111111B1748181A120F0F111B02824A1819121010111A172624171A11 +100111111A05E4F6A78D083FFC278D000000000700C8FE1405BD076D00140029003E0053 +0057005B005F000001321716171614070607062227262726343736373600321716171614 +070607062322272627263437363712321716171614070607062322272627263437363701 +3217161716140706070622272627263437363736012111213721112113211521044E2418 +1A111010101B17491819131010111B17FDE9491819131010111B172524181A111010101B +17491819131010111B172524181A111010101B025324181A111010101B17491819131010 +111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D067410111A1848181A121010111B174918 +191210FDEE10101C17491819121010111A1848181A12022210111A1848181A121010111B +1749181912FDFE10101C17491819121010111A1848181A1210030BF6A78D083FFC278D00 +0000000800C8FE1405BD076D00150019001D00210036004B006000750000013217161716 +151407060706222726272634373637360121112137211121132115210132171617161407 +060706222726272634373637360032171617161407060706232227262726343736371232 +171617161407060706232227262726343736370132171617161407060706222726272634 +37363736034524171A121010101C17491819121010111A18FDA804F5FB0B8D03DBFC258C +02C3FD3D026D24181A111010101B17491819131010111B17FDE9491819131010111B1725 +24181A111010101B17491819131010111B172524181A111010101B025324181A11101010 +1B17491819131010111B17010510111A172624181A111010111A1849181912100668F6A7 +8D083FFC278D03FA10111A1848181A121010111B174918191210FDEE10101C1749181912 +1010111A1848181A12022210111A1848181A121010111B1749181912FDFE10101C174918 +19121010111A1848181A12100000000900C8FE1405BD076D0016002C003000340038004E +0064007A0090000005343736373632171617161514070607062322272627260134373637 +363217161716151407060706222726272601211121372111211321152101321716171614 +070607062227262726353437363736003217161716151407060706232227262726343736 +371232171617161407060706232227262726353437363701321716171615140706070622 +27262726343736373601C110101B18481819131010111B172524181A1110021110111C17 +48181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D026D24181A11 +1010101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A +1719131010111B172524181A111010101B025324181A111010101B174A1719131010111B +177C24171A121010101C1724251819121010111A18023B24181A120F0F111B172524181A +121010111B1705F8F6A78D083FFC278D03FA10111A1848181A121010111B172425181912 +10FDEE10101C1724251819121010111A1848181A12022210111A1848181A121010111B17 +2425181912FDFE10101C1724251819121010111A1848181A1210000A00C8FE1405BD076D +0016002B004100450049004D00630079008F00A500000534373637363217161716151407 +060706232227262726013437363736321716171614070607062227262726013437363736 +321716171615140706070622272627260121112137211121132115210132171617161407 +060706222726272635343736373600321716171615140706070623222726272634373637 +123217161716140706070623222726272635343736370132171617161514070607062227 +262726343736373601C110101B18481819131010111B172524181A111001090F111B174A +171A121010111B1748191A120F010810111C1748181A121010111B1847181A1310FCF604 +F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A1719131010111B17FDE94A +1719131010111B172524181A111010101B174A1719131010111B172524181A111010101B +025324181A111010101B174A1719131010111B177C24171A121010101C17242518191210 +10111A18013224171A131010111C1748181A121010111B17012E24181A120F0F111B1725 +24181A121010111B1705F8F6A78D083FFC278D03FA10111A1848181A121010111B172425 +18191210FDEE10101C1724251819121010111A1848181A12022210111A1848181A121010 +111B172425181912FDFE10101C1724251819121010111A1848181A121000000B00C8FE14 +05BD076D0015002B00410057005B005F00630079008F00A500BB00000132171617161407 +060706222726272635343736373600321716171615140706070623222726272634373637 +123217161716140706070623222726272635343736370132171617161514070607062227 +262726343736373601211121372111211321152101321716171614070607062227262726 +353437363736003217161716151407060706232227262726343736371232171617161407 +060706232227262726353437363701321716171615140706070622272627263437363736 +044E24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A11 +1010101B174A1719131010111B172524181A111010101B025324181A111010101B174A17 +19131010111B17FC9F04F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A17 +19131010111B17FDE94A1719131010111B172524181A111010101B174A1719131010111B +172524181A111010101B025324181A111010101B174A1719131010111B17020E10111A18 +48181A121010101C17242518191210FDEE10111B1724251819121010111A1848181A1202 +2210111A1848181A121010101C172425181912FDFE10111B1724251819121010111A1848 +181A12100771F6A78D083FFC278D03FA10111A1848181A121010111B17242518191210FD +EE10101C1724251819121010111A1848181A12022210111A1848181A121010111B172425 +181912FDFE10101C1724251819121010111A1848181A12100000000C00C8FE1405BD076D +00140029003E00530069006D00710075008A009F00B400C9000001321716171614070607 +062227262726343736373600321716171614070607062322272627263437363712321716 +171614070607062322272627263437363701321716171614070607062227262726343736 +373603321716171615140706070622272627263437363736012111213721112113211521 +013217161716140706070622272627263437363736003217161716140706070623222726 +272634373637123217161716140706070623222726272634373637013217161716140706 +070622272627263437363736044E24181A111010101B17491819131010111B17FDE94918 +19131010111B172524181A111010101B17491819131010111B172524181A111010101B02 +5324181A111010101B17491819131010111B17E424171A121010101C1749181912101011 +1A18FDA804F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B1749181913101011 +1B17FDE9491819131010111B172524181A111010101B17491819131010111B172524181A +111010101B025324181A111010101B17491819131010111B17020E10111A1848181A1210 +10101C174918191210FDEE10111B17491819121010111A1848181A12022210111A184818 +1A121010101C1749181912FDFE10111B17491819121010111A1848181A1210010910111A +172624181A111010111A1849181912100668F6A78D083FFC278D03FA10111A1848181A12 +1010111B174918191210FDEE10101C17491819121010111A1848181A12022210111A1848 +181A121010111B1749181912FDFE10101C17491819121010111A1848181A12100000000D +00C8FE1405BD076D0014002A004000550069007F00830087008B00A100B700CD00E30000 +253437363736321716171614070607062227262726253437363736333217161716140706 +070622272627261034373637363217161716151407060706072227262700343736373637 +321716171614070607062227262724343736373E01171617161407060706222726270034 +3736373633321716171615140706070E0127262701211121372111211321152101321716 +171614070607062227262726353437363736003217161716151407060706232227262726 +343736371232171617161407060706232227262726353437363701321716171615140706 +07062227262726343736373603D410111C1748181A120F0F111B174A161A1310FDEF1011 +1B172524171A131010111C1748181A121010111B1847181A131010111C172424181A1202 +0210111B172524181A111010101B1848181913FDDE10111B1847181A131010111C174818 +1A12020210111B172524181A111010101B1848181913FCE304F5FB0B8D03DBFC258C02C3 +FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B17252418 +1A111010101B174A1719131010111B172524181A111010101B025324181A111010101B17 +4A1719131010111B179124171A131010111C1748181A121010111B172524171A13101011 +1C1748181A121010111B1701334A1819121010111A172624171A11100111111AFDAB4A16 +1A12100111111B1748181A120F0F111B174A161A12100111111B1748181A120F0F111B02 +824A1819121010111A172624171A11100111111A05E4F6A78D083FFC278D03FA10111A18 +48181A121010111B17242518191210FDEE10101C1724251819121010111A1848181A1202 +2210111A1848181A121010111B172425181912FDFE10101C1724251819121010111A1848 +181A12100000000800C8FE1405BD076D00030007000B00210037004D0063007900001321 +112137211121132115210132171617161407060706222726272635343736373600321716 +171615140706070623222726272634373637123217161716140706070623222726272635 +343736370132171617161514070607062227262726343736373603321716171614070607 +062322272627263437363736C804F5FB0B8D03DBFC258C02C3FD3D026D24181A11101010 +1B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A171913 +1010111B172524181A111010101B025324181A111010101B174A1719131010111B17E424 +171A121010101C1724251819121010111A18076DF6A78D083FFC278D03FA10111A184818 +1A121010111B17242518191210FDEE10101C1724251819121010111A1848181A12022210 +111A1848181A121010111B172425181912FDFE10101C1724251819121010111A1848181A +1210010910111A174A1819121010111A1848191A1110000900C8FE1405BD076D0016001A +001E00220038004E0064007A009000000132171617161514070607062322272627263437 +363736012111213721112113211521013217161716140706070622272627263534373637 +360032171617161514070607062322272627263437363712321716171614070607062322 +272627263534373637013217161716151407060706222726272634373637360332171617 +1614070607062322272627263437363736034524171A121010101C172425181912101011 +1A18FDA804F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A171913101011 +1B17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A +111010101B025324181A111010101B174A1719131010111B17E424171A121010101C1724 +251819121010111A18010510111A172624181A111010111A1849181912100668F6A78D08 +3FFC278D03FA10111A1848181A121010111B17242518191210FDEE10101C172425181912 +1010111A1848181A12022210111A1848181A121010111B172425181912FDFE10101C1724 +251819121010111A1848181A1210010910111A174A1819121010111A1848191A1110000A +00C8FE1405BD076D0015002B002F00330037004C00610076008B00A00000053437363736 +321716171614070607062322272627260134373637363217161716151407060706222726 +272601211121372111211321152101321716171614070607062227262726343736373600 +321716171614070607062322272627263437363712321716171614070607062322272627 +263437363701321716171614070607062227262726343736373603321716171614070607 +062227262726343736373601C110101B18481819131010111B172524181A111002111011 +1C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D026D2418 +1A111010101B17491819131010111B17FDE9491819131010111B172524181A111010101B +17491819131010111B172524181A111010101B025324181A111010101B17491819131010 +111B17E424171A121010101C17491819121010111A187C24171A121010101C1749181912 +1010111A18023B24181A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D +03FA10111A1848181A121010111B174918191210FDEE10101C17491819121010111A1848 +181A12022210111A1848181A121010111B1749181912FDFE10101C17491819121010111A +1848181A1210010910111A174A1819121010111A1848191A1110000B00C8FE1405BD076D +0016002B004100450049004D00630079008F00A500BB0000053437363736321716171615 +140706070623222726272601343736373632171617161407060706222726272601343736 +373632171617161514070607062227262726012111213721112113211521013217161716 +140706070622272627263534373637360032171617161514070607062322272627263437 +363712321716171614070607062322272627263534373637013217161716151407060706 +222726272634373637360332171617161407060706232227262726343736373601C11010 +1B18481819131010111B172524181A111001090F111B174A171A121010111B1748191A12 +0F010810111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD +3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A +111010101B174A1719131010111B172524181A111010101B025324181A111010101B174A +1719131010111B17E424171A121010101C1724251819121010111A187C24171A12101010 +1C1724251819121010111A18013224171A131010111C1748181A121010111B17012E2418 +1A120F0F111B172524181A121010111B1705F8F6A78D083FFC278D03FA10111A1848181A +121010111B17242518191210FDEE10101C1724251819121010111A1848181A1202221011 +1A1848181A121010111B172425181912FDFE10101C1724251819121010111A1848181A12 +10010910111A174A1819121010111A1848191A111000000C00C8FE1405BD076D0015002B +00410057005B005F00630079008F00A500BB00D100000132171617161407060706222726 +272635343736373600321716171615140706070623222726272634373637123217161716 +140706070623222726272635343736370132171617161514070607062227262726343736 +373601211121372111211321152101321716171614070607062227262726353437363736 +003217161716151407060706232227262726343736371232171617161407060706232227 +262726353437363701321716171615140706070622272627263437363736033217161716 +14070607062322272627263437363736044E24181A111010101B174A1719131010111B17 +FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A1110 +10101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02 +C3FD3D026D24181A111010101B174A1719131010111B17FDE94A1719131010111B172524 +181A111010101B174A1719131010111B172524181A111010101B025324181A111010101B +174A1719131010111B17E424171A121010101C1724251819121010111A18020E10111A18 +48181A121010101C17242518191210FDEE10111B1724251819121010111A1848181A1202 +2210111A1848181A121010101C172425181912FDFE10111B1724251819121010111A1848 +181A12100771F6A78D083FFC278D03FA10111A1848181A121010111B17242518191210FD +EE10101C1724251819121010111A1848181A12022210111A1848181A121010111B172425 +181912FDFE10101C1724251819121010111A1848181A1210010910111A174A1819121010 +111A1848191A11100000000D00C8FE1405BD076D0015002B00410057006E00720076007A +009000A600BC00D200E80000013217161716140706070622272627263534373637360032 +171617161514070607062322272627263437363712321716171614070607062322272627 +263534373637013217161716151407060706222726272634373637360332171617161514 +070607062322272627263437363736012111213721112113211521013217161716140706 +070622272627263534373637360032171617161514070607062322272627263437363712 +321716171614070607062322272627263534373637013217161716151407060706222726 +2726343736373603321716171614070607062322272627263437363736044E24181A1110 +10101B174A1719131010111B17FDE94A1719131010111B172524181A111010101B174A17 +19131010111B172524181A111010101B025324181A111010101B174A1719131010111B17 +E424171A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D +026D24181A111010101B174A1719131010111B17FDE94A1719131010111B172524181A11 +1010101B174A1719131010111B172524181A111010101B025324181A111010101B174A17 +19131010111B17E424171A121010101C1724251819121010111A18020E10111A1848181A +121010101C17242518191210FDEE10111B1724251819121010111A1848181A1202221011 +1A1848181A121010101C172425181912FDFE10111B1724251819121010111A1848181A12 +10010910111A172624181A111010111A1849181912100668F6A78D083FFC278D03FA1011 +1A1848181A121010111B17242518191210FDEE10101C1724251819121010111A1848181A +12022210111A1848181A121010111B172425181912FDFE10101C1724251819121010111A +1848181A1210010910111A174A1819121010111A1848191A1110000E00C8FE1405BD076D +0014002A004000550069007F00830087008B00A100B700CD00E300F90000253437363736 +321716171614070607062227262726253437363736333217161716140706070622272627 +261034373637363217161716151407060706072227262700343736373637321716171614 +070607062227262724343736373E01171617161407060706222726270034373637363332 +1716171615140706070E0127262701211121372111211321152101321716171614070607 +062227262726353437363736003217161716151407060706232227262726343736371232 +171617161407060706232227262726353437363701321716171615140706070622272627 +2634373637360332171617161407060706232227262726343736373603D410111C174818 +1A120F0F111B174A161A1310FDEF10111B172524171A131010111C1748181A121010111B +1847181A131010111C172424181A12020210111B172524181A111010101B1848181913FD +DE10111B1847181A131010111C1748181A12020210111B172524181A111010101B184818 +1913FCE304F5FB0B8D03DBFC258C02C3FD3D026D24181A111010101B174A171913101011 +1B17FDE94A1719131010111B172524181A111010101B174A1719131010111B172524181A +111010101B025324181A111010101B174A1719131010111B17E424171A121010101C1724 +251819121010111A189124171A131010111C1748181A121010111B172524171A13101011 +1C1748181A121010111B1701334A1819121010111A172624171A11100111111AFDAB4A16 +1A12100111111B1748181A120F0F111B174A161A12100111111B1748181A120F0F111B02 +824A1819121010111A172624171A11100111111A05E4F6A78D083FFC278D03FA10111A18 +48181A121010111B17242518191210FDEE10101C1724251819121010111A1848181A1202 +2210111A1848181A121010111B172425181912FDFE10101C1724251819121010111A1848 +181A1210010910111A174A1819121010111A1848191A11100000000900C8FE1405BD076D +00030007000B0022003A00500066007C0092000013211121372111211321152101343736 +373633321716171615140706070622272627262534373637363332171617161514070607 +062322272627261034373637363217161716151407060706072227262701343736373637 +32171617161407060706222726272625343736373E011716171614070607062322272627 +2600343736373633321716171615140706070E01272627C804F5FB0B8D03DBFC258C02C3 +FD3D01F210111B172524181A111010111A174A17191310FDEF10111A172624171A121010 +111B1724251819121010111A1848181A121010111B172425181912020210111B17242518 +19121010111A1848181A1210FDEE10111A1848181A121010111B17242518191210021210 +111B1724251819121010111A1848181A12076DF6A78D083FFC278D027C24171A12101010 +1C1724251819121010111A182524171A121010101C1724251819121010111A1801334A17 +1A121010111B172524171A11100111101BFDD024171A12100111111B1748181A12101011 +1B172524171A12100111111B1748181A121010111B17026B4A171A121010111B17252417 +1A11100111101B000000000A00C8FE1405BD076D0016001A001E0022003900510067007D +009300A90000013217161716151407060706232227262726343736373601211121372111 +211321152101343736373633321716171615140706070622272627262534373637363332 +171617161514070607062322272627261034373637363217161716151407060706072227 +26270134373637363732171617161407060706222726272625343736373E011716171614 +0706070623222726272600343736373633321716171615140706070E0127262703452417 +1A121010101C1724251819121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D01F210 +111B172524181A111010111A174A17191310FDEF10111A172624171A121010111B172425 +1819121010111A1848181A121010111B172425181912020210111B172425181912101011 +1A1848181A1210FDEE10111A1848181A121010111B17242518191210021210111B172425 +1819121010111A1848181A12010510111A172624181A111010111A1849181912100668F6 +A78D083FFC278D027C24171A121010101C1724251819121010111A182524171A12101010 +1C1724251819121010111A1801334A171A121010111B172524171A11100111101BFDD024 +171A12100111111B1748181A121010111B172524171A12100111111B1748181A12101011 +1B17026B4A171A121010111B172524171A11100111101B000000000B00C8FE1405BD076D +0016002C003000340038004F0067007D009300A900BF0000053437363736321716171615 +140706070623222726272601343736373632171617161514070607062227262726012111 +213721112113211521013437363736333217161716151407060706222726272625343736 +373633321716171615140706070623222726272610343736373632171617161514070607 +0607222726270134373637363732171617161407060706222726272625343736373E0117 +161716140706070623222726272600343736373633321716171615140706070E01272627 +01C110101B18481819131010111B172524181A1110021110111C1748181A121010111B18 +47181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D01F210111B172524181A111010111A +174A17191310FDEF10111A172624171A121010111B1724251819121010111A1848181A12 +1010111B172425181912020210111B1724251819121010111A1848181A1210FDEE10111A +1848181A121010111B17242518191210021210111B1724251819121010111A1848181A12 +7C24171A121010101C1724251819121010111A18023B24181A120F0F111B172524181A12 +1010111B1705F8F6A78D083FFC278D027C24171A121010101C1724251819121010111A18 +2524171A121010101C1724251819121010111A1801334A171A121010111B172524171A11 +100111101BFDD024171A12100111111B1748181A121010111B172524171A12100111111B +1748181A121010111B17026B4A171A121010111B172524171A11100111101B000000000C +00C8FE1405BD076D0016002B004100450049004D0064007C009200A800BE00D400000534 +373637363217161716151407060706232227262726013437363736321716171614070607 +062227262726013437363736321716171615140706070622272627260121112137211121 +132115210134373637363332171617161514070607062227262726253437363736333217 +161716151407060706232227262726103437363736321716171615140706070607222726 +270134373637363732171617161407060706222726272625343736373E01171617161407 +06070623222726272600343736373633321716171615140706070E0127262701C110101B +18481819131010111B172524181A111001090F111B174A171A121010111B1748191A120F +010810111C1748181A121010111B1847181A1310FCF604F5FB0B8D03DBFC258C02C3FD3D +01F210111B172524181A111010111A174A17191310FDEF10111A172624171A121010111B +1724251819121010111A1848181A121010111B172425181912020210111B172425181912 +1010111A1848181A1210FDEE10111A1848181A121010111B17242518191210021210111B +1724251819121010111A1848181A127C24171A121010101C1724251819121010111A1801 +3224171A131010111C1748181A121010111B17012E24181A120F0F111B172524181A1210 +10111B1705F8F6A78D083FFC278D027C24171A121010101C1724251819121010111A1825 +24171A121010101C1724251819121010111A1801334A171A121010111B172524171A1110 +0111101BFDD024171A12100111111B1748181A121010111B172524171A12100111111B17 +48181A121010111B17026B4A171A121010111B172524171A11100111101B000D00C8FE14 +05BD076D0015002B00410057005B005F0063007A009200A800BE00D400EA000001321716 +171614070607062227262726353437363736003217161716151407060706232227262726 +343736371232171617161407060706232227262726353437363701321716171615140706 +070622272627263437363736012111213721112113211521013437363736333217161716 +151407060706222726272625343736373633321716171615140706070623222726272610 +343736373632171617161514070607060722272627013437363736373217161716140706 +0706222726272625343736373E0117161716140706070623222726272600343736373633 +321716171615140706070E01272627044E24181A111010101B174A1719131010111B17FD +E94A1719131010111B172524181A111010101B174A1719131010111B172524181A111010 +101B025324181A111010101B174A1719131010111B17FC9F04F5FB0B8D03DBFC258C02C3 +FD3D01F210111B172524181A111010111A174A17191310FDEF10111A172624171A121010 +111B1724251819121010111A1848181A121010111B172425181912020210111B17242518 +19121010111A1848181A1210FDEE10111A1848181A121010111B17242518191210021210 +111B1724251819121010111A1848181A12020E10111A1848181A121010101C1724251819 +1210FDEE10111B1724251819121010111A1848181A12022210111A1848181A121010101C +172425181912FDFE10111B1724251819121010111A1848181A12100771F6A78D083FFC27 +8D027C24171A121010101C1724251819121010111A182524171A121010101C1724251819 +121010111A1801334A171A121010111B172524171A11100111101BFDD024171A12100111 +111B1748181A121010111B172524171A12100111111B1748181A121010111B17026B4A17 +1A121010111B172524171A11100111101B00000E00C8FE1405BD076D00140029003E0053 +0069006D00710075008B00A100B600CB00E000F500000132171617161407060706222726 +272634373637360032171617161407060706232227262726343736371232171617161407 +060706232227262726343736370132171617161407060706222726272634373637360332 +171617161514070607062227262726343736373601211121372111211321152101343736 +373633321716171614070607062227262726253437363736333217161716140706070622 +27262726103437363736321716171615140706070E0127262701343736373E0117161716 +1407060706222726272625343736373E0117161716140706070622272627260034373637 +36321716171615140706070E01272627044E24181A111010101B17491819131010111B17 +FDE9491819131010111B172524181A111010101B17491819131010111B172524181A1110 +10101B025324181A111010101B17491819131010111B17E424171A121010101C17491819 +121010111A18FDA804F5FB0B8D03DBFC258C02C3FD3D01F210111B172524181A11101011 +1A174918191310FDEF10111A172624171A121010111B17491819121010111A1848181A12 +1010111B1749181912020210111B17491819121010111A1848181A1210FDEE10111A1848 +181A121010111B174918191210021210111B17491819121010111A1848181A12020E1011 +1A1848181A121010101C174918191210FDEE10111B17491819121010111A1848181A1202 +2210111A1848181A121010101C1749181912FDFE10111B17491819121010111A1848181A +1210010910111A172624181A111010111A1849181912100668F6A78D083FFC278D027C24 +171A121010101C17491819121010111A182524171A121010101C17491819121010111A18 +013449171A121010111B172524171A11100111101BFDD024171A12100111111B1748181A +121010111B172524171A12100111111B1748181A121010111B17026C49171A121010111B +172524171A11100111101B000000000F00C8FE1405BD076D0014002A004000550069007F +00830087008B00A100B700CC00E100F6010B000025343736373632171617161407060706 +222726272625343736373633321716171614070607062227262726103437363736321716 +171615140706070607222726270034373637363732171617161407060706222726272434 +3736373E011716171614070607062227262700343736373633321716171615140706070E +012726270121112137211121132115210134373637363332171617161407060706222726 +272625343736373633321716171614070607062227262726103437363736321716171615 +140706070E0127262701343736373E01171617161407060706222726272625343736373E +011716171614070607062227262726003437363736321716171615140706070E01272627 +03D410111C1748181A120F0F111B1749171A1310FDEF10111B172524171A131010111C17 +48181A121010111B1847181A131010111C172424181A12020210111B172524181A111010 +101B1848181913FDDE10111B1847181A131010111C1748181A12020210111B172524181A +111010101B1848181913FCE304F5FB0B8D03DBFC258C02C3FD3D01F210111B172524181A +111010111A174918191310FDEF10111A172624171A121010111B17491819121010111A18 +48181A121010111B1749181912020210111B17491819121010111A1848181A1210FDEE10 +111A1848181A121010111B174918191210021210111B17491819121010111A1848181A12 +9124171A131010111C1748181A121010111B172524171A131010111C1748181A12101011 +1B170134491819121010111A172624171A11100111111AFDAB49171A12100111111B1748 +181A120F0F111B1749171A12100111111B1748181A120F0F111B0283491819121010111A +172624171A11100111111A05E4F6A78D083FFC278D027C24171A121010101C1749181912 +1010111A182524171A121010101C17491819121010111A18013449171A121010111B1725 +24171A11100111101BFDD024171A12100111111B1748181A121010111B172524171A1210 +0111111B1748181A121010111B17026C49171A121010111B172524171A11100111101B00 +0000000300C8FE140767076D000B0017001B000001221511143321323511342325213215 +11142321223511340111211101B8787804BF7878FB4104BFF0F0FB41F0012C044706F578 +F887787807797878F0F887F0F00779F0F7D30701F8FF000500C8FE140767076D00180024 +00300033003B000001160017161514232227231417233635230623223534373600012215 +111433213235113423252132151114232122351134010321033301232721072304D74101 +3B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F002 +4B86010CD29A01208444FEB64484031879FE7E26254EE49BCC6B6BCC9BE44E2526018204 +5678F887787807797878F0F887F0F00779F0FEACFE55020FFCBCDADA0000000400C8FE14 +0767076D0018002400300049000001160017161514232227231417233635230623223534 +373600012215111433213235113423252132151114232122351134012115213536370035 +342623220607353E013332161514010604D741013B091BC36C421E3B983B1E426CC31B09 +013BFD22787804BF7878FB4104BFF0F0FB41F0019E01A8FDAA223F01586855347A484D85 +3991AEFEB538031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678F88778780779 +7878F0F887F0F00779F0FC3F726E1F3801315E425123237B1C1C846C8BFEE43000000004 +00C8FE140767076D00180024003000590000011600171615142322272314172336352306 +23223534373600012215111433213235113423252132151114232122351134011E011514 +0623222627351E013332363534262B013533323635342623220607353E01333216151406 +04D741013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0 +FB41F0029A5C65BEB1397D463477436D786F6C565E5E61645F28665149803790A95A0318 +79FE7E26254EE49BCC6B6BCC9BE44E25260182045678F887787807797878F0F887F0F007 +79F0FD91126D527C861514791B1A4F464A4C6C3F3C3A3D12177311127663456000000005 +00C8FE140767076D0018002400300033003E000001160017161514232227231417233635 +230623223534373600012215111433213235113423252132151114232122351134090121 +033311331523152335213504D741013B091BC36C421E3B983B1E426CC31B09013BFD2278 +7804BF7878FB4104BFF0F0FB41F0028EFECB013516A6878790FE62031879FE7E26254EE4 +9BCC6B6BCC9BE44E25260182045678F887787807797878F0F887F0F00779F0FE97FE5D02 +1CFDE46DBABA79000000000400C8FE140767076D00180024003000510000011600171615 +142322272314172336352306232235343736000122151114332132351134232521321511 +142321223511340521152115363736333217161514070623222627351617163332363426 +2322060704D741013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB41 +04BFF0F0FB41F0011801FEFE791C1D1C1CA15E5E6160B03C7E42393E3E456F82826F3468 +36031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678F887787807797878F0F887 +F0F00779F0F05FCC0904044D4C83874B4A1212711B0E0D66AE6614150000000500C8FE14 +0767076D0018002400300040006000000116001716151423222723141723363523062322 +353437360001221511143321323511342325213215111423212235113401220706151417 +163332373635342726131526272623220706073637363332171615140706232226353437 +363332171604D741013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB +4104BFF0F0FB41F002475833333333585733333333AB313232318044440A26393A449154 +54585791A7B06C6CB6313232031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678 +F887787807797878F0F887F0F00779F0FD9E34355B5A343535345A5B3534016267140A0B +4B4C99311A1A4C4D847F4F4EDED4C675760809000000000400C8FE140767076D00180024 +003000370000011600171615142322272314172336352306232235343736000122151114 +332132351134232521321511142321223511341721150123012104D741013B091BC36C42 +1E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F0F00269FEA48801 +48FE33031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678F887787807797878F0 +F887F0F00779F0F030FCED02E400000600C8FE140767076D001800240030003D005B006A +000001160017161514232227231417233635230623223534373600012215111433213235 +113423252132151114232122351134002207061514163332373634272526272635343620 +171615140706071617161514070623222726353437363714171633323736353427262207 +0604D741013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0 +F0FB41F00297BA35356A5D5C363535FEEC542E2FA4011E52512E2F535A383555569E9F55 +5635362D2F2E55513130302FA6302F031879FE7E26254EE49BCC6B6BCC9BE44E25260182 +045678F887787807797878F0F887F0F00779F0FD5B2C2B4B4C562C2B962B5D1231324864 +743A3A644A303112123A37507941414141794E3938C63F26252524413F26252524000005 +00C8FE140767076D0018002400300050005F000001160017161514232227231417233635 +230623223534373600012215111433213235113423252132151114232122351134013516 +171633323736370607062322263534373633321716151407062322272613323635342726 +232207061514171604D741013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF +7878FB4104BFF0F0FB41F0012E313232308144430A233C394590A8575891A757586B6CB6 +313232CC58663333585535343433031879FE7E26254EE49BCC6B6BCC9BE44E2526018204 +5678F887787807797878F0F887F0F00779F0FBDF67140B0A4B4B9A2F1B1A9884814D4E6F +6FD4C6757608090172685C5A343535345A5C34340000000600C8FE140767076D00180024 +0030003E004A005100000116001716151423222723141723363523062322353437360001 +221511143321323511342325213215111423212235113400220706151417163237363534 +2F0132161514062322263534360111073537331104D741013B091BC36C421E3B983B1E42 +6CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F003EA8E323333328E3233337983 +AAAA83A28C8CFE74858C89031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678F8 +87787807797878F0F887F0F00779F0FEC85656ACAD56565656ADAC56AFDED3D4DEDED4D3 +DEFCAC02D1297427FCBD000400C8FE140767076D001800240030003D0000011600171615 +142322272314172336352306232235343736000122151114332132351134232521321511 +142321223511340533111407062B01353332363504D741013B091BC36C421E3B983B1E42 +6CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F001B27F41408F31285446031879 +FE7E26254EE49BCC6B6BCC9BE44E25260182045678F887787807797878F0F887F0F00779 +F0F0FDDC95464560546C000400C8FE140767076D001800240030004A0000011600171615 +142322272314172336352306232235343736000122151114332132351134232521321511 +14232122351134010E0123222635343633321617152E012322061514163332363704D741 +013B091BC36C421E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F0 +033C316539B5C8C9B43966302F6A367C7C7B7D376A2E031879FE7E26254EE49BCC6B6BCC +9BE44E25260182045678F887787807797878F0F887F0F00779F0FBEB1716E3CECDE51717 +742224AAACABAB242200000500C8FE140767076D0011001B00340040004C000001220623 +222635343633321615140607170712102623220610163332051600171615142322272314 +172336352306232235343736000122151114332132351134232521321511142321223511 +3402FB0411059E9B9C9E9F9C56587E5F0A55616055556061019441013B091BC36C421E3B +983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F0032C01D7DADBD7D7DB +A5C9286A38018001709E9EFE909E6E79FE7E26254EE49BCC6B6BCC9BE44E252601820456 +78F887787807797878F0F887F0F00779F000000400C8FE140767076D001800240030003B +000001160017161514232227231417233635230623223534373600012215111433213235 +113423252132151114232122351134173311013309012301112304D741013B091BC36C42 +1E3B983B1E426CC31B09013BFD22787804BF7878FB4104BFF0F0FB41F0F07F016AA4FE69 +01B8A7FE787F031879FE7E26254EE49BCC6B6BCC9BE44E25260182045678F88778780779 +7878F0F887F0F00779F0F0FE9F0161FE7AFE420193FE6D000000000500C8FE140767076D +00150021002D003000380000013637363332171615140709012635343736333217160122 +15111433213235113423252132151114232122351134010321033301232721072304CC16 +373E548B3E137DFEC4FEC07D133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F002 +4B86010CD29A01208444FEB6448401B6763E47952D3B9E9FFE6E01929DA03B2D95474104 +CC78F887787807797878F0F887F0F00779F0FEACFE55020FFCBCDADA0000000400C8FE14 +0767076D00150021002D0046000001363736333217161514070901263534373633321716 +012215111433213235113423252132151114232122351134012115213536370035342623 +220607353E013332161514010604CC16373E548B3E137DFEC4FEC07D133F8A563C37FD02 +787804BF7878FB4104BFF0F0FB41F0019E01A8FDAA223F01586855347A484D853991AEFE +B53801B6763E47952D3B9E9FFE6E01929DA03B2D95474104CC78F887787807797878F0F8 +87F0F00779F0FC3F726E1F3801315E425123237B1C1C846C8BFEE4300000000400C8FE14 +0767076D00150021002D0056000001363736333217161514070901263534373633321716 +012215111433213235113423252132151114232122351134011E0115140623222627351E +013332363534262B013533323635342623220607353E0133321615140604CC16373E548B +3E137DFEC4FEC07D133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F0029A5C65BE +B1397D463477436D786F6C565E5E61645F28665149803790A95A01B6763E47952D3B9E9F +FE6E01929DA03B2D95474104CC78F887787807797878F0F887F0F00779F0FD91126D527C +861514791B1A4F464A4C6C3F3C3A3D1217731112766345600000000500C8FE140767076D +00150021002D0030003B0000013637363332171615140709012635343736333217160122 +151114332132351134232521321511142321223511340901210333113315231523352135 +04CC16373E548B3E137DFEC4FEC07D133F8A563C37FD02787804BF7878FB4104BFF0F0FB +41F0028EFECB013516A6878790FE6201B6763E47952D3B9E9FFE6E01929DA03B2D954741 +04CC78F887787807797878F0F887F0F00779F0FE97FE5D021CFDE46DBABA790000000004 +00C8FE140767076D00150021002D004E0000013637363332171615140709012635343736 +333217160122151114332132351134232521321511142321223511340521152115363736 +3332171615140706232226273516171633323634262322060704CC16373E548B3E137DFE +C4FEC07D133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F0011801FEFE791C1D1C +1CA15E5E6160B03C7E42393E3E456F82826F34683601B6763E47952D3B9E9FFE6E01929D +A03B2D95474104CC78F887787807797878F0F887F0F00779F0F05FCC0904044D4C83874B +4A1212711B0E0D66AE6614150000000500C8FE140767076D00150021002D003D005D0000 +013637363332171615140709012635343736333217160122151114332132351134232521 +321511142321223511340122070615141716333237363534272613152627262322070607 +3637363332171615140706232226353437363332171604CC16373E548B3E137DFEC4FEC0 +7D133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F0024758333333335857333333 +33AB313232318044440A26393A44915454585791A7B06C6CB631323201B6763E47952D3B +9E9FFE6E01929DA03B2D95474104CC78F887787807797878F0F887F0F00779F0FD9E3435 +5B5A343535345A5B3534016267140A0B4B4C99311A1A4C4D847F4F4EDED4C67576080900 +0000000400C8FE140767076D00150021002D003400000136373633321716151407090126 +353437363332171601221511143321323511342325213215111423212235113417211501 +23012104CC16373E548B3E137DFEC4FEC07D133F8A563C37FD02787804BF7878FB4104BF +F0F0FB41F0F00269FEA4880148FE3301B6763E47952D3B9E9FFE6E01929DA03B2D954741 +04CC78F887787807797878F0F887F0F00779F0F030FCED02E400000600C8FE140767076D +00150021002D003A00580067000001363736333217161514070901263534373633321716 +012215111433213235113423252132151114232122351134002207061514163332373634 +272526272635343620171615140706071617161514070623222726353437363714171633 +3237363534272622070604CC16373E548B3E137DFEC4FEC07D133F8A563C37FD02787804 +BF7878FB4104BFF0F0FB41F00297BA35356A5D5C363535FEEC542E2FA4011E52512E2F53 +5A383555569E9F555635362D2F2E55513130302FA6302F01B6763E47952D3B9E9FFE6E01 +929DA03B2D95474104CC78F887787807797878F0F887F0F00779F0FD5B2C2B4B4C562C2B +962B5D1231324864743A3A644A303112123A37507941414141794E3938C63F2625252441 +3F2625252400000500C8FE140767076D00150021002D004D005C00000136373633321716 +151407090126353437363332171601221511143321323511342325213215111423212235 +113401351617163332373637060706232226353437363332171615140706232227261332 +3635342726232207061514171604CC16373E548B3E137DFEC4FEC07D133F8A563C37FD02 +787804BF7878FB4104BFF0F0FB41F0012E313232308144430A233C394590A8575891A757 +586B6CB6313232CC5866333358553534343301B6763E47952D3B9E9FFE6E01929DA03B2D +95474104CC78F887787807797878F0F887F0F00779F0FBDF67140B0A4B4B9A2F1B1A9884 +814D4E6F6FD4C6757608090172685C5A343535345A5C34340000000600C8FE140767076D +00150021002D003B0047004E000001363736333217161514070901263534373633321716 +012215111433213235113423252132151114232122351134002207061514171632373635 +342F0132161514062322263534360111073537331104CC16373E548B3E137DFEC4FEC07D +133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F003EA8E323333328E3233337983 +AAAA83A28C8CFE74858C8901B6763E47952D3B9E9FFE6E01929DA03B2D95474104CC78F8 +87787807797878F0F887F0F00779F0FEC85656ACAD56565656ADAC56AFDED3D4DEDED4D3 +DEFCAC02D1297427FCBD000400C8FE140767076D00150021002D003A0000013637363332 +171615140709012635343736333217160122151114332132351134232521321511142321 +223511340533111407062B01353332363504CC16373E548B3E137DFEC4FEC07D133F8A56 +3C37FD02787804BF7878FB4104BFF0F0FB41F001B27F41408F3128544601B6763E47952D +3B9E9FFE6E01929DA03B2D95474104CC78F887787807797878F0F887F0F00779F0F0FDDC +95464560546C000400C8FE140767076D00150021002D0047000001363736333217161514 +070901263534373633321716012215111433213235113423252132151114232122351134 +010E0123222635343633321617152E012322061514163332363704CC16373E548B3E137D +FEC4FEC07D133F8A563C37FD02787804BF7878FB4104BFF0F0FB41F0033C316539B5C8C9 +B43966302F6A367C7C7B7D376A2E01B6763E47952D3B9E9FFE6E01929DA03B2D95474104 +CC78F887787807797878F0F887F0F00779F0FBEB1716E3CECDE51717742224AAACABAB24 +2200000500D9FE140778076D0011001B0031003D00490000012206232226353436333216 +151406071707121026232206101633320136373633321716151407090126353437363332 +171601221511143321323511342325213215111423212235113402FB0411059E9B9C9E9F +9C56587E5F0A55616055556061018916373E548B3E137DFEC4FEC07D133F8A563C37FD13 +787804BF7878FB4104BFF0F0FB41F0032C01D7DADBD7D7DBA5C9286A38018001709E9EFE +909EFE30763E47952D3B9E9FFE6E01929DA03B2D95474104CC78F887787807797878F0F8 +87F0F00779F0000400C8FE140767076D00150021002D0038000001363736333217161514 +070901263534373633321716012215111433213235113423252132151114232122351134 +173311013309012301112304CC16373E548B3E137DFEC4FEC07D133F8A563C37FD027878 +04BF7878FB4104BFF0F0FB41F0F07F016AA4FE6901B8A7FE787F01B6763E47952D3B9E9F +FE6E01929DA03B2D95474104CC78F887787807797878F0F887F0F00779F0F0FE9F0161FE +7AFE420193FE6D000000000500C8FE140767076D00060012001E00210029000001300901 +300130012215111433213235113423252132151114232122351134010321033301232721 +072304CA0198FE68FE66FE88787804BF7878FB4104BFF0F0FB41F0024B86010CD29A0120 +8444FEB6448402F9FDF2FDFA0206060A78F887787807797878F0F887F0F00779F0FEACFE +55020FFCBCDADA000000000400C8FE140767076D00060012001E00370000013009013001 +300122151114332132351134232521321511142321223511340121152135363700353426 +23220607353E013332161514010604CA0198FE68FE66FE88787804BF7878FB4104BFF0F0 +FB41F0019E01A8FDAA223F01586855347A484D853991AEFEB53802F9FDF2FDFA0206060A +78F887787807797878F0F887F0F00779F0FC3F726E1F3801315E425123237B1C1C846C8B +FEE430000000000400C8FE140767076D00060012001E0047000001300901300130012215 +111433213235113423252132151114232122351134011E0115140623222627351E013332 +363534262B013533323635342623220607353E0133321615140604CA0198FE68FE66FE88 +787804BF7878FB4104BFF0F0FB41F0029A5C65BEB1397D463477436D786F6C565E5E6164 +5F28665149803790A95A02F9FDF2FDFA0206060A78F887787807797878F0F887F0F00779 +F0FD91126D527C861514791B1A4F464A4C6C3F3C3A3D1217731112766345600000000005 +00C8FE140767076D00060012001E0021002C000001300901300130012215111433213235 +113423252132151114232122351134090121033311331523152335213504CA0198FE68FE +66FE88787804BF7878FB4104BFF0F0FB41F0028EFECB013516A6878790FE6202F9FDF2FD +FA0206060A78F887787807797878F0F887F0F00779F0FE97FE5D021CFDE46DBABA790004 +00C8FE140767076D00060012001E003F0000013009013001300122151114332132351134 +232521321511142321223511340521152115363736333217161514070623222627351617 +1633323634262322060704CA0198FE68FE66FE88787804BF7878FB4104BFF0F0FB41F001 +1801FEFE791C1D1C1CA15E5E6160B03C7E42393E3E456F82826F34683602F9FDF2FDFA02 +06060A78F887787807797878F0F887F0F00779F0F05FCC0904044D4C83874B4A1212711B +0E0D66AE661415000000000500C8FE140767076D00060012001E002E004E000001300901 +300130012215111433213235113423252132151114232122351134012207061514171633 +323736353427261315262726232207060736373633321716151407062322263534373633 +32171604CA0198FE68FE66FE88787804BF7878FB4104BFF0F0FB41F00247583333333358 +5733333333AB313232318044440A26393A44915454585791A7B06C6CB631323202F9FDF2 +FDFA0206060A78F887787807797878F0F887F0F00779F0FD9E34355B5A343535345A5B35 +34016267140A0B4B4C99311A1A4C4D847F4F4EDED4C675760809000400C8FE140767076D +00060012001E002500000130090130013001221511143321323511342325213215111423 +21223511341721150123012104CA0198FE68FE66FE88787804BF7878FB4104BFF0F0FB41 +F0F00269FEA4880148FE3302F9FDF2FDFA0206060A78F887787807797878F0F887F0F007 +79F0F030FCED02E40000000600C8FE140767076D00060012001E002B0049005800000130 +090130013001221511143321323511342325213215111423212235113400220706151416 +333237363427252627263534362017161514070607161716151407062322272635343736 +37141716333237363534272622070604CA0198FE68FE66FE88787804BF7878FB4104BFF0 +F0FB41F00297BA35356A5D5C363535FEEC542E2FA4011E52512E2F535A383555569E9F55 +5635362D2F2E55513130302FA6302F02F9FDF2FDFA0206060A78F887787807797878F0F8 +87F0F00779F0FD5B2C2B4B4C562C2B962B5D1231324864743A3A644A303112123A375079 +41414141794E3938C63F26252524413F262525240000000500C8FE140767076D00060012 +001E003E004D000001300901300130012215111433213235113423252132151114232122 +351134013516171633323736370607062322263534373633321716151407062322272613 +323635342726232207061514171604CA0198FE68FE66FE88787804BF7878FB4104BFF0F0 +FB41F0012E313232308144430A233C394590A8575891A757586B6CB6313232CC58663333 +58553534343302F9FDF2FDFA0206060A78F887787807797878F0F887F0F00779F0FBDF67 +140B0A4B4B9A2F1B1A9884814D4E6F6FD4C6757608090172685C5A343535345A5C343400 +0000000600C8FE140767076D00060012001E002C0038003F000001300901300130012215 +111433213235113423252132151114232122351134002207061514171632373635342F01 +32161514062322263534360111073537331104CA0198FE68FE66FE88787804BF7878FB41 +04BFF0F0FB41F003EA8E323333328E3233337983AAAA83A28C8CFE74858C8902F9FDF2FD +FA0206060A78F887787807797878F0F887F0F00779F0FEC85656ACAD56565656ADAC56AF +DED3D4DEDED4D3DEFCAC02D1297427FCBD00000400C8FE140767076D00060012001E002B +000001300901300130012215111433213235113423252132151114232122351134053311 +1407062B01353332363504CA0198FE68FE66FE88787804BF7878FB4104BFF0F0FB41F001 +B27F41408F3128544602F9FDF2FDFA0206060A78F887787807797878F0F887F0F00779F0 +F0FDDC95464560546C00000400C8FE140767076D00060012001E00380000013009013001 +30012215111433213235113423252132151114232122351134010E012322263534363332 +1617152E012322061514163332363704CA0198FE68FE66FE88787804BF7878FB4104BFF0 +F0FB41F0033C316539B5C8C9B43966302F6A367C7C7B7D376A2E02F9FDF2FDFA0206060A +78F887787807797878F0F887F0F00779F0FBEB1716E3CECDE51717742224AAACABAB2422 +0000000500C8FE140767076D000600180022002E003A0000013009013001300322062322 +263534363332161514060717071210262322061016333201221511143321323511342325 +213215111423212235113404CA0198FE68FE66350411059E9B9C9E9F9C56587E5F0A5561 +6055556061FE75787804BF7878FB4104BFF0F0FB41F002F9FDF2FDFA0206024101D7DADB +D7D7DBA5C9286A38018001709E9EFE909E036F78F887787807797878F0F887F0F00779F0 +0000000400C8FE140767076D00060012001E002900000130090130013001221511143321 +3235113423252132151114232122351134173311013309012301112304CA0198FE68FE66 +FE88787804BF7878FB4104BFF0F0FB41F0F07F016AA4FE6901B8A7FE787F02F9FDF2FDFA +0206060A78F887787807797878F0F887F0F00779F0F0FE9F0161FE7AFE420193FE6D0003 +00C8FE140767076D00090015002100000113210113090113012101221511143321323511 +342325213215111423212235113404188E01D1FE898FFE89FE8790FE8901D0FE30787804 +BF7878FB4104BFF0F0FB41F00531FE47FEEFFE470111FEEF01B90111037D78F887787807 +797878F0F887F0F00779F0000000000500C8FE140767076D0023002F003B003E00460000 +053635062726272635343736333217263534201514073633321716151407060706271417 +012215111433213235113423252132151114232122351134010321033301232721072304 +553948AA351992103FAB4B317C01D87C314BAB3F10921935A94939FCC9787804BF7878FB +4104BFF0F0FB41F0024B86010CD29A01208444FEB64484FC4CFBC303010C439534248D13 +826AEAEA6A82138D243492460C0105C5FB4C07F178F887787807797878F0F887F0F00779 +F0FEACFE55020FFCBCDADA000000000400C8FE140767076D0023002F003B005400000536 +350627262726353437363332172635342015140736333217161514070607062714170122 +151114332132351134232521321511142321223511340121152135363700353426232206 +07353E013332161514010604553948AA351992103FAB4B317C01D87C314BAB3F10921935 +A94939FCC9787804BF7878FB4104BFF0F0FB41F0019E01A8FDAA223F01586855347A484D +853991AEFEB538FC4CFBC303010C439534248D13826AEAEA6A82138D243492460C0105C5 +FB4C07F178F887787807797878F0F887F0F00779F0FC3F726E1F3801315E425123237B1C +1C846C8BFEE430000000000400C8FE140767076D0023002F003B00640000053635062726 +272635343736333217263534201514073633321716151407060706271417012215111433 +213235113423252132151114232122351134011E0115140623222627351E013332363534 +262B013533323635342623220607353E0133321615140604553948AA351992103FAB4B31 +7C01D87C314BAB3F10921935A94939FCC9787804BF7878FB4104BFF0F0FB41F0029A5C65 +BEB1397D463477436D786F6C565E5E61645F28665149803790A95AFC4CFBC303010C4395 +34248D13826AEAEA6A82138D243492460C0105C5FB4C07F178F887787807797878F0F887 +F0F00779F0FD91126D527C861514791B1A4F464A4C6C3F3C3A3D12177311127663456000 +0000000500C8FE140767076D0023002F003B003E00490000053635062726272635343736 +333217263534201514073633321716151407060706271417012215111433213235113423 +252132151114232122351134090121033311331523152335213504553948AA351992103F +AB4B317C01D87C314BAB3F10921935A94939FCC9787804BF7878FB4104BFF0F0FB41F002 +8EFECB013516A6878790FE62FC4CFBC303010C439534248D13826AEAEA6A82138D243492 +460C0105C5FB4C07F178F887787807797878F0F887F0F00779F0FE97FE5D021CFDE46DBA +BA79000400C8FE140767076D0023002F003B005C00000536350627262726353437363332 +172635342015140736333217161514070607062714170122151114332132351134232521 +321511142321223511340521152115363736333217161514070623222627351617163332 +3634262322060704553948AA351992103FAB4B317C01D87C314BAB3F10921935A94939FC +C9787804BF7878FB4104BFF0F0FB41F0011801FEFE791C1D1C1CA15E5E6160B03C7E4239 +3E3E456F82826F346836FC4CFBC303010C439534248D13826AEAEA6A82138D243492460C +0105C5FB4C07F178F887787807797878F0F887F0F00779F0F05FCC0904044D4C83874B4A +1212711B0E0D66AE661415000000000500C8FE140767076D0023002F003B004B006B0000 +053635062726272635343736333217263534201514073633321716151407060706271417 +012215111433213235113423252132151114232122351134012207061514171633323736 +353427261315262726232207060736373633321716151407062322263534373633321716 +04553948AA351992103FAB4B317C01D87C314BAB3F10921935A94939FCC9787804BF7878 +FB4104BFF0F0FB41F002475833333333585733333333AB313232318044440A26393A4491 +5454585791A7B06C6CB6313232FC4CFBC303010C439534248D13826AEAEA6A82138D2434 +92460C0105C5FB4C07F178F887787807797878F0F887F0F00779F0FD9E34355B5A343535 +345A5B3534016267140A0B4B4C99311A1A4C4D847F4F4EDED4C675760809000400C8FE14 +0767076D0023002F003B0042000005363506272627263534373633321726353420151407 +363332171615140706070627141701221511143321323511342325213215111423212235 +11341721150123012104553948AA351992103FAB4B317C01D87C314BAB3F10921935A949 +39FCC9787804BF7878FB4104BFF0F0FB41F0F00269FEA4880148FE33FC4CFBC303010C43 +9534248D13826AEAEA6A82138D243492460C0105C5FB4C07F178F887787807797878F0F8 +87F0F00779F0F030FCED02E40000000600C8FE140767076D0023002F003B004800660075 +000005363506272627263534373633321726353420151407363332171615140706070627 +141701221511143321323511342325213215111423212235113400220706151416333237 +363427252627263534362017161514070607161716151407062322272635343736371417 +16333237363534272622070604553948AA351992103FAB4B317C01D87C314BAB3F109219 +35A94939FCC9787804BF7878FB4104BFF0F0FB41F00297BA35356A5D5C363535FEEC542E +2FA4011E52512E2F535A383555569E9F555635362D2F2E55513130302FA6302FFC4CFBC3 +03010C439534248D13826AEAEA6A82138D243492460C0105C5FB4C07F178F88778780779 +7878F0F887F0F00779F0FD5B2C2B4B4C562C2B962B5D1231324864743A3A644A30311212 +3A37507941414141794E3938C63F26252524413F262525240000000500C8FE140767076D +0023002F003B005B006A0000053635062726272635343736333217263534201514073633 +321716151407060706271417012215111433213235113423252132151114232122351134 +013516171633323736370607062322263534373633321716151407062322272613323635 +342726232207061514171604553948AA351992103FAB4B317C01D87C314BAB3F10921935 +A94939FCC9787804BF7878FB4104BFF0F0FB41F0012E313232308144430A233C394590A8 +575891A757586B6CB6313232CC58663333585535343433FC4CFBC303010C439534248D13 +826AEAEA6A82138D243492460C0105C5FB4C07F178F887787807797878F0F887F0F00779 +F0FBDF67140B0A4B4B9A2F1B1A9884814D4E6F6FD4C6757608090172685C5A343535345A +5C3434000000000600C8FE140767076D0023002F003B00490055005C0000053635062726 +272635343736333217263534201514073633321716151407060706271417012215111433 +213235113423252132151114232122351134002207061514171632373635342F01321615 +14062322263534360111073537331104553948AA351992103FAB4B317C01D87C314BAB3F +10921935A94939FCC9787804BF7878FB4104BFF0F0FB41F003EA8E323333328E32333379 +83AAAA83A28C8CFE74858C89FC4CFBC303010C439534248D13826AEAEA6A82138D243492 +460C0105C5FB4C07F178F887787807797878F0F887F0F00779F0FEC85656ACAD56565656 +ADAC56AFDED3D4DEDED4D3DEFCAC02D1297427FCBD00000400C8FE140767076D0023002F +003B00480000053635062726272635343736333217263534201514073633321716151407 +060706271417012215111433213235113423252132151114232122351134053311140706 +2B01353332363504553948AA351992103FAB4B317C01D87C314BAB3F10921935A94939FC +C9787804BF7878FB4104BFF0F0FB41F001B27F41408F31285446FC4CFBC303010C439534 +248D13826AEAEA6A82138D243492460C0105C5FB4C07F178F887787807797878F0F887F0 +F00779F0F0FDDC95464560546C00000400C8FE140767076D0023002F003B005500000536 +350627262726353437363332172635342015140736333217161514070607062714170122 +15111433213235113423252132151114232122351134010E012322263534363332161715 +2E012322061514163332363704553948AA351992103FAB4B317C01D87C314BAB3F109219 +35A94939FCC9787804BF7878FB4104BFF0F0FB41F0033C316539B5C8C9B43966302F6A36 +7C7C7B7D376A2EFC4CFBC303010C439534248D13826AEAEA6A82138D243492460C0105C5 +FB4C07F178F887787807797878F0F887F0F00779F0FBEB1716E3CECDE51717742224AAAC +ABAB24220000000500C8FE140767076D0023002F003B004D005700000536350627262726 +353437363332172635342015140736333217161514070607062714170122151114332132 +351134232521321511142321223511340122062322263534363332161514060717071210 +262322061016333204553948AA351992103FAB4B317C01D87C314BAB3F10921935A94939 +FCC9787804BF7878FB4104BFF0F0FB41F002330411059E9B9C9E9F9C56587E5F0A556160 +55556061FC4CFBC303010C439534248D13826AEAEA6A82138D243492460C0105C5FB4C07 +F178F887787807797878F0F887F0F00779F0FBBF01D7DADBD7D7DBA5C9286A3801800170 +9E9EFE909E00000400C8FE140767076D0023002F003B0046000005363506272627263534 +373633321726353420151407363332171615140706070627141701221511143321323511 +3423252132151114232122351134173311013309012301112304553948AA351992103FAB +4B317C01D87C314BAB3F10921935A94939FCC9787804BF7878FB4104BFF0F0FB41F0F07F +016AA4FE6901B8A7FE787FFC4CFBC303010C439534248D13826AEAEA6A82138D24349246 +0C0105C5FB4C07F178F887787807797878F0F887F0F00779F0F0FE9F0161FE7AFE420193 +FE6D000400C8FE140767076D00090013001F002B00000103210503250503252103130501 +132505130125012215111433213235113423252132151114232122351134041864FEBC01 +066501070105630105FEBB62AD01B2FEB860FE89FE8762FEB701B3FE4D787804BF7878FB +4104BFF0F0FB41F0046EFECEBEFECDBEBE0133BE01F5FE6F28FEE2FE54DFDF01A8012228 +035578F887787807797878F0F887F0F00779F000000000080099FF6A07BF075600070011 +00190023002B0033007F00B7000000343632161406223722061514333236353424140622 +26343632172206151433323635340034363216140622361406222634363206323E033713 +17073E043534262726273E0135342623220E011514170726353437262207161514072736 +35342E012322061514161706070E0115141E03172737131E031232173E01333216151407 +1615140E02070507250706070507250E01222627052725262F010527252E033534372635 +34363332161702E6425C42425C74112D0D112E01AF425C42425C17112C0D112DFE6F1620 +161620EC1620161620A16C4E2E1B090210860C3B71735636433730414962624F53824312 +8217012AA82A011782124382534F62624941303743365673713B0C861002091B2E38984C +2BCA8F8BAD5D9C4B839859019F04FDEC01020401FB16FE0D20A9FAA920FE0D1601FB0402 +01FDEC04019F5998834B9C5DAD8B8FCA2B03389067679067823D18123E18117590676790 +67DC3D18123E1811FD2236272736275D3627273627E024395D573E013A06FB1F4C6B7493 +4C4A9837332823835851696BA45C423F2653541B0E03030E1B5453263F425CA46B695158 +8323283337984A4C93746B4C1FFB06FEC63E575D3906020686BEB78C8A72ACC86CC8A17E +331A4423011F1FAE40AC6D8A8A6DAC40AE1F1F0123441A337EA1C86CC8AC728A8CB7BE86 +0000000B004BFF6A092D068A00090014001800220028002C008E0099009F00FD01070000 +012226343633321614062732363534232207061514173315230122263436333216140601 +37170727370115233525170607061514171E0133323736373635342726273716173E0437 +1633323E04373E013534272E0423220E020F0106072624200407262F012E0323220E0307 +06151416171E053332371E04173625323635342322070615140107170727370134372726 +2723222E06272E0135343E01373E0133321E0217161F01363736201716173736373E0333 +3216171E02151406070E072B01060F01161514070607060706070E012226272627262726 +2726010620271617163332360379323E3D33323E3D1E122D0E111716356E6E0249333D3E +32333D3EFB5333C5393F2A03946EFDD2423F11053934B18B694580423905113F42181205 +1E0D140A0152272038252F16360C312E3402261E2B2B121120231013133F593DFEF7FEBE +FEF73D593F131310232011122B2B1E2602342E310C36162F2538202752010A140D1E0512 +0306122D0E11171601EDAA2A3F39C5FACB0D1539100A213B2B351B37133F0A423A292C23 +3387411D333219172511412837A401A4A43728411125171932331D418733232C293A420A +3F13371B352B3B210A1039150D0D2260091620423688C48836422016095F230D033461FE +BA61111F379D6B7E02D16E826E6E826E1B4115111F1D1912D4D2018B6E826E6E826E02B5 +2DDABA1894FD4DD2D29376244411194B3B3722080D443B4B19114424760D0F136835574D +27140B0C2011310A2A4C372E3402281A23130E221119194E83656D6D65834E191911220E +13231A2802342E374C2A0A3111200C0B14274D573568130F4E4115111F1D1912029ABB94 +18BADAFBEF2A2C50DD4909081B0D2C0F39093A6B52345F35223253122718192715521D1B +4E4E1B1D52152719182712533222355F34526B3A09390F2C0D1B080949DD502C2A302873 +424430482E261B1B262E483044427328FEE016162415272600000008003DFF6A081D0714 +00110019002800320041004E007F00B80000013716333237163332371706232227062322 +01170622273716320106070623222726353437363332160722061514173E013726053E01 +33321716151407062322272617163332373635342726232206053635342E022726270607 +26232207262706070E031514172517051617251705161716203736372537053637253705 +2635343E0337363F01171E01173632173E013F011716171E041514071707270607170727 +02070621202726030727372627072702A65C455B3A54543A5B455C6C904A4445499001B8 +3E7AC27A3E5A86FEFC2B512028463615352F4146654E2430052B45121601B8056446422F +35153646282051790C1A302810040C0D2C400193120A0A16041B2AF399749B9D7499F32A +1B05150B0A1201420AFEBF0E17011E1AFEE167A4B501EAB5A467FEE11A011E150FFEC00A +FAC01107130C1F0517260F3C8FFD5D69E2695DFD8F3B102617051F0C1307116C0A6D0F20 +6E1A6D75C3D9FEE0FEDFD9C1766D1A6D1E106D0A018C62402C2C4062641C1C01A8783D3D +782E01064C1C0B23242D47302C612630240E0F073425111D43612C30472D24230B1C0403 +16181C0E0C033BA782742E663E7619A1A73AC41515C43AA7A11B7241652E74822B422C45 +40793E7AE777848477E77A3E79394C2C423D7C8F396A77419318758C3A091689610D0D61 +8916093A8C75189341776A398F7C0F420E51572E3E2EFEF78E9E9E8C010B2E3E2E51570E +4200000B00AAFF6A0896070B00070011001D00250031003B00470068009800AA00BC0000 +003436321614062237220615143332363534032235343736333215140706001406222634 +363201343332171615142322272601220615143332363534013716333236371706212226 +122037363534273E0235342726232206072E012322070615141E01170615141712200417 +16173633321615140706070E012B0106070E01070620272E012726272322262726272635 +343633321736373603262322070E011514171617163B0136372625060716173332373637 +36353426272623220319527452527491163710163810182C251E182C25020D5274525274 +FEFF181F252C181F252C011E1637101638FCE36A82CE60B43B6AA8FEEE80EA8A01C0A57C +541E1D1F565A8247711F1C7047825A561F1D1E547CD0016A0137616910191D6A90080D1C +228659070F3A38AB6D94FEE0946DAB383A0F075986221C0D08906A1D19106961D618200D +15282B0717471F2604083C35057410353C0804261F4717072B28150D2004087452527452 +6831130E31130EFE521520241F1520241F020C7452527452FE05151F2420151F24016B31 +130E31130EFDA854A4584C54D672FEDECB9AB886952C305E318A696F453535456F698A31 +5E302C9586B89A0600A47C889B06A4872C365B3F4B697B706EAD374C4C37AD6E707B694B +3F5B362C87A4069B887CFDCC15070F523A1E33972F15877B576060577B87152F97331E3A +520F07000000000900AAFF6A07AD066E000B00160022002E00340039003E004200460000 +013436333216151406232226253436321615140623222605100021200011100021200003 +100021200011100021200001211000200005363711230311231116253637230535231602 +82513B3A52523A3B510242527453533A3B51FC6D01BF013C013D01BDFE43FEC3FEC4FE41 +87020E01740175020CFDF4FE8BFE8CFDF2011404DBFE95FDFCFE9402B2584CA488A44C02 +0C481C64FD20651C03FF3B51513B3A53533A3B51513B3A5353DBFEC4FE4301BD013C013D +01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C016AFEFEFE96016ADF0B2A0125FEA601 +5AFEDB2A995264B8B866000900AAFF6A07AD066E000B00170023002F0035003A003F0043 +004700000110002120001110002120000310002120001110002120000134363216152334 +262206152134363216152334262206150721100020000536371123031123111625363723 +05352316013101BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE +8CFDF203BC8CC48C873D543DFD298CC48C873D543DDF04DBFE95FDFCFE9402B2584CA488 +A44C020C481C64FD20651C02EAFEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE +8BFE8CFDF4020C02168BC5C58B537777538BC5C58B53777753ACFEFEFE96016ADF0B2A01 +25FEA6015AFEDB2A995264B8B8660009005FFF6A08F9066E00030007000C001100170023 +002F0059007E000001352316053637230111231116173637112325211000200013343632 +161523342622061521343632161523342622061505100021200011140717161716151406 +2227262F010207002120012603070607062226353437363F012637100021200011342706 +232227262F011716172627262120070607363F0107060706222706033C651C0329481C64 +FE4CA44CE0584CA4FD4E04DBFE95FDFCFE94588CC48C873D543D01C98CC48C873D543DFB +BD020E01740175020C025D3B181E364C1F19120B2AD1FEFAFE8BFE8CFEF9D12B0A12191F +4C361E173C5C028701BF013C013D01BD011B25261F19122D8829182F9ADEFEC2FEC4E09A +2F1829882D12191F4C1B0101A1B866505264FEA6015AFEDB2A0B0B2A012587FEFEFE9601 +6A01AE8BC5C58B537777538BC5C58B53777753A20175020FFDF1FE8B1D1B1F14181F2526 +361F193920FEE9D1FEFA0106D001161E39191F3626251F19131E1C1DFEC4FE4301BD013C +14131A1F1939882D0E0FC79AE0E09BC6100D2D8839191F1B1400000600AAFF6A07AD066E +000B00160022002E0034003C000001343633321615140623222625343632161514062322 +260510002120001110002120001310002120001110002120001321100020002521161716 +2437360282513B3A52523A3B510242527453533A3B51FBE6020E01740175020CFDF4FE8B +FE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE418D04DBFE95FDFCFE940442FC57215C +8E01948E5C03FF3B51513B3A53533A3B51513B3A5353DB0175020FFDF1FE8BFE8CFDF402 +0C0174FEC4FE4301BD013C013D01C0FE40FEB9FEFEFE96016A7B755C8E018E5C00000006 +00AAFF6A07AD066E000B00170023002F0035003E00000134363216152334262206152134 +363216152334262206150510002120001110002120001310002120001110002120001321 +1000200025211617163732373602168CC48C873D543D01C98CC48C873D543DFBBD020E01 +740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE418D04DBFE95FD +FCFE940442FC57215C8ECACA8E5C038C8BC5C58B537777538BC5C58B53777753A2017502 +0FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FEB9FEFEFE96016A7B +755C8E018E5C000700AAFF6A07AD066E000B00170023002F003B0041004A000001171607 +060706222635343705343632161523342622061521343632161523342622061505100021 +200011100021200013100021200011100021200013211000200025211617163732373606 +A8411C01011A1B4C361BFBAF8CC48C873D543D01C98CC48C873D543DFBBD020E01740175 +020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE418D04DBFE95FDFCFE94 +0442FC57215C8ECACA8E5C042C8038222B1A1B362C2335208BC5C58B537777538BC5C58B +53777753A20175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FE +B9FEFEFE96016A7B755C8E018E5C000600AAFF6A07AD066E0006000D00190025002B0033 +000001251707170725271505273727370110002120001110002120001310002120001110 +002120001321100020002521161716243736047C01274DC6C64DFED9A0FED94DC6C64DFD +F5020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE418D04 +DBFE95FDFCFE940442FC57215C8E01948E5C0427CE6E8B8A6ECE5555CE6E8A8B6EFDF501 +75020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FEB9FEFEFE9601 +6A7B755C8E018E5C00000007008FFF6A07C80763000F001F002A003500490057005F0000 +0137161716043736371706070620272601100021200011102F0106212027070605343632 +161514062322262534363332161406232226013424212004151407161110002120001110 +372625363534242120041514173621201726232207163332021D731C288F01938E271D73 +2532B6FDFCB632FEEF01BF013C013D01BDDE0EE5FED7FED8E60EE00394527453533A3B51 +FDBE513B3A52523A3B51FE0D021E017F0180021CFDE2FDF4FE8BFE8CFDF2E3FE05DBBCFE +43FEC3FEC4FE41BDF4014A014B43B0DEDDB0B0DDDE01D1472C268D018E272C483932B5B5 +330151FEC4FE4301BD013C013DE00E39390EE02A3B51513B3A53533A3B5151765253025C +86BEBE86825CFCFEA5FE8CFDF4020C0174015BFC5C02364A517272514A36CFF56E6E1C00 +0000000700AAFF6A07AD07300003000E0019001D002D0039004A00000105072501343632 +161514062322262534363332161406232226011705270137161716203736371706070620 +2726011000212000111000212000013620172511161110002120001110371102D2012F4D +FED1023F527453533A3B51FDBE513B3A52523A3B5103044DFED14DFDC6731C288E01948E +271D732532B6FDFCB632FEEF01BF013C013D01BDFE43FEC3FEC4FE4101E284012A8401BB +ADFDF4FE8BFE8CFDF2AE0567D46FD5FEBC3B51513B3A53533A3B515176525301EC6ED56F +FD3E472C268E8E272C483932B5B5330151FEC4FE4301BD013C013D01C0FE40021D2A2AEC +FDD0E7FED1FE8CFDF4020C0174012FE70230000500AAFF6A07AD066E000F001B00270032 +003900000137161716203736371706070620272601100021200011100021200003100021 +200011100021200001343633321614062322262D011707170725021D731C288E01948E27 +1D732532B6FDFCB632FEEF01BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020C +FDF4FE8BFE8CFDF201D8513B3A52523A3B5101FA01274DC6C64DFED901D1472C268E8E27 +2C483932B5B5330151FEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFD +F4020C02873B515176525364CE6E8B8A6ECE000500AAFF6A07AD066E000F001B00270033 +003F00000137161716203736371706070620272603343632161523342622061521343632 +1615233426220615051000212000111000212000131000212000111000212000021D731C +288E01948E271D732532B6FDFCB6322C8CC48C873D543D01C98CC48C873D543DFBBD020E +01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4101D1472C26 +8E8E272C483932B5B53301F38BC5C58B537777538BC5C58B53777753A20175020FFDF1FE +8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40000000000500AAFF6A07AD066E +000B00170037004300510000013436321615233426220615213436321615233426220615 +05100021323726270623200011331400200035331407161D013611100021200003100021 +2000111000212000050607161716333237363734272604668CC48C873D543DFD298CC48C +873D543DFE9401BF013C887665412B2DFEFEFE9487011D0194011C877B37D1FE43FEC3FE +C4FE4187020E01740175020CFDF4FE8BFE8CFDF205104F5C1F26241B100D21010102038C +8BC5C58B537777538BC5C58B53777753A2FEC4FE4329306C05016A0102CAFEE5011CC9D5 +A0646304DA0133013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C36432428181708 +133905062E00000500AAFF6A07AD066E000F001B00270033003F00000137161716203736 +371706070620272601140622263533141632363521140622263533141632363501100021 +2000111000212000131000212000111000212000021D731C288E01948E271D732532B6FD +FCB63204008CC48C873D543DFE378CC48C873D543DFD3F020E01740175020CFDF4FE8BFE +8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4101D1472C268E8E272C483932B5B53302 +EF8BC5C58B537777538BC5C58B53777753FE620175020FFDF1FE8BFE8CFDF4020C0174FE +C4FE4301BD013C013D01C0FE4000000500AAFF6A07AD066E0017002F003B004700570000 +0132171615060F0123272635263736331617161733343736213217161533363736373217 +1607140F0123272627343736011000212000111000212000131000212000111000212000 +1337161716203736371706070620272605A61A193C0132A8029F3E010A263F29201C0A01 +0D23FD493D230D010A1C20293F260A013E9F02A832013C19FE12020E01740175020CFDF4 +FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE41EC731C288E01948E271D732532 +B6FDFCB63204C00E253F413ECFBE444C151944012020310E1D47471D0E31202001441915 +4C44BECF3E413F250EFE2A0175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C01 +3D01C0FE40FDAA472C268E8E272C483932B5B5330000000400AAFF6A07AD066E00170023 +0033003C000001100021200011342723151406222635140622263D012306071000212000 +11100021200025371617160437363717060706202726032126272621200706013101BF01 +3C013D01BD546EA6ECA6A6ECA66E5587020E01740175020CFDF4FE8BFE8CFDF20173731C +288F01938E271D732532B6FDFCB6326004941618DEFEC2FEC4E01802EAFEC4FE4301BD01 +3CC3A04F648E8E64648E8E644FA0C30175020FFDF1FE8BFE8CFDF4020C5B472C268D018E +272C483932B5B533033B1A19E0E019000000000500AAFF6A07AD066E000300070011001D +002900000135211521352115133237363717060706210110002120001110002120001310 +00212000111000212000049801AAFBD401AA6CCA8E271D732532B6FEFEFC7E020E017401 +75020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE41038C87878787FDAC +8E272C483932B502390175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01 +C0FE40000000000500AAFF6A07AD066E0003000E00190025003100000135211501343633 +321614062322262534363216151406232226051000212000111000212000131000212000 +111000212000024C03C0FC76513B3A52523A3B510242527453533A3B51FBE6020E017401 +75020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4101718787028C3B51 +517652533A3B51513B3A5353D90175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD01 +3C013D01C0FE40000000000500AAFF6A07AD066E00030007000B00170023000001352115 +2135211501352115011000212000111000212000131000212000111000212000049801AA +FBD401AAFE8C03C0FA9E020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE +43FEC3FEC4FE41038C87878787FDE5878701790175020FFDF1FE8BFE8CFDF4020C0174FE +C4FE4301BD013C013D01C0FE4000000500AAFF6A07AD066E000B0017001B002900370000 +011000212000111000212000031000212000111000212000053521150135213216151406 +2322263534352135213216151406232226353435013101BF013C013D01BDFE43FEC3FEC4 +FE4187020E01740175020CFDF4FE8BFE8CFDF201A203C0FC0201783A52523A3B51015601 +783A53533A3B5102EAFEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFD +F4020C058787022D87513B3A53533A030287513B3A53533A0302000800AAFF6A07AD066E +0008000C00100014001F002A003600420000011716140622263437013521151325370D01 +272517013436333216140623222625343632161514062322260510002120001110002120 +001310002120001110002120000650411B364C361BFC3D03C00BFEA84D0158FBDD4D0158 +4DFEE9513B3A52523A3B510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8C +FDF28701BF013C013D01BDFE43FEC3FEC4FE4103AC80354F36364F35FE45878702A5F06F +F16E6EF16FFEAF3B51517652533A3B51513B3A5353910175020FFDF1FE8BFE8CFDF4020C +0174FEC4FE4301BD013C013D01C0FE400000000500AAFF6A07AD066E0003000F001B0027 +003300000135211513140622263533141632363521140622263533141632363501100021 +2000111000212000131000212000111000212000024C03C0368CC48C873D543DFE378CC4 +8C873D543DFD3F020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3 +FEC4FE410171878703178BC5C58B537777538BC5C58B53777753FE620175020FFDF1FE8B +FE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40000500AAFF6A07AD066E0003000E +001900250031000025270117013436333216140623222625343632161514062322260510 +00212000111000212000131000212000111000212000029539036739FC86513B3A52523A +3B510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D +01BDFE43FEC3FEC4FE41AC7B01967B01BB3B51517652533A3B51513B3A5353D90175020F +FDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40000500AAFF6A07AD066E +0003000F001B001F00420000012701170110002120001110002120000310002120001110 +002120000901370103220706070E01272627262322072736373632171617163332373637 +3632171617072602C95F010E5FFD5A01BF013C013D01BDFE43FEC3FEC4FE4187020E0174 +0175020CFDF4FE8BFE8CFDF204E5FEF25F010E9A34290B1846C4461C0C22353820791019 +46C4461C0C223534280C1846C4461810791F035C5F010E5FFE80FEC4FE4301BD013C013D +01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C01E6010E5FFEF2FE57691D2262016328 +215F5F392822626228225F691E2262622228395F0000000500AAFF6A07AD066E000B0016 +0023002F003B000001343633321615140623222625343632161514062322260337273705 +15071715052737270110002120001110002120001310002120001110002120000282513B +3A52523A3B510242527453533A3B51CE50904101033C3CFEFD419050FCB4020E01740175 +020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4103FF3B51513B3A5353 +3A3B51513B3A5353FE243E4E768D582E2E588D764E3E01230175020FFDF1FE8BFE8CFDF4 +020C0174FEC4FE4301BD013C013D01C0FE40000600AAFF6A07AD066E0017002200290036 +0042004E00000132171615060F0123272635263736331617161733343736013436333216 +14062322262D011707170725033727370515071715052737270110002120001110002120 +00131000212000111000212000066A1A1A3C0232A8029E3E020A264028201C0A020C22FC +56513B3A52523A3B5101FA01274DC6C64DFED98650904101033C3CFEFD419050FCB4020E +01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4102AF0E253F +413ECFBE444C151944012020310E1D47014E3B515176525364CE6E8B8A6ECEFE173E4E76 +8D582E2E588D764E3E01230175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C01 +3D01C0FE4000000500AAFF6A07AD066E000B001700240030003C00000134363216152334 +262206152134363216152334262206150337273705150717150527372701100021200011 +100021200013100021200011100021200002168CC48C873D543D01C98CC48C873D543DF7 +50904101033C3CFEFD419050FCB4020E01740175020CFDF4FE8BFE8CFDF28701BF013C01 +3D01BDFE43FEC3FEC4FE41038C8BC5C58B537777538BC5C58B53777753FE5D3E4E768D58 +2E2E588D764E3E01230175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01 +C0FE40000000000500AAFF6A07AD066E000C001800240030003C00000137273705150717 +150527372701140622263533141632363521140622263533141632363501100021200011 +100021200013100021200011100021200003F650904101033C3CFEFD419050024C8CC48C +873D543DFE378CC48C873D543DFD3F020E01740175020CFDF4FE8BFE8CFDF28701BF013C +013D01BDFE43FEC3FEC4FE4101E93E4E768D582E2E588D764E3E02C18BC5C58B53777753 +8BC5C58B53777753FE620175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D +01C0FE400000000600AAFF6A07AD066E000B00160022002E0038003E0000013436333216 +151406232226253436321615140623222605100021200011100021200003100021200011 +1000212000253521152314062226352123141632360282513B3A52523A3B510242527453 +533A3B51FC6D01BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE +8CFDF201A203C0F28CC48C0155CE3D543D03FF3B51513B3A53533A3B51513B3A5353DBFE +C4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C6387878BC5C58B +537777000000000600AAFF6A07AD066E000A0011001D0029003300390000013436333216 +14062322262D011707170725051000212000111000212000031000212000111000212000 +253521152314062226352123141632360282513B3A52523A3B5101FA01274DC6C64DFED9 +FCB501BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE8CFDF201 +A203C0F28CC48C0155CE3D543D03FD3B515176525364CE6E8B8A6ECEE8FEC4FE4301BD01 +3C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C6387878BC5C58B537777000006 +00AAFF6A07AD066E000B0017001E0025002F003500000110002120001110002120000310 +002120001110002120000115052737273705251707170725013521152314062226352123 +14163236013101BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE +8CFDF20332FED94DC6C64D01C701274DC6C64DFED9FDD003C0F28CC48C0155CE3D543D02 +EAFEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C02B155CE6E +8A8B6ECECE6E8B8A6ECEFE0787878BC5C58B53777700000700AAFF6A07AD066E00030007 +0012001D00290035004600000125370D0127251701343633321614062322262534363216 +151406232226051000212000111000212000131000212000111000212000133637362017 +16170726272623260706070617FEA84D0158FBDD4D01584DFEE9513B3A52523A3B510242 +527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43 +FEC3FEC4FE41EC2532B60204B63324731C288ECACA8E271D0416F06FF16E6EF16FFEAF3B +51517652533A3B51513B3A5353910175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD +013C013D01C0FE40FD253833B5B53338482C278E018E272C0000000800AAFF6A07AD066E +000F001300170022002D0039004500560000252736373632171617072627262207060125 +370D01272517013436333216140623222625343632161514062322260510002120001110 +00212000131000212000111000212000133637362017161707262726232607060703D579 +101846C44618107907081E541E08023BFEA84D0158FBDD4D01584DFEE9513B3A52523A3B +510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01 +BDFE43FEC3FEC4FE41EC2532B60204B63324731C288ECACA8E271D9F3928226262222839 +13103C3C100364F06FF16E6EF16FFEAF3B51517652533A3B51513B3A5353910175020FFD +F1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FD253833B5B53338482C27 +8E018E272C00000700AAFF6A07AD066E0003000E0019001D002900350046000001170527 +053436333216140623222625343632161514062322260105072501100021200011100021 +2000131000212000111000212000133637362017161707262726232607060705864DFED1 +4DFE2B513B3A52523A3B510242527453533A3B51FE0E012F4DFED1FE25020E0174017502 +0CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE41EC2532B60204B6332473 +1C288ECACA8E271D05676ED56FDE3B51517652533A3B51513B3A535301ECD46FD5FDF101 +75020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FD253833B5B533 +38482C278E018E272C00000800AAFF6A07AD066E0010001C0028002C0037004200460056 +000001363736201716170726272623260706070110002120001110002120000310002120 +001110002120000105072501343632161514062322262534363332161406232226011705 +2703273637363217161707262726220706021D2532B60204B63324731C288ECACA8E271D +FEA101BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE8CFDF202 +28012F4DFED1023F527453533A3B51FDBE513B3A52523A3B5103044DFED14D8279101846 +C44618107907081E541E08014C3833B5B53338482C278E018E272C01E5FEC4FE4301BD01 +3C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C03F1D46FD5FEBC3B51513B3A53 +533A3B515176525301EC6ED56FFC0C392822626222283913103C3C100000000500AAFF6A +07AD066E000B001700230034004F00000110002120001110002120000310002120001110 +002120000114062226353314163236350136373620171617072627262326070607011407 +0607171607060706222635343F0126272635331416323635013101BF013C013D01BDFE43 +FEC3FEC4FE4187020E01740175020CFDF4FE8BFE8CFDF203488CC48C873D543DFEB22532 +B60204B63324731C288ECACA8E271D03B2463A4D201C01011A1B4C361B204D3A46873D54 +3D02EAFEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C03128B +C5C58B53777753FCC43833B5B53338482C278E018E272C03838C62510E3F37232B1A1B36 +2C23353F0D52628C537777530000000500AAFF6A07AD066E00100017001E002A00360000 +013637362017161707262726232607060701251707170725271505273727370110002120 +00111000212000131000212000111000212000021D2532B60204B63324731C288ECACA8E +271D01EC01274DC6C64DFED9A0FED94DC6C64DFDF5020E01740175020CFDF4FE8BFE8CFD +F28701BF013C013D01BDFE43FEC3FEC4FE41014C3833B5B53338482C278E018E272C0322 +CE6E8B8A6ECE5555CE6E8A8B6EFDF50175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301 +BD013C013D01C0FE4000000600AAFF6A07AD066E0003000700180021002D003900000127 +01170901370901363736201716170726272623260706070117160E012226343705100021 +200011100021200003100021200011100021200002C95F010E5F01B8FEF25F010EFC2F25 +32B60204B63324731C288ECACA8E271D03DA421B01364C361CFB0701BF013C013D01BDFE +43FEC3FEC4FE4187020E01740175020CFDF4FE8BFE8CFDF2035C5F010E5FFEF2010E5FFE +F2FD913833B5B53338482C278E018E272C032380354F36364F35BEFEC4FE4301BD013C01 +3D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C00000600AAFF6A07AD066E000B0016 +0022002E0034003C00000134363332161514062322262534363216151406232226051000 +212000111000212000131000212000111000212000012134002000052126272620070602 +82513B3A52523A3B510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF2 +8701BF013C013D01BDFE43FEC3FEC4FE4104F3FC0F012701A30127FCA902BD18456BFED2 +6A4503FF3B51513B3A53533A3B51513B3A5353DB0175020FFDF1FE8BFE8CFDF4020C0174 +FEC4FE4301BD013C013D01C0FE40FCA4E80146FEBA61604C74754C000000000800AAFF6A +07AD066E000300070012001D00290035003B004300000125370D01272517013436333216 +140623222625343632161514062322260510002120001110002120001310002120001110 +0021200001213400200005212627262007060617FEA84D0158FBDD4D01584DFEE9513B3A +52523A3B510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF01 +3C013D01BDFE43FEC3FEC4FE4104F3FC0F012701A30127FCA902BD18456BFED26A450416 +F06FF16E6EF16FFEAF3B51517652533A3B51513B3A5353910175020FFDF1FE8BFE8CFDF4 +020C0174FEC4FE4301BD013C013D01C0FE40FCA4E80146FEBA61604C74754C000000000E +00AAFF6A07AD066E000B00130019004E005A0066006C00780084008A00900096009C00A2 +000001141633323635342623220601212627262007060521340020000110002120001134 +2715231126271123350E0122272335263437112627112311060711161407152306222627 +152311060711233506071000212000111000212000013436333216151406232226253637 +350607013426232206151416333236271406232226353436333216011516173526272627 +1536372526271516172506071536372506071516170496744A55696F4F4C72FE3702BD18 +456BFED26A45033FFC0F012701A30127FB0D01BF013C013D01BD35441E25441E6D8E3844 +2B2B21224422212B2B44388E6E1D44251E443687020E01740175020CFDF4FE8BFE8CFDF2 +0471221718211F1A1623FD9E202323200109724C4F6F69554A748523161A1F2118172202 +1F241F206721221F24010E21222B18FC4F2320192A010E2221241F041A526C75494F6F6F +FCE9604C74754CE6E80146FEBA0137FEC4FE4301BD013C9B85BA013B2E29FE6E59364724 +403C9C3C013D0401FD6A02960104FEC33C9C3C402446375901932A2EFEC5BC869C017502 +0FFDF1FE8BFE8CFDF4020C02A418212118162320FC0A01A20F12FE914F6F6F4F49756C52 +192023161821210179A3010A8C13280A08F21409261E1884212DD2191D9C2D21FD070BC3 +0914000800AAFF6A07AD066E00030007000B000F001B0027002D00350000012701170901 +370901270117090137010510002120001110002120001310002120001110002120000121 +34002000052126272620070602C95F010E5F01B8FEF25F010EFCDB5F010E5F01B8FEF25F +010EFABC020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE +4104F3FC0F012701A30127FCA902BD18456BFED26A4504135F010E5FFEF2010E5FFEF2FE +585F010E5FFEF2010E5FFEF23F0175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD01 +3C013D01C0FE40FCA4E80146FEBA61604C74754C0000000700AAFF6A07AD066E0011001D +0029003500410047004F0000013736373633321716171615140706070623031406222635 +331416323635211406222635331416323635011000212000111000212000131000212000 +111000212000012134002000052126272620070605C455251F19160D0D24120A050C271F +3B128CC48C873D543DFE378CC48C873D543DFD3F020E01740175020CFDF4FE8BFE8CFDF2 +8701BF013C013D01BDFE43FEC3FEC4FE4104F3FC0F012701A30127FCA902BD18456BFED2 +6A4502C874320F0B040C2214150E0F24141001C08BC5C58B537777538BC5C58B53777753 +FE620175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FCA4E801 +46FEBA61604C74754C0000><000600AAFF6A07AD066E0006000D00190025002B00330000 +012517071707252715052737273701100021200011100021200013100021200011100021 +20000121340020000521262726200706047C01274DC6C64DFED9A0FED94DC6C64DFDF502 +0E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4104F3FC0F +012701A30127FCA902BD18456BFED26A450427CE6E8B8A6ECE5555CE6E8A8B6EFDF50175 +020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40FCA4E80146FEBA61 +604C74754C0000000005005FFF6A08F9066E0005000C0013003D00620000252134002000 +0115052737273705251707170725051000212000111407171617161514062227262F0102 +07002120012603070607062226353437363F012637100021200011342706232227262F01 +1716172627262120070607363F010706070622270606A4FC0F012701A30127FDB8FED94D +C6C64D01C701274DC6C64DFED9FC2E020E01740175020C025D3B181E364C1F19120B2AD1 +FEFAFE8BFE8CFEF9D12B0A12191F4C361E173C5C028701BF013C013D01BD011B25261F19 +122D8829182F9ADEFEC2FEC4E09A2F1829882D12191F4C1B01CBE80146FEBA027455CE6E +8A8B6ECECE6E8B8A6ECEE80175020FFDF1FE8B1D1B1F14181F2526361F193920FEE9D1FE +FA0106D001161E39191F3626251F19131E1C1DFEC4FE4301BD013C14131A1F1939882D0E +0FC79AE0E09BC6100D2D8839191F1B14000500AAFF6A07AD066E00070013001E002A0036 +000000343632161406220134363332161514062322262534363216151406232226051000 +212000111000212000131000212000111000212000032C96D49696D4FEC0513B3A52523A +3B510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D +01BDFE43FEC3FEC4FE41015AD49696D496033B3B51513B3A53533A3B51513B3A5353DB01 +75020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE40000000000700AA +FF6A07AD066E000300070012001D00250031003D00000125370D01272517013436333216 +140623222625343632161514062322260034363216140622011000212000111000212000 +1310002120001110002120000617FEA84D0158FBDD4D01584DFEE9513B3A52523A3B5102 +42527453533A3B51FEE84B6A4B4B6AFCB3020E01740175020CFDF4FE8BFE8CFDF28701BF +013C013D01BDFE43FEC3FEC4FE410416F06FF16E6EF16FFEAF3B51517652533A3B51513B +3A5353FE146A4B4B6A4B01A60175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C +013D01C0FE400000000800AAFF6A07AD066E00080010001400180023002E003A00460000 +01171614062226343700343632161406220125370D012725170134363332161406232226 +253436321615140623222605100021200011100021200013100021200011100021200006 +50411B364C361BFD1D96D49696D40255FEA84D0158FBDD4D01584DFEE9513B3A52523A3B +510242527453533A3B51FBE6020E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01 +BDFE43FEC3FEC4FE4103AC80354F36364F35FE2ED49696D4960352F06FF16E6EF16FFEAF +3B51517652533A3B51513B3A5353910175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301 +BD013C013D01C0FE4000000000070023FF3C0835066E0007000F00130017001F006B0094 +000000263436321614062026343632161406252725170525370500343632161406220135 +2E01270211343637363700200116171E011510030E010715233534373637361334262206 +070607060F010E011D01233506202715233534262F01262726272E012206151217161716 +1D01251620373534373637363736373E0137363736372627262007060716171E02171617 +16171617161505155152745353FD495151755252FEF94D01584D027EFEA84D0158FCC896 +D49696D4FDA14856188A683D3BAE010702E90106AE3B3D688A1856488726811D7A02273A +25060E6929385E271B886EFEFB79881B275E3829690E06253A27027A1D8126014C770109 +6C1B309138113C1102080C0C330F11328FDEFD86E08F33120F33180802113C113891301B +0328537551517553537551517652EE6EF16FF0F06FF1FCD6D49696D496FE78CB3E5B3101 +170127536B0ADFB00108FEF8AFE00A6B53FED9FEE9315B3ECBE7241F6E39F001121C2823 +6DF3712C25401C3C3FFB4C1E204EFB3F3C1C40252C71F36D23281CFEEEF0396E1F24E7DB +262521593B6556221344780F863836300E0AB590E0E08FB50B0E306E860F784413225665 +3B590000000900AAFF6A07AD066E000B00170023002F003B0047004F005B006700000114 +062322263534363332161734363332161514062322263714163332363534262322061734 +363332161514062322262534262322061514163332362714062322263534363332160234 +36321614062201100021200011100021200013100021200011100021200003F2925C6A84 +8B635F8F748F5F638B846A5C9230744A55696F4F4C7285221718211F1A1623FEA7724C4F +6F69554A748523161A1F211817221196D49696D4FCE8020E01740175020CFDF4FE8BFE8C +FDF28701BF013C013D01BDFE43FEC3FEC4FE41041A6688935B638B8B63638B8B635B9388 +66526C75494F6F6F4F18212118162320194F6F6F4F49756C5219202316182121FD28D496 +96D49602260175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE4000 +000A00AAFF6A07AD066E004A0056005E00620066006B007000740079007E000001100021 +200011342711231126271517071123352315233523152335231523113311331127371735 +262007153717071133113311233523152335231523352315231127373506071123110607 +100021200011100021200024343632161406221311331101333527173335072711151735 +260135071503060715371135072715013101BF013C013D01BD40441E2606064434444444 +444444445B4D96A7FE62A7964D5B4444444444444434440606251F444187020E01740175 +020CFDF4FE8BFE8CFDF203024B6A4B4B6A134401104444883403313419FCF944441B1934 +310302EAFEC4FE4301BD013CAA8FFE8501F32C28670409FE2D8888888888880238FED701 +103F6F697B60607B696F3FFEF00129FDC888888888888801D3090468282CFE0C017D90AB +0175020FFDF1FE8BFE8CFDF4020C196A4B4B6A4B01640238FDC8010FB12FE06304220123 +7F247A16FE6FE02FB101A413177924FEDB81220463000000000800AAFF6A0C71076C0009 +0013001D00250031003D0049005500000121150333152135132325211503331521351323 +252115033315213513230034363216140622011406222635331416323635211406222635 +3314163236350110002120001110002120001310002120001110002120000AF6017BF8F8 +FE7BF8EEFE17017BF8F8FE7BF8EEFE17017BF8F8FE7BF8EEFC884B6A4B4B6A024B8CC48C +873D543DFE378CC48C873D543DFD3F020E01740175020CFDF4FE8BFE8CFDF28701BF013C +013D01BDFE43FEC3FEC4FE41076C68FEF45C68010C5C68FEF45C68010C5C68FEF45C6801 +0CFA7F6A4B4B6A4B03448BC5C58B537777538BC5C58B53777753FE620175020FFDF1FE8B +FE8CFDF4020C0174FEC4FE4301BD013C013D01C0FE400000000500AAFF6A07AD066E000B +00170023002B003700000127372737173717071707270510002120001110002120000310 +00212000111000212000043436321614062201273727371737170717072702955F71715F +71725F71715F72FE2B01BF013C013D01BDFE43FEC3FEC4FE4187020E01740175020CFDF4 +FE8BFE8CFDF2028296D49696D4011E5F71715F71725F71715F7203285F71725F71715F72 +715F71AFFEC4FE4301BD013C013D01C0FE40FEC30175020FFDF1FE8BFE8CFDF4020C1CD4 +9696D49602645F71725F71715F72715F71000000000400AAFF6A07AD066E000A00150021 +002D00000134363332161406232226253436321615140623222605100021200011100021 +20001310002120001110002120000282513B3A52523A3B510242527453533A3B51FBE602 +0E01740175020CFDF4FE8BFE8CFDF28701BF013C013D01BDFE43FEC3FEC4FE4103FD3B51 +517652533A3B51513B3A5353D90175020FFDF1FE8BFE8CFDF4020C0174FEC4FE4301BD01 +3C013D01C0FE4000000A00AAFF6A07AD066E000B000F00130017001B001F00240028002C +003700001310002120001110002120000135211533352115252725170525370501211121 +012116212025361307011127120102272621200706031721AA020E01740175020CFDF4FE +8BFE8CFDF2016C01AAD801AAFBFF4D01584D027EFEA84D0158FC39031EFCE20354FC76C4 +010101020114CB16E1FBD4E21605D71BBEDEFEC2FEC4E0BE1CE0042C02EA0175020FFDF1 +FE8BFE8CFDF4020C01BA87878787E66EF16FF0F06FF1FC870118FE6193D9C901119EFEC4 +013C9EFEEF01B30102BFE0E0BFFEFF9D0009003AFF6A082607140008001D0052008F009D +00AB00B500BB00C100000032371706232227371716333237161514070604202427263534 +371633320114172517051617251705161716171620373637363725370536372537053635 +342E02272627060726232207262706070E03072635343E0337363F01171E01173632173E +013F011716171E0415140717072706071707270607020706212027260326270727372627 +072725342E01220E011523343632161521342E01220E0115233436321615012627060715 +1633323F01153637062305352322271603EC825C3E786365763EA072749E92051D36FEED +FE9CFEEC3B230395A079FD5D07014D05FEB4080E013416FEC8121666A3B501EAB5A1671A +10FECD16012E0E08FEBA050147060A0A16041B2AF399749B9D7499F32A1B05150B0A8005 +07130C1F0517260F3C8FFD5D69E2695DFD8F3B102617051F0C13070569046B0815681664 +131B74C0D9FEE0FEDFD9C0741D105E16621409650403311531423115878CC48C01C91531 +423115878CC48CFE3A24281A322B242920886033483AFE47153F3F3402FC2E783D3D78FC +25422019444781938F7D4B5317174201C724621543164A41693F6C3A31E277848476E338 +356A3F673E4C15431552342E663E7619A1A73AC41515C43AA7A11B724165BD4D45396A77 +419318758C3A091689610D0D618916093A8C75189341776A39444D07440756572440223C +3CFEFD8C9E9E8C010340362040224F600744512B5A4545592C8CC4C48C2B5A4545592C8C +C4C48CFE03070B070A910504846327460A6161094300000000090060FF6A08F80714003F +0048005D00A800B600C400CE00D400DA0000013635342E02272627060726200726270607 +0E03151417363F01070E0123222716172517051617161716203736373637253705363706 +2322262F0117160432371706232227371716333237161514070604202427263534371633 +320522263534363F0134270727372635343E0337363F01171E01173632173E013F011716 +171E041514071707270607171E011514062322262F010607020706202726032627070601 +342E01220E011523343632161521342E01220E0115233436321615012627060715163332 +3F01153637062305352322271607BA020A0A16041A2AF29A74FEC87498F42A1C05150A0A +021850882C11332C301C0810013416FEC8121666A4B501E9B6A2661A10FECC16012E1008 +1C302C34102E8A52FCC8825C3E786266763EA072749E92061E36FEEEFE9CFEEC3C220296 +A078FC7C25373E345A086604640608120C200418260E3C8FFD5E69E16A5DFD8E3C102616 +051F0C14060468046A06045A343C36242C34120E182C74C0D8FDC0DAC0742A1A102402E4 +1630423214888CC48C01CA1630423214888CC48CFE3A24281A322C242820886034483AFE +46143E4034037C282C2E663E7619A1A73AC41515C43AA7A11B7241652E2A272C192C8833 +3C25574C693F6C3A31E277848476E338356A3F674460283C33882C19AF2E783D3D78FC25 +422019444781938F7D4B531717424136252C34111D034A0744064D45396A77419318758C +3A091689610D0D618916093A8C75189341776A39444D07440734191D11342C25363C332A +6862FEFD8C9E9E8C01035D702D6F01C42B5A4545592C8CC4C48C2B5A4545592C8CC4C48C +FE03070B070A910504846327460A6161094300000009003AFF6A082607140008001D002A +005F009C00AA00B400C300D0000000323717062322273717163332371615140706042024 +272635343716333217062322271E01323637062322011417251705161725170516171617 +1620373637363725370536372537053635342E02272627060726232207262706070E0307 +2635343E0337363F01171E01173632173E013F011716171E041514071707270607170727 +060702070621202726032627072737262707272534363332161706070623222726372206 +1514173E0137260514070623222726273637363332160716333237363534272623220603 +EC825C3E786365763EA072749E92051D36FEEDFE9CFEEC3B230395A0797072773F3F35C0 +EABD32483A75FC7D07014D05FEB4080E013416FEC8121666A3B501EAB5A1671A10FECD16 +012E0E08FEBA050147060A0A16041B2AF399749B9D7499F32A1B05150B0A800507130C1F +0517260F3C8FFD5D69E2695DFD8F3B102617051F0C13070569046B0815681664131B74C0 +D9FEE0FEDFD9C0741D105E16621409650401E263433E5D07304C2128433808FD2430052B +45121502FF08384325244D2F08322D3B4363A40C1A2E2A10040C0D2C4002FC2E783D3D78 +FC25422019444781938F7D4B53171742681F09454C4D450A024E24621543164A41693F6C +3A31E277848476E338356A3F673E4C15431552342E663E7619A1A73AC41515C43AA7A11B +724165BD4D45396A77419318758C3A091689610D0D618916093A8C75189341776A39444D +07440756572440223C3CFEFD8C9E9E8C010340362040224F6007449F465F573B45180B21 +17492B200D0D062F210F2D1D17210C1943422A265F7A031415190C0B033500000006003D +FF6A081D071400110019004A0083009800AD000001371633323716333237170623222706 +23220117062227371632253635342E02272627060726232207262706070E031514172517 +0516172517051617162037363725370536372537052635343E0337363F01171E01173632 +173E013F011716171E041514071707270607170727020706212027260307273726270727 +013E0133321615140F012327262734363332171615253216173334373633321615060F01 +23272635343602A65C455B3A54543A5B455C6C904A4445499001B83E7AC27A3E5A8602BA +120A0A16041B2AF399749B9D7499F32A1B05150B0A1201420AFEBF0E17011E1AFEE167A4 +B501EAB5A467FEE11A011E150FFEC00AFAC01107130C1F0517260F3C8FFD5D69E2695DFD +8F3B102617051F0C1307116C0A6D0F206E1A6D75C3D9FEE0FEDFD9C1766D1A6D1E106D0A +02E30A3A2D2E3E3E9F02A83102402F3D230D01A82D3A0A010D233D2F400231A8029F3E3E +018C62402C2C4062641C1C01A8783D3D782E3882742E663E7619A1A73AC41515C43AA7A1 +1B7241652E74822B422C4540793E7AE777848477E77A3E79394C2C423D7C8F396A774193 +18758C3A091689610D0D618916093A8C75189341776A398F7C0F420E51572E3E2EFEF78E +9E9E8C010B2E3E2E51570E42019530423D314A4ABECF3D422A48471B10724230101B4748 +2A423DCFBE4A4A313D000000000A003DFF6A081D071400030007000F001E002800370044 +007500AE00B800000105072D011705271317062227371632010607062322272635343736 +3332160722061514173E013726053E013332171615140706232227261716333237363534 +2726232206053635342E02272627060726232207262706070E0315141725170516172517 +051617162037363725370536372537052635343E0337363F01171E01173632173E013F01 +1716171E0415140717072706071707270207062120272603072737262707270132373637 +170607062102D2012F4DFED103014DFED14D733E7AC27A3E5A86FEFC2B51202846361535 +2F4146654E2430052B45121601B8056446422F35153646282051790C1A302810040C0D2C +400193120A0A16041B2AF399749B9D7499F32A1B05150B0A1201420AFEBF0E17011E1AFE +E167A4B501EAB5A467FEE11A011E150FFEC00AFAC01107130C1F0517260F3C8FFD5D69E2 +695DFD8F3B102617051F0C1307116C0A6D0F206E1A6D75C3D9FEE0FEDFD9C1766D1A6D1E +106D0A03EFCA8E281C732235B5FEFD0567D46FD56E6ED56FFE3D783D3D782E01064C1C0B +23242D47302C612630240E0F073425111D43612C30472D24230B1C040316181C0E0C033B +A782742E663E7619A1A73AC41515C43AA7A11B7241652E74822B422C4540793E7AE77784 +8477E77A3E79394C2C423D7C8F396A77419318758C3A091689610D0D618916093A8C7518 +9341776A398F7C0F420E51572E3E2EFEF78E9E9E8C010B2E3E2E51570E42FE7F8E282B48 +3635B5000008003DFF6A081D0714000E001800270034006900A800B500BE000001060706 +23222726353437363332160722061514173E013726053E01333217161514070623222726 +17163332373635342726232206053635342E02272627060726232207262706070E031514 +1716172517051617251705171617162037363F01253705363725370032173E013F011716 +171E04151406150607170727060717072707020706212027260327072737262707273726 +2735343E0337363F01171E01171337273705150717150527372702323717062322273703 +6C2B512028463615352F4146654E2430052B45121601B8056446422F3515364628205179 +0C1A302810040C0D2C400193120A0A16041B2AF399749B9D7499F32A1B05150B0A020709 +01420AFEBF0E17011E1AFEE10266A3B501EAB5A16703FEE11A011E150FFEC00AFDD3E269 +5DFD8F3B102617051F0C130701030D6C0A6D0F206E1A6D0474C0D9FEE0FEDFD9C074036D +1A6D1E106D0A6C0E0307130C1F0517260F3C8FFD5DA350904101033C3CFEFD4190500B84 +5B3E776465763E03A84C1C0B23242D47302C612630240E0F073425111D43612C30472D24 +230B1C040316181C0E0C033BA783732E663E7619A1A73AC41515C43AA7A11B7241652E0F +2482412B422C4540793E7A05E277848476E3057A3E79394C2C4203130D618916093A8C75 +189341776A39071C078A570F420E51572E3E2E08FEFD8C9E9E8C0103082E3E2E51570E42 +0F60812A396A77419318758C3A09168961FB3A3D4E778D592E2D598D774E3D01DA2E783D +3D780000000B003DFF6A081D0714000E0018002700340065009E00A600AF00B800BC00C0 +00000106070623222726353437363332160722061514173E013726053E01333217161514 +07062322272617163332373635342726232206053635342E022726270607262322072627 +06070E0315141725170516172517051617162037363725370536372537052635343E0337 +363F01171E01173632173E013F011716171E041514071707270607170727020706212027 +260307273726270727013632170726220725362017072623220700323717062322273701 +17052725050725036C2B512028463615352F4146654E2430052B45121601B8056446422F +35153646282051790C1A302810040C0D2C400193120A0A16041B2AF399749B9D7499F32A +1B05150B0A1201420AFEBF0E17011E1AFEE167A4B501EAB5A467FEE11A011E150FFEC00A +FAC01107130C1F0517260F3C8FFD5D69E2695DFD8F3B102617051F0C1307116C0A6D0F20 +6E1A6D75C3D9FEE0FEDFD9C1766D1A6D1E106D0A034746C446601E541EFE90B50206B560 +8ECAC8900117845B3E776465763E01F64DFED14DFE7B012F4DFED103A84C1C0B23242D47 +302C612630240E0F073425111D43612C30472D24230B1C040316181C0E0C033BA782742E +663E7619A1A73AC41515C43AA7A11B7241652E74822B422C4540793E7AE777848477E77A +3E79394C2C423D7C8F396A77419318758C3A091689610D0D618916093A8C75189341776A +398F7C0F420E51572E3E2EFEF78E9E9E8C010B2E3E2E51570E42FE1363635F3C3CF5B5B5 +608E8E01FA2E783D3D78023D6ED56FD4D46FD5000005003DFF6A081D07140020002C0034 +0064008E0000011406071716140622263534350622273716323717363F012E0135331416 +3236352114062226353314163236350336201707262007012610363F01363F01171E0117 +3632173E013F0117161F011E011407170727060717072702002000030727372627072705 +122132243725370536372537053635342F01262706072620072627060F01061514172517 +05161725170642804D201B364C366EC2763E5B845B33070F204D80873D543DFE378CC48C +873D543DF7B50206B5608EFE6E90FDD51120161417260F3C8FFD5D69E2695DFD8F3B1026 +17142B0B116C0A6D0F206E1A6D75FE64FDBFFE66766D1A6D1E106D0A013BD701DEF50159 +67FEE11A011E150FFEC00A0141121A1E102BF39974FEC87499F32A1B10201301420AFEBF +0E17011E1A04888BB40E3F354F36362C0303353D782E2E63181D3F0EB48B537777538BC5 +C58B53777753FCDAB5B5608E8E01C67C0106C46A61758C3A091689610D0D618916093A8C +7561CCA0C87C0F420E51572E3E2EFEF7FED4012A010B2E3E2E51570E42E6FE1EFBE77A3E +79394C2C422B8251888DAE62A73AC41515C43AA7A154A86E6B822B422C4540793E000000 +0006003DFF6A081D071400340073007B00830087008B0000013635342E02272627060726 +232207262706070E0315141716172517051617251705171617162037363F012537053637 +25370032173E013F011716171E0415140615060717072706071707270702070621202726 +03270727372627072737262735343E0337363F01171E0117032126272620070605213437 +363332000301370105270117072A120A0A16041B2AF399749B9D7499F32A1B05150B0A02 +070901420AFEBF0E17011E1AFEE10266A3B501EAB5A16703FEE11A011E150FFEC00AFDD3 +E2695DFD8F3B102617051F0C130701030D6C0A6D0F206E1A6D0474C0D9FEE0FEDFD9C074 +036D1A6D1E106D0A6C0E0307130C1F0517260F3C8FFD5D8602BD18456BFED26A45033FFC +0F9393D2D1012895FEF25F010EFCDB5F010E5F02DA83732E663E7619A1A73AC41515C43A +A7A11B7241652E0F2482412B422C4540793E7A05E277848476E3057A3E79394C2C420313 +0D618916093A8C75189341776A39071C078A570F420E51572E3E2E08FEFD8C9E9E8C0103 +082E3E2E51570E420F60812A396A77419318758C3A09168961FB47614B74754BE7E7A4A3 +FEBA017E010E5FFEF25F5F010E5F00000001FFB9049A00C706120003000A400300030400 +10D4CC3011330323C775990612FE88000002FCD7050EFF2905D90003000700A5400D0400 +CE0602080164000564040810D4FCDCEC310010D43CEC3230004BB00E544BB011545B58BD +00080040000100080008FFC03811373859014BB00E544BB00D545B4BB017545B58BD0008 +FFC000010008000800403811373859014BB011544BB019545B58BD000800400001000800 +08FFC03811373859004BB0185458BD0008FFC00001000800080040381137385940116001 +600260056006700170027005700608015D0133152325331523FE5ECBCBFE79CBCB05D9CB +CBCB00000001FD7304EEFEF005F60003007F40110203000301000003420002FA04010303 +0410C410C0310010F4CC304B5358071005C9071005C95922004BB00C5458BD0004FFC000 +010004000400403811373859004BB00E5458BD00040040000100040004FFC03811373859 +402006021502250125023602460256026A016702090F000F011F001F012F002F01065D01 +5D01330323FE37B9E49905F6FEF800000001FCB6050EFF4A05E9001D0075402116100F03 +130C0701000308170CC30413C31B08FA1E10010F00071656180756091E10D4ECD4EC1139 +393939310010F43CECD4EC321217391112173930004BB00C5458BD001EFFC00001001E00 +1E00403811373859004BB00E5458BD001E00400001001E001EFFC03811373859B4100B1F +1A025D01272E012322061D012334363332161F011E013332363D01330E01232226FDFC39 +191F0C24287D6756243D303917220F20287D026754223B0539210E0B322D066576101B1E +0D0C3329066477100001FD0C04EEFE8B05F60003008940110102030200030302420001FA +040103030410C410C0310010F4CC304B5358071005C9071005C95922004BB00C5458BD00 +04FFC000010004000400403811373859004BB00E5458BD00040040000100040004FFC038 +11373859402A06000601160012012400240135014301550055019F009F01AF00AF010E0F +000F031F001F032F002F03065D015D01132303FDC7C499E605F6FEF8010800000001FCCF +04EEFF3105F800060077400A04000502FA070402060710D4C439310010F43CC43930004B +B00C5458BD0007FFC000010007000700403811373859004BB00E5458BD00070040000100 +070007FFC03811373859014BB00E5458BD0007FFC0000100070007004038113738594013 +0F000F010C041F001F011D042F002F012D0409005D01331323270723FDA2BCD38BA6A68B +05F8FEF6B2B200000001FCCF04EEFF3105F800060086400A03040100FA070305010710D4 +C439310010F4C4323930004BB00C544BB009545B4BB00A545B4BB00B545B58BD0007FFC0 +00010007000700403811373859004BB00E5458BD00070040000100070007FFC038113738 +59014BB00E5458BD0007FFC0000100070007004038113738594013000003030006100012 +03100620002203200609005D01033317373303FDA2D38BA6A68BD304EE010AB2B2FEF600 +0001FCC70506FF3905F8000D000003232E0123220607233E01333216C7760D6353526110 +760AA08F909F050636393738777B7A000001FCC70506FF3905F8000D006A400E070004C3 +0BFA0E0756080156000E10D4ECD4EC310010F4FCCC3230004BB00C5458BD000EFFC00001 +000E000E00403811373859004BB00E5458BD000E00400001000E000EFFC0381137385901 +4BB00E544BB00F545B58BD000EFFC00001000E000E0040381137385901331E0133323637 +330E01232226FCC7760D6353526110760AA08F909F05F836393738777B7A00000001FD9A +050EFE6605DB00030047B700CE02040164000410D4EC310010D4EC30004BB00E544BB011 +545B58BD00040040000100040004FFC03811373859004BB0185458BD0004FFC000010004 +00040040381137385901331523FD9ACCCC05DBCD0002FCE604EEFFB205F6000300070013 +40070004030708000410CC310010D43CCC32300133032303330323FEF9B9E4998BB9E499 +05F6FEF80108FEF80002FC4E04EEFF1A05F60003000700000113230321132303FD07C499 +E40208C499E405F6FEF80108FEF801080001000000000096009600030000353315239696 +96960000000200000000019000960003000700003733152327331523FA9696FA96969696 +969600000003000000000190019000030007000B00001333152317331523273315237D96 +967D9696FA969601909664969696000000030000FF060190009600030007000B00001733 +152313331523273315237D96967D9696FA969664960190969696000000020000FF060096 +0096000300070000153315231133152396969696649601909600000000040000FF060190 +009600030007000B000F000017331523113315230733152311331523FA96969696FA9696 +96966496019096649601909600010082FFEC07EF029D00260000011417163B0115232227 +262706070423222724032637330615141716332437363736353427331606EF463F3F3C66 +744750189BE9FEFDD3C47CFEB7010140B841CB68970104BEC77B3B1DB81301F8D03B35B8 +49534283353A266501088A5C5E887D432202373A6D34773E374B0000FFFFFFEC00000187 +02581006144E0000FFFFFFEC0000027E02581006144F000000020082FFA5085C0311002C +003E00002506070421242724032637330615141716213225372627263534373637363332 +171617161514071633211521222736353427262726232207060706151417160678686EFE +E8FEF0FECF7DFEB7010140B841CB51012BE8011D1C221D520416BC3A3452518912045009 +070107FEF4696F68050E34222818163D13062931242B183C01276701068A5C5E88734D1F +36041B2C7C791F249B4B17325396251E906A01B8DF417A141B4727190A193C131238424E +0002FFEC0000033F03D9000F003000000136353427260706070607141716333201333237 +3637363706070627263534373637363332171617161514070607062321025629421F2C34 +28280137282A48FDAEF154974F3C1F0F3F61824E620817964E4C5A42602E174E4A7C6D91 +FEBF02192B4D3B331901012A2933502619FEB7170C5D3032370202455781342C94452432 +4866338CD08F882C270000000002FFEC0000042003080021003500000116171615140706 +07163B0115232227062B0135333237262726353437363736333207220706070615141716 +173637363534272627260266B71808151D2427937FD988B9B988D97F93271D24150817B9 +1E413F3F0F1238180A181F443F24190B17391302FA5096322A4931433211B83939B81128 +4D2E4C2C308F570EB80A2035151D2B3544161545322E181A34210A000002006BFE0C06C0 +02E4002E0040000025262726272635343736373633321716171617163321152123060706 +070627242726353437330615141716213237361336272627262322070607061514171617 +1604B8422E79399306289329577038633214061A040107FEF4302D3D809EAFCDFE8E5E45 +3EB83E1B3C01068C96D56C0416132F2227221533150A394C642F0A050D232B6DB93D1AA5 +45133258913AD601B87A46914E560102BE8A7DA6606B9B4C3A824462015A7A574B23190A +1B3A1E193C2C3B1409000000FFFFFFEC0000033F03D91006172B0000FFFFFFEC00000420 +03081006172C0000FFFFFFEC0000018703E81026172800001007172100E00352FFFFFFEC +0000027E03E81026172900001007172100E0035200020000000001B601B7000B00170000 +25342726220615141716323E011407062227263437363217013C1C1C52381C1C52387A3F +40B83F40403FB840DC281C1D38292A1C1B3885B840404040B8403F3FFFFF0082FE9007EF +03201027172202BC028A1027173202A9FE90100617270000FFFFFFECFEF4020603E81026 +172800001027172200630352100717320050FEF4FFFFFFECFEF4027E03E8102617290000 +1027172200630352100717320050FEF4FFFF0082FFEC07EF03B61027172402BC03201006 +17270000FFFFFFEC000001F304E2102617280000100717240063044CFFFFFFEC0000027E +04E2102617290000100717240063044CFFFF009DFE0C053E05AF1026149900001007057C +008AFF38FFFFFFEC0000045C054B1026149A00001007057C0058FED4FFFFFFEC0000053E +054B1026149B00001007057C0058FED4FFFF009DFE0C053E05AA10261499000010071725 +023F0514FFFFFFEC0000045C05461026149A000010071725020D04B0FFFFFFEC0000053E +05461026149B000010071725020D04B0FFFF009DFE0C053E05AA10261499000010071723 +01C2041AFFFFFFEC0000045C05461026149A000010071723019003B6FFFFFFEC0000053E +05461026149B000010071723019003B6FFFF009DFE0C053E04B010261499000010271721 +023F041A1007172401E70019FFFFFFECFE3E045C044C1026149A0000102717240190FF38 +10071721020D03B6FFFFFFECFE3E053E044C1026149B000010271724019CFF3810071721 +020D03B6000100000533035F072B0003000009013501035FFCA1035F0695FE9E96016200 +000100D5FE56052705D50013004A402111110102010211101110420B950A11020300AF10 +130B100111021C0436111C001410DCECFCEC113939CC31002F3CEC323939DCEC304B5358 +071004ED071004ED5922B21F1501015D1333011133111407062B01353332373635011123 +D5B802E2B85251B5FEE9692626FD1EB805D5FB83047DFA17D660609C3031AD047DFB8300 +0001000F0000021F0460000B00324011020BA9050800BC0605020108080B00460C10FC3C +3CEC323231002FE4DC3CEC3230400B100D400D500D600D700D05015D1333113315231123 +11233533C3B8A4A4B8B4B40460FE08A4FE3C01C4A40000000002FEF2FE56022E0460000E +0017000013203534213311331133152306070603232217163332373621FED1010EC1B8B5 +BF12355220B57703047B692612FE56DDCD0460FBA09B703F6001103341301700FFFF0192 +066303E808331027007100BD023D1007171604BC01550000FFFF0192066103E808341027 +007100BD00FF1007171604BC025B0000FFFF0192065E03E808331027171E04BC01501007 +007100BD023D0000FFFF0193066303E5085A1027171704F002641007171604BC01550000 +FFFF0193066303E5085A10271719048C02641007171604BC01550000FFFF0192066103E8 +085A1027171704F002641007007100BD00FF0000FFFF0192066103E8085A10271719048C +02641007007100BD00FF0000FFFF0176066A040A08331027171804C0015C1007007100BD +023D0000FFFF018B066303ED085A1027171B04BC02621007171604BC01550000FFFF0176 +066A040A08561027171604BC027D1007171804C0015C0000FFFF018B066303ED08571027 +171B04BC01751007171E04BC027C0000FFFF0176066A0430085A10271717054002641007 +171804C0015C0000FFFF018B06630518083A1027171A04BC017510071717062802440000 +FFFF018B0663046D083A1027171905E202441007171A04BC01750000FFFF01760663040A +08751027171A04BC01751007171804C0028C0000FFFF01760656040A08591027171D04BC +01501007171804C002700000FFFF0183065603F5085A1027171D04BC01501007171704F0 +02640000FFFF0183065603F5085A1027171D04BC015010071719048C02640000FFFF0183 +065603F5088B102702BA04AB02101007171D04BC01500000FFFF018B06630507085B1027 +02BA061001E01007171A04BC01750000FFFFFC9A047BFF50066E102602B20000100702B8 +FEF8005A000100000000012C012C0003000011211121012CFED4012CFED4000000010000 +FFE3034F05D5000F00003D011E013332363511331110062322265BC2688F71CAD3F760BE +3DEC515195CB03EEFC12FEE6EA2C0000FFFFFFABFE0C051302261026176100001007057F +0206F91E0001FFABFE0C04F6022600180000013316171617163B01152322270207042135 +203736373635340278B81E030A492A65C3FA823244FBFEE4FEBE0130CBDA230A0226701E +674D2CB83EFEEA8597B8808AD03A487EFFFF0090FEC8062307C41027057F02BC01901006 +14D50000FFFFFFEC0000026007C41027057FFF530190100614D60000FFFFFFEC000002BA +07C41027057FFF530190100614D70000FFFF0082FEF006BF03461027057F00BCFD121006 +14E70000FFFFFFECFED4023804401027057FFF2BFE0C102717220063FED41006144E0000 +FFFFFFECFED4027E04401027057FFF2BFE0C102717220063FED41006144F0000FFFFFFAB +FE0C047E04721027057F00BCFE3E100614A50000000100C1000002390614000B0039B506 +020800460C10FCECC44BB00E534BB010515A58B90006FFC038593100B400970687072FEC +E430400D100D400D500D600D700DF00D06015D13331114163B011523222635C1B84C690B +20B5A30614FB8299619CC0D6FFFF00910000045E02EE100614E10000FFFF0071FFE30525 +05F0100601E40000FFFF0071FFE30471050F100601E50000FFFF0096FE75020B047B1026 +00F300001007029DFF4A00000002004F0000027704600003000700003733132313211321 +C786B28624FEB2DA014E640398FC0404600000000002FF16FE5602770460000800160000 +05132303060736373605233733323736371321030607060135CA86D0233548324BFEDCDC +143169302F1DE9014EDE296465160412FBD0B5540F3048F46430319904ACFB8CD6606000 +FFFFFFD3FE760267047B102702B0FF1D0000100600F30000FFFF00BFFE890179047B1026 +00F30000100702D4031D0000000200F000D801C304FB0003000700001333152311331523 +F0D3D3D3D301D6FE0423FE0000010097000002F605D5000B002B40160A02950181090495 +0605021C04031C0A080B1C090A0C10D432EC3210FC32EC3231002FEC32F4EC3230132115 +23113315213533112397025ECACBFDA2C9CA05D5AAFB7FAAAA048100000101AD02950509 +033F00030000011521350509FCA4033FAAAA0000FFFF00C804CB033808F2102705730000 +0258100605790000FFFF00C804CB033809551027057400000258100605790000FFFF00C8 +04BA033808E810270579000001F4100605730000FFFF00C804CB03380802102705760000 +0258100605790000FFFF00C804CB033809551027057700000258100605790000FFFF00C8 +04BA03380820102705790000012C100605760000FFFF00DC04BF0324079E102705760000 +01F41006057C0000FFFF00DC04BF032408F110270577000001F41006057C0000000100C1 +0000024E05D50005001A400D045402AF00040704030801040610FCFCFCC431002FECEC30 +331133113315C1B8D505D5FABE9300000001FFEC0000024E05D50007001E400F01045407 +AF0201090400080604040810C4FCFCFCC431002FECEC323025331521353311330179D5FD +9ED5B893939305420001FFEC0000017905D50005001A400D025404AF0007040008040402 +0610C4FCFCEC31002FECEC302901353311330179FE73D5B89305420000020071FFE304A6 +0393000700150038400D151745051C081311011C0D451610F4ECD4B610113011A011035D +3939ECFCC43100400B07A00F0803A00A8C13A0082FECF4EC10D4EC300010162036102620 +0106232200100020001514073315010DB90106B9B9FEFA012A4E59C3FEEB011501860115 +68ED023EFEFAB9B90106B9FD091D011501860115FEEBC3A97F9300000002FFECFFE304A6 +03930007001900414011181B45051C0E0B08170415011C10450D1A10C4F4ECD4B6101530 +15A015035D1739ECFCC43100400D07A0130C03A00A8C0E17A0190C2F3CEC32F4EC10D4EC +300010162036102620010622272135332635340020001514073315010DB90106B9B9FEFA +012A4EB24EFE4AED6801150186011568ED023EFEFAB9B90106B9FD091D1D937FA9C30115 +FEEBC3A97F9300000002FFECFFE304210393000700150038400D1745051C13080F011C0A +45151610C4F4ECD4B6100F300FA00F035D3939ECEC3100400B07A00D1303A0118C08A013 +2FECF4EC10D4EC3000101620361026200326353400200010002322272135010DB90106B9 +B9FEFAED68011501860115FEEBC3594EFE4A023EFEFAB9B90106B9FD9C7FA9C30115FEEB +FE7AFEEB1D9300000001003D0000037805D9000A0034400D0508020A0C0706081C030402 +0B10DCC432FCC432DCC41112393100400C0502080303010603810A87012FECEC32111217 +393029011101331B01330111210378FDDEFEE7C2B3B3C2FEE7016A032A02AFFE5D01A3FD +51FD69000001FFEC0000037805D9000C0036400E080B05000E0A090B1C060705030D10C4 +DCC432FCC432DCC41112393100400C08050B03010906810C0387012FEC32EC3211173930 +25152135211101331B013301110378FC74016AFEE7C2B3B3C2FEE7939393029702AFFE5D +01A3FD51FD6900000001FFEC0000032705D9000A0034400D0609030C0807091C04050301 +0B10C4DCC432FCC432CC1112393100400C06030903040A07048101870A2FECEC32111217 +39302335211101331B0133011114016AFEE7C2B3B3C2FEE793029702AFFE5D01A3FD51FC +D60000000001003D000004D003710008000029010901230133013304D0FEECFEA2FEA2C3 +01A4FA016D8802BDFD430371FD2200000001FFEC000004D00371000A0000290135330133 +01331521010100FEEC88016DFA016D88FEECFEA29302DEFD229302BD0001FFEC0000047F +0371000800002901353301330123010100FEEC88016DFA01A4C3FEA29302DEFC8F02BD00 +000100BA0000054F037100090024400B090B04071C05041C02040A10FCECD4FCFCC43100 +B60603A3080587012FEC32F43C3029011133112111331133054FFB6BB90255B9CE0371FD +2202DEFD220000000001FFEC0000054F0371000B0028400C0B0D040A1C08061C0404020C +10C4FCECD4FCFCC43100B70805A303060A87012FEC3232F43C3029013533113311211133 +1133054FFA9DCEB90255B9CE9302DEFD2202DEFD220000000001FFEC0000048103710009 +0024400B0B04001C08061C0404020A10C4FCECD4FCEC3100B60805A3030687002FEC32F4 +3C30290135331133112111330481FB6BCEB90255B99302DEFD2202DE000100BA0000054F +037100090024400A080B04061C020104040A10FC3CD4FCFCC43100B704A006A30702A000 +2FEC32F4EC3033352111213521113315BA030EFCF203C7CE93024B93FD2293000001FFEC +0000054F037100090024400A080B04061C020404010A10C4FCD4FCFCC43100B704A006A3 +0702A0002FEC32F4EC30233521112135211133151403DCFCF203C7CE93024B93FD229300 +0001FFEC0000048103710007002040090904001C040604030810C4FCD4FCEC3100B606A0 +00A304A0022FECF4EC3001112135211121350481FB6B03DCFCF20371FC8F93024B930000 +00020071000004D405E20013001F003A400E1221451B1C0C101C01151C06452010FCECDC +B24001015DFCDCB2400C015DECFCC43100400B189501101E95099111A0002FECF4ECD43C +EC30211126272E01343E01201E01140607060711211500141E01323E01342E0122060204 +524A728585E4010CE68383734B520218FC395B9CB89D5A5A9DB89C030E0B2031A8C5A962 +62A9C5A831200BFD859304AD7060383860706038380000000002FFEC000004D405E20015 +0021003E400F0123451D1C11151C06171C0B45042210C4FCECDCB24006015DFCDCB24011 +015DECFCC43100400C1A95061520950E910005A0022FEC32F4ECD43CEC30252115213521 +1126272E01343E01201E01140607060700141E01323E01342E01220602BC0218FB180218 +524A728585E4010CE68383734B52FE515B9CB89D5A5A9DB89C939393027B0B2031A8C5A9 +6262A9C5A831200B019F706038386070603838000002FFEC0000044F05E20013001F003A +400E21451B1C0E121C03151C0845012010C4FCECDCB24006015DFCDCB24011015DECEC31 +00400B189503121E950B9102A0132FECF4ECD43CEC302335211126272E01343E01201E01 +14060706071100141E01323E01342E012206140218524A728585E4010CE68383734B52FE +515B9CB89D5A5A9DB89C93027B0B2031A8C5A96262A9C5A831200BFCF204AD7060383860 +70603838000200BA0000054F04A60008000C0026400A020600050B040D0C0E0D10D4C410 +FCDCCCCC323100B6020305070BA0092FECDCDCDCCC300110331522073315230121352102 +11F2840285F1033EFB6B0495039C010A669A9EFCF89300000002FFEC0000054F04A60008 +000C0022B7030600050C0E0B0D10C4D4C4DCCCCC323100B6020305070BA0092FECDCDCDC +CC30011033152207331523012135210211F2840285F1033EFA9D0563039C010A669A9EFC +F89300000002FFEC0000048104A60008000C00244009020600050C040E0B0D10C4DCFCDC +CCCC323100B6020305070BA0092FECDCDCDCCC30011033152207331523012135210211F2 +840285F10270FB6B0495039C010A669A9EFCF89300020071000006B505EE0013001D0037 +400F121F110801041408020E1B0809451E10FCECD43CEC32D4FCDCC43100400D030501A0 +1D0F18A00A9111A0002FECF4ECD43CEC32CC3021112111231123222610362017161D0121 +11331501353427262206141633052EFE439CF8BCB0B101665E8B0276CEFC205D38C66D72 +5E02DEFEC4013CD60155E56698B6C9FD22930371C9835D3898C683000002FFEC000006B5 +05EE0015001F003B40101221110801041608020E1D080945152010C4FCECD43CEC32D4FC +DCC43100400E030501A01F0F1AA00A910011A0132FEC32F4ECD43CEC32CC302511211123 +1123222610362017161D0121113315213501353427262206141633052EFE439CF8BCB0B1 +01665E8B0276CEF93702E95D38C66D725E93024BFEC4013CD60155E56698B6C9FD229393 +02DEC9835D3898C6830000000002FFEC000005E705EE0013001D0037400F1F1108010414 +08020E1B080945131E10C4FCECD43CEC32D4FCCC3100400D030501A01D0F18A00A9100A0 +112FECF4ECD43CEC32CC3025112111231123222610362017161D01211121350135342726 +2206141633052EFE439CF8BCB0B101665E8B0276FA0502E95D38C66D725E93024BFEC401 +3CD60155E56698B6C9FC8F9302DEC9835D3898C683000000000100C10000039505D5000B +002C400B0A0D07031305091C01040C10FCFC3CFC3CDCC43100400B05A0070004A002AF0A +A0002FECF4EC10DCEC30331121152111211521112115C10283FE3501CBFE35021C05D593 +FE2F92FDB49300000001FFEC0000039505D5000D0030400C0C0F090513070B1C0304010E +10C4FCFC3CFC3CDCC43100400C07A0090006A004AF020CA0002FEC32F4EC10DCEC302335 +33112115211121152111211514D50283FE3501CBFE35021C93054293FE2F92FDB4930000 +0001FFEC0000034405D5000B002C400B0D060913070B1C0404020C10C4FCFC3CFC3CCC31 +00400B08A00A0007A005AF02A0002FECF4EC10DCEC302901353311211521112115210179 +FE73D50283FE3501CBFE3593054293FE2F9200000001003D0000039505D5000B002C400B +0A0D04081200041302060C10DC3CFC3CFCFCC43100400B04A0020005A007AF0AA0002FEC +F4EC10DCEC302111213521112135211133150208FE3501CBFE350283D502DF9201D193FA +BE9300000001FFEC0000039505D5000D0030400C0C0F040A120602130408010E10C4DC3C +FC3CFCFCC43100400C05A0030008A00AAF0C02A0002FEC32F4EC10DCEC30233521112135 +211121352111331514021CFE3501CBFE350283D593024C9201D193FABE9300000001FFEC +000002C005D5000B002C400B0D040B120307130509020C10C4DC3CFC3CFCEC3100400B07 +A0050008A00AAF02A0002FECF4EC10DCEC3029013521112135211121352102C0FD2C021C +FE3501CBFE35028393024C9201D19300000200BA0000048C05D5000A0017002A400C1619 +1B071C130E021C0C041810FCFC3CD4ECFCC43100400901A00F0D810316A00B10EC32ECD4 +EC30012111213237363534272601113311213217161514073315029CFED0012C52342C2C +3DFDD9B90155697F584FD302DEFDB55D4F7A7D495FFD2205D5FD9C875FD2A48293000000 +0002FFEC0000048C05D5000E0019002E400D0D1B1B161C0A05111C0304011A10C4FCFC3C +D4ECFCC43100400A10A006048102120DA0002FEC3232ECD4EC3023353311331121321716 +1514073315012111213237363534272614CEB90155697F584FD3FE10FED0012C52342C2C +3D930542FD9C875FD2A4829302DEFDB55D4F7A7D495F00000002FFEC0000040805D5000A +0019002A400C1B1B071C1510021C0E040C1A10C4FCFC3CD4ECEC3100400901A0110F810D +03A00B2FEC32ECD4EC300121112132373635342726013533113311213217161514070623 +029CFED0012C52342C2C3DFD0BCEB90155697F5858586C02DEFDB55D4F7A7D495FFD2293 +0542FD9C875FD2AD87850000000100C1000004F905D500130040400B12150409111C0500 +06041410F4DCB25F00015D39FCD4FCC400400B0B0500060AA0078111A0002FECF4EC3911 +393940070B120A12050605071005ECEC3130213402272627033521150513161716171617 +211502AA1B354D89C30363FD999D883442060201019899015F96DCDC0139569301FEEFEA +B8E69C40399300000001FFEC000004F905D500160044400C1518040C141C080209040117 +10C4F4DCB25F02015D39FCD4FCC400400C0E0800090DA00A811402A0002FEC32F4EC3911 +393940070E120D12080908071005ECEC3130233521262726272627033521150513161716 +17161721151402BB03080D354D89C30363FD999D883442060201019893585DB096DCDC01 +39569301FEEFEAB8E69C4039930000000001FFEC0000042405D500130040400B15040C13 +1C08020904011410C4F4DCB25F02015D39FCD4EC00400B0E0800090DA00A8102A0002FEC +F4EC3911393940070E120D12080908071005ECEC31302335212627262726270335211505 +1316171612071402BB03080D354D89C30363FD999D8834420B0193585DB096DCDC013956 +9301FEEFEAB8E6FEC97100000001003D000002E105D50008003A4009070A0403051C0103 +0910D4DCFC1139DCC43100400D0212011203040303048106A0082FECF4CCB21F03015D07 +1004ECECB48D028D01025D302111013501331133150154FEE90115BAD504DFFEC7F60139 +FABE93000001FFEC000002E105D5000A003E400A090C0605071C0305010B10C4D4DCFC11 +39DCC43100400E041203120506050506810802A00A2FEC32F4CCB21F05015D071004ECEC +B48D048D03025D302335211101350133113315140168FEE90115BAD593044CFEC7F60139 +FABE93000001FFEC0000020C05D50008003A40090A0605071C0305010910C4D4DCFC1139 +CC3100400D0412031205060505068102A0082FECF4CCB21F05015D071004ECECB48D048D +03025D30233521110135013311140168FEE90115BA93044CFEC7F60139FA2B00000300C1 +000007C0041A0011001A00230031400E10251C0E1C12181C1B211C01042410FCECD4ECD4 +FCFCC43100400B15A00A1EA004101A23A0002FEC3232D4ECD4EC3033113412333216173E +0133321716151133150134262322061511210134262322061D0121C1F4C67D78352596D3 +D25E88D5FE71A45A817F01FEFD48A45A6C9401FE01EEA600FF4B2D2DD2588068FDB99302 +7C8280C595FE6E0163837FA7D2F200000003FFEC000007C0041A0013001C00250035400F +122704101C141A1C1D231C0304012610C4FCECD4ECD4FCFCC43100400C17A00C20A00611 +1B2402A0132FEC323232D4ECD4EC30233533113412333216173E01333217161511331501 +34262322061511210134262322061D012114D5F4C67D78352596D3D25E88D5FE71A45A81 +7F01FEFD48A45A6C9401FE93015BA600FF4B2D2DD2588068FDB993027C8280C595FE6E01 +63837FA7D2F200000003FFEC000006EB041A0011001A00230031400E2504101C12181C1B +211C0304012410C4FCECD4ECD4FCEC3100400B15A00C1EA006022219A0112FEC3232D4EC +D4EC30233533113412333216173E01333217161511033426232206151121013426232206 +1D012114D5F4C67D78352596D3D25E88BAA45A817F01FEFD48A45A6C9401FE93015BA600 +FF4B2D2DD2588068FD26027C8280C595FE6E0163837FA7D2F20000000001003D000003DC +05D5000D002D400C0C0F091F0B071C05011F030E10DCFC3CFC3CFCDCC43100400A0901A0 +030706810CA0002FECFCDC3CFC3C3021112135211133112115211121150188FEB5014BB8 +014BFEB5019C03819301C1FE3F93FD12930000000001FFEC000003DC05D5000F0031400D +0E110B1F0D091C07031F05011010C4DCFC3CFC3CFCCCC43100400B0B03A005090881020E +A0002FEC32F4DC3CFC3C302335211121352111331121152111211514019CFEB5014BB801 +4BFEB5019C9302EE9301C1FE3F93FD12930000000001FFEC0000038B05D5000D002D400C +0F0C1F000A1C08041F06020E10C4DCFC3CFC3CFCCC3100400A0C04A0060A088102A0002F +ECF4DC3CFC3C3029013521112135211133112115210240FDAC019CFEB5014BB8014BFEB5 +9302EE9301C1FE3F930000000002003D0000065805D500030015003E40111417101F0E12 +1C000C0A021C04081F061610DCFC3CFC3CDC3CFC3CFCDCC43100400E050111A0070B0F09 +0D810313A0042FEC32F43CDC3C3CEC323230251121110711213521113311211133112115 +211121150404FE3DB9FEB5014BB901C3B8014CFEB4019C9302EEFD129303819301C1FE3F +01C1FE3F93FD12930002FFEC0000065805D5001300170042401212190E1F0C101C140A08 +161C02061F04001810C4DCFC3CFC3CDC3CFC3CFCDCC43100400F0F1503A005090D070B81 +111701A0132FEC3232F43CDC3C3CEC323230233521112135211133112111331121152111 +21152511211114019CFEB5014BB901C3B8014CFEB4019CFDACFE3D9302EE9301C1FE3F01 +C1FE3F93FD12939302EEFD120002FFEC0000060805D500110015003E4011170F1F0D1112 +120B09141C03071F05011610C4DCFC3CFC3CDC3CFC3CFCCC3100400E101304A0060A0E0C +08811502A0002FEC32F43CDC3C3CFC3C3C30290135211121352111331121113311211521 +0311211104BCFB30019CFEB5014BB901C3B8014CFEB4B8FE3D9302EE9301C1FE3F01C1FE +3F93FD1202EEFD12000200BA0000054F0371000500090025400B040B04021C08071C0104 +0A10FCECD4FCFCC43100B706A002A30308A0002FEC32F4EC3033112111331501112111BA +03C7CEFC2402550371FD229302DEFDB5024B00000002FFEC0000054F03710007000B002A +400C060D04041C0A091C0304010C10C4FCECD4FCFCC43100400908A004A3020905A0002F +EC3232F4EC3023353311211133150111211114CE03C7CEFC2402559302DEFD229302DEFD +B5024B000002FFEC000004810371000500090025400B0B04051C08071C0404020A10C4FC +ECD4FCEC3100B709A004A30307A0002FEC32F4EC30290135331121051121110481FB6BCE +03C7FCF202559302DE93FDB5024B00000002003D0000051405D5000200080043B4070A05 +040910D4C4C4C43100400A4201950481029507A0032FECECF4EC304B5358401202110601 +0200110505060211030111040403050710EC10EC0710EC0810EC59012101070121012115 +03C6FD74014673FE300486FE6801E90542FB7EC005D5FABE930000000002FFEC00000514 +05D50002000A0046B5090C0706040B10C4D4C4C43100400B420195068102950409A0032F +EC32ECF4EC304B53584012021108010200110707080211050111060605050710EC10EC07 +10EC0810EC59012101053521012101211503C6FD740146FD6C01E9FE680486FE6801E905 +42FB7EC0930542FABE9300000002FFEC000004C305D5000200080043B40A0706040910C4 +D4C4C43100400A4201950681029504A0032FECECF4EC304B535840120211080102001107 +07080211050111060605050710EC10EC0710EC0810EC5901210105352101210103C6FD74 +0146FD6C01E9FE680486FE300542FB7EC0930542FA2B0000000300C1000005F305D5000A +000E001F003840101E2104051C171D001C120E0C1C10042010FCECD43CFC3CD4ECFCC431 +00400D1C0BA001110AA013810D1EA00F2FEC32F4ECDC3CEC32300133323736353427262B +01011121110111211121321716150607062F01112115036DA85D251F1F1D69A4FE0D013B +FE0C01F401B084404501445C8CD40286037151453D3C645EFD9CFDB5024BFD2203710264 +85907C915B7C0101FDB593000003FFEC000005F305D5000A000E0021003C401120230405 +1C191F001C140E0C1C1204102210C4FCECD43CFC3CD4ECFCC43100400E1E0BA001130AA0 +1581110D20A00F2FEC3232F4ECDC3CEC32300133323736353427262B0101112111013533 +11211121321716150607062F01112115036DA85D251F1F1D69A4FE0D013BFD37D501F401 +B084404501445C8CD40286037151453D3C645EFD9CFDB5024BFD229302DE026485907C91 +5B7C0101FDB593000003FFEC0000056E05D5000A000E001F003840102104051C1A0F001C +150E0C1C1304112010C4FCECD43CFC3CD4ECEC3100400D1F0BA001140AA01681120DA010 +2FEC32F4ECDC3CEC32300133323736353427262B01011121111321353311211121321716 +150607062F01036DA85D251F1F1D69A4FE0D013BB8FC7FD501F401B084404501445C8CD4 +037151453D3C645EFD9CFDB5024BFD229302DE026485907C915B7C0101000000000100C1 +0000045105D5000D002E400D0C0F040B1C090107031C05040E10FCFC3CDC3CFCFCC43100 +400A0402A006080A810CA0002FECF4DCCCFCCC30211121112311331121113311331502C5 +FEB5B9B9014BB8D40381FEFC029BFEFC01C1FABE930000000001FFEC0000045105D5000F +0032400E0E11040D1C0B0309051C0704011010C4FCFC3CDC3CFCFCC43100400B0604A008 +0A0C81020EA0002FEC32F4DCCCFCCC30233521112111231133112111331133151402D9FE +B5B9B9014BB8D49302EEFEFC029BFEFC01C1FABE930000000001FFEC0000037D05D5000D +002E400D0F040C1C0B0309051C0704010E10C4FCFC3CDC3CFCEC3100400A0604A0080A0C +8102A0002FECF4DCCCFCCC3023352111211123113311211133111402D9FEB5B9B9014BB8 +9302EEFEFC029BFEFC01C1FA2B00000000020070000005DF05D5000A001D0033400E1C1F +04181F161A1C0B0107111B1E10FCCCD43CFC3CFCFCC43100400C190CA0001702A015811B +A01D2FECF4ECD43CEC323001211121220706151417160111212227263534373633211121 +152111211501DC0130FED452342C2C3D016EFEAB6B7D5858586C0232014CFEB4022102F7 +024B5D4F7A7D495FFD090264875FD2AD8785FD2293FE2F930002FFEC000005DF05D5000A +001F0037400F1E21041A1F181C1C0D0107131B0C2010C4FCCCD43CFC3CFCFCC43100400D +1B0EA0001902A017811D0DA01F2FEC32F4ECD43CEC323001211121220706151417160135 +2111212227263534373633211121152111211501DC0130FED452342C2C3DFE550319FEAB +6B7D5858586C0232014CFEB4022102F7024B5D4F7A7D495FFD099301D1875FD2AD8785FD +2293FE2F930000000002FFEC0000050A05D5000A001D0033400E1F041A1F181C1C0D0107 +131B0C1E10C4FCCCD43CFC3CFCEC3100400C1B0EA0001902A017810DA01D2FECF4ECD43C +EC323001211121220706151417160135211121222726353437363321112115211101DC01 +30FED452342C2C3DFE550319FEAB6B7D5858586C0232014CFEB402F7024B5D4F7A7D495F +FD099301D1875FD2AD8785FD2293FD9C0001003D000003DC05D500090024400A080B041F +061C001F020A10DCFCFCFCDCC43100B70106A0038107A0092FECF4EC3230211121352115 +211121150188FEB5034EFEB5019C05429393FB51930000000001FFEC000003DC05D5000B +0029400B0A0D061F081C021F04010C10C4DCFCFCFCDCC4310040090803A006810209A000 +2FEC32F4EC323023352111213521152111211514019CFEB5034EFEB5019C9304AF9393FB +519300000001FFEC0000038B05D500090024400A0B071F091C031F05020A10C4DCFCFCFC +CC3100B70904A0068103A0002FECF4EC3230290135211121352115210240FDAC019CFEB5 +034EFEB59304AF93930000000002003D0000051405D5000200080040B3080A030910D4C4 +C43100400942019505810602A0032FEC32F4EC304B535840120111050201001106060501 +11040211030304050710EC10EC0710EC0810EC5925090107013301331503C6FEBAFEBAFD +01D0E6019889930482FB7E9305D5FABE930000000002FFEC0000051405D50002000A0042 +B3090C040B10C4C4C43100400A4201950681080402A0032FEC3232F4EC304B5358401201 +1107020100110808070111060211050506050710EC10EC0710EC0810EC59250901053533 +013301331503C6FEBAFEBAFEB2890198E6019889930482FB7E93930542FABE930002FFEC +000004C305D5000200080040B30A08040910C4C4C43100400942019506810402A0032FEC +32F4EC304B53584012011107020100110808070111060211050506050710EC10EC0710EC +0810EC5925090105353301330103C6FEBAFEBAFEB2890198E601D0930482FB7E93930542 +FA2B000000020071000004D405D5001C00280000211126272E0134373637363735213521 +1116171E01140607060715211500141E01323E01342E0122060204524A728543456F4A52 +FEB502035845728484724A530218FC395B9CB89D5A5A9DB89C01860B1F31A6C653562F1F +0BF393FE7B0D1E32A6C6A631200BF2930322705F38385F70603838000002FFEC000004D4 +05D5001E002A00002335213526272E01343736373637352135211116171E011406070607 +15211500141E01323E01342E012206140218524A728543456F4A52FEB502035845728484 +724A530218FC395B9CB89D5A5A9DB89C93F30B1F31A6C653562F1F0BF393FE7B0D1E32A6 +C6A631200BF2930322705F38385F7060383800000002FFEC0000044F05D5001C00280000 +2335213526272E01343736373637352135211116171E0114060706071100141E01323E01 +342E012206140218524A728543456F4A52FEB502035845728484724A53FE515B9CB89D5A +5A9DB89C93F30B1F31A6C653562F1F0BF393FE7B0D1E32A6C6A631200BFE7B0322705F38 +385F706038380000000100C10000045105D500090025400B080B04011C07031C05040A10 +FCECD4ECFCC43100B70402A0058107A0002FECF4FCCC302111211123112111331502C5FE +B5B902BCD40542FEC501CEFABE9300000001FFEC0000045105D5000B002A400C0A0D0402 +1C08041C0604010C10C4FCECD4ECFCC4310040090504A008810209A0002FEC32F4FCCC30 +2335211121112311211133151402D9FEB5B902BCD49304AFFEC501CEFABE93000001FFEC +0000037D05D500090025400B0B04031C09051C0704010A10C4FCECD4ECEC3100B70604A0 +088102A0002FECF4FCCC30233521112111231121111402D9FEB5B902BC9304AFFEC501CE +FA2B0000000100C10000045104E6000B0028400C0B0D04091C0107031C05040C10FCFC3C +DCFCFCC43100B70608A004020AA0002FECD4CCFCCC3021112111231133112111331502C5 +FEB5B9B90203D4034FFEFC029BFEFCFCB19300000001FFEC0000045104E6000D002D400D +0D0F040B1C0309051C0704010E10C4FCFC3CDCFCFCC431004009080AA00604020CA0002F +EC32D4CCFCCC3023352111211123113311211133151402D9FEB5B9B90203D49302BCFEFC +029BFEFCFCB193000001FFEC0000037D04E6000B0028400C0D040B1C0309051C0704010C +10C4FCFC3CDCFCEC3100B7080AA0060402A0002FECD4CCFCCC3023352111211123113311 +21111402D9FEB5B9B902039302BCFEFC029BFEFCFC1E0000000200C10000044205D50003 +000D002F400D0D0F040B1C0703011C0509040E10FC3CECDC3CFCFCC43100400B00A00608 +A00A81020CA0042FEC32F4ECD4EC300111211101112111213521113315017A013BFE0C01 +F4FE0C02ACD502DEFDB5024BFD22037101D193FABE9300000002FFEC0000044205D50003 +000F0033400E0F11040D1C0903011C070B04051010C4FC3CECDC3CFCFCC43100400C00A0 +080AA00C8106020EA0042FEC3232F4ECD4EC300111211101353311211121352111331501 +7A013BFD37D501F4FE0C02ACD502DEFDB5024BFD229302DE01D193FABE9300000002FFEC +0000036D05D50003000D002F400D0F040D1C0903011C070B04050E10C4FC3CECDC3CFCEC +3100400B00A0080AA00C810602A0042FEC32F4ECD4EC3001112111013533112111213521 +11017A013BFD37D501F4FE0C02AC02DEFDB5024BFD229302DE01D193FA2B000000020071 +000004D405D5000B002600424010252845071C1F1A231C170D011C12452710FCECDCB240 +0D015D3CFC3CDCB2401F015DECFCC43100400D0495230D0A951A17198124A00C2FECECD4 +3CECD43CEC3000141E01323E01342E012206131126272E0134373637363711331116171E +011406070607152115010D5B9CB89D5A5A9DB89C9C524A728543456F4A52B85845728484 +724A5302180322705F38385F70603838FC7E01860B1F31A6C653562F1F0B0186FE7B0D1E +32A6C6A631200BF2930000000002FFEC000004D405D5000B0028004640110C2A45071C23 +1E271C1B11011C16450F2910C4FCECDCB24011015D3CFC3CDCB24023015DECFCC4310040 +0E049527110A951E1B1D812810A00E2FEC32ECD43CECD43CEC3000141E01323E01342E01 +220601152135213526272E0134373637363711331116171E01140607060715010D5B9CB8 +9D5A5A9DB89C036CFB180218524A728543456F4A52B85845728484724A530322705F3838 +5F70603838FD119393F30B1F31A6C653562F1F0B0186FE7B0D1E32A6C6A631200BF20000 +0002FFEC0000044F05D5000B0026004240102845071C1F1A231C170D011C1245262710C4 +FCECDCB2400D015D3CFC3CDCB2401F015DECEC3100400D0495230D0A951A1719810CA025 +2FECECD43CECD43CEC3000141E01323E01342E012206133526272E013437363736371133 +1116171E011406070607112135010D5B9CB89D5A5A9DB89C9C524A728543456F4A52B858 +45728484724A53FD300322705F38385F70603838FD11F30B1F31A6C653562F1F0B0186FE +7B0D1E32A6C6A631200BFE7B9300000000020071000004D405D5001E002A000021112627 +2E013437363736373521352115211516171E01140607060715211500141E01323E01342E +0122060204524A728543456F4A52FEB5034EFEB55845728484724A530218FC395B9CB89D +5A5A9DB89C01860B1F31A6C653562F1F0BF39393F20D1E32A6C6A631200BF2930322705F +38385F70603838000002FFEC000004D405D50020002C000025152135213526272E013437 +363736373521352115211516171E0114060706071500141E01323E01342E01220604D4FB +180218524A728543456F4A52FEB5034EFEB55845728484724A53FE515B9CB89D5A5A9DB8 +9C939393F30B1F31A6C653562F1F0BF39393F20D1E32A6C6A631200BF2028F705F38385F +706038380002FFEC0000044F05D5001E002A0000290135213526272E0134373637363735 +21352115211516171E01140607060700141E01323E01342E01220602BCFD300218524A72 +8543456F4A52FEB5034EFEB55845728484724A53FE515B9CB89D5A5A9DB89C93F30B1F31 +A6C653562F1F0BF39393F20D1E32A6C6A631200B019D705F38385F7060383800FFFFFCEC +03FBFF1006201007029CFBFEFF1A0000FFFF00AA0286068205D512270F61000002861027 +0F61000003CA10070F610000050D0000FFFF00AA0286068305D512270F61000002861027 +0F61000103C910070F620000050D0000FFFF00AA0286068205D512270F61000002861027 +0F62000003C910070F610000050D0000FFFF00AA0286068205D512270F61000002861027 +0F62000003C910070F620000050D0000FFFF00AA0286068205D512270F62000002861027 +0F61000003C910070F610000050D0000FFFF00AA0286068205D512270F62000002861027 +0F61000003C910070F620000050D0000FFFF00AA0286068205D512270F62000002861027 +0F62000003C910070F610000050D0000FFFF00AA0286068205D512270F62000002861027 +0F62000003C910070F620000050D0000000101970518051B05E0000B003A400D02010003 +060504070A09080B0C10D440093F0B6F0B8F0B900B045DDC3939D4DC3939D4CC39393100 +B6050407010A0B0C10D4CC32DC3CCC30010723272307232723072327051B643232AF3232 +32AF32326405E0C864646464C8000000000100C9FE5605FC05D5000E0000212311012111 +3311012111331501230533C4FD6AFEF0C402960110C9FE928604E1FB1F05D5FB1F04E1FA +D5AAFE56000100C1FE5605380460000E00002123110123113311013311331501230480B7 +FDE4ECB7021BEDB8FEDE7B0383FC7D0460FC7F0381FC3999FE560000000200AEFE560458 +047B001F0020000025350E0123222635113311141633323635113311100221222627351E +013332360103A043B175C1C8B87C7C95ADB8FEFEFA61AC51519E52B5B4FEDD6A426663F0 +E702A6FD619F9FBEA4027BFC2BFEE2FEE91D1EB32C2ABD04D0000000FFFF007DFE900447 +0352102717320119FE90100614A10000FFFF007DFEA2044703521027172101A9FEA21006 +14A10000FFFF007DFEA2044705F71027054BFFC2FF061027172101A9FEA2100614A10000 +FFFF007DFFDA044705781027172400FA04E2100614A10000FFFF007DFFDA044705AA1027 +172600FA0514100614A10000FFFFFFABFE0C047E0226102717320258FE0C100614A50000 +FFFFFFABFE0C047E0226102717210334FE48100614A50000FFFFFFABFE0C047E02261027 +172100B40028102717210334FE48100614A50000FFFFFFABFE0C047E03B61027172201F4 +0320100614A50000FFFFFFABFE0C047E04B01027172601F4041A100614A50000FFFF0082 +FE0C0A4703B6102614A9000010271721055F03201007172105F5FEA2FFFFFFECFEA2060A +03E8102614AA0000102717210307035210071721039DFEA2FFFFFFECFEA2073703E81026 +14AB0000102717210307035210071721039DFEA2FFFF0082FE0C0A4702EE102614A90000 +100717240578FF06FFFFFFECFE0C060A02EE102614AA0000100717240320FF06FFFFFFEC +FE0C073702EE102614AB0000100717240320FF06FFFF0082FE0C0A4704B0102614A90000 +1027172304E20320100717240578FF06FFFFFFECFE0C060A04B0102614AA000010271723 +028A0320100717240320FF06FFFFFFECFE0C073704B0102614AB000010271723028A0320 +100717240320FF06FFFF0082FE0C09E102E5102614B10000100717220578FEA2FFFFFFEC +FEA2063202E5102614B20000100717220258FEA2FFFFFFECFEA2070402E5102614B30000 +100717220258FEA2FFFF0082FE0C09E104B0102614B1000010071723047E0320FFFFFFEC +0000063204B0102614B200001007172301900320FFFFFFEC0000070404B0102614B30000 +1007172301900320FFFF0090000007AC0614102614B900001007172303840352FFFFFFEC +000005D40614102614BA000010071723027C0352FFFFFFEC000006A40614102614BB0000 +10071723027C0352FFFF0075FE0C04B20546102614C1000010071723012C03B6FFFFFFEC +000003F80640102614C2000010071723012C04B0FFFFFFEC000003F00546102614C30000 +10071723012C03B6FFFF0082FEA2085C03111026172A000010071721036BFEA2FFFFFFEC +FED4033F03D91026172B00001007172101A8FED4FFFFFFECFED4042003081026172C0000 +1007172101A8FED4FFFF0082FEA2085C044C1026172A00001027172105F503B610071721 +036BFEA2FFFFFFECFED4033F05141026172B00001027172101A9047E1007172101A8FED4 +FFFFFFECFED40420047E1026172C00001027172101BB03E81007172101A8FED4FFFF0082 +FDA8085C03111026172A00001007172402EEFEA2FFFFFFECFDDA033F03D91026172B0000 +10071724012CFED4FFFFFFECFDDA042003081026172C000010071724012CFED4FFFF006B +FE0C06C004011026172D0000100717210401036BFFFFFFEC0000033F05141026172E0000 +1007172101A9047EFFFFFFEC00000420047E1026172F00001007172101BB03E8FFFF006B +FE0C06C004FB1026172D0000100717230384036BFFFFFFEC0000033F060E1026172E0000 +10071723012C047EFFFFFFEC0000042005781026172F000010071723013E03E800010082 +FFA70882061400370000253224363D01342725243D013437363701210106070617161705 +161D011417163B011523222627060423212227241134373306151417163304F6B8016B31 +79FC59FE9D0B13A002B00160FC685F0F090406AE03CAF82F0F162E5A5C390B53FE6CA1FE +0CC080FEB63FB841CB689758966C05096B129137BE07063E6360019BFDE837230A23351B +9626F8455E1105B8323039822662010B8A5C5E887E4222000001FFEC0000068406140020 +000023352132373627262725243D01343736370121010607061716170516151407062314 +0508932E13030D63FC59FE9D0B11A202B00160FC685F0F090406AE03CAF83E5FDFB8682C +1C6E0F9137BE08053E6261019BFDE837230A23351B9626F871659B000001FFEC00000706 +06140029000023352132373635342725243D013437363701210106070617161705161D01 +1417163B011523222627062314050888391070FC59FE9D0B11A202B00160FC685F0F0904 +06AE03CAF82F0F162E5A5A380B60A7B8681D187F119137BE08043F6261019BFDE837230A +23351B9626F8455F1005B82F28570000FFFF0082FFA707D9061410261425000010071732 +05460384FFFFFFEC000003CF0614102614D200001007173201F40384FFFFFFEC0000047F +0614102614D300001007173201F40384FFFF0090FFC906D20614102614D1000010071721 +02EE0546FFFFFFEC000003CF0672102614D200001007172100C805DCFFFFFFEC0000047F +0672102614D300001007172100C805DCFFFF0090FDA806D20614102614D1000010071724 +0258FEA2FFFFFFECFDDA03CF0614102614D200001007172400C8FED4FFFFFFECFDDA047F +0614102614D300001007172400C8FED4FFFF0082FFA707D9072B10261429000010071732 +05460384FFFFFFEC000003CF072B1026142A00001007173201F40384FFFFFFEC0000047F +072B1026142B00001007173201F40384FFFF0082FEA207D9072B10261429000010071722 +0258FEA2FFFFFFECFED403CF072B1026142A00001007172200C8FED4FFFFFFECFED4047F +072B1026142B00001007172200C8FED4FFFF0082FFA707D9083410261429000010071723 +038406A4FFFFFFEC000003CF08341026142A000010071723004B06A4FFFFFFEC0000047F +08341026142B000010071723004B06A4FFFF0090FEC80623079E102614D5000010071721 +046A0708FFFFFFEC000001AF079E102614D600001007172101010708FFFFFFEC000002BA +079E102614D700001007172101010708FFFF0090FEC806230834102614D5000010071723 +03E806A4FFFFFFEC0000020F0834102614D6000010071723007F06A4FFFFFFEC000002BA +0834102614D7000010071723007F06A4FFFF0090FCE006230614102614D5000010071724 +0226FDDAFFFFFFECFE0C01F30614102614D60000100717240063FF06FFFFFFECFE0C02BA +0614102614D70000100717240081FF06FFFF0093FCC7062B02BC10261435000010271721 +023F02261007172102BCFCC7FFFFFFECFED4018703E81027172100E00352102617280000 +1007172100E0FED4FFFFFFECFED4027E03E81027172100E0035210261729000010071721 +00E0FED4FFFF0093FCAE062B02BC10261435000010271721023F0226100717320226FCAE +FFFFFFECFEF4020603E81027172100E00352102617280000100717320050FEF4FFFFFFEC +FEF4027E03E81027172100E00352102617290000100717320050FEF4FFFF0093FE0C062B +03B61026143500001007172301F40226FFFFFFEC000001F304E210261728000010071723 +00630352FFFFFFEC0000027E04E210261729000010071723006303520002013500000200 +05D5000300090062400F07008302810408070400030501000A10FC3CEC32393931002FF4 +FCCC30014BB00B5458BD000A00400001000A000AFFC03811373859014BB00F544BB01054 +5B4BB013545B58BD000AFFC00001000A000A00403811373859B6000B200B500B035D0123 +35331123111333130200CBCBCB15A21404D7FEFA2B028F0165FE9B000002008FFFE303AC +05D5002000240086402F201A05020406190010860F880C002183230C95138C2381250622 +1916090501001A2209001C01221C21260F091C162510DCECD4FCECD4EC11123911123911 +12391239310010E4F4EC10FECD10F4EE123939173930014BB010544BB012545B4BB01354 +5B58BD0025FFC000010025002500403811373859400B7404740574067407761C055D0133 +1514060F010E0115141633323637150E012322263534363F013E01373E01351323353301 +F4BE375A5A3A33836D4EB4605EC067B8E04959583026080706C4CACA04449C6582575835 +5E31596E4643BC3938C29F4C8956562F3519153C36010EFEFFFF008FFFE303AC05D31207 +12570000017500000001FFEC000002810258000D00002506232135213237363D01331514 +0225489DFEAC011D632C31B85656B82C316AD9D9BB000000000100DDFDD407B208230007 +00001321112111211121DD06D5FEADFBD0FEAE0823F5B1092DF6D300000100DDFDD407B2 +0823000700001311211121112111DD015204300153FDD40A4FF6D3092DF5B10000010023 +FDD407660823000B00001321112109012111213509014E06F3FAB403B6FC2B0590F8BD04 +02FC290823FEEFFC0AFBC8FEF0D3046D04150000000100A5FD990540089000160000013E +013216170726232207030E0123222627371633323702AB0CE4D6B31CD21850640C5C0BE6 +6A6BB31CD21850640C06E8ECBCB0C715B8F8F87DECBCB0C715B8F700FFFF00A5FD990848 +08901026184000001007184003080000FFFF00A5FD990B51089010271840061100001027 +184003080000100618400000000300A5FD9905400890002D0036003F0000013637363332 +171617072623220703161716151407060703060706232227262737163332371326272635 +3437363713363736273427262701130607061714171602AB0C7570686D644E1DD2185064 +0C148C5D9C9C7AA2150B7670686D644E1DD21850640C1584669C9D75A6C3563F62016434 +3FFEF51F563F66016A3406E8EC605C634DC715B8F8FE5B226AAFCFDD9C781CFE39EC605C +634DC715B8F701B022679DDBDDA27A1BFC9D1742669296643319FD760298184169929266 +33000000000400A5FD9908480890004D0051005A00630000013637363332171617072623 +220703211336373633321716170726232207031617160714070607030607062322272627 +371633323713210306070623222726273716333237132627263534373637132113210113 +0607061714171601033637362734272602AB0C7570686D644E1DD21850640C14021C150C +7570696D644E1CD11851640C148C5E9C019B7AA2160B7570696D644E1CD11851640C13FD +E4150B7670686D644E1DD21850640C1584669C9D75A6C2021C22FDE4FEF31F563F66016A +34045120563F6201643406E8EC605C634DC715B8F8FE6C01B8EC605C634DC715B8F8FE5B +226AAFCFDD9C781CFE39EC605C634DC715B8F7019DFE40EC605C634DC715B8F701AE2368 +9DDBDDA27A1BFC9002BFFD5B0298184169929266330271FD6917426692966433000500A5 +FD990B500890006D00710075007E00870000013637363332171617072623220703211336 +373633321716170726232207032113363736333217161707262322070316171615140706 +070306070623222726273716333237132103060706232227262737163332371321030607 +062322272627371633323713262726353437363701132103290113210113060706171417 +1601033637363534272602AB0C7570686D644E1DD21850640C14021C150C7570696D644E +1CD11851640C14021D150C7570686D644E1CD11851640B148C5D9C9C7AA2150B7670686D +654E1CD21850640C14FDE4160B7570696D644E1CD11851640C13FDE4150B7670686D644E +1DD21850640C1584669C9D75A605E622FDE422FCF8021C22FDE4FEF31F563F66016A3407 +591F563E62653406E8EC605C634DC715B8F8FE6C01B8EC605C634DC715B8F8FE6C01B8EC +605C634DC715B8F8FE5B226AAFCFDD9C781CFE39EC605C634DC715B8F7019DFE40EC605C +634DC715B8F7019DFE40EC605C634DC715B8F701AE23689DDBDDA27A1BFC9002BFFD4102 +BFFD5B0298184169929266330271FD691742669296643300000100A5FD9905E908900033 +00000126232207031617161D01371701230137173534272627030E012322262737163332 +3713060706152334373637133E0133321617046E1853620B177E59A88566FEF44DFEF666 +8270483E3E0BE26670B51DD21853620B3D5B427A909A7A9A180BE26670B51D0704B8F8FE +292258A7DC2A8365FEF4010C65832AA36D4810FAE9E9BFACCB15B8F704FF174278A2DB9B +7A1701F1E9BFADCA000300A5FD9905D1089000080011003E000001130607061714171601 +3427033E013727371326232207031617161737170706070607030E012322262737163332 +37132627263534373637133E0133321617026D1F563F66016A34021FD71F56730DBF6599 +1853620B147E6B9A016566EC25557AA2150BE16C6BB51DD21853620B1584669C9D75A615 +0BE16C6BB51D01D30298184169919366330145D854FD69178E1BC1650358B8F8FE5B1F6D +9DAF6565ED5F587F1CFE39E9BFACCB15B8F701B022679DDBDDA27A1B01BFE8C0ADCA0000 +000300A5FD9905D4089000080012003D0000011306070617141716012E0127033E013707 +271326232207031E011F010727140007030E012322262737163332371326272635343736 +37133E0133321617026D1F563F66016A340204314D3E1F56950A7666FC1853620B148CC5 +19E8666AFEEEA3150BE16C6BB51DD21853620B1584669C9D75A6150BE16C6BB51D01D302 +981841699193663301B75D4419FD6917B75B77660426B8F8FE5B22DE59E8666AAFFEF01B +FE39E9BFACCB15B8F701B022679DDBDDA27A1B01BFE8C0ADCA0000000001FFF8FDD40950 +082300060000090121090121010541040FFEE1FC73FC73FEE1040C0823F5B10925F6DB0A +4F0000000001FFF8FDD40950082300060000090121090121010407FBF1011F038D038D01 +1FFBF4FDD40A4FF6DC0924F5B1000000000100C6FDD40882082300100000012000190123 +111000200019012311100004A4FECBFE4BF4027502D20275F4FE4F073BFEC0FE9BF93E06 +E501C801A2FE5EFE38F91B06C201620143000000000100C6FDD408820823001000000120 +00190133111000200019013311100004A4013501B5F4FD8BFD2EFD8BF401B1FEBC014101 +6506C1F91BFE38FE5E01A301C706E5F93FFE9EFEBC00000000030052FDC30AFE08750003 +001D00370000012111210020070607060215141217161716203736373612353402272627 +002004171617161110070607060420242726272611103736373604D101AEFE5201B9FE3C +C6C2A1A4A1A1A4A1C2C601C4C6C2A1A4A2A2A4A1C2FD3F023301E3C9C964636364C9C9FE +1DFDCDFE1CC9C964636364C9C9041CFDFB05595452A1A3FE7AE7E1FE7AA2A153535352A2 +A30186E0E70186A3A1520159CBC8C9F3F0FEE3FEE9F2F0CAC7CCCCC7C9F1F30116011CF1 +F3C9C70000030052FDC30AFE087500190033003F00000020070607060215141217161716 +203736373612353402272627002004171617161110070607060420242726272611103736 +3736051121152111231121352111068AFE3CC6C2A1A4A1A1A4A1C2C601C4C6C2A1A4A2A2 +A4A1C2FD3F023301E3C9C964636364C9C9FE1DFDCDFE1CC9C964636364C9B50388039BFC +65EEFC65039B07705452A1A3FE7AE7E1FE7AA2A153535352A2A30186E0E70186A3A15201 +59CBC8C9F3F0FEE3FEE9F2F0CAC7CCCCC7C9F1F30116011CF1F3C9B56AFC65F1FC65039B +F1039B0000030052FDC30AFE087500190033003F00000020070607060215141217161716 +203736373612353402272627002004171617161110070607060420242726272611103736 +3736130901170901070901270901068AFE3CC6C2A1A4A1A1A4A1C2C601C4C6C2A1A4A2A2 +A4A1C2FD3F023301E3C9C964636364C9C9FE1DFDCDFE1CC9C964636364C9B583028D028D +AAFD73028DA8FD73FD73AA028DFD7307705452A1A3FE7AE7E1FE7AA2A153535352A2A301 +86E0E70186A3A1520159CBC8C9F3F0FEE3FEE9F2F0CAC7CCCCC7C9F1F30116011CF1F3C9 +B5FEB8FD73028DAAFD73FD73A8028DFD73AA028D028D0000FFFF00A5FD990E5908901026 +18400000102718400611000010271840091900001007184003080000000100A5FD990540 +0890001F0000051633323713213521133E0133321617072623220703211521030E012322 +262701771853620B2DFE7D018F230BE16C6BB51DD21754620B220183FE722F0BE16C6BB5 +1DDBB8F703AEF102E5E9BFADCA15B8F8FD3FF1FC2FE9BFACCB000000000100A5FD990540 +08900027000005163332371321352113213521133E013332161707262322070321152103 +211521030E012322262701771853620B1FFE8B018111FE6E019D150BE16C6BB51DD21853 +620B140175FE80110191FE63200BE16C6BB51DDBB8F7028EF00154ED01C5E9BFADCA15B8 +F8FE5FEDFEACF0FD4FE9BFACCB000000000100A5FD9905400890001F0000012623220703 +011501030E0123222627371633323713013501133E0133321617046E1853620B2501C9FE +2C2C0BE16C6BB51DD21853620B25FE3901D22C0BE16C6BB51D0704B8F8FD090101E2FEF9 +FC5CE9BFACCB15B8F702F8FEFEE3010703A4E9BFADCA0000000200A5FD9905400890002C +003500000126232207031617072627033637170607030E01232226273716333237132627 +263534373637133E0133321617011306070617141716046E1853620B14D868A039711F2D +1E584C61150BE16C6BB51DD21853620B1584669C9D75A6150BE16C6BB51DFD2D1F563F66 +016A340704B8F8FE5B3AC05C662EFD6908189E330DFE39E9BFACCB15B8F701B022679DDB +DDA27A1B01BFE8C0ADCAFABA029818416991936633000000000100A5FD9905E808900034 +00000126232207033637363D01072701330107271514070607030E012322262737163332 +3713262726353314171617133E013332171617046E1853620B3D5B427A8466010C4C010A +66829C789A180BE36A6BB51DD21853600D178158A89071483F3E0BE36A6B674C1F0704B8 +F8FB02174278A12B8466010CFEF466842BDD987A18FE10E9BFACCB15B8F701D72358A8DA +A16E48100517E9BF634ACA00000200A5FD99054008900003002100000121112103132111 +21133E01321617072623220703211121030E01222627371633320266012AFED617180213 +FE151C0BE5D6B21DD21850630D1401BBFE0F120BEFCEB01DD218506503BFFE99FD0C020C +03180260ECBCB1C615B8F8FE87FB62FE94F5B3B1C615B800000200A5FD99054008900003 +002C000001211121130607133E0132161707262322070316171610070607030E01232226 +273716333237131633323610260266012AFED68C3A2C1F0BE5D6B21DD21850640C148C5D +9C9C7AA2150BE66A6BB21DD21850630D1F394C9DBFD103BFFE99021C0307027EECBCAFC8 +15B8F8FE5B226AAFFE559D781CFE39ECBCB1C615B8F7026C17D00132B3000000000200A5 +FD9906470890000300310000012111210116333237131617163332361026232007133E01 +33321617072623220703363332001000232227030E0123222627039E012BFED5FDD91853 +620B26404F9E8D8CD4C3A0FED3762208E66A6BB51DD21853620B155360EF0124FEC8DB7B +67180BE36A6BB51D03CAFE99FCC2B8F7031D58284CCD0128CAE40358E8C0ADCA15B8F8FE +4B1BFEB7FE54FECB2FFE12E9BFACCB00000300A5FD99054008900003000C003400000121 +112104361026200615141603163332371326272610373637133E01333216170726232207 +0316171610070607030E0123222627025F012AFED6011FD1C3FED2CCCAE51853620B1584 +669C9D75A6150BE16C6BB51DD21853620B148C5D9C9C7AA2150BE16C6BB51D03CDFE99AD +CC0128CACD9392CCFD6CB8F701B022679D01B8A27A1B01BFE8C0ADCA15B8F8FE5B226AAF +FE549C781CFE39E9BFACCB00000300A5FD9905400890001E002200260000371121133E01 +321617072623220703211121030E01232226273716333237130103331121231133D701BD +170BE5D6B21DD21850640C15018CFE41140BEF6665B31DD21850640C13011622F3FE43F2 +D0DB043B01D2ECBCB1C615B8F8FE52FBC5FE66F5B3AEC915B8F701770378FD4B02B5FD4B +0001FFA3FD9906420890002F0000012623220703213216100623353637262721030E0123 +222627371633323713210107013501170121133E013332171617046E1853620B24017E8D +C5CB87880B0B88FE792F0BE16C6BB51DD21853600D2DFE63010988FE4301BD88FEF701A6 +260BE16C6B674C1F0704B8F8FD14CAFEE8C6C0068F8B08FC29E9BFACCB15B8F703B4FEF9 +8801BC6601BC88FEF90310E9BF634ACA000100A5FD990540089000250000012623220703 +371709010727030E0123222627371633323713072709013717133E0133321617046E1853 +620B1CDEA9FE9C0164A9FB230BE16C6BB51DD21853620B21E1AD0167FE9CAAFC200BE16C +6BB51D0704B8F8FDA9DEABFE9BFE9CAAF9FD1BE9BFACCB15B8F702AAE2AB01640165A9FD +029CE9BFADCA0000000100A5FD9905400890002C00000516333237130607061511231110 +1237133E0133321617072623220703161716190123111027030E01232226270177185362 +0B405C2D4AABDBAB140BE16C6BB51DD21853620B137F587AABAF400BE16C6BB51DDBB8F7 +0529174671DDFE4701D10125010A1801A7E9BFADCA15B8F8FE72226289FED1FE2F01B901 +5847FAC0E9BFACCB000100A5FD9905400890002900000126232207033613113311100503 +0E0123222627371633323713262726190133111017133E0133321617046E1853620B3CCE +01ABFE7E180BE16C6BB51DD21853620B1782597AABB23D0BE16C6BB51D0704B8F8FB2036 +017301BAFE2EFDF237FE11E9BFACCB15B8F701D6216289012F01D2FE46FEA64504FAE9BF +ADCA0000000200A5FD99054E09DC0016001A0000013E013216170726232207030E012322 +262737163332370115213502AB0BE5D6B21DD21850640C5C0BE66B6AB21DD21850640C02 +FFFBA906E8ECBCB1C615B8F8F87DECBCB1C615B8F70A78E2E200000000020097FC4C0540 +08900016001A0000013E013216170726232207030E012322262737163332370135211502 +AB0BE5D6B21DD21850640C5C0BE66B6AB21DD21850640CFE48045606E8ECBCB1C615B8F8 +F87DECBCB1C615B8F7FCE8E3E30000000001000000025999F5875B945F0F3CF5001F0800 +00000000D17E0EE400000000D17E0EE4F7D6FC4C0E5909DC000000080000000100000000 +00010000076DFE1D00000EFEF7D6FA510E59000100000000000000000000000000001852 +04CD00660000000002AA0000028B00000335013503AE00C506B4009E051700AA079A0071 +063D0081023300C5031F00B0031F00A40400003D06B400D9028B009E02E30064028B00DB +02B2000005170087051700E1051700960517009C051700640517009E0517008F051700A8 +0517008B0517008102B200F002B2009E06B400D906B400D906B400D9043F009308000087 +05790010057D00C905960073062900C9050E00C9049A00C906330073060400C9025C00C9 +025CFF96053F00C9047500C906E700C905FC00C9064C007304D300C9064C0073058F00C9 +0514008704E3FFFA05DB00B20579001007E90044057B003D04E3FFFC057B005C031F00B0 +02B20000031F00C706B400D90400FFEC040000AA04E7007B051400BA0466007105140071 +04EC007102D1002F05140071051200BA023900C10239FFDB04A200BA023900C107CB00BA +051200BA04E50071051400BA05140071034A00BA042B006F03230037051200AE04BC003D +068B005604BC003B04BC003D043300580517010002B201040517010006B400D9028B0000 +03350135051700AC051700810517005E0517005202B201040400005C040000D70800011B +03C5007304E5009E06B400D902E300640800011B040000D5040000C306B400D90335005E +0335006204000173051700AE0517009E028B00DB040001230335008903C5006004E500C1 +07C1008907C1008907C10062043F008F0579001005790010057900100579001005790010 +0579001007CB000805960073050E00C9050E00C9050E00C9050E00C9025C003B025C00A2 +025CFFFE025C00060633000A05FC00C9064C0073064C0073064C0073064C0073064C0073 +06B40119064C006605DB00B205DB00B205DB00B205DB00B204E3FFFC04D700C9050A00BA +04E7007B04E7007B04E7007B04E7007B04E7007B04E7007B07DB007B0466007104EC0071 +04EC007104EC007104EC00710239FFC7023900900239FFDE0239FFF404E50071051200BA +04E5007104E5007104E5007104E5007104E5007106B400D904E50048051200AE051200AE +051200AE051200AE04BC003D051400BA04BC003D0579001004E7007B0579001004E7007B +0579001004E7007B05960073046600710596007304660071059600730466007105960073 +04660071062900C9051400710633000A05140071050E00C904EC0071050E00C904EC0071 +050E00C904EC0071050E00C904EC0071050E00C904EC0071063300730514007106330073 +0514007106330073051400710633007305140071060400C90512FFE5075400C9058F0078 +025CFFE40239FFD3025C00030239FFF2025CFFF50239FFE4025C00B002390096025C00C9 +023900C104B800C9047200C1025CFF960239FFDB053F00C904A200BA04A200BA047500C9 +023900C1047500C902390088047500C9030000C1047500C902BC00C1047FFFF202460002 +05FC00C9051200BA05FC00C9051200BA05FC00C9051200BA068200CD05FC00C9051200BA +064C007304E50071064C007304E50071064C007304E50071088F0073082F0071058F00C9 +034A00BA058F00C9034A0082058F00C9034A00BA05140087042B006F05140087042B006F +05140087042B006F05140087042B006F04E3FFFA0323003704E3FFFA0323003704E3FFFA +0323003705DB00B2051200AE05DB00B2051200AE05DB00B2051200AE05DB00B2051200AE +05DB00B2051200AE05DB00B2051200AE07E90044068B005604E3FFFC04BC003D04E3FFFC +057B005C04330058057B005C04330058057B005C0433005802D1002F0514002005E1FF97 +057D00C9051400BA057D00000514000005A0007305960073046600710633000A068DFF97 +057D00C90514007104E50071050E0083064C007504EA00A4049AFF9602D1FF7F06330073 +057E000807DF00BA02D400C9025C000A05F700C904A200B90239000A04BC003D07CB00B2 +05FCFF96051200BA064C0073074E006704E5007607970073061300710537FF97051400B9 +058F00C905140072042B0064050E00C902B0FEF20323003704E300180323003704E3FFFA +06DD00AD051200B0061D004E05C400C905F3FFFC05D8003D057B005C04330058055400A0 +0554005C049F006804330071051700960554005D049F006804150058051400BA025C00C9 +03F000C903AC0014025D00C90B6000C90A6400C9093C007106AF00C9064B00C903A700C1 +077300C9076400C9066100BA0579001004E7007B025CFFFE0239FFE0064C007304E50071 +05DB00B2051200AE05DB00B2051200AE05DB00B2051200AE05DB00B2051200AE05DB00B2 +051200AE04EC00710579001004E7007B0579001004E7007B07CB000807DB007B06330073 +051400710633007305140071053F00C904A2FFE9064C007304E50071064C007304E50071 +055400A0049F00580239FFDB0B6000C90A6400C9093C0071063300730514007108E700C9 +057500C905FC00C9051200BA0579001004E7007B07CB000807DB007B064C006604E50048 +0579001004E7007B0579001004E7007B050E00C904EC0071050E00C904EC0071025CFFA7 +0239FFC3025C00050239FFE3064C007304E50071064C007304E50071058F00C7034A0082 +058F00C9034A00BA05DB00B2051200AE05DB00B2051200AE05140087042B006F04E3FFFA +032300370504009C042C0047060400C90512FFF005E200C906B400710596007104E20071 +057B005C043300580579001004E7007B050E00C904EC0071064C007304E50071064C0073 +04E50071064C007304E50071064C007304E5007104E3FFFC04BC003D03CC008A06BE00BA +03D100370239FFDB07FC007107FC00710579FFFD0596000C046600090475000A04E3FFB2 +042B006F0433005804D3005003D50050057D000A05DB000C05790010050E00C904EC0071 +025CFF960239FFDB0640007305140071058F000A034A000E04E3FFF604BC000B04CD00AE +05140071051400BA051400BA0465007F04660071051400710592007104EC007104EC0071 +068E007C045300850441008506340085055000710239FFDB059100710514007105090071 +04C4006004C40060051200AE051200BA051200BA0239000E02B500A602F90074032A004B +03E6004D023A00C105A600C107CB00BA07CB00BA07CB00BA052BFFDB052300BA051200B3 +04E5007106DD007105D30094054700700350000003500000034F0000034A00BA034900BA +043E0084043E007404D400BA04D400BA042B006F02B0FFD902B0FFD903B1003702B0FEF2 +03230037032300370512000004F1007104C900C104BC003D068B005604BC003D04E30066 +0433005804330058049F0058049F006D04150058041500580415005804150058064C0073 +04A300BA0550007105AA0071053B00BA0256FEF2055600BA040E00BA05D1007104150058 +04150058081D007108760071081A007106A4003704E10037063A003706C9002F05A500C1 +053C00C1041F0036041F0036054A0000054F0000033C0075033100750166FFE902120075 +025D0048025E004803080020041F003602FB0026023A00A003AE00A0028B00AE028B00B2 +028B00C4027500750275007502F5007502F500750400010B0400010B040000C1040000C1 +040000C1040000C1023300D6040000D504000173040000AA023300D6040000D5040000AA +0400017302B2006F02B2006F02750075027500750400011F0400011F031E0064028A0064 +040000C70400019A040000EE0400014C040000B6040000F00286FFFF040000EF03680075 +0154007A02FC0075038D007502F5007503F200D603F200D603F200D603F200D603F200D6 +040000C1040000D5042500AE040000EE040000B60000FCA80000FD710000FCC10000FCB4 +0000FCD90000FBEC0000FCBF0000FDA20000FCD70000FD370000FCEC0000FCF40000FCC5 +0000FDBC0000FCF00000FC5D0000FCBF0000FCBF0000FE1F0000FD900000FD900000FF79 +0000FCA80000FD710000FD240000FDC40000FE550000FEF00000FD800000FD0B0000FD0B +0000FD240000FD0B0000FD7A0000FD770000FDA20000FCD50000FD280000FD6A0000FD23 +0000FD4C0000FDBC0000FCF00000FC630000FCC50000FCBF0000FCBF0000FCBF0000FCB4 +0000FCD90000FBEC0000FBEC0000FB8C0000FD780000FAED0000FB680000FA120000FDAC +0000FCF10000FD210000FC630000FD2B0000FE060000FBEC0000FCA80000FD710000FCB4 +0000FD900000FCE70000FDC60000FCD50000FD1F0000FD150000FD1F0000FCB60000FCB6 +0000FCB60000FC630000FD33000000000000FD780000FCBF0000FD2B0000FD780000FF2E +0000FC900000FC700000FC700000FC700000FC700000FD2A0000FC700000FC77053C00C9 +048B00C106E500C9052E00C9023A00A0023A00A005FC00C9053300BA040001B60465007F +046600710465007F02B2009E04000173040000D7058A0010028B00DB05F8FFE706F8FFF3 +0344FFED0680FFF20699FFE1069BFFDB02B5000505790010057D00C9047500C905790010 +050E00C9057B005C060400C9064C0073025C00C9053F00C90579001006E700C905FC00C9 +050E00C9064C0073060400C904D300C9050E00C904E3FFFA04E3FFFC064C0073057B003D +064C0073061D004E025C000604E3FFFC0546007104530085051200BA02B500A604A10095 +05460071051B00C004BC002004E5007104530085045A006B051200BA04E5007102B500A6 +04B700BF04BC003D051700AE0478004A0476006B04E5007104D1004A051400BA04B20071 +0512007104D1006404A1009505470070049F003B0547007006B3008702B5000504A10095 +04E5007104A1009506B30087053F00C904EA00A704F400710597005706BDFFE105970057 +0547007006B30041054F0070064C007304E500710530008B04B20071049A00C903ABFF40 +054700B3054700BF06EC0072050500770778007306B300870611007305460071065500C9 +04EB002D057E004F04DB006406240073050000360598007304E5007104E3002C044A0037 +054F0070051400BA046600710239FFDB064C007304EC007104EC00C404D700C9051400BA +0596007306E700C90535007F0514005505A000730596007305A00073050E00C9050E00C9 +064AFFFA04E100C90596007305140087025C00C9025C0006025CFF9608C00054085C00C9 +064AFFFA05AE00C905FC00C904E00023060400C905790010057D00C9057D00C904E100C9 +06400065050E00C9089E00280521008705FC00C905FC00C905AE00C90604005406E700C9 +060400C9064C0073060400C904D300C90596007304E3FFFA04E0002306E30079057B003D +063600C9057C00AF088E00C908C000C906A9003C070F00C9057D00C90596006F08A300D3 +058F008804E7007B04EF007004B700BA043400BA0588006B04EC00710735004604410085 +053300BA053300BA04D500BA051D004C060900BA053B00BA04E50071053B00BA051400BA +0466007104A9003C04BC003D06D7007004BC003B057200BA04BA0096075200BA078900BA +05A7003E065100BA04B700BA0464007106BC00C104D0007404EC007104EC00710500002F +043400BA04640071042B006F023900C10239FFF40239FFDB0738004C073000BA0537002F +04D500BA053300BA04BC003D053B00BA0778007306B30087062A001E0560001E078A00D3 +05FE00C10709001006440033094700C9080300C1064C007304E5006B083700C9069800C1 +051700730453005B06DA001007030032064C007304E50071064000100552003206400010 +0552003207F00073073C007107A000730611007109700076083900980778007306B30087 +05960073046600710405003B0000FBDA0000FD070000FDB30000FDB30000F9CA0358F7D6 +0358F858062E00C9056A00C1057D002104B7002604D300C9051400BA04E100C9043400BA +0566004704B9003804FE00C9043D00BA089E002807350046052100870441008505AE00C9 +04D500BA05AE00C904D500BA05AE002104D5003D06DA003206A7002A060400C9054900C1 +081D00C9070400C108A600C9075300C107060073058B0071059600730466007104E3FFFA +04A9003C04E3FFFC04BC003D04E3FFFC04BC003D057B003D04BC003B0779FFFA06740005 +057C00AF04BA0096057C00AF04BA0096057C00AF051200BA0787001405D3000F07870014 +05D3000F025C00C9089E002807350046053F00C904D500BF06350036055D002E060400C9 +054900C1063600C9057200C1057C00AF04BA0096071A00C9063200C1023900C105790010 +04E7007B0579001004E7007B07CB000807DB007B050E00C904EC0071064C007504EC0071 +064C007504EC0071089E0028073500460521008704410085055400A0049F005805FC00C9 +053300BA05FC00C9053300BA064C007304E50071064C007304E50071064C007304E50071 +0596006F0464007104E0002304BC003D04E0002304BC003D04E0002304BC003D057C00AF +04BA009604E100C9043400BA070F00C9065100BA0566004704B90038057B003D04BC003B +057B003D04BC003B057D009104B70071080C0091072D007107CC00C906F400AB056E00C9 +04B500AB0893003607A8002E08E700C907BD00C10633007305470071062FFFFA05B00005 +04EA00A40453008506040054051D004C095A005407F3004C072700C906EA00BA08410088 +07E30074064C00730514007107E90044068B005605AE00C904D500BA08A50054073E004C +08A600C9074C00BA065800C9057600BA062100B205DB00B20607005D0607005A05DB00B2 +062D00B2051F00BC05DB00B206E000B20607005D058700BC044400BC076000BC06E80108 +05DB00B205BA0046062000A80607006406230069065500B205D2009205D50000060F00A0 +05DB00B205B400780667005A062500A8065500B205DB00B20607006405A40096058D00B2 +05F300A0044D00BC067C0078060E0032064C00730652006E02750075028B00B201E00000 +02E4000501E7FFFF033E00080400005A07CB00AE051200BA05430071054E00BA051200AE +05140071041E00BA051200BA05E800BA05430071051200BA022C00BA07D700BA04FB0071 +051200AE051200BA04DD006A051300BA0508008C051200AE022CFFD40512FFD903FD0000 +051200BA033C000A07CB00AE047B006E053000BA051200AE051200AE07CA00AE051200BA +0511006F037A00BA07CA00AE0517002904E0006F06710046067E00AE02B200F002E30064 +00000244000000AB000000FF000000FF00000244000001C7000001C70000016300000163 +00000000000000000000012F0000024E0000024402E3006400000163025C00D100000519 +000000C5025C00D10388006600000163055900BA04A00058034C0058045E0058053A00BA +022E00BA02C50058053A00BA053000B901CA0088044C0058043B0058048C0058054F00BA +056F0058022E00BA03340058053100B905020058051E00BA04FF00BA0451005804BF0058 +05AD00BA0484005805AB00580542001403C400BA0362008802A50088035300BA052800BA +0519000005190000060E008507D00085029500DB000000FC028B00DB043F009303C300A3 +0239FFB50239006C03DDFFAB0239006C06430082023900C1078800820431008B07880082 +07880082052A009D052A009D052A009D0390007D0390007D03DDFFAB03DDFFAB09C40082 +09C4008209AC008209AC0082076600900766009004C6007504C600750258FFEC084B0082 +0635006B0698009005D0009004F4008C05E000930431008B03DDFFAB0643008206430082 +000000DC000000DC000000DC000000DC000000DC000000DC000000C8000000EC00000098 +0000014F0000014F000000DC040000F3044C01B8044C0116044C0052044C004C044C00AF +044C0087044C0055044C003C044C003C044C0064044C008502990000028B00B2045C0057 +078800820635006B000001C90256007A0788008207880082078800820788008207880082 +078800820788008207880082052A009D052A009D052A009D052A009D052A009D052A009D +052A009D0390007D0390007D0390007D0390007D0390007D0390007D0390007D0390007D +0390007D03DDFFAB03DDFFAB03FCFFAB043DFFAB04E2FFAB043DFFAB03DDFFAB03DDFFAB +03DDFFAB09C4008209C4008209C4008209AC008209AC00820766009004C60075084B0082 +084B0082084B0082084B0082084B0082084B00820635006B0635006B07290082086E0082 +072900820698009006980090069800900729008207290082072900820729008207290082 +0729008205D0009005D0009005D0009005D0009005E0009305E0009305E0009305E00093 +05E0009305960090052A009D03DDFFAB03DDFFAB03DDFFAB03DDFFAB0643008206430082 +064300820431008B044C01B8044C0116044C0052044C004C044C0052044C006A044C00D0 +044C003C044C003C044C006405170087051700E1051700E1051700E1051700E1051700E1 +051700E1051700D6051700D60517009D023900C1049200710364003D04BC003D053B00BA +053B00BA04C00071053B00BA06A10071038100C10381003D047800BA04E500C102CD003D +07AC00C103C8003D0644003D053B00BA0500003D05DF00C1043D00C105CB007003C8003D +0500003D04C00071043D00C1043D00C1042E00C104C0007104C00071000000D900000042 +000001A4000000BF0000005B000000420000005B0000019A000000D5028200C8028200C8 +047B0064047B006402E3FFEC051700B0055D00820578008B0581008903DC006D0506007F +0578008B05800073055B00BA0522008105290000053E00570545005F0500004905000049 +05F60059062200BA057E0068057E0068059D0040058000730578008B0532004F050F0073 +05A0004F068D007F051000730578008B064D00CB050F006F0000FB600450007B0450FC9A +0000FB400000FB400000FB400000FB400000FCFE0000FCA70000FB600000FB1C054E0081 +0300007A0542007A03AEFFD30461005C03EE00BC056400930000FD120000FB7F0000FB3E +0000FC4E0000FB1C0000FC9A051700870520006305200063055D00160500006305000063 +05A00059055D008205640096056A0069083A007F083A007F06FE007805DE006E056E006F +06AD006E04EC0064062400500607003C0750007903A0003C04F5007806BE003C070F0078 +04FF008206D600640640003205090050074B006E04F7008204F6008206D6006406ED0064 +05CA005A050A008304F7006E0500007804F50082068B007906FD007804EC008204FC0064 +0500008205CC003C06C1003C04C400820582006F04C4003C04C0006E05E8005004100064 +0424006404A6005A068B006404100064041A00640401002906680064042400640415005A +08840064042E0064042E0082064A00640410006404240064065E0064042E0082053C005A +042E000A069A0064041A0064064A0064042400640424003C042E00820492005A042E0064 +0424008304290064042E008203A200140410006404240078041000640410006404240064 +046F0064069F0064046A005A0410006404920050041000640395006E0297006405790010 +057900100579001005790010062700C9062700C9062700C9062700C90627007706270077 +06AE00C906AD001006AE00C906AD001006AE00C906AD001007BC00C9080E00C907BC00C9 +080E00C9062700C907BC00C9080E007707BC00C9080E007706270077020C00C904580089 +0363005E0363005E031D0089031D008903250089031D008903BA004803150089020C00C9 +031D0089031D0089031D008908B800100745001007A000C908EF00770579001005790010 +057900100579001005D5007705D5007705D5007705D5007705D5007705D5007706AE00C9 +0579001006AE00C906AD001006AE00C906AD001007BC00C9080E007707BC00C9080E0077 +07BC00C9080E007707BC00C9080E007705D50077041100890189008905DB00B205DB00B2 +05DB00B205DB00B205D7007705D7007705D7007705D7007705D7007905D70079075E00C9 +071C00B2075E00C9071C00B2075E00C9071C00B2076C00C907340077076C00C907340077 +079400C907340079079400C907340079079400F003790089070500B2070500B206ED0077 +071F007905070046050700C9050700C9050700C9050700460507004605070046050700C9 +050700C906E100C9062A0046068500C9068700C9068500C9068700C906E100C9062A0046 +06E100C9062A0046068500C9068700C9068500C9068700C9068500F00341008903410038 +06010046063300C906010046063300C905070046050700C9050700C9050700C905070046 +0507004605070046050700C9050700C906E100C9062A0046068500C9068700C9068500C9 +068700C906E100C9062A004606E100C9062A0046068500C9068700C9068500C9068700C9 +068500F0037A0089037A008904E10077047500C9047500C9047500C904E1007704E10077 +04E10077047500C9047500C905FE00C90626007705F800C9061C00C905F800C9061C00C9 +05FE00C90626007705FE00C90626007705F800C9061C00C905F800C9061C00C905F800F0 +03160089041100890316008906D0007706D0004606D0004606D0000B06D0007706D00077 +06D0007706D0004606D00046088E00C908470077087900C906D00046087900C906D00046 +06D0004604CD0089039F008904CD008906D0007706D0004606D0004606D0004606D00077 +06D0007706D0007706D0004606D00046088E00C908470077087900C9083D0046087900C9 +083D0046088E00C908470077088E00C90847007708AA00C9083D004608AA00C9083D0046 +04CD008905D5007704D3004604D300C904D300C904D300C904D3004604D3004604D30046 +04D300C904D300C906AD00C906080046065600C9062B00C9065600C9062B00C906AD00C9 +0608004606AD00C906080046065600C9062B00C9065600C9062B00C9065600F003590089 +035D00890359008905B2004605B2004605B2004607220046072200460722004607220046 +074700C906FA0046074700C906FA0046074700C906FA0046091F00C908CC0046091F00C9 +08CC0046091F00C908CC0046091F00C908CC00460520008905040077050400C9050400C9 +050400C9050400770504007705040077050400C9050400C906C100C9063F0077068700C9 +068C00C9068700C9068C00C906C100C9063F007706C100C9063F0077068700C9068C00C9 +068700C9068C00C9068700F003590089031D008903E00089075300C90753007707530077 +075300C9075300C9075300C904D300C904D300C904D300C904D3004604D3004604D30046 +06AD00C9060800460359008905D500770579009605790096057900960579009605CF004D +05CF004D05CF007705CF0077076400C9080E00770411008905DB00B205DB00B205DB00B2 +05DB00B205DB00B205DB00B205D7005305D7005305D7007905D70079079400C907340079 +0411008906A5004606A5007706A5007706A5004606A5004606A5007706A5007704820089 +060400C903E0008908600089086000890860008908600089086000890860008908600089 +069A008906A5004606A5007706A5004606A500770A1300890A1300890A13008908040089 +080400890A1300890A130089059900890895008906D0004606D0004606D0007706D00077 +06D0004606D0004604CD0089052500770525002105250021052500770525007705250021 +052500210359000A050700C9062900C90623007303BF008903BF0089038D007508600089 +0A7A00890D0F00890D0F00890B0000890B0000890D0F00890D0F008903D1FFEC03F1FFEC +05B2FFEC0773FFEC0934FFEC0AF5FFEC03F1FFEC05B2FFEC0773FFEC0933FFEC0AF5FFEC +03FCFFEC05BFFFEC0782FFEC0945FFEC0B08FFEC03F1FFEC05B2FFEC0770FFEC0932FFEC +0AF5FFEC03FCFFEC0605FFEC064FFFEC09A3FFEC0933FFEC0577FFEC040F0071040EFFEC +04BC003D05BD000B07DB007B04B0003E0466007104D700BA04D7002403ED00BA04530082 +023900C10328000004A200BA04AA0002060900BA053300BA04E500710466007F05790071 +0579007105790033082F007104E5007104E500710432009704D0003204D0003204A9003C +049800BA05E500AE079500AE051A002F04BC003D068B0056043300580435007804AA00B3 +04BC003D048300970432009704B9005A051D004C0372000A04E800050375007F03E1007F +032F007F032F005303E7004803CA007F017C007F017CFFBD034E007F02CE007F0459007F +03C5007F03C5007F03F7004803840047030A007F0380007F0314FFFC03B0007004FB002B +0322004D0322004D033E0047052F004D036D0075033E0047035600470356004702E20054 +02E00052033E0047016E007A0368007504FC007503450075034F004702F60047034F0047 +034F0047036D0075025C0023033D006E03C2006E04FC0075035600260337007902FB0014 +031500470353004702E90025016E007A02120075033D006E035600260337007902FB0014 +034B00790353004702E90025051400BA03CA007F02F900740556003202390091033E0047 +02F6004702F60047034F004702E20054025F001E01DDFFE9033E0047033D006E02170049 +01FF007A021600490217004901E0FF5601FF007A01E1005B0303007104FC007504FC0075 +0349FFE903D6007503450071034F0047034F004702E20046024BFFE9025C00230410005E +0359004702E400750340007A0356002602EE0037037E003702EE003703230037034F0047 +0000FC5B0000FC5B0000FC5B0000FC5B0000FC420000FC420579001004E7007B057D00C9 +051400B8057D00C9051400BA057D00C9051400BA0596007304660071062900C905140071 +062900C905140071062900C905140071062900C905140071062900C905140071050E00C9 +04EC0071050E00C904EC0071050E00C904EC0071050E00C904EC0071050E00C904EC0071 +049A00C902D1002F0633007305140071060400C9051200B8060400C9051200BA060400C9 +0512FFED0604001105120002060400C9051200BA025C00000239FFD3025C00070239FFF4 +053F00C904A200BA053F00C904A200BA053F00C904A200BA047500C9024D00C904750003 +024DFFFD047500C90239FFF4047500C90239FFDE06E700C907CB00BA06E700C907CB00BA +06E700C907CB00BA05FC00C9051200BA05FC00C9051200BA05FC00C9051200BA05FC00C9 +051200BA064C007304E50071064C007304E50071064C007304E50071064C007304E50071 +04D300C9051400BA04D300C9051400BA058F00C9034A00BA058F00C9034A00BA058F00C9 +034A00BA058F00C9034A005405140087042B006F05140087042B006F05140087042B006F +05140087042B006F05140087042B006F04E3FFFA0323003704E3FFFA0323003704E3FFFA +0323003704E3FFFA0323003705DB00B2051200AE05DB00B2051200AE05DB00B2051200AE +05DB00B2051200AE05DB00B2051200AE0579001004BC003D0579001004BC003D07E90044 +068B005607E90044068B005607E90044068B005607E90044068B005607E90044068B0056 +057B003D04BC003B057B003D04BC003B04E3FFFC04BC003D057B005C04330058057B005C +04330058057B005C04330058051200BA03230004068B005604BC003D04E7007B02D1002F +02D1000202D1002F062600B204E500710579001004E7007B0579001004E7007B05790010 +04E7007B0579001004E7007B0579001004E7007B0579001004E7007B0579001004E7007B +0579001004E7007B0579001004E7007B0579001004E7007B0579001004E7007B05790010 +04E7007B050E00C904EC0071050E00C904EC0071050E00C904EC0071050E00C904EC0071 +050E00C904EC0071050E00C904EC0071050E00C904EC0071050E00C904EC0071025C005A +02390044025C00C9023900BF064C007304E50071064C007304E50071064C007304E50071 +064C007304E50071064C007304E50071064C007304E50071064C007304E50071074E0067 +04E50076074E006704E50076074E006704E50076074E006704E50076074E006704E50076 +05DB00B2051200AE05DB00B2051200AE06DD00AD051200B006DD00AD051200B006DD00AD +051200B006DD00AD051200B006DD00AD051200B004E3FFFC04BC003D04E3FFFC04BC003D +04E3FFFC04BC003D04E3FFFC04BC003D062700C903D10020054600710546007105460071 +054600710546007105460071054600710546007105790010057900100705000507050006 +062700070669000405AA000705F100040453008504530085045300850453008504530085 +0453008505B0000705B0000707BA000507CC000607300007076C0004051200BA051200BA +051200BA051200BA051200BA051200BA051200BA051200BA06B2000706AF000708B00005 +08B600060837000708680004077800070793000402B5009B02B5009102B5FFB102B5FFBB +02B5000502B5FFD302B5FFCB02B5FFC6030A000702FE0007051400050514000604900007 +04CC000403EA000703F1000404E5007104E5007104E5007104E5007104E5007104E50071 +066F000706C9000708C2000508CC00060782000707C3000404A1009504A1009504A10095 +04A1009504A1009504A1009504A1009504A100950645000707FB000608190004072E0004 +06B3008706B3008706B3008706B3008706B3008706B3008706B3008706B30087066B0007 +06BF000708B7000508C300060791000707C70004075F0007079E00040546007105460071 +0453008504630085051200BA053C00BA02B5FF8D02B500A604E5007104E5007104A10095 +04A1009506B3008706B30087054600710546007105460071054600710546007105460071 +054600710546007105790010057900100705000507050006062700070669000405AA0007 +05F10004051200BA051200BA051200BA051200BA051200BA051200BA051200BA051200BA +06B2000706AF000708B0000508B600060837000708680004077800070793000406B30087 +06B3008706B3008706B3008706B3008706B3008706B3008706B30087066B000706BF0007 +08B7000508C300060791000707C70004075F0007079E0004054600710546007105460071 +05460071054600710546007105460071057900100579001005BBFFFC058A001005790010 +04000186040001B604000186040000B6040000B6051200BA051200BA053C00BA051200BA +051200BA0670FFFC05F8FFE70772FFFC06F8FFF3060400C904000089040000B4040000B6 +02B5FFEB02B5FFE302B5FFD802B5000502B5FFE402B5FFE6025CFFF5025C000303CDFFFC +0344FFED0400007E04000095040000B604A1009504A1009504A1009504A10095051400BA +051400BA04A1009504A1009504E3FFFC04E3FFFC06C3FFFC0699FFE1057B0007040000AA +040000D7040000AA06B3008706B3008706B3008706B3008706B300870787FFFC0680FFF2 +0761FFFC069BFFDB061D004E040001730400018604000000080000000400000008000000 +02A30000020000000156000005170000028B00000199000000CC00000000000000000000 +00000000000000000000000002E3006402E3006405170064040000640800006408000000 +040001040400FFEC028B00AE028B00B2028B00AE028B00B2042500AE042500AE042500AE +042500AE040000390400003904B8013304B8013302AD00EC055700EC080000EC028B00DC +00000000000000000000000000000000000000000000000000000000019900000ABC0071 +0DE2007101D1002802FD00280429002801D1002802FD00280429002802B6000B0333009E +033300C106B400C303E20093043F00930400FFEC066EFFA7066EFFA70200FFAA0800003D +040000DD0156FE89031F00B0031F00B00760004A05DD009305DD009303FA0064051700EC +040000D8040000D80400003D02B2011D066EFFA70400003D0399009108000064066EFFA7 +06B4013804B000FA054E002806B4016606B40166028B00DB0661006406B40070028B00DB +028B00DB01C7000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000003350057016E007A0335003F033500660335005C +0335006C033500590335005304390089043900890439008901F7006F01F7006703300075 +03350057033500890335005E033500620335003F033500660335005C0335006C03350059 +0335005304390089043900890439008901F7006F01F700670322004D03560047034F0047 +038D007503560047033C0075036800750154007A04FC007503300075036D007502FC0075 +025C0023070400560517007305170060051700850517008107CB00BA051700220A2E00C9 +089700C907E9003B0646005F051700710517000005170028051700140A2E00D00517002E +051700440517005D0517001006310075051700A5051700140517006A0517000A05170043 +0000FC130000FC3D0000FC3D0000FC3D0000FC130000FB500000FC3D0826004308260043 +0596007308FC00C30523FFD60826003C0889003C04EA00A40596006F079D00C307E80049 +0609000206CC00C9051200480512005A03C2003B0594006A05C30044034EFFE4068B0020 +066800C6085200360800011B0594006E059C00C8064C00730662004206830053065600C8 +072C00A9057900C9082801030898FFFC080001270579001005F5005C049F0058061D004E +061D004E04EE001802B50044053F00C905790010064A005C05A0008206D6007D04BC0057 +04D800A2064A0054049A00C9088E003903B2006805F600670564FFFB03BA001B05280056 +030A00460768005A098D0097059E002405D20000053C00C806CA00C9067C0019063300A3 +047500080475000704E3FFFC068D005705AA005A04EC005B02CF004F02CFFF16063D003B +0436005107C1008907C100890AF6008907C1008907C1005E07C1008907C1005E07C10062 +07C1003F07C1008907C1006607C1008907C1006207C1006607C1006C048B0089025C00C9 +03F000C9058400C9076200C9057900100761001008F500100A890010075700C9057B003D +0777003D090D003D047500C905960073062900C906E700C9023900C103A900C1051900C1 +067E00C104BC003D067D003D07ED003D095D003D068D00C104BC003B0694003B0804003B +023900C1046600710514007107CB00BA09F60079062900C909F6007905A000730465007F +0596007307C1005706B4006406B401A306B4007506B401A306B4006406B401A306B40120 +06B4012006B4012006B4012006B4006406B4007506B4002C06B4001606B4006406B401A5 +06B4007506B401A506B4006406B4007506B4006406B401A506B4007506B401A506B401A5 +06B4006406B4007606B4006406B4007606B4006406B4006406B4012A06B4015A06B401AC +06B4015A06B401AC06B401DD06B4006406B4002D06B4004F06B400DE06B4007006B400D3 +06B4009D06B4006406B4006406B4030506B401A306B4007506B4007506B4030506B401A3 +06B4006406B4007706B4006406B4006406B4007806B4007606B4007806B4006406B40064 +06B4006406B4006406B4007506B4006406B401A506B4007506B401A506B4006406B401A4 +06B4012006B400BC06B400BC06B4012006B4006406B4007506B4006406B4007506B401A3 +06B401A306B4006406B401A306B4007506B401A306B4006B06B4007506B4003706B4015E +06B4004806B4015E06B4015E06B4015E06B4014006B4015E06B4015E06B4015E06B40075 +06B4007A06B4007A06B4015E06B4007506B4007706B4007506B4006406B4007506B40064 +06B4006406B4007506B4006406B4003706B4007506B4003705790010051700870423005F +050E00C9050E00C906F8009B055AFFFA055AFFFA06F800AF06F800AF05BE00D906F800AF +06F800AF05BE00D90517012C060E009C060E009C0564001906B400D906B400D906B400D9 +02B200000518018A06B4010505020144050201580519003D0519003D0519003D05B700DC +06AA00DC06B4011B072C00AF072C00AF06B400ED040001B0040000660400011004000066 +05DB010805DB010805DB010805DB0108042B00750650007508750075042B007506500075 +08750075042B0075042B0075042B00750517007905170079021500A10517007906B400D9 +06B400D906B400D806B400D906B400D906B400D906B400A206B400D9030000D006B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D806B400D906B400D906B400D906B400D906B600D906B600D9 +080000CF080000CD06B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400DA06B400DA06B400DA06B400DA086000940860009403B600B006B400D806B400D9 +06B400D906B400D906B400D906B400D906B400DA06B400D906B400D906B400D006B400D0 +06B400D006B400D006B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400CC06B400CC06B400CC06B400CC06B400BE06B400D906B400BE06B400D9 +06B400BE06B400BE05DB010805DB010805DB010806B400D906B400D906B400D906B400D9 +063E00D9063E00D906B400BB06B400BB06B400BB06B400BB06B400BB06B400BB06B400BB +06B400BB06B400BB06B400BB06B400BB06B400BB06B400BB06F800AF06F800AF06F800AF +06F800AF042A00AF042A00AF06F800AF06F800AF06F800AF06F800AF06F800AF06F800AF +06F800AF06F800AF06B400D906B400D906B400D906B400D906B400D906B400D908000079 +0800007906B4006206B40079042A00EE05DB00C805DB00C805DB00C806B4011B06B4011B +0690FFFA0690FFFA0690008C0690008C05020082028B00DB050200F906B400D9080000D9 +080000D9080000D9080000D9080000D906B400D905DC006305DC006306B400BE06B400D9 +06B400D206B400D206B4017C06B400D906B400D906B400D90B6100940B61009406B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400E106B400D906B400D906B400D9 +06B400D508000396080000EC080000EC080000EC0800005806F800AF05BE00D906F800AF +06F800AF05BE00D906F800AF06F800AF0800005806F800AF05BE00D906F800AF05BE00D9 +06F800D904D1004A04D100720514009206B401A306B401A306B401A306B401A303E8013B +031F00B0031F00C7031F00B0031F00C7067802F406780064067802F40678006406B400D9 +041B0006080000F706B400D903C000B003C0008603C000B003C00086042B01AF042B002A +0938009C0938009C0B50009C0938009C0B8C00780B50000106FC009602B500A6051400BA +06B3008705460071060F001A0938009C06FC0023040000B0040000B0040000B0040000B0 +0400028D040000B0040000B0040000B0040000B0040000B00400028D040000B0060002A3 +060000A8060002A3060002A3060000A8060002A3060000A8042B01AF06B40037078F00BA +06FC009606270006051700590514FF8305140092072C0098072C0098072C0098072C0098 +072C0098072C0098072C0098072C0098072C0098072C009804D1FFEC04D1FFEC04D10218 +04D101C804D1003C04D1003C04D1021804D101C804D1003C04D1003C04D1021804D101C8 +04D1021804D1021804D101C804D101C804D1FFEC04D1FFEC04D1FFEC04D1FFEC04D10218 +04D1021804D101C804D101C804D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1021804D10218 +04D101C804D101C804D101C804D101C804D101C804D101C804D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1003C04D1003C04D1021804D101C804D1FFEC04D1017804D1021804D10178 +04D1017804D1FFEC04D1FFEC04D1FFEC04D1021804D1017804D1017804D1FFEC04D1FFEC +04D1FFEC04D1021804D1017804D1017804D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC +04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1FFEC04D1021804D1FFEC +04D1FFEC04D1021804D1FFA704D1FFA704D1FFA704D1FFEC04D1021804D1026804D10218 +04D1FFEC04D101C804D1026804D101C804D1FFEC04D101C804D1FFEC04D101C80627FFEC +0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC +0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC0627FFEC062703130627FFEC0627FFEC +0627FFEC0627FFEC062705710627FFEC062703130627FFEC0627FFEC0627FFEC0627FFEC +0627FFEC062703130627FFEC0627FFEC078F00BA078F00BA078F00BA078F00BA078F00BA +078F00BA078F00BA078F00BA078F00BA078F00BA056C00BA056C00BA078F00BA078F00BA +046700BA046700BA06270006062700060627000606270006040400060404000606270006 +062700060404000604040006062700060627000606270006062700060404000604040006 +062700060627000604040006040400060627000606270006062700060627000606270006 +06FB007003F4000606FB007006FB007206FB007006FB007006FB007006FB007006FB0070 +06FB007006FB007006FB007006FB007004370070043700BA065400BA07C300BA07C300BA +07C300BA0319000603190006031900060319000606FB007006FB00700627000606270006 +062700060627000604B80133078F00BA078F00BA078F00BA078F00BA078F00BA06270006 +062700060627000608F40070078F00BA078F00BA078F00BA078F00BA06FB007006FB0070 +06FB007006FB007006270006062700060627000606A400BA06A400BA05DC00BA05DC00BA +06270006072C00AB08000068072C0064072C00AA072C0083072C0085072C0085049500AA +072B00AA072C00AA071B007D071B007D055F007D081A007D09F7008C0A010091072C00B8 +072C00B7072C00B70442009A072C0064072C0098072C00AC072C00AC072C009F072C00AB +072C00AC072C00AC072C00B204DF0093072C00B104DF0093072C007D072C00AC072C00AA +072C0064055A006405F800AA053200AA064500AA045C00AA072C00AA072C00B2072C00AA +05AF00AA072C00AB072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C0087085700AA085700AA085700AA072C00AA072C02DD +072C00AA04E900AF05DC00AF05DC00AF072C00A2072C0153072C01C0072C00F8072C0104 +072C01EC072C005D072C00B7072C00C0072C00E7072C011E072C006D072C00AB072C0045 +072C00A9072C00C0072C00B0072C0141072C00C9072C00E2072C0155072C01B6072C0151 +072C0130072C00C9072C00E2072C0155072C01B6072C014C072C0130072C0143072C00B9 +072C0158072C00E4072C0142072C00B6072C0158072C00E4072C00D803C600AC051B00AC +072C0178072C00BC03C600B502DC00AC03DF00AD05FC00AC06200084072C00AC072C009C +072C009C072C009C072C009C072C009C072C009C072C009C072C009C072C00AC072C00AA +072C00AA072C00AA072C013106F4009606F4009606F4009606F4009606F4009606F40096 +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C0158072C0158072C006A072C00C6072C010D0454007D072C0079072C007D +072C0129072C00C2072C0106072C0105072C0105072C00ED072C00ED072C0064059E00AA +080900AF08B700A2096600AF073900AF06B4010D06B400CF06B4018006B4000906B4017F +06B4017F06B401FA06B4016406B4005406B4000A06C000D206B401B105DC00AF05DC0159 +05DC00B005DC00B006CC007805DC018E05DC014406B4005606B4005606B4005606B40056 +05DC00AF06B4001606B4005606B4001606B4004906B4005606B4005606B4007806B40084 +06B401B306B4002B06B400B606B4003506B400B606B4005806B4008A06B4013306B400ED +06B4010306B400AF06B400F106B400FC06B4007006B4007006B4007006B4007006B40152 +06B4010C06B4013E06B4007006B400BB06B4005406B4005606B4005306B4005406B40057 +06B4005706B4002F06B4005606B4003006B4002F06B4003006B4003006B4003006B40032 +06B4008406B4009806B4007006B4004006B4005406B400BB06B4005406B4005406B40054 +06B4007006B400A706B400A706B400A106B400A106B4006E06B4006E06B4005406B40056 +06B400A106B400B606B4009C06B4008206B400A106B4006106B4006106B4005406B40054 +072C0066072C007A072C007A072C007A072C007A072C00AA06B4030506B402B006B40206 +029400AE02940078044E00AE044E007806B4013E06B4019E06B4014E06B4006E06B40158 +06B4007E06B400A006B4019106B4019106B4021D06B4021D06B401B906B401DB06B40123 +06B4013906B4015506B4017706B402C406B4026C06B4016606B401A1072C0098072C0098 +072C0098072C0098072C0098072C0098072C0098072C0098072C0098072C009806B40009 +06B4000906B4000906B4000906B4000906B4000906B4000906B4000906B4000906B40009 +06B4000906B4000906B4000906B4000906B4000906B4000906B4000906B4000906B40009 +06B4000906B4007506B400FC06B4007506B400FC06B4007506B4007506B4007506B40075 +06B4007506B4007506B4007506B400E406B400E406B400E406B4007506B4007506B401EC +06B4007506B4007506B4007506B4002B06B4002B06B4011506B4011506B4007F06B4007F +06B4013C06B4008206B400A506B4007506B400A706B400A706B4007506B400A706B40073 +06B4009606B400A206B400A206B40075031F006E031F004F03F4000603F600B003F600AF +031F00B7031F00A4047300B7047300A406B4005B06B4005906B4004F06B4004F09420075 +0B7800640B7800750B7800640B7800640B7800750B7800640B7800640B7800750B780064 +0B7800750B78007505DC000005DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC038405DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC038405DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC0384 +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC038405DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC038405DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC038405DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC038405DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC038405DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC038405DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC038405DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC038405DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC038405DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC0384 +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC038405DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC038405DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C +05DC012C05DC012C05DC012C05DC012C05DC012C05DC012C06B4006406B4007506B40100 +06B401000577004F0577004F05DF010005DF010006B400D9080000D9080000D9080000D9 +080000D9080000D9080000D9080000D903F4000606B400D906B400D90800003A0800003A +0800003A0A9A0075042B0075042B0075042B0075042B0075042B0075042B0075042B0075 +042B0075042B0075042B0075042BFFBE042B0075042B0075042B0075042B0075042B006B +06B4011906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400DA06B400DA06B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D906B400D9 +06B400B506B4012006B400B506B4012006B4003706B4003706B4015E06B4015E06B400B5 +06B4012006B400B506B4012006B4003706B4015E06B0007506B0007506B0004806B00048 +078F00BA078F00BA078F00BA078F00BA06270006062700060627000606270006078F00BA +06F4002406F4002406FC009606FC009606FC002208F4007006F4004D06F4004D0475000A +0239000A0475FFD704D3000A058F00C904E700480323FFE8060400C9051200BA053F00C9 +04A200BA057B005C043300580640007306E700C9057900100640007305E0003D09060044 +07B1005604BC0068053C00C9048B00C1054700700350000004E5007103ED00620166FFE9 +0372000A05140087057B005C04BA007B04C200320484005004D0007F04B3006907490050 +05030000079D006C04C3008E04DB006807A1005004F5006D04C300500769006804C20068 +0673008E0773006804AD006804BD006607630068079F007B069F006404C4005004C20068 +04B8006804BC005004BC005604F7007A075D005004B7003C04B1006004A6004607500050 +04C4006404C2007A04BD007C052200680735007A052C0071071A0073071A007305750040 +0578004305150040047E00960579001005790010050E00C9050E00830576009606FF00DB +057B005C03ED0096057B005C071A0073071A00730267009605040029060400C9053F003E +04380096057B005C05280096050E00C90405006F079F006F063A006F05FC00C904F700CC +025C00C9063A006F025C00C90604009F05100063071A0073071A0073060400C9029000C9 +05FE0096071A0073071A00730596007306240064057B003D0596007304FA00C905790010 +060400C9050E00C9064E0073048900C9064E0073041F0036043F008F06B400D9031F00B0 +031F00C7031F00B0031F00C7043F0093072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA057D00C904D300C904D30046062900C9 +04E3FFFA04E3FFFA06330073053F00C9053FFFD5041800000596007305A00073057B005C +049A00C9049A00C906E700C905FC00C9047500C905140087058F00C9058F003B05790010 +05790010060400C9063300A3041800C907E90044057B003D04E3FFFC057D009105790010 +05790010050E00C9050E0083025C00C9064C007305DB00B205DB00B20475000806230073 +026600AF0266009204C500AF04C500AF02660092026600AF04B400AF04B400AF05140072 +042B006402D400C902B500A60970007608390098083B003C0740003E08A3007306BC0070 +07D000D306BE00C1087F0065074B006B088800540735004C096D00C9081000BA064C0073 +04E5007106D8007305B300710ADD0073082600710708003A0642FFFA057A003C04E3FFFA +04A9003C057C00AF051200BA03F200D603F200D603F200D603F200D603F200D603F200D6 +03F200D603F200D603F200D603F200D603F200D603F200D603F200D603F200D603F200D6 +02F4006602F40066020500C3020500C3020500C30315008902D9008903C6007303C60073 +060400C9051200BA0706FFFA05AD003704EA00A40453008503ED00BA042B006F09FF0010 +07E1007B09A0001007EB007B0923001007D9007B07C50010068B007B07C50010068B007B +07AC0010068B007B05A000730465007F053F000A04AA000E057100C9032300C104A70053 +036A00780674000A05A2000A0ADD00730826007104D3000A0514FFFB05DF003206320032 +064C00730514007104D7000A0514FFFB04D7000A0514FFFB0475000B023900C105E200C9 +051200BA02B200F0030200A003350135023300C5057C00AF03E6004D062E00C9055500BA +0633000405140004053F000404A2000405FC000405120004058F0004034A000405140004 +042B00040668FF97049D007F05270047075200BA049A007704D3004606E700C9025C00C9 +0998004401B500C201E7008D020E006E021C00600223005A01E7008D01B500C201E7008D +020E006E021C0060020E006E01E7008D01B500C201E7008D020E006E021C0060020E006E +01E7008D01B500C201E7008D0223005A021C0060020E006E01E7008D01B500C2023300D6 +07D0009607D0009607D0009607D0009604A4006E04A4006E04FD006E071D006E04AE006F +04A4006E0539006E070E006E0471006E04A4006E0959006E04B6006E04B8008206F4006E +04A4006E04B7006E074F006E04B8008205D9006E04AA003206FA006E04B7006E0729006E +04B6006E04B6006E04B900830531007004B6006E04B7008304CA006E04B8008204210032 +04A3006E04AC007804A4006E04A4006E04A5006E051B006E07A4006E0773007A06770064 +041000640410006404100064041000640410006404100064041000640410006404100064 +04100064042400640424006404240064064B0064064B0064064B0064064B0064064B0064 +045F003C045F003C045F003C045F003C045F003C045F003C04E500710583002F050A002F +050A002F07BC002F07BC002F057D002F06E3006F099D00AB099D00AE099100AE097D00AE +0C3C00AE01CA00880000015602A5004A0516004E06D900AE06310058073F00BA062C0058 +06BF005806D700BA067500580701006006B400D905AB005805AB004E05AB005805AB0058 +055900BA055900BA055900BA04A00058034C0058045E0058053A00BA02D80059033F0059 +053000B902A40059044C0058043B0058048C0058056F005803320058053100B9051E00BA +04FF00BA04BF005805AD00BA0484005805AB005805420014022E00BA04A00058043B0058 +04FF00BA050800580788008207DB0082023AFFEC026AFFEC0788008207DB0082023AFFEC +026AFFEC0788008207DB0082023AFFEC026AFFEC0788008207DB0082023AFFEC026AFFEC +0788008207DB0082023AFFEC026AFFEC0788008207DB0082023AFFEC026AFFEC084B0082 +0848008203D3FFEC040CFFEC084B00820848008203D3FFEC040CFFEC052A009D052A009D +04F2FFEC052AFFEC052A009D052A009D04F2FFEC052AFFEC052A009D052A009D04F2FFEC +052AFFEC052A009D052A009D04F2FFEC052AFFEC0390007D0433007D0390007D0433007D +0390007D0433007D0390007D0433007D03DDFFAB046AFFAB03DDFFAB046AFFAB07290082 +0729008203CFFFEC046BFFEC072900820729008203CFFFEC046BFFEC0729008207290082 +03CFFFEC046BFFEC072900820729008203CFFFEC046BFFEC05E000930617009305E00093 +06170093023AFFEC026AFFEC05960090050E00900438FFEC03B0FFEC0698009006BE0090 +03CFFFEC046BFFEC03DDFFAB0422FFAB03DDFFAB0422FFAB03DDFFAB0422FFAB03DDFFAB +0422FFAB0643008206AB0082023AFFEC026AFFEC023AFFEC026AFFEC0643008206AB0082 +023AFFEC026AFFEC00000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000 +0000FC70000000000000FD2A00000000025800080258FFEC025800080218006902580008 +025800080258FFEC025800080258FFEC025800080258FFEC0258FFF40258FFEC02580018 +0258FFEC03C300A30239FFB50270FFB50239006C0270006C03DDFFAB0422FFAB0239006C +0270006C0643008206AB0082023AFFEC026AFFEC023900C1027000C10788008207DB0082 +023AFFEC026AFFEC0431008B044A00910788008207DB0082023AFFEC026AFFEC07880082 +07DB0082023AFFEC026AFFEC052A009D052A009D04F2FFEC052AFFEC052A009D052A009D +04F2FFEC052AFFEC052A009D052A009D04F2FFEC052AFFEC0390007D0433007D0390007D +0433007D03DDFFAB046AFFAB03DDFFAB046AFFAB09C400820A33008206B4FFEC0723FFEC +09C400820A33008206B4FFEC0723FFEC09AC008209CD008206CBFFEC06F0FFEC09AC0082 +09CD008206CBFFEC06F0FFEC0766009007980090065EFFEC0690FFEC0766009007980090 +065EFFEC0690FFEC04C600750442007504C6FFEC03DCFFEC04C6007504420075042FFFEC +03DCFFEC084B00820848008203D3FFEC040CFFEC0635006B06AC006B03D3FFEC040CFFEC +0698009006BE009003CFFFEC046BFFEC05D00090060F00900270FFEC02A6FFEC04F4008C +0553008C0449FFEC04A0FFEC05E0009306170093023AFFEC026AFFEC0431008B044A0091 +0438FFEC03B0FFEC03DDFFAB0422FFAB0643008206AB00820643008206AB0082023AFFEC +026AFFEC0490FF2E04C6FF2E0490FFE504C6FFE50490001704C600170490005404C60054 +00000000000000000000000000000000000000000834001E060E006404DC00C804800064 +04CE00C8046400C8046400C803840096050000C8073A0096024400C8051900C8045F00C8 +0B6A00C8070D00C8076400C8073A0096057D00C806F200C804B0009604DA00C803880096 +05920064051800C8057D009605780096067A006404380096047600C8047600C803840064 +060E0064024400C8060E0064057D0096068B0064072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA072C00AA05ED001005D900C9068D00E805D600C9056300C90633007303240066 +0322FF96060500C9053D00C9083300C4064C00720514008805AAFFFA06A400B105C80010 +08DD00440672006C05A8FFFC054B007B05AA00940466007105AA009404EC006F03B9002F +05AA007105E500BA02CF00E602CFFFD7053500BA02CF00E6092200BA05E500BA04E50071 +05AA007105AA007103DF00BA042B006F03B9003705EA00B104DC003D074800560568004C +04FF003D04B8005805790010057D00C905960073062900C9050E00C9049A00C906330073 +060400C9038C0097025CFF96053F00C9047500C906E700C905FC00C9064C007304D300C9 +064C0073058F00C90514008704E3FFFA05DB00B20579001007E90044057B003D04E3FFFC +057B005C04E7007B051400BA046600710514007104EC007102D1002F05140071051200BA +023900C10239FFDB04A200BA023900C107CB00BA051200BA04E50071051400BA05140071 +034A00BA042B006F03230037051200AE04BC003D068B005604BC003B04BC003D04330058 +051700880517006B051700820517009C0517005A0517009405170071051700410517008B +0514006A05170087051700E1051700960517009C051700640517009E0517008F051700A8 +0517008B05170081023900C107880082052A009D0390007D03DDFFAB03DDFFAB052A009D +07660090064300820698009005D0009004F4008C05E0009309C4008204C60075084B0082 +09AC00820635006B03DDFFAB09C400820788008207880082052A009D0390007D09AC0082 +0766009004C600750788008205E00093084B00820635006B0334FFEC0556FFEC04CEFFEC +0556FFEC0334FFEC04C9FFEC036AFFEC04DFFFEC0334FFEC0718FFEC04C6FFEC03D3FFEC +072FFFEC03D3FFEC0718FFEC0334FFEC0334FFEC0556FFEC072FFFEC042FFFEC054000C1 +076200C106A800C1076200C108CD00C1054000C1063F00C106B900C1054000C1092400C1 +073600C1064300C1093B00C1064300C1092400C1054000C1054000C1076200C1093B00C1 +08CD00C1073600C1054000C1064300C10AE900C80AE900C80AE900C80AE900C80AE900C8 +0AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C8 +0AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C8 +0AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C8 +0AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C8 +0AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C80AE900C8 +068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8 +068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8 +068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8 +068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8 +068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8068500C8 +068500C8068500C8068500C8068500C8068500C8082F00C8082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8084000D9082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8 +082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8082F00C8 +082F00C8085800990978004B0857003D094000AA085700AA085700AA0959005F085700AA +085700AA085700AA085700AA0857008F085700AA085700AA085700AA085700AA085700AA +085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA +085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA +085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA085700AA +085700AA085700AA085700AA0959005F085700AA085700AA085700AA08570023085700AA +085700AA0CD500AA085700AA085700AA085700AA0857003A095900600857003A0857003D +0857003D0857003D0857003D0857003D0857003D0000FFB90000FCD70000FD730000FCB6 +0000FD0C0000FCCF0000FCCF0000FCC70000FCC70000FD9A0000FCE60000FC4E00960000 +019000000190000001900000009600000190000007DB0082023AFFEC026AFFEC08480082 +03D3FFEC040CFFEC06AC006B03D3FFEC040CFFEC023AFFEC026AFFEC01B6000007DB0082 +023AFFEC026AFFEC07DB0082023AFFEC026AFFEC052A009D04F2FFEC052AFFEC052A009D +04F2FFEC052AFFEC052A009D04F2FFEC052AFFEC052A009D04F2FFEC052AFFEC035F0000 +05FC00D50239000F02EEFEF2057801920578019205780192057801930578019305780192 +05780192057801760578018B057801760578018B057801760578018B0578018B05780176 +057801760578018305780183057801830578018B0000FC9A012C00000418000004E2FFAB +04E2FFAB060F00900270FFEC02A6FFEC06AB0082023AFFEC026AFFEC046AFFAB023900C1 +044A00910596007104E200710239009602CF004F02CFFF160239FFD3023900BF02B200F0 +038C009706B401AD000000C8000000C8000000C8000000C8000000C8000000C8000000DC +000000DC023A00C1023AFFEC023AFFEC049200710492FFEC0492FFEC0364003D0364FFEC +0364FFEC04BC003D04BCFFEC04BCFFEC053B00BA053BFFEC053BFFEC053B00BA053BFFEC +053BFFEC04C0007104C0FFEC04C0FFEC053B00BA053BFFEC053BFFEC06A1007106A1FFEC +06A1FFEC038100C10381FFEC0381FFEC0381003D0381FFEC0381FFEC047800BA0478FFEC +0478FFEC04E500C104E5FFEC04E5FFEC02CD003D02CDFFEC02CDFFEC07AC00C107ACFFEC +07ACFFEC03C8003D03C8FFEC03C8FFEC0644003D0644FFEC0644FFEC053B00BA053BFFEC +053BFFEC0500003D0500FFEC0500FFEC05DF00C105DFFFEC05DFFFEC043D00C1043DFFEC +043DFFEC05CB007005CBFFEC05CBFFEC03C8003D03C8FFEC03C8FFEC0500003D0500FFEC +0500FFEC04C0007104C0FFEC04C0FFEC043D00C1043DFFEC043DFFEC043D00C1043DFFEC +043DFFEC042E00C1042EFFEC042EFFEC04C0007104C0FFEC04C0FFEC04C0007104C0FFEC +04C0FFEC0000FCEC072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA072C00AA +072C00AA06B30197062E00C9056A00C1051200AE0433007D0433007D0433007D0433007D +0433007D046AFFAB046AFFAB046AFFAB046AFFAB046AFFAB0A33008206B4FFEC0723FFEC +0A33008206B4FFEC0723FFEC0A33008206B4FFEC0723FFEC09CD008206CBFFEC06F0FFEC +09CD008206CBFFEC06F0FFEC07980090065EFFEC0690FFEC0442007504C6FFEC03DCFFEC +0848008203D3FFEC040CFFEC0848008203D3FFEC040CFFEC0848008203D3FFEC040CFFEC +06AC006B03D3FFEC040CFFEC06AC006B03D3FFEC040CFFEC086E008206F2FFEC06F2FFEC +0729008203CFFFEC046BFFEC06BE009003CFFFEC046BFFEC06BE009003CFFFEC046BFFEC +0729008203CFFFEC046BFFEC0729008203CFFFEC046BFFEC0729008203CFFFEC046BFFEC +060F00900270FFEC02A6FFEC060F00900270FFEC02A6FFEC060F00900270FFEC02A6FFEC +06170093023AFFEC026AFFEC06170093023AFFEC026AFFEC06170093023AFFEC026AFFEC +03350135043F008F043F008F0334FFEC089000DD089000DD079F002305E400A508ED00A5 +0BF500A505E400A508ED00A50BF500A505E400A505E400A505E400A50947FFF80947FFF8 +094700C6094700C60B5000520B5000520B5000520EFE00A505E400A500A500A500A500A5 +00A500A500A500A500A5FFA300A500A500A500A50097000000><00000000000000440000 +00440000004400000044000000A800000114000001EC000003440000046C000006D40000 +072800000798000007F00000088C000008E000000918000009440000096C000009B80000 +0A3C00000AAC00000BAC00000C9400000D5000000E1000000EE800000F74000010480000 +111C0000115C000011AC0000122400001268000012E0000013B800001538000016340000 +16E40000177C000017FC0000185C000018B000001958000019B4000019FC00001A6C0000 +1B9400001BD800001CD400001D7C00001E0800001E8800001F5400002068000021600000 +21D00000225400002334000024F000002594000026580000271800002778000027C40000 +28180000285C00002888000028D800002A0400002A9C00002B3400002BCC00002CA00000 +2D3800002E0000002E7800002EC800002F44000030340000307000003134000031AC0000 +3250000032F0000033900000340000003560000035DC0000366000003784000039A80000 +3B2C00003CF800003DC400003EA000003ED000003FBC0000404000004040000040D40000 +41A0000042580000437000004494000044DC00004624000046DC00004800000048DC0000 +49A8000049E000004A0C00004B5800004BA400004C1C00004C8C00004D2C00004DE80000 +4E3800004EEC00004F4800004F7400004FD40000502C000050B400005184000051A40000 +51C4000051E4000052DC000052F40000530C000053380000536800005398000054D80000 +55B0000055C8000055E0000055F80000561C000056400000565800005670000056940000 +56B800005784000057B4000057CC000057E40000581000005844000058740000593C0000 +5A7400005A8C00005AA400005AD400005B0C00005B2400005BAC00005CD400005CF80000 +5D1C00005D3C00005D6800005DA000005DE000005F9C00005FB400005FCC00005FE40000 +60080000602C000060440000605C00006080000060A4000062540000626C000062840000 +629C000062BC000062E80000631400006370000064E4000064FC00006514000065340000 +65640000657C0000661C0000664C00006678000066A0000066CC000066EC000067040000 +671C000067340000674C0000677000006788000067A0000067B8000067DC000067F40000 +680C000068340000684400006900000069180000693C00006960000069840000699C0000 +69B4000069CC000069E400006A0800006A3400006A5800006A7C00006A9400006AAC0000 +6AD000006AE800006B0000006B1800006B4800006B8C00006C1400006CA800006CCC0000 +6CF000006D1400006D3800006D5C00006D8000006D9800006DB000006DE000006E2C0000 +6E5000006E7400006E9800006EBC00006ED400006EEC00006FDC00006FF40000702C0000 +70440000706800007080000070A4000070BC000070F00000716C000071FC000072200000 +72440000725C00007274000072A4000072D0000072E8000073800000741C000074480000 +746800007494000074B4000074CC000074E400007584000076AC000076C4000076DC0000 +76F40000770C00007730000077580000777000007788000077B0000077D4000077EC0000 +78040000782C000078540000786C00007884000078B0000078D400007958000079F40000 +7A2000007A4400007A7400007A9800007AC400007AE800007B0000007B2400007B3C0000 +7B5400007B6C00007B8400007BA800007BCC00007BF000007C1000007C3400007C4C0000 +7C6400007C7C00007CA400007CBC00007CE400007D3C00007E0800007ED000007EE00000 +7F9400008020000080C80000815800008218000082D4000082E400008378000083F40000 +849C00008574000085D00000868000008738000087B8000088700000893C00008A500000 +8B2000008B7C00008BEC00008CA800008DBC00008E1C00008F0400008FC4000090940000 +90A400009148000091600000917800009220000092CC0000936400009424000094E00000 +95B4000096FC0000970C000097A800009840000098D00000996C000099F800009A100000 +9A2800009AD400009B6000009C1C00009DEC00009EF40000A0000000A0E00000A1B00000 +A2C00000A32C0000A3A00000A4340000A4C00000A5340000A5C80000A6080000A6200000 +A6A00000A6B00000A6C80000A6E00000A6F80000A7100000A7280000A7400000A7580000 +A7700000A7880000A7A80000A7C80000A7F00000A8180000A8300000A8500000A8700000 +A8940000A8AC0000A8C40000A8DC0000A8F40000A90C0000A9240000A93C0000A9540000 +A9640000A97C0000A9940000A9AC0000A9C40000A9DC0000A9F40000AAC80000ABBC0000 +ABE80000AC000000AC180000AC5C0000AC740000AC8C0000ACA40000ACBC0000ACD40000 +ACEC0000AD140000AD2C0000AD440000AD5C0000AD740000AD8C0000AE240000AEBC0000 +AED40000AEEC0000AF040000AF1C0000AF340000AF4C0000AF640000AF7C0000AF940000 +AFAC0000AFC40000AFDC0000AFF40000B00C0000B0240000B03C0000B0540000B06C0000 +B0840000B09C0000B0B40000B0CC0000B0E40000B0FC0000B1140000B12C0000B1440000 +B15C0000B1740000B18C0000B1A40000B1BC0000B1D40000B1EC0000B2040000B21C0000 +B2A80000B33C0000B3540000B3980000B40C0000B4B00000B5BC0000B6A00000B78C0000 +B8840000B89C0000B8B40000B8CC0000B8E40000B8FC0000B9140000B92C0000B9440000 +B95C0000B9740000B98C0000B9A40000B9BC0000B9D40000BA280000BAAC0000BB140000 +BB780000BC580000BD3C0000BDAC0000BE440000BED40000BF400000BF940000C04C0000 +C0A40000C1280000C1B00000C28C0000C34C0000C35C0000C3C40000C4700000C50C0000 +C5A80000C6680000C7240000C84C0000C8D80000C9280000C9880000CAAC0000CB440000 +CBDC0000CC940000CD1C0000CDFC0000CEBC0000CF6C0000D0500000D0F40000D2140000 +D2240000D2340000D3500000D41C0000D4A40000D5880000D6400000D6DC0000D7D00000 +D8C40000D9400000D9D80000DA840000DAF80000DB080000DB5C0000DC080000DC900000 +DCE80000DE100000DEE00000DFB40000E0A40000E14C0000E1E00000E2880000E31C0000 +E3C80000E4780000E4880000E4F00000E5580000E5E40000E6580000E6E40000E7400000 +E79C0000E8B00000E9540000EA600000EAD40000EB800000EBE00000EC740000ECE80000 +ED680000EE0C0000EEC40000EF540000EFE40000F20C0000F2980000F3000000F3F00000 +F5100000F6240000F6B40000F7480000F7DC0000F8740000F9100000F99C0000F9AC0000 +FA7C0000FB3C0000FB4C0000FBDC0000FC140000FC500000FD080000FDC40000FE800000 +FF100000FFD80001009400010138000101C40001029400010328000103A00001046C0001 +04D000010540000105C400010660000106D400010764000107C4000108240001087C0001 +08F8000109640001099C000109E0000109F000010A0800010A1800010A2800010A600001 +0AC800010B3000010BB800010C4400010C6C00010C9400010CF800010D6C00010DC80001 +0E2400010E5400010E6400010E8000010EA000010ED000010EE400010F0400010F200001 +0F8C00010FDC00010FF000011004000110280001104C0001107800011094000111200001 +1188000111F4000112540001131400011380000113B8000113EC00011438000114600001 +14F8000115300001154000011574000115C0000116000001164C00011680000116940001 +16AC000116BC000116D00001179C000117F800011850000118B8000118CC000118E00001 +18F4000119080001194C000119C000011A3400011A4800011A5C00011AC400011AF00001 +1B0C00011B7C00011B9800011BE400011C0C00011C3400011C5800011C7400011C880001 +1C9C00011CC000011CE000011D0000011D7400011DC400011DE800011E0C00011E380001 +1E5400011E8400011EB400011ECC00011EE000011F4C00011F7000011F8400011F980001 +1FC000011FE40001202C00012068000120A4000120B8000120EC00012100000121140001 +21280001213C00012198000121B4000121D0000121F0000122100001225C000122800001 +22A8000122F00001232400012384000123A0000123B4000123C8000123DC000123EC0001 +240000012414000124380001246000012488000124A80001252400012598000125B00001 +25E8000126100001261000012650000126680001267C000126BC000126D0000127340001 +27480001275C0001277800012794000128080001281C0001284400012854000128640001 +2894000128C4000128F40001292400012934000129440001297800012988000129A00001 +29B8000129C800012A1800012A4800012A6000012A7000012A8C00012AA800012AC40001 +2ADC00012AF800012B1000012B2800012B3800012B4800012B8000012BEC00012BFC0001 +2C0C00012C1C00012CCC00012CDC00012CEC00012D5000012D6000012D7000012DD00001 +2DE000012DF000012E0000012E7C00012E8C00012E9C00012F5C00012F6C000130000001 +30B0000130D4000130F80001311000013128000131400001315800013170000132C00001 +335C000133DC000134C00001359000013634000136B000013778000137C8000138480001 +38CC000138DC0001396C00013A2800013A3800013AB400013B4800013BF400013C880001 +3CE800013D7400013E3400013F4000013FD4000140680001408000014098000140B00001 +40C8000140E000014120000141F000014298000143500001436C000143840001444C0001 +44FC000145A8000146600001470C000147B00001484C0001485C0001490C000149680001 +49D800014A5000014AAC00014BC400014C9C00014D3000014DBC00014E7000014F580001 +4FD000015040000150F4000151A400015238000152D000015340000153AC000154480001 +5500000155100001552000015530000155A00001560C0001561C0001562C0001563C0001 +570C000157980001581800015828000158400001585800015870000159100001599C0001 +59B400015A6000015A7000015A8000015A9000015AA000015B3800015BC800015C380001 +5C5000015C6800015C8000015CD800015CE800015D6800015D7800015DB000015E3C0001 +5E4C00015F4000015FF8000160A4000160E400016178000161D8000161E8000161F80001 +62080001625000016260000162700001628000016304000163B8000163C8000164200001 +647C000164D800016544000165C4000165DC00016650000166FC000167BC000168580001 +686800016930000169CC00016A0400016A8C00016A9C00016B8C00016C5000016CC00001 +6D0000016D9000016DF000016E7800016ED000016EE000016F2800016F3800016F480001 +6F8C00016F9C0001708400017094000170EC00017168000171C400017230000172AC0001 +72C400017334000173D80001747C0001750C00017524000175C400017668000176800001 +7724000177340001774400017754000177640001780000017894000179240001793C0001 +79540001796C000179C400017A9C00017AAC00017B4C00017BE000017C7000017CE00001 +7D2400017D6400017DC000017E1800017F0000017FE400018060000180D8000181C40001 +82A000018304000183680001837800018388000183D4000184240001843C000184540001 +84F80001857C000186740001876000018780000187A0000187B8000187D00001886C0001 +890400018950000189E800018A1C00018A4400018A6C00018AAC00018BF000018CAC0001 +8CC400018CDC00018D7800018E1800018E7800018F0000018F4000018F8400018FE40001 +9044000190D000019160000191C4000192240001923C0001925400019298000192DC0001 +932800019370000193BC00019408000194A800019548000195B800019624000196880001 +96E800019780000198180001990C000199E800019A0000019A1800019A7400019ACC0001 +9ADC00019B7800019BC000019C0400019C4C00019C9400019D0400019D7400019DE80001 +9E7400019EC400019F1000019F7000019F800001A05C0001A1580001A1F40001A28C0001 +A29C0001A2B40001A2CC0001A3280001A38C0001A3D80001A4240001A49C0001A5140001 +A5500001A58C0001A6040001A6940001A6E00001A72C0001A73C0001A7680001A7880001 +A7B80001A7F00001A8000001A8100001A8340001A8580001A8680001A8780001A8900001 +A8A80001A8C00001A8D80001A8F00001A9080001A9180001A9280001A9400001A9580001 +A9700001A9880001A9B80001A9E40001A9F40001AA040001AA1C0001AA340001AA4C0001 +AA640001AA7C0001AA940001AAAC0001AAC40001AADC0001AAF40001AB0C0001AB240001 +AB700001ABBC0001ABD40001ABEC0001AC780001AD040001AD640001ADC40001AE140001 +AE640001AEDC0001AF580001B0280001B0F40001B1D40001B2B80001B36C0001B4280001 +B4CC0001B56C0001B57C0001B6100001B6A00001B7280001B7AC0001B8340001B8440001 +B8540001B8D40001B9540001B9B80001BA200001BA800001BAF80001BB640001BBFC0001 +BC0C0001BC1C0001BC2C0001BC3C0001BC900001BCDC0001BD880001BE380001BEE00001 +BF8C0001BFBC0001BFEC0001C0640001C0BC0001C12C0001C1800001C1D80001C2500001 +C27C0001C2CC0001C3700001C3DC0001C4280001C44C0001C4AC0001C5600001C5AC0001 +C6080001C6C80001C7180001C7980001C7EC0001C8880001C8DC0001C9640001C9B00001 +CA200001CA7C0001CB280001CB800001CB900001CBE40001CC900001CCDC0001CD940001 +CDBC0001CE3C0001CEC40001CED40001CF740001CFAC0001CFD00001CFEC0001D0340001 +D0480001D0C00001D0E00001D15C0001D1A80001D2240001D26C0001D2C00001D3480001 +D3740001D3B80001D4600001D4D40001D5180001D5380001D5B40001D6400001D6840001 +D6940001D7580001D7A00001D8500001D89C0001D8C80001D9140001D99C0001D9AC0001 +DA3C0001DAB80001DB740001DBE00001DBF00001DC3C0001DCB00001DCF80001DD080001 +DD280001DDA00001DE240001DE340001DEDC0001DF280001DF4C0001DF740001DFB40001 +DFE40001E0380001E09C0001E0C40001E0EC0001E16C0001E1980001E1D40001E1FC0001 +E20C0001E26C0001E2980001E2C40001E2E00001E30C0001E3440001E3700001E3980001 +E3E00001E4300001E46C0001E5540001E5D00001E6700001E6C00001E73C0001E7780001 +E7F00001E8600001E9200001E9600001E9D40001EA5C0001EAC00001EB340001EC100001 +EC500001ECB40001ED640001EDFC0001EEA80001EF700001F0080001F0CC0001F1580001 +F1D80001F2F00001F3980001F3B00001F3C80001F3E00001F4100001F4580001F4F80001 +F5A80001F5E80001F6340001F6540001F6B00001F6E00001F7500001F7C00001F7D80001 +F7F00001F8080001F8200001F8380001F8540001F86C0001F8840001F89C0001F8B40001 +F8CC0001F9340001F94C0001F9A00001F9B80001F9FC0001FA140001FAD40001FAEC0001 +FBB00001FBC80001FC340001FC4C0001FCCC0001FCE40001FCFC0001FD140001FD2C0001 +FDE80001FE480001FED40001FEEC0001FF540001FFD40002008000020098000200C40002 +01640002018C000201A80002023400020250000202CC00020310000203580002036C0002 +03800002040C000204340002045000020480000204D800020554000205D8000206400002 +0688000206D800020728000207A0000207D400020814000208380002086C000208D00002 +09800002099C000209F000020A0800020A2000020A3800020A5800020A7000020A880002 +0AA000020AB800020AD000020AE800020B0000020B1800020B3000020B4800020B600002 +0B7800020B9000020BA800020BC800020BE000020BF800020C1000020C2800020C400002 +0C5800020C7000020C8800020CA000020CB800020CD800020CF000020D0800020D200002 +0D4000020D5800020D7800020D9000020DA800020DC000020DD800020E9400020EAC0002 +0ECC00020EE400020EFC00020F1400020F2C00020F4400020FD800021070000210880002 +10A0000210B8000210D0000210E800021100000211180002113000021148000211600002 +117800021190000211A8000211C0000211E0000212400002125800021278000212900002 +13500002137000021388000213A0000213B8000213D0000213E0000213F8000214100002 +142000021430000214400002145000021460000214E000021560000215C4000215D40002 +15E4000215F400021678000216D00002171C00021764000217B0000217F4000218340002 +1884000218E00002197C000219AC00021A2800021A8000021AA800021AEC00021B340002 +1BE400021C4000021CD000021D2400021D7800021DF000021E7000021ECC00021F680002 +1FC0000220400002208C000220F800022194000221EC00022278000222BC000223280002 +23A8000223F000022440000224A40002255C000225E40002261000022658000226800002 +26DC0002274800022790000227F80002282000022860000228A4000228E4000229280002 +2984000229B000022A5000022B1400022BE400022C8C00022D3400022DE800022EC40002 +2F900002301C000230E0000231A4000232640002330C000233A800023444000235140002 +35C4000236880002374C000237C00002388C0002394C00023A2C00023AD400023B9C0002 +3C5800023D1400023DF400023E7C00023E9800023F2800023FA400023FBC000240200002 +4090000241080002418C000241D400024250000242B40002432C000243C80002445C0002 +4474000244F80002459C00024610000246C4000246EC00024774000248100002485C0002 +4870000248BC00024920000249A000024A1800024AA400024B2800024BCC00024C8C0002 +4CFC00024E0000024E6C00024F5400025064000250CC0002511800025168000251DC0002 +522400025278000252DC0002534000025368000253CC0002543C000254B0000255040002 +558C000255D40002562400025680000256CC00025724000257A80002582C0002586C0002 +58B0000258F00002595400025994000259FC00025A6800025AC000025B3000025B780002 +5BC000025C1000025C4C00025CBC00025CFC00025D4C00025DE800025E3000025E940002 +5F1000025F9C00025FEC00026058000260D00002613C00026180000261E0000262800002 +62E800026344000263AC0002640800026464000264E4000265300002659C000266040002 +66B00002670800026784000267E000026860000268BC0002692C00026978000269F40002 +6A6C00026AB800026B3000026BA400026C1800026C7800026CDC00026D5400026DD40002 +6E4C00026EB800026F1400026F9000026FF0000270200002706C000270D8000270F00002 +710000027118000271840002719C000271B4000271CC0002723C000272540002726C0002 +72840002729C000272B4000272CC000272E4000272FC000273140002732C000273440002 +735C000273740002738C000273A4000273BC000273D4000273EC00027418000274380002 +745800027494000274D00002750C0002755000027584000275CC000275E4000276000002 +762C00027650000276680002768000027698000276B0000276C0000276D8000276E80002 +7700000277640002777C00027794000277AC000278100002782800027840000278580002 +787000027888000278A0000278B8000278D0000278E80002790000027918000279300002 +7948000279600002797800027990000279F400027A2400027A3400027A4C00027AB80002 +7AD000027B3800027B5000027B6800027B8000027BE800027C0000027C1800027C300002 +7C4800027C6000027C7800027C9000027CA800027CC000027CD800027CF000027D080002 +7D2000027D3800027D5000027D6800027DC400027DDC00027DF400027E0C00027E240002 +7EA000027EB800027F3400027F4C00027FCC00027FE400027FFC0002807C000280940002 +80AC000280C4000280DC000280F40002810C000281240002813C000281540002816C0002 +81840002819C000281B4000281CC000281E4000281FC00028274000282C8000282E00002 +82F800028310000283280002837C00028394000283EC000284040002845C000284740002 +848C000284E4000284FC000285140002852C000285440002855C000285740002858C0002 +85A4000285BC000285D4000285EC000286040002861C000286340002864C000286640002 +86BC000286F40002872C000287440002877C00028794000287CC000287E4000287FC0002 +880C000288240002883C000288540002886C000288840002889C000288B4000288CC0002 +88E4000288FC000289140002892C000289440002895C000289740002898C000289C80002 +8A2800028A4800028AC400028ADC00028B5C00028B7400028BEC00028C0400028C1C0002 +8C9400028CAC00028CC400028CDC00028CF400028D0C00028D2400028D3C00028D540002 +8DA400028DE400028E3800028E8C00028EA400028EF800028F1000028F6400028F7C0002 +8F9400028FE8000290000002901800029030000290480002906000029078000290900002 +90A8000290C0000290D8000290F000029108000291200002913800029150000291880002 +91C4000292180002923000029288000292A0000292F40002930C000293240002937C0002 +9394000293AC000293C4000293DC000293F40002940C000294240002943C000294540002 +946C000294840002949C000294B4000294CC000294E4000294FC0002954C000295D00002 +96040002965C000296B4000296CC0002974800029760000297E0000297F8000298100002 +982800029840000298580002987000029888000298A0000298B8000298D0000298E80002 +9900000299180002993000029948000299BC00029A1400029A2C00029A8800029AA00002 +9AFC00029B1400029B2C00029B8800029BA000029BB800029BD000029BE800029C000002 +9C1800029C3000029C4800029C6000029C7800029C9000029CA800029CC000029CD80002 +9CF000029D0800029D6800029D7800029DB400029E2000029E8C00029EF000029F080002 +9F6C00029F8400029FEC0002A0040002A06C0002A0D40002A0EC0002A1500002A1680002 +A1800002A1EC0002A2280002A2DC0002A2F40002A3A80002A3C00002A4780002A4900002 +A5440002A55C0002A5740002A58C0002A5F80002A6740002A6F00002A76C0002A7E80002 +A8000002A8180002A8940002A8AC0002A92C0002A9440002A95C0002A9740002A9EC0002 +AA500002AAB80002AAD00002AB340002AB4C0002ABB00002ABC80002AC280002AC380002 +AC480002AC600002AC780002AC900002ACA80002ACC00002ACD80002ACF00002AD080002 +AD880002AE080002AE8C0002AF140002AF2C0002AF440002AF5C0002AF740002AF8C0002 +AFA40002AFBC0002B0300002B0DC0002B15C0002B1740002B1F00002B2080002B2880002 +B2A00002B2E40002B3200002B35C0002B3740002B3B00002B3C80002B4040002B41C0002 +B4580002B4DC0002B4EC0002B55C0002B58C0002B5EC0002B6240002B63C0002B6540002 +B66C0002B6840002B69C0002B6B40002B6CC0002B6E40002B7000002B7280002B7580002 +B7940002B7D80002B8280002B8500002B8800002B8BC0002B9000002B9500002B9840002 +B9D00002BA340002BAB00002BB440002BB740002BBB80002BC100002BC7C0002BCFC0002 +BD440002BD9C0002BDD40002BE6C0002BF440002BF6C0002BFA00002BFD40002C00C0002 +C05C0002C1380002C1A40002C1B40002C1FC0002C2540002C2840002C3140002C3380002 +C3740002C3840002C3BC0002C3CC0002C3DC0002C3EC0002C3FC0002C4540002C4AC0002 +C53C0002C5F80002C6300002C6680002C6AC0002C71C0002C78C0002C79C0002C7D80002 +C81C0002C8780002C8EC0002C8FC0002C90C0002C91C0002C96C0002C98C0002C99C0002 +C9C40002C9D40002CA300002CA400002CA740002CAC00002CB3C0002CB8C0002CBBC0002 +CBEC0002CC5C0002CC8C0002CCA80002CCD80002CD0C0002CD2C0002CD640002CD940002 +CDC40002CE240002CEE00002CF2C0002CF980002CFC00002D0040002D03C0002D0BC0002 +D1440002D1AC0002D2840002D2E80002D3500002D3C40002D46C0002D5000002D5900002 +D6240002D6480002D67C0002D7180002D7AC0002D8300002D8980002D8D40002D90C0002 +D9700002D9B00002D9FC0002DA480002DAC40002DAEC0002DB4C0002DB840002DC0C0002 +DC880002DCE00002DCF40002DD080002DD1C0002DD300002DD440002DD580002DDB40002 +DDC80002DDDC0002DE5C0002DE6C0002DEAC0002DF380002DF740002DFDC0002E03C0002 +E0C00002E1500002E1E00002E2240002E2680002E2F00002E33C0002E3740002E3A40002 +E3D00002E40C0002E4640002E4980002E4D80002E4F80002E5900002E60C0002E66C0002 +E6D00002E7000002E7700002E8000002E8A80002E8F80002E9500002E9B80002EA300002 +EA740002EAD80002EB000002EB300002EB7C0002EBD80002EC440002ECB00002ECD00002 +ECF40002ED180002ED380002ED640002ED940002EDAC0002EDC40002EDDC0002EDF40002 +EE0C0002EE240002EE3C0002EE540002EE6C0002EE840002EE9C0002EEB40002EECC0002 +EEE40002EEFC0002EF140002EF2C0002EF440002EF5C0002EF740002EF8C0002EFA40002 +EFBC0002EFD40002EFEC0002F0040002F01C0002F0340002F0540002F0740002F08C0002 +F0A40002F0BC0002F0D40002F0EC0002F1040002F11C0002F1340002F14C0002F1640002 +F17C0002F1940002F1AC0002F1C40002F1DC0002F1F40002F20C0002F2240002F23C0002 +F2540002F26C0002F2840002F29C0002F2B40002F2CC0002F2E40002F3040002F31C0002 +F3340002F34C0002F3640002F37C0002F3940002F3AC0002F3C40002F3DC0002F3F40002 +F40C0002F4240002F43C0002F4540002F46C0002F4840002F49C0002F4B40002F4CC0002 +F4E40002F4FC0002F5140002F5340002F54C0002F5640002F57C0002F5940002F5AC0002 +F5C40002F5DC0002F5F40002F60C0002F6240002F63C0002F6540002F66C0002F6840002 +F69C0002F6B40002F6CC0002F6E40002F6FC0002F7140002F7340002F7540002F76C0002 +F7840002F79C0002F7B40002F7CC0002F7E40002F7FC0002F8140002F82C0002F8440002 +F85C0002F8740002F88C0002F8A40002F8BC0002F8D40002F8EC0002F9040002F91C0002 +F9340002F94C0002F9640002F97C0002F9940002F9AC0002F9C40002F9DC0002F9F40002 +FA0C0002FA240002FA3C0002FA540002FA6C0002FA840002FA9C0002FAB40002FACC0002 +FAE40002FAFC0002FB140002FB2C0002FB440002FB5C0002FB740002FB8C0002FBA40002 +FBBC0002FBD40002FBEC0002FC040002FC1C0002FC340002FC4C0002FC640002FCB40002 +FCFC0002FD940002FDA40002FDBC0002FDD40002FDEC0002FE040002FE1C0002FE340002 +FE4C0002FE640002FE7C0002FE940002FEAC0002FEC40002FEDC0002FEF40002FF0C0002 +FF240002FF3C0002FF540002FF6C0002FF840002FF9C0002FFB40002FFCC0002FFE40002 +FFFC000300140003002C000300440003005C000300740003008C000300A4000300BC0003 +00D4000300EC000301040003011C000301340003014C0003016C000301840003019C0003 +01B4000301CC000301E4000301FC000302140003022C000302440003025C000302740003 +028C000302A4000302BC000302D4000302EC00030304000303240003033C000303540003 +036C000303840003039C000303B4000303CC000303E4000303FC000304140003042C0003 +04440003045C000304740003048C000304A4000304BC000304D4000304EC000305040003 +051C000305340003054C000305640003057C00030594000305AC000305C4000305DC0003 +05F40003060C00030624000306500003068C000306A4000306BC000306D4000306EC0003 +07040003071C000307340003074C000307640003077C00030798000307B0000307CC0003 +07E800030800000308180003083000030848000308600003087800030890000308A80003 +08C4000308E0000308FC00030914000309300003094C000309640003097C000309940003 +09AC000309C4000309DC000309F400030A0C00030A2800030A4400030A6000030A780003 +0A9400030AB000030ACC00030AE800030B0000030B1800030B3000030B4800030B600003 +0B7800030B9000030BA800030BC400030BE000030BFC00030C1400030C3000030C4C0003 +0C6800030C8400030C9C00030CB400030CCC00030CE400030CFC00030D1400030D2C0003 +0D4400030D6000030D7800030D9400030DB000030DC800030DE000030DF800030E100003 +0E2800030E4000030E5800030E7000030E8C00030EA400030EC000030EDC00030EF40003 +0F0C00030F2400030F3C00030F5400030F6C00030F8400030F9C00030FB400030FD00003 +0FEC00031004000310200003103C00031058000310740003108C0003109C000310B40003 +10C4000310DC000310EC00031104000311140003112C0003113C00031154000311640003 +117C0003118C000311A4000311BC000311D4000311EC000312040003121C000312340003 +124C000312640003127C00031294000312AC000312C4000312DC000312F40003130C0003 +13240003133C000313540003136C000313840003139C000313B4000313CC000313E40003 +13FC000314140003142C000314440003145C000314740003148C000314A4000314BC0003 +14D4000314EC000315040003151C000315340003154C000315640003157C000315940003 +15AC000315C4000315DC000315F40003160C000316240003163C000316540003166C0003 +16840003169C000316B4000316CC000316E4000316FC0003170C00031724000317340003 +1744000317B4000317C4000317DC000317F40003180C000318240003183C000318540003 +1870000318800003189C000318AC000318C4000318E0000318F800031910000319280003 +194000031958000319680003198000031998000319B0000319C8000319E4000319F40003 +1A1000031A2800031A4000031A5800031A7000031A8800031A9800031AB000031AC80003 +1AE000031AF800031B1000031B2800031B4400031B5400031B7000031B8800031B980003 +1BA800031BC000031BD800031BF000031C0800031C2000031C3C00031C4C00031C680003 +1C7800031C9000031CA000031D0C00031D0C00031D0C00031D0C00031D0C00031D0C0003 +1D0C00031D0C00031D0C00031D0C00031D0C00031D0C00031D0C00031D0C00031D0C0003 +1D0C00031D0C00031D3800031D4800031D7400031DA000031DCC00031DF400031E0C0003 +1E2400031E6000031E9C00031ED400031EF800031F5400031FB00003200C000320400003 +20980003211C0003215C00032178000321A0000321E0000322340003224C0003224C0003 +224C0003224C0003224C0003224C0003224C0003224C0003224C000323D8000324E80003 +250800032520000325400003255C0003257400032594000325B8000326280003269C0003 +26CC000326E80003276400032778000327D8000328380003286400032888000328A40003 +28EC0003291C0003294C000329640003297C0003299800032A0400032A3C00032A6C0003 +2A9C00032AB000032AE000032AF800032B1400032B3800032B9800032BAC00032C000003 +2C3400032C5C00032C9C00032CE800032D0C00032D4800032DAC00032DDC00032E180003 +2E1800032E1800032E1800032E1800032E1800032E1800032E1800032E1800032E180003 +2E1800032E1800032E1800032E6C00032EB000032FBC00033020000330B0000330D80003 +31880003321400033248000332640003328C000332C40003330000033374000333880003 +339C000333B0000333C4000333D8000333EC0003340000033414000334280003343C0003 +345000033464000334780003348C000334A0000334B4000334C8000334DC000334F00003 +3504000335180003352C0003354000033554000335680003357C00033590000335A40003 +3618000336C80003375800033798000338000003388400033908000339EC00033ABC0003 +3B6800033BCC00033BE400033D9800033DE000033E3800033FC400034060000340F80003 +418C000341F800034298000343280003435C000343DC00034440000344A0000344C40003 +44E40003450C000345340003456C000345B0000345E8000346A800034780000347F80003 +481000034938000349D400034A6C00034A7C00034A9000034AA800034BE800034CD80003 +4D2800034D8400034DE400034E9000034F24000350040003508400035100000351440003 +53EC0003549400035548000355B800035658000357700003586C0003591C00035A340003 +5ADC00035B7C00035BDC00035C6800035CBC00035CFC00035D6800035D7800035D880003 +5E7C00035EB000035EC000035ED000035FC80003608C0003611400036190000362580003 +633800036364000364A8000364FC000365A000036630000366BC000367340003677C0003 +67F000036874000369080003697C000369AC000369F000036A4400036AB400036AEC0003 +6B2400036B5000036BD400036C8400036D2400036D5C00036DC400036E5C00036EAC0003 +6ECC00036EEC00036F1400036F3400036F5400036F7400036F9400036FB400036FD40003 +6FF40003701400037034000370540003707400037094000370AC000370BC000370D40003 +70F40003710C0003711C00037134000371540003717C00037194000371A4000371BC0003 +71DC000371EC000371FC0003720C0003721C0003722C00037244000372640003727C0003 +728C000372A4000372C4000372EC00037304000373140003732C0003734C0003735C0003 +736C0003737C0003738C000374100003758C0003766C0003767C0003768C000376F80003 +7718000377C80003787800037928000379D800037AF800037C1C00037C4C00037C7C0003 +7CAC00037CDC00037D2800037D7400037E1C00037EC400037F1000037F5C00037FA80003 +7FF40003803C00038088000380C400038100000381C40003820000038254000382D40003 +836000038424000384E8000385FC0003865C000386A4000386DC000387140003874C0003 +8784000387BC000387F400038878000388F800038934000389A000038A4800038AF00003 +8B6000038BD000038BF800038C2000038C9400038D0400038D2800038D4C00038D680003 +8D8400038DA000038DE800038E3000038E7800038EC000038EDC00038EF800038F600003 +8FE00003904800039088000390C80003910800039148000391A400039200000392400003 +9280000392C0000393000003934000039380000393DC0003943800039490000394E80003 +953000039578000395C4000396100003964C00039688000396CC00039710000397500003 +9790000397F400039848000398A80003990800039974000399F400039A4800039A8C0003 +9AD000039B2C00039C0800039C2400039C8400039CC800039D1000039D6C00039DC40003 +9E2000039E9000039EC400039EF800039F4800039F840003A0580003A0F00003A1240003 +A18C0003A24C0003A2B00003A2E00003A3440003A3E80003A43C0003A4A00003A5440003 +A5980003A5C00003A6040003A6480003A6E00003A70C0003A7480003A7DC0003A7EC0003 +A8000003A84C0003A8600003A8700003A8D00003A8E80003A9000003A9980003AA500003 +AA700003AA940003AB400003ABEC0003AC080003AC740003AC9C0003AD600003AD8C0003 +ADB40003ADF40003AE340003AE840003AE9C0003AEBC0003AF880003B0C00003B2680003 +B3080003B3D00003B4980003B4BC0003B4E00003B4FC0003B5280003B5400003B5740003 +B5A40003B5C40003B6240003B6840003B7000003B7480003B7A80003B8080003B8740003 +B8DC0003B95C0003B9D00003BA640003BAF40003BBE80003BC880003BD380003BE200003 +BE940003BEEC0003BFA80003C01C0003C0340003C0540003C0740003C0940003C0B80003 +C0D80003C1340003C1A00003C1E80003C22C0003C26C0003C2B80003C2FC0003C3DC0003 +C4600003C4EC0003C5780003C5D00003C6300003C69C0003C72C0003C7B80003C8000003 +C8400003C8A80003C90C0003C9540003C9980003CA200003CA8C0003CAE00003CB340003 +CBA00003CC0C0003CC8C0003CD040003CDB80003CE6C0003CEB00003CEF80003CF840003 +D00C0003D0480003D0800003D0D40003D1240003D1B40003D2400003D2B00003D3200003 +D3640003D3A80003D4140003D4800003D4D40003D5240003D5A00003D6200003D68C0003 +D6F80003D7580003D7700003D7D00003D8100003D8540003D8880003D8BC0003D8E40003 +D9080003D9D40003DA880003DB580003DC140003DCC80003DDB00003DE940003DF540003 +E0080003E0580003E0940003E0F80003E1300003E16C0003E1900003E1B80003E1DC0003 +E2000003E2300003E2600003E2900003E2D00003E30C0003E3500003E3AC0003E4000003 +E46C0003E4F00003E5780003E5A80003E5D40003E60C0003E6440003E6B00003E71C0003 +E76C0003E7B00003E7D40003E8080003E8400003E8740003E8C80003E8F80003E9240003 +E9500003E9B40003EA140003EA480003EA740003EAA80003EB140003EB580003EB980003 +EBD80003EC040003EC340003EC980003ECD00003ED080003ED8C0003EE100003EE880003 +EF000003EF5C0003EFC00003EFF80003F02C0003F0940003F0F40003F14C0003F1A00003 +F1D80003F2100003F2640003F2B80003F34C0003F3E00003F4480003F4B00003F4FC0003 +F5480003F5C00003F6380003F6C00003F7480003F7A00003F7F80003F86C0003F8DC0003 +F9000003F9200003F9440003F9680003F9DC0003FA440003FA9C0003FAB40003FB280003 +FB880003FBFC0003FC680003FCDC0003FD440003FDA00003FE140003FE780003FEAC0003 +FF240003FF500003FF880003FFB80003FFE40003FFFC0004001C00040074000400940004 +00B4000400D4000400F40004011C000401440004016C00040194000401B4000402080004 +0304000403280004034C0004036C0004038C000403AC000404040004045C000404A80004 +04E00004054C000405AC00040A6400040AD000040B4400040B5400040B6400040B740004 +0B8400040BB400040C0000040C4400040C7C00040C9800040CD000040D0800040D240004 +0D5C00040D7C00040D9800040DBC00040DE000040DFC00040E1C00040E5000040EA80004 +0EE000040EFC00040F3400040F8C00040FC000040FDC000410300004105C000411040004 +111400041180000411F40004121C0004138C0004159000041820000419B000041BBC0004 +1E2000041F9800042294000424FC0004276800042784000427A0000427BC000427D80004 +280C000428440004287C000428B8000428F00004293000042978000429C4000429E80004 +2A0C00042A3000042A5400042A7800042A9C00042AC000042AE400042B0400042B280004 +2B4C00042B7000042B9000042BB000042BD000042BF400042C1C00042C4400042C700004 +2C9C00042CC400042CF400042D2000042D4800042D7000042D9800042DC400042DF00004 +2E1800042E4800042E7400042E9C00042EC400042EF000042F1C00042F4400042F6C0004 +2F9800042FC400042FEC000430100004303C0004306800043090000430B8000430E40004 +311000043138000431680004319C000431D000043204000432380004326C000432A00004 +32D8000433100004334800043380000433B4000433E80004341C00043450000434840004 +34A8000434D4000435000004352C000435540004357C000435A8000435D4000436080004 +36340004366000043694000436C0000436EC000437200004374C00043778000437AC0004 +37DC000438100004385400043884000438B8000438F80004392C0004395C0004399C0004 +39D000043A0000043A4000043A8800043ACC00043B2800043B5800043B8800043BB80004 +3BE800043C0800043C2400043C6400043C8000043C9C00043CB800043CD400043CF00004 +3D0C00043D2800043D4400043D6800043D9000043DB400043DDC00043DF000043E0C0004 +3E2800043E4400043E6000043E7C00043E9800043EB400043ED000043EEC00043F080004 +3F2400043F4000043F5C00043F7800043F9400043FA80004405C00044370000444180004 +442C000444400004445C0004447800044494000444B8000444E000044504000445280004 +45440004456C00044590000445AC000445D80004462C000446440004469C000446FC0004 +4838000448C00004494800044B0800044B2400044B5000044B6C00044B9800044BB40004 +4BE000044C0000044C3400044C5000044C7800044C9400044CBC00044CD800044D040004 +4D2000044D4C00044D6800044D9400044DB000044DDC00044DF800044E2400044E400004 +4E6C00044E8800044EB400044ED000044EFC00044F1C00044F5000044F94000450240004 +5078000450E0000451D80004528C00045348000453AC000454040004545C000454B80004 +55140004557C000455CC000455F40004561C00045658000456D000045724000457780004 +57A4000457D0000457FC0004582C00045870000458B4000458D0000458EC000459040004 +591C0004596400045990000459BC000459E400045A0C00045A4800045A8C00045AB80004 +5AE000045B4C00045B8C00045BCC00045C0C00045C4C00045CCC00045D4C00045DCC0004 +5E4C00045E7400045E9C00045EC400045EF000045F0C00045F3800045F5400045F7C0004 +606C000460D80004619000046A7400046D5800046D9000046DF400046E4400046EA80004 +6F3C00047008000470D400047138000471DC000474600004774000047778000477F00004 +7864000478BC00047A1C00047BC800047C1000047C4400047D3800047E0C00047EA40004 +7F3C00048000000480C80004818C00048250000485D80004865400048778000489180004 +8C8000048D0000048D8800048DFC00048E6000048EE400048F74000490BC000491EC0004 +92880004931C000493D0000493F000049410000494300004945000049470000494900004 +94B0000494D00004964400049720000497FC000498A8000499C400049A5800049AE80004 +9BA000049C1800049C9000049D0400049D5C00049DB800049E4C00049EF800049F600004 +9FD40004A06C0004A0D00004A1900004A2700004A3400004A3B40004A4580004A4B40004 +A5500004A63C0004A6BC0004A7E40004AA100004AAAC0004ABC80004AD100004AE240004 +AFB80004B0B00004B1300004B1F80004B2E00004B3740004B3E80004B48C0004B4D40004 +B5740004B63C0004B6900004B6C00004B7E40004B8D00004B9100004B96C0004B9D00004 +BA300004BA840004BAD00004BB640004BC240004BD0C0004BF180004C00C0004C1300004 +C26C0004C36C0004C4900004C5C40004C6B40004C7880004C8940004C9CC0004CB240004 +CC280004CCC40004CD340004CDE80004CEDC0004D0140004D1900004D3500004D3EC0004 +D4AC0004D5140004D5A00004D5BC0004D5E40004D5FC0004D6140004D62C0004D6440004 +D6C40004D7140004D7980004D8900004D93C0004DA600004DB1C0004DBA40004DC480004 +DD500004DF380004E1FC0004E3BC0004E4000004E4400004E4C40004E5100004E5F00004 +E6D80004E7C00004E8500004E9300004EA440004EB200004EBFC0004EC700004ECB80004 +ED300004EE340004EF800004F0C40004F1000004F1840004F1DC0004F2380004F2900004 +F3000004F3680004F3D00004F4380004F4E00004F5C00004F6D40004F8440004F8DC0004 +FA6C0004FBD00004FD5C0005011C000502A800050420000504AC0005055C000508140005 +09B400050ACC00050BB800050CCC00050DB400050E3C00050EA000050F0800050F4C0005 +0F8C0005105800051150000511D0000512040005124000051284000512B4000513140005 +13940005147800051510000515CC0005174C000518D000051C3000051C9000051D400005 +1DA800051E3400051EC400051F8400052010000520900005210C0005217C000521D00005 +22380005229C000522F4000523B8000523FC00052454000524A400052514000525D00005 +277C0005297000052A9C00052CCC00052E2C000530A4000535F800053730000539800005 +3A4400053B0C00053BE000053D6400053EE400054044000541A4000542F00005445C0005 +44C8000545340005459C000546040005466800054684000546A0000546BC000546FC0005 +4748000547600005477800054854000548EC000549A800054A1800054A8C00054BAC0005 +4C9400054D0000054D7000054DAC00054DE800054E1000054E3C00054E6400054E900005 +4EB800054EE400054F1000054F3C00054FA000055004000550800005512C0005526C0005 +52F40005540C000555A00005561400055860000559EC00055B6000055C3400055D380005 +5E5000055F2C0005602C00056144000562100005633C0005645400056584000566040005 +66B40005677C00056808000568B800056980000569F800056AD000056B9800056C5C0005 +6C8C00056CB800056CE400056D1000056D4000056DCC00056DF400056E1C00056E700005 +6EC400056EEC00056F1C00056F4C00056F6C00056FBC0005700C00057038000570680005 +70B0000570F80005714C0005719C000571E80005723800057290000572E8000573540005 +73F80005745000057498000574F00005757C000575FC0005768800057758000577E80005 +791000057A3C00057AC800057B2800057B8800057BC400057BF800057C2C00057C540005 +7C7C00057C9400057CAC00057D0400057D5800057E1C00057EDC00057FD8000580880005 +81380005825800058298000582D80005833400058370000583AC000583F8000584440005 +84C8000584C8000584DC000584F00005850C000585200005853C000585580005857C0005 +8590000585AC000585C8000585EC000586080005862C000586500005867C000586900005 +86AC000586C8000586EC000587080005872C000587500005877C00058798000587BC0005 +87E00005880C000588300005885C00058888000588BC000588D0000588EC000589080005 +892C000589480005896C00058990000589BC000589D8000589FC00058A2000058A4C0005 +8A7000058A9C00058AC800058AFC00058B1800058B3C00058B6000058B8C00058BB00005 +8BDC00058C0800058C3C00058C6000058C8C00058CB800058CEC00058D1800058D4C0005 +8D8000058DBC00058DD000058DEC00058E0800058E2C00058E4800058E6C00058E900005 +8EBC00058ED800058EFC00058F2000058F4C00058F7000058F9C00058FC800058FFC0005 +90180005903C000590600005908C000590B0000590DC000591080005913C000591600005 +918C000591B8000591EC000592180005924C00059280000592BC000592D8000592FC0005 +93200005934C000593700005939C000593C8000593FC000594200005944C000594780005 +94AC000594D80005950C000595400005957C000595A0000595CC000595F80005962C0005 +96580005968C000596C0000596FC000597280005975C00059790000597CC000598000005 +983C00059878000598BC000598D0000598EC000599080005992C000599480005996C0005 +9990000599BC000599D8000599FC00059A2000059A4C00059A7000059A9C00059AC80005 +9AFC00059B1800059B3C00059B6000059B8C00059BB000059BDC00059C0800059C3C0005 +9C6000059C8C00059CB800059CEC00059D1800059D4C00059D8000059DBC00059DD80005 +9DFC00059E2000059E4C00059E7000059E9C00059EC800059EFC00059F2000059F4C0005 +9F7800059FAC00059FD80005A00C0005A0400005A07C0005A0A00005A0CC0005A0F80005 +A12C0005A1580005A18C0005A1C00005A1FC0005A2280005A25C0005A2900005A2CC0005 +A3000005A33C0005A3780005A3BC0005A3D80005A3FC0005A4200005A44C0005A4700005 +A49C0005A4C80005A4FC0005A5200005A54C0005A5780005A5AC0005A5D80005A60C0005 +A6400005A67C0005A6A00005A6CC0005A6F80005A72C0005A7580005A78C0005A7C00005 +A7FC0005A8280005A85C0005A8900005A8CC0005A9000005A93C0005A9780005A9BC0005 +A9E00005AA0C0005AA380005AA6C0005AA980005AACC0005AB000005AB3C0005AB680005 +AB9C0005ABD00005AC0C0005AC400005AC7C0005ACB80005ACFC0005AD280005AD5C0005 +AD900005ADCC0005AE000005AE3C0005AE780005AEBC0005AEF00005AF2C0005AF680005 +AFAC0005AFE80005B02C0005B0700005B0BC0005B1080005B1540005B19C0005B1E00005 +B2AC0005B3740005B4580005B5380005B5840005B5C00005B5FC0005B6300005B6640005 +B68C0005B6C00005B6F40005B7140005B75C0005B7B40005B8740005B9480005BA2C0005 +BA540005BABC0005BB380005BBA40005BC500005BCF00005BD640005BDF40005BE880005 +BF2C0005BFB00005C0480005C0C40005C14C0005C1D00005C2300005C2900005C2A00005 +C2B80005C2D80005C3140005C34C0005C3640005C37C0005C3940005C3AC0005C3C40005 +C3DC0005C4AC0005C5780005C5C80005C6180005C6DC0005C7980005C7FC0005C8580005 +C8DC0005C95C0005C9EC0005CA7C0005CAD80005CB3C0005CBA40005CC080005CC440005 +CC800005CC980005CCB00005CCF40005CD3C0005CD840005CDD40005CE4C0005CEC80005 +CF5C0005CFF40005D0780005D0C00005D1040005D1680005D1C80005D21C0005D2700005 +D2E80005D35C0005D43C0005D51C0005D5EC0005D6BC0005D7080005D7500005D7980005 +D7E00005D8280005D8700005D8CC0005D8F40005D91C0005D9440005D9700005D99C0005 +D9C80005D9F40005DA2C0005DA640005DA980005DAD00005DB080005DB3C0005DB680005 +DB940005DBC00005DBE80005DC180005DC480005DC780005DCA80005DD540005DD780005 +DDB40005DDF80005DE200005DE4C0005DE880005DEAC0005DEE80005DF300005DF700005 +DFE00005E03C0005E1780005E2280005E2840005E2940005E2E40005E3240005E3640005 +E3980005E3CC0005E4640005E4B40005E4EC0005E54C0005E5980005E6080005E6680005 +E6C80005E6F00005E7180005E7980005E7E40005E8600005E8940005E8A80005E8D40005 +E9800005E9D80005EA400005EAB40005EB280005EBC40005EC0C0005EC740005ECF00005 +ED6C0005EDA80005EE0C0005EE880005EF140005EF740005EFD40005F0140005F0840005 +F0F00005F1380005F1A40005F2100005F2B40005F3140005F3540005F3980005F3EC0005 +F43C0005F49C0005F4FC0005F5640005F5D80005F61C0005F6900005F6F00005F74C0005 +F7A80005F7F80005F8440005F8D00005F9440005FA0C0005FAF00005FB980005FC400005 +FCF80005FD500005FD600005FD700005FD800005FD900005FE7C0005FF000005FF900005 +FFF0000600800006015000060258000602BC00060324000603840006044C000604B80006 +05240006058000060590000605E4000606AC0006076800060778000607B8000607C80006 +0848000608A40006094400060A1000060AB800060B9C00060BFC00060C5400060CDC0006 +0DA800060E8C00060F9000060FE400060FF400061130000611B0000611C0000612040006 +1270000613340006138800061474000614B4000615300006154800061568000615880006 +15A8000615C8000615D8000615F000061608000616200006163800061650000616680006 +168000061698000616B0000616C8000616E0000616F80006171000061728000617400006 +17580006177000061788000617A0000617B8000617D0000617E800061800000618180006 +183000061848000618600006187800061890000618A8000618C0000618D8000618F00006 +1908000619200006193800061950000619680006198000061998000619B0000619C80006 +19E0000619F800061A1000061A2800061A4000061A5800061A7000061A8800061AA00006 +1AB800061AD000061AE800061B0000061B1800061B3000061B4800061B6000061B780006 +1B9000061BA800061BC000061BD800061BE800061BF800061C4000061C5000061C600006 +1C8400061C9400061CA400061CDC00061CEC00061CFC00061D0C00061D1C00061D2C0006 +1D3C00061D4C00061D5C00061D6C00061D7C00061D8C00061DF000061E0000061E100006 +1E2000061E3000061E6C00061E7C00061E8C00061E9C00061F0C00061F1C00061F2C0006 +1F3C00061F6C00061F7C00061F8C00061F9C000620080006201800062088000620A40006 +20C8000620E0000620F80006212C0006215800062184000621AC000621BC000621CC0006 +21DC000621EC000622C80006237800062390000623A80006242000062488000624D80006 +2560000625C00006261C0006265C0006269C000626E00006272000062738000627500006 +27C400062838000628580006287800062A0000062A6000062AC400062AFC00062B3C0006 +2BB400062BC400062BEC00062C1400062C3C00062C6400062C8C00062CB400062CDC0006 +2D0400062D2C00062D5000062D7000062D9400062DB800062DDC00062DFC00062E280006 +2E5400062E8000062EAC00062EC000062F0400062F4800062F7800062FA800062FEC0006 +3044000630C80006315800063168000631F8000632240006323400063298000633740006 +33FC000634AC000635080006359C000635E400063660000636BC0006374C000637B00006 +38480006385800063868000638B0000638F8000639240006394C0006397C000639A80006 +3A3000063AB400063B6C00063C1000063C6C00063CD800063D5000063DE800063E740006 +3EDC00063F3800063FA000063FF80006406400064084000640A4000640FC000641500006 +416000064188000641E40006423800064248000642A8000642E400064330000643C00006 +4474000644D4000645300006459C0006460C000646A0000646F800064790000648240006 +486C000648B800064950000649600006498C000649D400064A1000064A2C00064A780006 +4AA400064AC400064AE800064B0C00064B3000064B5000064B6400064B7800064B8C0006 +4BA000064BC400064BD800064BEC00064C0000064C1400064C3800064C4C00064C600006 +4C7400064C8800064CAC00064CC000064CD400064CE800064D0400064D2C00064FD80006 +528800065534000657E400065838000658A000065914000659BC00065A1800065A880006 +5B0C00065B8C00065BE000065C4400065CF400065D6000065DC000065E3400065E980006 +5EF000065F7C00065FCC0006604C000660B400066164000661C400066258000662C00006 +63340006639C0006641C00066470000664E40006655C000665B000066628000666980006 +671000066788000667EC0006685C000668E00006697400066A0000066AD000066B240006 +6B8400066BF400066C4C00066CB000066D1400066D7400066DD800066E4800066EB80006 +6F1000066F6800066FCC00067044000670C400067154000671CC00067254000672CC0006 +7350000673E000067458000674E000067564000675D8000676880006772C000677AC0006 +788C0006794C000679CC00067AC400067B9000067C3800067CD400067D8400067E4C0006 +7E6400067E9800067EB000067EFC00067F6800067F9000067FE000068034000680600006 +80A8000680E4000681440006816C000681840006819C000681BC000681DC000681F40006 +820C000682240006823C000682540006826C00068284000682A0000682B8000682D00006 +82EC000683040006831C000683340006834C000683640006837C00068394000683AC0006 +83C4000683DC000683F40006840C000684240006843C000684540006846C000684840006 +8508000685200006853800068550000685680006858000068598000685B0000685C80006 +85E0000685F8000686100006862800068640000686580006867000068688000686A00006 +86B8000686D0000686E80006870000068718000687300006874800068760000687780006 +8790000687A8000687C0000687D8000687F0000688080006882000068838000688500006 +88680006888000068898000688B0000688C8000688E0000688F800068910000689280006 +8940000689580006897000068988000689A0000689B8000689D0000689E800068A000006 +8A1800068A3000068A4800068A6000068A7800068A9000068AA800068AB800068B640006 +8B7400068B8400068B9C00068BB400068BCC00068BE400068BFC00068C1400068C2C0006 +8C4400068C5C00068C7400068C8C00068CA400068CB400068D2C00068D4400068D5C0006 +8D7400068D8C00068D9C00068E4000068E5000068E6000068E7800068E9000068EA80006 +8EC000068ED800068EF000068F0800068F2000068F3800068F5000068F6800068F800006 +8F9800068FB000068FC800068FE000069010000690500006906000069070000690880006 +90A0000690A0000690A0000690A0000690A0000690A0000690A0000690A0000690A00006 +90A0000690A0000690A0000690A0000690A0000690A0000690A0000690A0000690C80006 +90F0000691380006918000069194000691AC000691C0000691F4000692080006921C0006 +92340006924800069260000692740006928C000692A0000692B8000692CC000692E40006 +92F40006930C000693240006933C000693540006936C000693840006939C000693B40006 +93CC000693E4000693FC0006941400069424000694580006947000069488000694A00006 +94B8000694D0000694E80006950000069518000695300006954800069560000695780006 +9590000695A8000695C0000695D8000695F00006960800069618000696AC000697140006 +9798000697B0000697C8000697E0000697F8000698080006986C000698840006989C0006 +98AC0006990000069918000699300006994000069A1400069AA400069B4400069B5C0006 +9B7400069B8C00069BA400069BB400069C9000069D1C00069DC000069DD800069DF00006 +9E0800069E2000069E3000069EB400069F2000069FA400069FBC00069FD400069FEC0006 +A0040006A0140006A09C0006A0F40006A1840006A19C0006A1B40006A1CC0006A1E40006 +A1FC0006A2140006A22C0006A2440006A25C0006A2740006A28C0006A2A40006A2B40006 +A3800006A3EC0006A4740006A4840006A4F80006A52C0006A5700006A5800006A6200006 +A6900006A7200006A7380006A7500006A7680006A7800006A7900006A8080006A8C40006 +A9540006A9640006A9F80006AA080006AAAC0006AAC40006AADC0006AAF40006AB0C0006 +AB240006AB3C0006AB540006AB6C0006AB840006AB9C0006ABF00006AC580006AC580006 +AC580006AC580006AC580006AC580006ACE40006AD200006AD980006ADC40006AE140006 +AE540006AE840006AEB00006AEEC0006AF880006AFA40006AFDC0006B0040006B0440006 +B0740006B0D00006B1600006B1A80006B1E00006B2480006B2A00006B2D80006B3040006 +B32C0006B3700006B3EC0006B4240006B4A40006B4E80006B5300006B5540006B59C0006 +B5AC0006B5D80006B5E80006B61C0006B6540006B6700006B68C0006B6A80006B6C40006 +B6E00006B7080006B7300006B75C0006B7840006B7AC0006B7D80006B8000006B8280006 +B8500006B8780006B8A00006B8CC0006B8F40006B91C0006B9480006B9700006B9980006 +B9C00006B9E80006BA100006BA3C0006BA640006BA8C0006BAB80006BAE00006BB080006 +BB300006BB580006BB800006BBAC0006BBD40006BBFC0006BC280006BC500006BC780006 +BCA00006BCC80006BCF00006BD1C0006BD440006BD6C0006BD980006BDC00006BDE80006 +BE100006BE380006BE600006BE8C0006BEB40006BEDC0006BF080006BF300006BF580006 +BF800006BFA80006BFD00006BFFC0006C0240006C04C0006C0780006C0A00006C0C80006 +C0F00006C1180006C1400006C16C0006C1940006C1BC0006C1E80006C2100006C2380006 +C2600006C2880006C2B00006C2DC0006C3040006C32C0006C3580006C3800006C3A80006 +C3D00006C41C0006C4AC0006C5180006C5580006C5940006C6200006C65C0006C6A80006 +C6EC0006C71C0006C7780006C8140006C8B00006C8E80006C95C0006C9980006CA000006 +CA540006CA9C0006CB400006CBDC0006CC540006CCF00006CD680006CDD00006CE980006 +CF0C0006CF440006CFA00006CFE40006D0300006D0EC0006D1600006D1EC0006D2880006 +D3240006D3700006D43C0006D4980006D50C0006D5480006D5A80006D5FC0006D64C0006 +D6900006D6A00006D6B00006D6C00006D6D00006D6E00006D6F00006D7000006D7100006 +D7200006D7300006D7400006D7500006D7600006D7700006D7800006D7900006D7A00006 +D7B00006D7C00006D7D00006D7E00006D7F00006D8000006D8100006D8200006D8300006 +D8400006D8500006D8600006D8700006D8800006D8900006D8A00006D8B00006D8C00006 +D8D00006D8E00006D8F00006D9000006D9100006D9200006D9300006D9400006D9500006 +D9600006D9700006D9800006D9900006D9A00006D9B00006D9C00006D9D00006DA3C0006 +DA7C0006DB0C0006DBA00006DBEC0006DC5C0006DCE80006DD240006DE000006DE8C0006 +DE9C0006DEAC0006DEBC0006DECC0006DEDC0006DEEC0006DEFC0006DF0C0006DF1C0006 +DF2C0006DF3C0006DF4C0006DF5C0006DF6C0006DF7C0006DF8C0006DF9C0006DFAC0006 +DFBC0006DFCC0006DFDC0006DFEC0006DFFC0006E00C0006E01C0006E02C0006E03C0006 +E04C0006E05C0006E06C0006E07C0006E08C0006E09C0006E0AC0006E0BC0006E0CC0006 +E0DC0006E0EC0006E0FC0006E10C0006E11C0006E1340006E14C0006E2080006E2700006 +E2880006E2F40006E3280006E3A00006E3B80006E4480006E4580006E4700006E4FC0006 +E50C0006E5240006E53C0006E5540006E56C0006E5840006E5940006E5AC0006E5C40006 +E6980006E7180006E79C0006E7B40006E83C0006E8CC0006E8E40006E9900006EA040006 +EA1C0006EAC40006EADC0006EAF40006EB0C0006EB240006EB3C0006EB540006EB6C0006 +EB840006EBD00006EC840006ECB00006ECE80006ED640006EE200006EF1C0006F0580006 +F1D40006F3880006F4040006F4C00006F5C00006F6FC0006F8780006FA340006FC280006 +FCE40006FDE00006FF1C0007009800070248000704440007067800070774000708B00007 +0A3000070BEC00070DE80007102400071298000713CC0007154000071700000718FC0007 +1B3800071DB400072068000721E00007239C0007259C000727D800072A5400072D100007 +3004000731C4000733CC000736140007389C00073B6000073E6800074198000741C40007 +41FC0007427800074334000744300007456C000746E80007489C00074918000749D40007 +4AD400074C1000074D8C00074F480007513C000751F8000752F400075430000755AC0007 +57680007596400075B9800075C9400075DD000075F500007610C00076308000765440007 +67B8000768EC00076A6000076C1C00076E1800077054000772C000077574000776EC0007 +78A800077A9800077CD400077F500007820C00078500000786C4000788CC00078B140007 +8D980007905C0007934C0007967C000796D800079794000798700007996C00079A2C0007 +9B1800079C3000079CDC00079E1000079F240007A0140007A0C80007A1A00007A2800007 +A33C0007A3F40007A4CC0007A5C40007A6800007A7680007A87C0007A9240007AA540007 +AB640007AC500007AD000007ADD40007AEB00007AF680007AFF80007B0A80007B1780007 +B2080007B2C80007B3B00007B4300007B5380007B6200007B6E40007B76C0007B8180007 +B8CC0007B9580007B9D40007BAAC0007BBA40007BCBC0007BD940007BE9C0007BFCC0007 +C0940007C1E40007C3140007C4200007C4F00007C5E40007C6E00007C7B40007C8580007 +CA600007CD480007CF700007D1900007D2900007D3900007D5280007D6080007D6E80007 +D7EC0007D8C00007D9FC0007DB080007DBE00007DCC00007DDCC0007DEAC0007DFD40007 +E0A80007E1500007E2080007E29C0007E35C0007E4500007E5080007E5C00007E6B80007 +E78C0007E8A00007E9780007EA500007EB280007EBF80007ECC40007EDC40007EEF40007 +EFF40007F1280007F2380007F30C0007F3F00007F4D00007F5D00007F7CC0007F8B80007 +F9CC0007FAA00007FBEC0007FCB00007FD940007FE9400080060000801A0000803200008 +044000080514000805BC0008069C000808E400080B6800080DD800080FE0000812180008 +1458000816A80008186800081A2800081A4C00081B1800081BB400081C8400081D2C0008 +1DC800081E7400081EA800081F4800081FA800081FE400082010000820280008204C0008 +207C000820AC000820D0000821080008218400082194000821A400082264000823000008 +23A0000824680008247800082488000824A0000824B80008250800082528000825480008 +25680008258000082598000825B0000825C8000825E0000825F800082610000826280008 +2640000826580008267000082688000826A8000826C8000826E800082708000827980008 +27F80008284800082864000828800008289C000828B8000828D4000828F00008290C0008 +292800082944000829600008297C00082998000829B4000829D0000829EC00082A080008 +2A2400082A4000082A5C00082A7800082A9000082AAC00082AE400082AFC00082B500008 +2B6800082B8000082B9800082BB000082BD000082BF000082C0800082C6C00082C7C0008 +2C8C00082C9C00082CB400082CE000082D3800082D5000082D6800082D8C00082DE40008 +2E0000082E1800082E3000082E4800082E6000082E7800082E9000082EA800082EC00008 +2EF800082F3800082F7000083000000830A0000831300008319800083208000832700008 +32A0000832D0000832FC0008334C000833A4000833F00008343C00083488000834D00008 +357800083628000836CC0008372800083780000837D80008387000083914000839B00008 +3A0C00083A7000083ACC00083B2800083B8C00083BE800083C6800083CEC00083D700008 +3DFC00083E9400083F2000083F8400083FF000084054000840F8000841A4000842480008 +42AC000843180008437C000844080008449C000845280008457C000845D80008462C0008 +46A4000847240008479C00084844000848F40008499C00084A0000084A6C00084AD00008 +4B6800084C0C00084CA400084CF400084D4C00084D9C00084E1000084E8800084EFC0008 +4F80000850080008508C000850DC0008513400085184000851DC0008523C000852940008 +530000085374000853E0000854A40008557000085634000856BC00085748000857D00008 +57E4000858080008582C000858500008587400085898000858BC000858E0000859040008 +5970000859AC000859E800085A5400085A6C00085A8400085AA400085ABC00085AD40008 +5AEC00085B0400085B2400085B3C00085B5400085B7400085B9400085BB400085BCC0008 +5BE400085BFC00085C1C00085C3C00085C5C00085C7400085C8C00085CA400085CBC0008 +5CD400085CEC00085D0400085D1C00085D3400085D4C00085D6400085D7C00085D940008 +5DAC00085DC400085DE400085E0400085E2400085E3C00085E5400085E6C00085E840008 +5E9C00085EB400085ECC00085EE400085EFC00085FA8000860180008609C000860B40008 +60CC000860E4000860FC000861140008612C000861440008615C000861740008618C0008 +61A4000861BC000861D4000861EC000862040008621C000862340008624C000862640008 +627C00086294000862AC000862C4000862DC000862F40008630C00086324000863440008 +636400086384000863A4000863C4000863E4000863FC000864140008642C000864BC0008 +65B4000865C8000865FC000866240008664C00086688000866D8000866F0000867100008 +67E00008691C00086AC400086B6800086C3800086D0800086D3800086D6800086DB40008 +6E0000086EC400086F9C00087088000870B00008711C000871A000087214000872C80008 +7370000873E80008748000087524000875D400087658000876F800087780000878100008 +789C000878FC0008795C0001000018610354002B0068000C000200100099000800000415 +021600080004B8028040FFFBFE03FA1403F92503F83203F79603F60E03F5FE03F4FE03F3 +2503F20E03F19603F02503EF8A4105EFFE03EE9603ED9603ECFA03EBFA03EAFE03E93A03 +E84203E7FE03E63203E5E45305E59603E48A4105E45303E3E22F05E3FA03E22F03E1FE03 +E0FE03DF3203DE1403DD9603DCFE03DB1203DA7D03D9BB03D8FE03D68A4105D67D03D5D4 +4705D57D03D44703D3D21B05D3FE03D21B03D1FE03D0FE03CFFE03CEFE03CD9603CCCB1E +05CCFE03CB1E03CA3203C9FE03C6851105C61C03C51603C4FE03C3FE03C2FE03C1FE03C0 +FE03BFFE03BEFE03BDFE03BCFE03BBFE03BA1103B9862505B9FE03B8B7BB05B8FE03B7B6 +5D05B7BB03B78004B6B52505B65D40FF03B64004B52503B4FE03B39603B2FE03B1FE03B0 +FE03AFFE03AE6403AD0E03ACAB2505AC6403ABAA1205AB2503AA1203A98A4105A9FA03A8 +FE03A7FE03A6FE03A51203A4FE03A3A20E05A33203A20E03A16403A08A4105A096039FFE +039E9D0C059EFE039D0C039C9B19059C64039B9A10059B19039A1003990A0398FE039796 +0D0597FE03960D03958A410595960394930E05942803930E0392FA039190BB0591FE0390 +8F5D0590BB039080048F8E25058F5D038F40048E25038DFE038C8B2E058CFE038B2E038A +8625058A410389880B05891403880B03878625058764038685110586250385110384FE03 +8382110583FE0382110381FE0380FE037FFE0340FF7E7D7D057EFE037D7D037C64037B54 +15057B25037AFE0379FE03780E03770C03760A0375FE0374FA0373FA0372FA0371FA0370 +FE036FFE036EFE036C21036BFE036A1142056A530369FE03687D036711420566FE0365FE +0364FE0363FE0362FE03613A0360FA035E0C035DFE035BFE035AFE0359580A0559FA0358 +0A035716190557320356FE035554150555420354150353011005531803521403514A1305 +51FE03500B034FFE034E4D10054EFE034D10034CFE034B4A13054BFE034A4910054A1303 +491D0D05491003480D0347FE0346960345960344FE0343022D0543FA0342BB03414B0340 +FE033FFE033E3D12053E14033D3C0F053D12033C3B0D053C40FF0F033B0D033AFE0339FE +033837140538FA033736100537140336350B05361003350B03341E03330D0332310B0532 +FE03310B03302F0B05300D032F0B032E2D09052E10032D09032C32032B2A25052B64032A +2912052A25032912032827250528410327250326250B05260F03250B0324FE0323FE0322 +0F03210110052112032064031FFA031E1D0D051E64031D0D031C1142051CFE031BFA031A +42031911420519FE031864031716190517FE031601100516190315FE0314FE0313FE0312 +11420512FE0311022D05114203107D030F64030EFE030D0C16050DFE030C0110050C1603 +0BFE030A100309FE0308022D0508FE030714030664030401100504FE03401503022D0503 +FE0302011005022D0301100300FE0301B80164858D012B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B002B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B1D00>]def +/CharStrings 5 dict dup begin +/.notdef 0 def +/space 3 def +/a 68 def +/s 86 def +/M 48 def +end readonly def + +systemdict/resourcestatus known + {42 /FontType resourcestatus + {pop pop false}{true}ifelse} + {true}ifelse +{/TrueDict where{pop}{(%%[ Error: no TrueType rasterizer ]%%)= flush}ifelse +/FontType 3 def + /TrueState 271 string def + TrueDict begin sfnts save + 72 0 matrix defaultmatrix dtransform dup + mul exch dup mul add sqrt cvi 0 72 matrix + defaultmatrix dtransform dup mul exch dup + mul add sqrt cvi 3 -1 roll restore + TrueState initer end + /BuildGlyph{exch begin + CharStrings dup 2 index known + {exch}{exch pop /.notdef}ifelse + get dup xcheck + {currentdict systemdict begin begin exec end end} + {TrueDict begin /bander load cvlit exch TrueState render end} + ifelse + end}bind def + /BuildChar{ + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec + }bind def +}if + +FontName currentdict end definefont pop +%!PS-TrueTypeFont-1.0-1.0 +%%Title: STIXGeneral-Italic +%%Copyright: Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the American Chemical Society, the American Institute of Physics, the American Mathematical Society, the American Physical Society, Elsevier, Inc., and The Institute of Electrical and Electronic Engineers, Inc. Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright (c) 1990 by Elsevier, Inc. All rights reserved. +%%Creator: Converted from TrueType to type 42 by PPR +15 dict begin +/FontName /STIXGeneral-Italic def +/PaintType 0 def +/FontMatrix[1 0 0 1 0 0]def +/FontBBox[-970 -305 1429 1023]def +/FontType 42 def +/Encoding StandardEncoding def +/FontInfo 10 dict dup begin +/FamilyName (STIXGeneral) def +/FullName (STIXGeneral-Italic) def +/Notice (Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the American Chemical Society, the American Institute of Physics, the American Mathematical Society, the American Physical Society, Elsevier, Inc., and The Institute of Electrical and Electronic Engineers, Inc. Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright (c) 1990 by Elsevier, Inc. All rights reserved. STIX Fonts(TM) is a trademark of The Institute of Electrical and Electronics Engineers, Inc.) def +/Weight (Italic) def +/Version (Version 1.0.0) def +/ItalicAngle -17.43909 def +/isFixedPitch false def +/UnderlinePosition -75 def +/UnderlineThickness 50 def +end readonly def +/sfnts[<000100000006004000020020676C79668E0E8F5C0000006C0002228468656164 +F159E6C6000222F00000003668686561060A06A20002232800000024686D74780116464A +0002234C000010A06C6F6361047A369C000233EC000010A46D6178700472008900024490 +0000002000020027FFF50130029B000B00170000372736373E0133321514070603140623 +2226353436333216891131160A1F1C2C2738402117161C2115171DB105E38E4034332853 +78FEB4151F1E18151F2000000002009001A501B0029A000A00150000012336373E013332 +161514052336373E013332161514015A170D09041F130D14FEF7170D09041F140D1301A5 +9E2D1416100C1EBB9E2D1416100C2000000200020000021C02A4001B001F000001072307 +33072307233723072337233733372337333733073337330F01230733021C0B68365E0B68 +4F3E50824F3E50600B6A36610B6A503D5082503D505182368201D0368F36D5D5D5D5368F +36D4D4D4D4368F0000030020FFA701F102DB0026002E003500000107272E0127071E0115 +14070E012307233726273F011E0117132E013534373E013B013733071607232206151416 +1F0103363534262701F11C0F02252E385836432841441523165F361A1004323D454C3B2A +1E463A121122104870103542232D08449F1F3602637202393C11ED414E3F5C331E11595B +12307B034C43130129314B373D2D20163F441105412C23341B70FEE70D832A2E2C000000 +00040050FFED02C102C2000B001900360046000025140623222635343633321607342623 +22070E01151416333236030123010E012322271615140607062322263534363332171633 +3236370534262726270E01070615141633323602C16440334675482E3217231C1D14212D +201834522AFE522F018A1C35282329042C2426303244744D1A222F312E3445FF00091017 +151F1A0F301F1A354FD55B8D503A50833D362730172375331D277F023BFD2C029220190E +1715346822254F3A55801C271E43891610080B170F17194C571F27820003004CFFEE02D3 +029A002F0039004300002517062322270E01232226353437363726353436333216151407 +060716173E01353426273533150E010706071E01333203342322061514173E010326270E +01151416333202C80B2A5A4A3C395B3354624B258F0756492D3A3822521B3C2E21151EBC +25232714421E392325A2272029053D2E5131364A59453B3D3F0B464F2D2251485A3D1E3E +2A34586A362837301E2A8E5F343C1B110D021311041D391E58322C021D3E424026262642 +FE493EBE1D633938460000000001008401A500F1029A0009000013233637363332161514 +9B170D0A092A101301A5A82627120D1D0001002AFF4B013B029D000E0000011706021514 +161707263534373E01012E0D6B61141C12634A1B50029D0F65FED4A8496F4C069FCAA979 +2C53000000010010FF4C0121029D0011000013371E0117161514070607273E0137363534 +AB1323200D134E34820D3E431A310298053F4F37594F948659710F417451989991000000 +0001008000FF01EC029A004F0000011716171615142322272E042F011617161514223534 +37363506070E0107062322263534373E0137272627263534363332171617342726353433 +321615140706153E0137363332161514070E01014825201F4026141A080C10061A031201 +13084A0A12030D191A141B180F143E2324210C2D303D130E161B2A3112082311130B1021 +1A291313111239222701CB131004092325200A0D0E0512020D4035180A28260B1E323E02 +080F171921110F260A060E14071A06072510171F2F1D303B1B0F2A17120A26333516172B +1516112407041000000100560000024E01FA000B0000252315233523353335331533024E +DB42DBDB42DBDCDCDC42DCDC0001FFFBFF7F00870065000E000017273635342726353436 +3332161514050A4C14191D161A20811231290E0F131E121A2B1F58000001003100C0011A +00FF0003000001072337011A0DDC0E00FF3F3F000001001BFFF5008A0064000A00003714 +0623222635343632168A22181520202E212B162020161821220000000001FFBFFFEE0182 +029A00030000090123010182FE8649017A029AFD5402AC0000020020FFF901F102A4000D +001C0000011406070623222635343E01321607342322070E011514163332373E0101F168 +4D3D414C52578E9A524F54433527402C2549382B3601A96EE0372B766973D683821C825F +47F6503F48644CDF000100320000019902A4001D0000013332151407030615143B011521 +353E0137133635342B01220735373E0101910305068F124215FEE93B2D08880823031526 +17305702A4070815FDF9400A200F0F04161D01E81E061F020F050A100001000C000001C4 +02A4001E000013273637363332161514060F011533323637170721353736373635342623 +22651522303749475B3B5ABCB52B2A121132FE9ED26225093E396001FB0753252A614832 +5C5CC0051B26078611E068531422364300010010FFF901D102A4002C0000132736333216 +15140607151E011514070623222635343633321716333237363534262B01353637363534 +262322B210307B3A4A4556322D5D4772353A18131E222423342926554B1877383E30283D +02370568443637441E0313453875523E201913181E202E2D514F4F1015272B3F29320000 +00020001000001DF02A4000A000F00000103330723072337213701072301153301DF7960 +0E652E4F31FEFA1401A14104FED0D402A4FE4D3FB2B24301AF71FEC2040000000001000F +FFF901EB029A001D000001072307161514062322263534363332171E0233323635342726 +27353701EB17E627DCAE7C2F3B171116220F0F140E446B35376F68029A47582AB57BA81C +1D11191C0D09078053522B2D1010DA000002001EFFF9020902AE00140024000001150E01 +0717363332161514062322263436373E010334262322070E011514163332373E0102095A +B43404223A46619569585A615746822941343E24171D372B402F1A2002AE100C875F0215 +644C72A472CABF443732FE803644382373373C47452770000001004BFFF80219029A000B +00000901230127232206072737210219FE7C46016F02D73036250F5201750290FD680256 +031D2C0A880000000003001EFFF901ED02A4001900240030000013352E01353436333216 +15140607151E011514062322263534362534262206151416173E01033426270E01151416 +333236E132256452505D4A5A4132795E54736701243B6036263940323827534451553639 +4B01680138432847514B3D3B481B013C5636536954544567C536453B31213C351E3BFEB3 +2B3C561A68413B4B5600000000020017FFEF01EC02A40019002800000127062322263534 +3637363216151406070E0107353E01373E01133426232207061514163332373E01016004 +48394254322B3FAC5F645C3C75643B56382E454E342F3E2E3534304329151F0110022D64 +4F3B712739756568BF472E2E11130D2C2E265D011A3E463D466943482E16770000020032 +FFF5010501B9000A00150000011406232226353436321602140623222635343633320105 +21181620202E2164211816202117160180171F2016182121FEAA2E1F201617220002001A +FF7F010501B9000A00190000011406232226353436321603273635342726353436333216 +1514010521181620202E21E10A4C14191D161A200180171F2016182121FDE71231290E0F +131E121A2B1F580000010054FFF6025002040006000005253525150D010250FE0401FCFE +5C01A40AE642E648BFBF0000000200560078024E01820003000700000121352111213521 +024EFE0801F8FE0801F8014042FEF64200010054FFF602500204000600002505352D0135 +050250FE0401A4FE5C01FCDCE648BFBF48E6000000020084FFF401D802980020002B0000 +37273E0137363534262322061514171615140623223534363332151406070E0107171406 +2322263534363216D8120E273F542B25212508061911244C3B99305337340B0E2015171D +1E2C1FAE034F4D3F5450232F19170A110D0C0D1731303D7C31493E29452CA4151D1C1618 +1F21000000020076FFEE0326029A0030003D000025170623222635343633321615140623 +22270623222635343E013332173733070615143332363534262322061514163332033423 +2206070615141633323602AE0C7A6696CED39891B46E4A4910344526352F5E3932120A45 +3D07282F51AA757B9B9D80610D281434132F1F1C2B4C441D39C38E91CAAE77608B48473B +2E3675563425EC1B0C287A4F6B9CB08D83A601753E1E193E4A272D880002FFCD00000234 +029C001A001D00002123353E0135342F012307061514171523353E01370133131E011727 +0B010234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D951002181B0812836F29 +191E03101007326301F0FDD73E2203F60107FEF90003FFF80000024C028D001800240031 +0000133332151407060715161514062321353E0137133635342627133332363534262322 +0E01070307061514333236353426272682FAD040224D7E957EFEF0291D0D790B1E2F7626 +6267383D17110E05492023435869261D22028D96462B1710012670626610061C2E01B427 +19160F04FEEE4E4A3834030F12FF007785072A58522D400A0C00000000010042FFEE02B1 +029A00200000010727262322070E01151433323637170E01232226353436373633321716 +33323702B1251212836D5A3537AF3E67401243814D759066556675453014151B0B0298C8 +03A36138A055CD34430F4F468B7A67BB3C491107160000000002FFF8000002BC028D0013 +0024000013213216151407062321353E013713363534262717030615143332373E013534 +2726232206830115869EA06AB9FEFF2B1B0C7A0B202CB3761644945D343A4E2F54201F02 +8D8C81B17C531008182C01BD271415130135FE5A500F255A329F4F793B230F000001FFFF +0000027A028D00300000010727363534262322060F013332363717072736353427262B01 +070615143332373637170721353E013713363534262735027A1F16034A762A1A05424E42 +2C1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E028D9A0219152D1C +0911E9204104E8051F1518070F707F132227144E08A21008172B01BA251B141104100000 +0001000800000285028D003000000107273635342E012322060F0136323E033717072736 +3534262322270706151416171523353E01371336353426273502852015031C505B231C05 +40312D34111C0C0D124C110823281C373B0E222BFB291E0C7B0A1B31028D9A02160B2721 +0E0D13E30109071B171B05EB0529141C1003D5320A121203101003202B01BA2419150F04 +1000000000010034FFEE02D2029A0031000001150E010F01062322272E01353436373633 +3217163332371707272627262322070E011514163332373E01373635342F013502D23125 +0B316F7E894C232764536874483E1A1224180F31120322305D754E32386A604722090E10 +1A2F20013F10031B2ABB3E4C236A3467B63A48180A2205C503402A3B633FA052636E2209 +2339571A1D0403100001FFF800000301028D0033000001150E0107030615141617152135 +3E013F01210706151416171523353E01371336353426273521150E010F01213736353426 +273503012C1C0C84031E31FEEE33290A40FEE34203192DF5291F0E760C1D30010F2D2D0A +36011D2E0E2028028D1008182AFE1F0C0C1510051010042227E9F40A0C16100610100420 +3201AE2E12150F051010032024C6A4340B131403100000000001FFF800000180028D0017 +000001150E01070306151416171523353E01371336353426273501802A1B0D780F192CF4 +2A1F0B780D2028028D10051A2EFE533517140E051010081A2901B92E1014150210000000 +0001FFFAFFEE01EB028D001C000001150E0107030E012322263534363216151406151432 +3713363426273501EB2B1A0D671D5452354019281F0346127F0C1E2F028D1006172DFE91 +686E302818201B13061104274001C62C2811041000010007000002D2028D003500000115 +220705131E0117152135373635342E032F010706151416171523353E0137133635342627 +3521150E010F01373635342F013502D21C31FED6A817222FFEED1D2E030A0610027B410B +1D2AF72B1A0C7C0E222D010E2A2E0B35998F2418028D1025E1FEDB281505101003041C06 +0E130B1C04D7ED260D171104101005182A01BD320C1514021010031F28C26D65240F0403 +100000000001FFF80000022F028D001D0000250721353E01371336353426273521150E01 +070306151416333236373637022F3AFE032A1A0E7A0B1F2E01112E2B0C780A2C3D4D4324 +2929B4B410041A3001B62519161203101003202AFE53261017120E181B5500000001FFEE +00000368028D0028000001150E01070306151416170721353E01371301230B0106151417 +1523353E01371336353427353313010368291D0B7B0E242D01FEF131280D7EFE88113E76 +0946C5282517720C4AB538014F028D10071B28FE45320C141501101005222F01CBFDCF02 +22FE5420162B051010052F520191290B1E0410FE1401EC000001FFECFFF102D7028D0022 +000001150E030703230B0106151416171523353E013713262335331B0136353426273502 +D71B15190D109212E672091F27C62B241B75173FA0CF6A081B2A028D100607232A37FE05 +0226FE5A1F17151303101006335F019F3610FE0D01841C101B1404100002003CFFEE02BB +029A000F001F0000011406070623222635343637363336160734262322070E0115141633 +32373E0102BB735C696D6377624F707D6B7669453C6357353D433F5C4E3B4601A26BC43E +477C6E5BC1456101863447517548B64E52575F4AD6000000000200000000025D028D001C +0027000013333215140607062322270706151416171523353E0137133635342627170716 +333237363534232292F1DA251F45883723350E1E27F42B1B0E74111C2BAE451D17572947 +822C028D94284D1A3908C136091213041010061B31019E3D161411052DF50518285E7B00 +0002003BFF4A02BB029A00270037000017273E01372E0135343637363332161514060706 +0F013332171633323637170607062322272623220134262322070E011514163332373E01 +450A3A3344555B5C516F82657C8E72446036192358561936482A10181C536C425237263B +01D5453C6E542D3D443872542E3DA91029283C117B615DB8465E866775E13C2506391415 +212C0B22194B1A12026A47527D43B9494B5B7C46C00000000002FFF30000024C028D0023 +002E0000133332161514070607171E0133152303270706151416171523353E0137133635 +34272627170716333236353426232284FC616B42275C59112521937B42370D1A29F32821 +0C770C0E0C2DAE3F1F1459643E3A31028D4C414E321E15EB2E2410014405CB2E11171404 +101005222E01AF2E11120A08062CE3055046343C00010011FFEE01FC029B003500000107 +273426232206151416171E0115140623222726232207233717061514163332363534262F +012E01272E013534363332171632363701FC28123245343C2043432E73572642200F210A +12221402513F3A491A2227031C082D1E674B34211D22120A029BC8035350352F26334344 +5232556E170C20E002090E495E47392135252A031F08303D294C580E0B0A10000001003B +00000279028D001A00000107273635342B0103061514161F011521353E01371322060727 +3702792C1103653A890E161F23FEE0342C0B8F775324122A028DA402191551FE16311117 +1103031010051E28020F2B51049B000000010066FFEE02FD028D002C000001150E040703 +060706222635343F0136353426273521150E010F01061514163332363F01363534262735 +02FD1515140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B54662240171E2802 +8D1005090D242A2CFEE37435345C4837A1BD200D1414011010052234E28D2635416C7CE9 +55121614041000000001004CFFEE02B0028D00180000011506070123032E01273533150E +011514171B01363534273502B02018FE8B135211172AF02F1D0141D2313E028D100127FD +9901F7692A05101005141A0A05FE450168541F1B0710000000010047FFEE038A028D002E +000001150E0107012303230323032E012726273533150E011514171B01272E0127353315 +0E01151416151B013635342735038A1D1B15FED3143205DE133D080A0B0A29EB291F012C +AC08041B2CEC271D0228BC1743028D10091A28FDBC01C5FE3B0202462D0B0A0510100414 +1B0D07FE6C015C412616021010041418030B01FE64016D2C182901100001FFE30000028F +028D0031000001150E010F01171E01171521353E0135342F0107061514331523353E013F +01032E01273521150E0115141F01373635342735028F213725A965122433FEF12B220E43 +971742D528486C53670F252B0107291E103B91153D028D100B292BC2FF2D1A0610100111 +131023A5AC1A1621101007437E6100FF251C041010051113112793A718131F0310000000 +0001004E00000279028D0026000001150E01070307061514161F011521353E013F01032E +01273533150E01141F01373635342F0135027915161BDB241E171E24FEDF332B0D3B4C0B +1929EF2C1C241F2795281D028D10071521FEF27B6718131002031010031F2FCD010B2816 +061010040F227F6E2EB02518040310000001FFFA0000025E028D00150000090133323637 +363717072135012322070E0107273721025EFE1DA1474B1B29241336FE0701DFAB662816 +1517132D01E6027FFDA50C141E4A03A90E025B1B0F1F2B059300000000010015FF670187 +0297000E0000010723220607030615143B010723130187073E1515059E06313807BCC502 +971B1214FD6518051C1B03300001FFD7FFEE013F029A0003000005230133013F47FEDF48 +1202AC000001000CFF67017E0297000E00000103233733323637133635342B0137017EC5 +AD073E161405A0043139070297FCD01B1217029E10091A1B00010000012D01A6029A0007 +0000012303230323133301A6448F018E44B53C012D0121FEDF016D0000010000FF8301F4 +FFB5000300000521352101F4FE0C01F47D3200000001007801EC01370298000900000123 +27263534363332170137208C131410161201EC6A0F1110121700000000020011FFF501DC +01B90020002E000025170E0123223534370E0123222635343E013332173F021706070215 +143332370334262322070E0115143332373601CF0D38351E2817385E352C38578641430D +0B033D070105590E0F25471E1A443F212C3C3B444D6F0B442A29195A54493D374C9E663A +300307030411FEBC270D2901171A205C31782E4A626F000000020017FFF501D902AB001A +0028000013333E0133321615140E012322263D0113363534262735363717071334232207 +0E0115143332373E01A30132593634405E974B2E548E09132D4B4E05158D46454420282E +42392E400122534440384D9C63201506020B240B10070211090E0552FEDE567336832E22 +3B3085000001001EFFF501A901B90022000025170E012322263534363736333216151406 +2226353436353423220706151416333236015E102E5738474C44374D5A2E3B1C28130F25 +3F2E5B2E2B27406B0A3A324E4C437D2D3D2E23141C1711091D0A14306089363C26000000 +0002000FFFF3020F02AB00270036000001170F010615143332363717062322263534370E +01232226353436333216173337363534262735360334262322070E0115141633323E0102 +0906273C41120D1D280C4D4613160D2B53343239C1631F1D05012D0E162A4D591D122F2C +32421F1B2D5C3B02AB0698DBEF0714192E0A7218151E3B493B3D3877D81C1EA331161008 +021107FECE1A1F2E35933E22276298000002001FFFF5019C01B90015001F000025170623 +2226353436333216151406070614163332362707363736353423220601660C58743D4ABB +70272B99830A332A213C9811703132232A5B6D0C6C4B3E75C624204268111452321FBB2C +1B2F3031276100000001FF6DFF3101A802A6002E00001333363736333216151406232226 +353436353423220607330723030223222635343633321615140615143332371323295D27 +21364E253119110E170A162A3A166B066C484088222D1610111509143E23585B01AC7E2F +4D231B111A16110A12030D6D7720FEC0FEE5231A1119151007110510A201A40000030008 +FF3201D701B9002800350044000001231615140623222726232206151417161514062322 +26353436372635343726353436333217163B010734262322061514163332373603342627 +262722060706151433323601D731097346120D03040D17478E70624E592D481B4D4F7A4F +3A280B053C771B1D304A201C3223211B3F50180A072F0B147F3B4C016E12213F6503011A +0F1812225D4354373527372B101730241C504A611C08392720644823283B38FE87232F19 +0704260E1921663700010013FFF701DE02AB002D000025170E0123222635343F01363534 +232206070E010723133634262B0135363717033633321615140706151433323601D10D2E +3E2614181437071C1B55281F241D4B920D1A0F1B4C510779826421203021100C1D760D41 +311713114BCF1C0519503C305B6F022A32180D0F091206FE41D3251E2B966518121D0000 +00020031FFF50108028E000A00230000011406232226353436321603170E01232235343F +0136353426273536371703061514333201081D14161A1B281E2A0D2A3A25311630091729 +2E72045E0A0E190257151E1D18171E21FE050B4131371D52B1200C0F080110041503FEA9 +220A0F000002FF84FF310117028C000B002A000001140623222635343633321607030E01 +232226353436333215140615143332363713363534262B0135363701171E14171F201514 +1F21681F5B41232C1811270C121E291A4810151B1A2D7C0256141E1D17161E21B5FE6679 +72221B121A250C0F070C4E680124410F110E1003160000000001000EFFF501CD02AB0029 +000001150E010717163332373637170E012322262726270F0123133637262B0135363717 +03373635342B013501CD24506D25371A1518050A0F232E1E1623181E1E28304B8D0E0403 +30124B5206782B8F250E01AC1002376158812A08120B3F2E232F3A5320B4021032261810 +091206FE38216E1E1210000000010029FFF5011702AB00190000010306151433323F0117 +0E012322353437133635342B0135363701179908111B28190E2D41293104890331124655 +02A6FDB71D0E1239230A473638110D020C0A0916100813000001000CFFF702C001B90042 +0000250706232235343F01363534262322070E01072337363534232206070E0107233736 +35342B0135371707363332161514073E01373633321615140F0106151433323F0102C005 +4D43280D3A070C082F54252A1D4B1B441619522A1923244B2B372919A2033A825A1D1F28 +354724201F1B1F03440A0C162B1569076B2E0F33E31C0A090C753469735DEB24194F4027 +587790B72D16101F02D1D3201C267254551714231C0D0FFB260710351A0000000001000E +FFF701DA01B90031000025170E0123222635343F01363534232206070E01072313363534 +2627353E013717073E0133321615140F010615143332373601CC0E333623141B102C0E18 +1D4532211F244B60021B2627601B04434A6C311F220A380E10132A07750D462B1B1B103B +A236191D4549314E79015E0A070F0B011008110602DA7666211C1923CB320B1235080000 +0002001BFFF501D401B9000F001E00000114060706232226353436373633321607342623 +2207061514163332373E0101D439315369464D654F38403E4F542621433D4A2A243D3C22 +28012D397B31534942539E2A1E4B282D315B71732C2F532E810000000002FFB5FF3301D8 +01B900210031000013073633321615140623222706151416331523353E0137133635342B +012737321506173426232206070E0115141633323736D71D4B60373CBB6E2421281B23CB +201B0973102C16029C0501A61D21274E12162B1B154E434501AC6471423B7BCC11A50110 +0D1010011A2401B03A0D1B0F160405772C29442E399C1911155F600000020019FF2F01E4 +01B9001A0029000001030615143B011523353E0137130E0123222635343E013332173707 +34262322070E0115143332373E0101E4A502390FED2E24095A3B5A3B2F355989433F0E10 +1F221929343342352929335301ACFDBD08031F101003181D01225E463E364D9E653E3145 +192331318C3E4D242CAF00000001002D0000019C01B9001E00003F013637363332161406 +232227262322070E01072313363534232207353717B010223C2B24151A1916131008081B +372229244C51102608179B03DF234B3F2D1C2E1F1A0E5E3A677901243B171A03111B0200 +00010010FFF3016E01BA0029000001072326232206151417161514062322272623220723 +3733163332363534272635343633321716333237016E14100E4B1B203644513F1D1A1517 +140910141014502428383F42371424160E140A01BA8C741E1B26404F393D490A09159F88 +28252D464F34323A0A07120000010026FFF5012802220021000001072303061514333236 +37170E01232235343713232734373E013736333215140F010128055457020F0C20250D2E +3B262E104E4B012119521A060809011C01AC20FEB80806101F30074633250A4001280612 +0706412709080502670000000001002AFFF501DB01B9002A000025170E0123223534370E +012235343F01363534262335363717030615143332373E0137330306151433323601CD0E +303624302D59616A1B230E16274551045903142D612125284A4F130B0C227709492E302D +98926535126E8B3A090C0C0E081303FE9D0907199332536EFED548061120000000010014 +FFEE01AA01B9002D00003F013E013534272635343633321615140706070E0623223D0134 +272E01232207353637363332161716CF1F36531219161018203F3740091A0C13090C0804 +09180A171C140B37390B09060711124620378D230C1116170F1325163A615544091C0E13 +090A031B188A8C3924010D090C041A585B0000000001000FFFEE028801B9003500000113 +3E0135342726353436333216151403070623222703070607062322262F0126272E012B01 +3536373E0233321617161713363332019320634216151710161ED409230A08031D591720 +310F0704020408130711171C4F10050D08030806051907C006070801AAFEA07B69200E15 +14150C1320185BFEFE0B2B23013698263D5E121D5BA44E1B0F0D0D040104020A1878A101 +300A00000001FFE5FFF501BF01B9003F000013173E013332161406232226232207141F01 +163332373E0237170E012322262F01070E012322263534363332171633323F01272E0123 +220F01273736333216F30C3541211217130F091E0D1D4508200A14101F030806040F2B32 +1D181A091D581F2719171B130F0C140F0B1122521C0A11130C1B12030B50251316016339 +513E15201411790D20862928040B090409432F1E2677772A1A16140F150B0931747C2C18 +070510041D2400000001FFE8FF3201AA01B90033000037173E0135342726353436333216 +1514070E012322263534363332163332373E013736353426272E01232207353637363733 +3216F31543371A1A1711161E834E932B171C1710131E0C121E0D33070A3E140E211E100F +160A2942040B3DBA6E6E7320101011161015201757DA819E181311171C210D430B140F1E +FB33251B04110402080ABA000001FFFEFFAF017C01AC0024000009011E01171633323534 +2726353E0133321615140623222726232207270123220607273721017CFEDF2E2D1D2224 +1B050801140C101334292A47442517160901368828240F1020011B01A1FEA80A1E2B320F +050A100C0D1514111F2D2C2A120901741725047400010033FF4F019702AF002200000107 +0E010F010E0107161514070615141617072E0135343736353426273E013F013E01019703 +2F2E102D103131382C16181D0345382022171E302B0F30165202AF0B0C353BA83E360D18 +271F9F54121C1E080B022329216F742516180B0D2E37B24F3D00000000010069FFEE00AB +029A0003000017231133AB42421202AC0001FFF9FF4F015D02AF0021000013371E011514 +0706151416170E010F010E0123373E013F013E013726353437363534DD0345382022171E +302B0F30165354032F2E102D103032382C1602A40B022329236D752416180B0D2E37B24F +3D0B0C353BA83E360D18271F9F5015340001002800B701F601430017000001170E012322 +27262322060727363332171E0333323601D2242139232E393B36192F0D242C4E2F43041D +181E0A1D2301433230262123252332561F020E0B0A1D00000002003BFF33014101DA000A +001600000114062322263534363216071706070E01232235343736014120141620212C1D +61112E1A0A211B28263201A3141F2016151F1F9E04CCA63E363229536C0000000002004D +FF7101D80230002A00320000010716171615140623222635343736353427031633323637 +170E0123222707233726272635343637363F01031306070E011514019E2A2D191E1B1512 +15090623890B0D26402B102D583606052F1F3025153251472A472AB6842F26292D023078 +04121723141C16120C110D051401FE7904252E0A3A33018588081127534D8E2C1A0B78FD +F901780A262A7C3D440000000002000AFFF80205029E002F003A000013333E0137363332 +161514232226232206073307230E01071633323F011706232226270E0123222635343633 +321737231726232206151416333236526D0F2B2639502934271E0C222A2D237C077F1317 +1F5A4627280A0B29652539321C291B1F223028221C197049231A171E191214280169576D +2D442A2229586AAE2A514244261C0709641A27251A221E1C280BCCF61919141015200000 +0002FFEAFFF6020A0216001B002700002507270623222707273726353437273717363332 +1737170716151407273426232206151416333236020A30623B42443B6032622929623260 +3C43453862306027271D553B3E58563E3D5528326229296232603B434539623060272760 +30623944433C7F3E58573E40595800000001001C0000025D028D0037000001150E010F01 +3307230F013307230615141F011521373E013F012337333727233733272E01273533150E +0115141F0136373E0137342F0135025D191B17B5950CA80618B20CB21F3624FEE504362C +0D19A40CA31703A20C8B3F0C1C30ED2E1B271D13095D44012A19028D100B151DE0280852 +285E18290403101005202D5428500A28D82817061010040E101B8164150C6C5D19180403 +1000000000020069FFEE00AB029A0003000700001323113311231133AB42424242018701 +13FD54011300000000020035FF5E01CD029A00450053000013372E013534363332161514 +0623222634373635342623220615141F0116151406232227071E01151406232226353436 +33321514070615141633323635342F012635343633321734262726232206151416333236 +F902261D534039491C18141A150D23192B311A7B294135130E0227185943394F1E1A2F1B +0F2B1C2D39324C473E2C199C192039321C26792C1B26018E032F3C213746382B191E1928 +120C070D122D2628209632403945070332311E3B4E3E2E1A1E2B1E100A070F163228273A +5752473348D919342B4C261C35942A000002006B01FC0195025E000B0017000001140623 +222635343633321607140623222635343633321601951D14151C1D15131DC81D14151C1D +15131D022D141D1D15141C1E13141D1D15141C1E00030029FFEE02CF029A001D00280034 +000025070E010706232226353436333217161D0123262322061514163332363F01140623 +2226103633321607342623220615141633323602210F01080C402F5F7079612E3515100F +5C434749442B3815BEC58C8FC6C68D90C337A57674A9A97473A8F044080604126655596F +12060F45585953545B282C4E8BC5C6011EC8C8937FB2B27B7AB1B0000002002A01960160 +02A4001F002C00000137060F0106151433323F01170E0123223534370E01232226353436 +3332173707342722061514163332373E01012B35300603010407180E072521141F112633 +25242A7B423209071C13285F120B2728141A02A102A8230F030407180F0A291919132B33 +2425214682231E3324027B3710183C1D4500000000020035002501BD01930016002C0000 +373536373633321514070607161716151423222E01272627353637363332151407060716 +17161514222E012726D767185809065B1F22060E3E07061C27092CB967185809065B1928 +060E3E0E1C250A2CD80950154D071D531C270E1A74110721340A32220950154D071D5317 +2C0E1A74110721330B32000000010056006C024E018200050000252335213521024E42FE +4A01F86CD44200000001003100C0011A00FF0003000001072337011A0DDC0E00FF3F3F00 +00040029FFEE02CF029A001F00260031003D00002523222627262723151416171523353E +013D013426273533321615140717161727333235342B0105140623222610363332160734 +2623220615141633323602333F0D1E1A151E2F0C24A0240C0C24AF353D4F392E1DE62A44 +45290182C58C8FC6C68D90C337A57674A9A97473A892222D252D61200D040F0F040D20ED +210C040F312A47204E3E16AA594EAE8BC5C6011EC8C8937FB2B27B7AB1B0000000010063 +0214019B02470003000001072137019B0BFED30B02473333000200650186018302A4000A +00150000011406232226353436321607342622061514163332360183533E3A5353785327 +3D563D3D2A2B3E02153D52543B3C53533C2C41402D2C4140000200560000024E0238000B +000F000001231523352335333533153311213521024EDB42DBDB42DBFE0801F8011AA0A0 +42DCDCFEA442000000010021010F014402A4001C000013273637363332161514060F0117 +3332363717072335373635342623225B0D121927352E41224172026C1B1A0C0B21E7855D +2E2032023C082A15213B2A1A2F3F6E050E14084F12825B301C2700000001002B010C0153 +02A400280000132736333216151406071716171615140623223534363332171633323635 +34262B0135363534262322950A224728372B3A041C0A15685149110D0E181C0F2430352F +0D931D15220260073D2C201D2614020E0A18264558270C10111335282E33091C42161E00 +000100B401EE0193029800080000133736321615140F01B4A20A1E1511AB01EEA00A150F +120A6A000001FFE2FF2F01F101AC002B00000103061514333237070E0123223534370623 +222706151416151406232234373637133303061514163332371301F14E061A172103202E +1D39044D4D4C1B0202272022261A0855584303252044364901ACFEB01B071C1A0920193B +0C135A4C151B19221A414D5A4942220176FEE40A14202546013900000002003CFF85026D +028D0022002C000001070E0107030615141F010723132303233736373637363F01262726 +353436373E0133031306070E0115141716026D06382009A00B31170594C83AC894053A10 +0A0312321E47252D201B22614D684F3E271C24210F028D15041422FDA82A081404021502 +F3FD0D150508060933C27003191F3D2F5F202824FEA30148042D20632E351E0F00010046 +00C700B50136000A00003714062322263534363216B522171620202E21FD162020161821 +220000000001FFE2FF2700B6000000170000330736333216151406232227371633323534 +262322072737772C0F0A262C4334263711291B351A140C14093C4203251F2630151D0F29 +13190708600000000001002B010F011C02A4001800000133321407030615141617152335 +3E0137133635342B013536010E0905045613141DB5231A045A0515222102A40A0BFEEE3A +100B08011010040A0B011D0F06111305000200430196016A02A4000B0018000001140623 +222635343633321607342326061514163332373E01016A7D492C357A4C2B36491F294D15 +111D141B23025045752D2648732F162F01743F1519161D5700020037002501BF01930015 +002B000025070607062322353437363727263534321E011716170F01060706232235343F +012726353433321E0117161701BF242F2C570A065B231E143E0E1B27093112A2242F2C57 +0A065B41143E07061C260A3112D71B23274D071D531F242872130720350A391B091B2327 +4D07135D432872130721330B391B000000040021FFF602E002A4000A000D0011002C0000 +01033307230723372337010F013313012301053536373633321514070306151416171523 +353E0137133635342302E04F37093A1E3F20A80D010F2FBD8138FE393301C7FE34093D34 +0805035613141DB5231A045A05190196FEFD2B68692A010349BA0211FD5202AE2913010B +0A050709FEEE3A100B08011010040A0B011D0F061100000000030022FFF602ED02A4001C +0020003B000001273637363332161514060F011733323637170723353736353426232213 +012301053536373633321514070306151416171523353E0137133635342302040D121928 +352D41224172026C1B1A0C0B21E7855D2E20324FFE393301C7FE43083E34080503561314 +1DB5231A045A0518012D082A15223C2A1A2F3F6E050E14084F12825B301C270142FD5202 +AE2913010B0A050709FEEE3C0E0B08011010040A0B011D0F0710000000040017FFF602E0 +02A4000A000D0011003B000001033307230723372337010F01331301230105273E013332 +1615140607151617161514062322353436333217163332363534262B0135363534262322 +02E04F37093A1E3F20A80D010F2FBD8139FE393301C7FE2A0A0F382228372B3A1B0F1568 +5149110D0E181C0F2430352F0D931D15220196FEFD2B68692A010349BA0211FD5202AE44 +071C212C201D261403071018264558270C10111335282E33091C42161E0000000002001C +FF33016F01D9000A002C0000001406232226353436333207170E01070E01151416333236 +3534263534363216151406232226353436373E0137016F1E16171E1F151634110D243F31 +262A2620260F1624154F3E454E335336330A01BB2C1F1F16151FBC024B4548394627282C +1A16081E0D11151B17303D3D3C2F4A442B422A000003FFCD000002340392001A001D0027 +00002123353E0135342F012307061514171523353E01370133131E0117270B0101232726 +3534363332170234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D950145208C13 +141016121002181B0812836F29191E03101007326301F0FDD73E2203F60107FEF901E06A +0F111012170000000003FFCD000002340392001A001D002600002123353E0135342F0123 +07061514171523353E01370133131E0117270B01133736321615140F010234F52D200214 +DC3B163EBA222B39011D1A5C0A1C28C32D9575A20A1E1511AB1002181B0812836F29191E +03101007326301F0FDD73E2203F60107FEF901E2A00A150F120A6A000003FFCD00000234 +038F001A001D002400002123353E0135342F012307061514171523353E01370133131E01 +17270B01012327072337330234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D95 +0175245289279D311002181B0812836F29191E03101007326301F0FDD73E2203F60107FE +F901E06868A900000003FFCD0000023C036A001A001D003100002123353E0135342F0123 +07061514171523353E01370133131E0117270B0101330623222726232207233E01333217 +163332360234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D9501711C16491227 +4718240F1D0F2E272036291514171002181B0812836F29191E03101007326301F0FDD73E +2203F60107FEF90264680F1D2F3A3018131300000004FFCD000002340358001A001D0029 +003500002123353E0135342F012307061514171523353E01370133131E0117270B010114 +062322263534363332160714062322263534363332160234F52D200214DC3B163EBA222B +39011D1A5C0A1C28C32D95017B1D14151C1D15131DC81D14151C1D15131D1002181B0812 +836F29191E03101007326301F0FDD73E2203F60107FEF90221141D1D15141C1E13141D1D +15141C1E0004FFCD0000023403BD001A001D0028003300002123353E0135342F01230706 +1514171523353E01370133131E0117270B01001406232226353436333216342623220615 +141633320234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D95014A3B292B393B +282A19271C1A27251C1D1002181B0812836F29191E03101007326301F0FDD73E2203F601 +07FEF9027C523A3A2A283B803826271A1C2600000002FFE50000038F028D004000430000 +010727363534262322060F01323E0337170727363534262B010615143B01323637170721 +353E013F0123060706151416171523353E0137013E0135342F0135170133038F21110145 +7B2316073D36342F19130E124511062D492847331E706F391240FE0E2C1D0731BE140564 +1828BB131D2401711709261A7BFEF4AD028D99020D17351D0D18DE0408191D1F04E80424 +101F13FD0D1D365205A410041319AA190578250F0B051010041B2D01D11D0F091703020F +28FEAA0000010042FF2702B1029A00380000010727262322060706151433323637170E01 +23222707363332161514062322273716333235342623220727372E0135343E0133321716 +33323702B125120C8D31672B6CAF3E65431143814D1A18230F0A262C4334263711291B35 +1A140C140938516171BC6931441D0C1B0B0298C803A3352D72BACD34420E4F4604340325 +1F2630151D0F29131907085A12895E6DC4761107160000000002FFFF0000027A03920030 +003A0000010727363534262322060F013332363717072736353427262B01070615143332 +373637170721353E01371336353426273525232726353436333217027A1F16034A762A1A +05424E422C1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E0185208C +1314101612028D9A0219152D1C0911E9204104E8051F1518070F707F132227144E08A210 +08172B01BA251B14110410596A0F1110121700000002FFFF0000027A0392003000390000 +010727363534262322060F013332363717072736353427262B0107061514333237363717 +0721353E0137133635342627353F0136321615140F01027A1F16034A762A1A05424E422C +1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2EB9A20A1E1511AB028D +9A0219152D1C0911E9204104E8051F1518070F707F132227144E08A21008172B01BA251B +141104105BA00A150F120A6A0002FFFF0000027A038F0030003700000107273635342623 +22060F013332363717072736353427262B01070615143332373637170721353E01371336 +353426273525232707233733027A1F16034A762A1A05424E422C1D1244140707113D4E20 +244F814A2638103EFE052B1A0C7B0B1F2E01B8245289279D31028D9A0219152D1C0911E9 +204104E8051F1518070F707F132227144E08A21008172B01BA251B14110410596868A900 +0003FFFF0000027A03580030003C00480000010727363534262322060F01333236371707 +2736353427262B01070615143332373637170721353E0137133635342627352514062322 +26353436333216071406232226353436333216027A1F16034A762A1A05424E422C1D1244 +140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E01C01D14151C1D15131DC81D +14151C1D15131D028D9A0219152D1C0911E9204104E8051F1518070F707F132227144E08 +A21008172B01BA251B141104109A141D1D15141C1E13141D1D15141C1E0000000002FFF8 +0000018E039200170021000001150E01070306151416171523353E013713363534262735 +2523272635343633321701802A1B0D780F192CF42A1F0B780D20280105208C1314101612 +028D10051A2EFE533517140E051010081A2901B92E1014150210596A0F11101217000000 +0002FFF80000019E039200170020000001150E01070306151416171523353E0137133635 +342627353F0136321615140F0101802A1B0D780F192CF42A1F0B780D202836A20A1E1511 +AB028D10051A2EFE533517140E051010081A2901B92E10141502105BA00A150F120A6A00 +0002FFF8000001C2038F0017001E000001150E01070306151416171523353E0137133635 +342627352523270723373301802A1B0D780F192CF42A1F0B780D20280139245289279D31 +028D10051A2EFE533517140E051010081A2901B92E1014150210596868A900000003FFF8 +000001C9035800170023002F000001150E01070306151416171523353E01371336353426 +273525140623222635343633321607140623222635343633321601802A1B0D780F192CF4 +2A1F0B780D202801401D14151C1D15131DC81D14151C1D15131D028D10051A2EFE533517 +140E051010081A2901B92E10141502109A141D1D15141C1E13141D1D15141C1E0002FFF8 +000002BC028D0018002C00001321321615140607062B01353E013F012337333736353426 +2713070615143332373E013534262322060F0133078201148B9B564A6EC1F52B1A0C415A +0C5A2E0B1E2F6C2E16459759333B6C6A2019053F990C028D8984579E38531008172BEC2A +A42719141104FEC9A4500F255A3398516C6F0C11E22A00000002FFECFFF102D7036A0022 +0036000001150E030703230B0106151416171523353E013713262335331B013635342627 +3537330623222726232207233E013332171633323602D71B15190D109212E672091F27C6 +2B241B75173FA0CF6A081B2A501C164912274718240F1D0F2E27203629151417028D1006 +07232A37FE050226FE5A1F17151303101006335F019F3610FE0D01841C101B140410DD68 +0F1D2F3A301813130003003CFFEE02BB0392000F001F0029000001140607062322263534 +3637363336160734262322070E011514163332373E010323272635343633321702BB735C +696D6377624F707D6B7669453C6357353D433F5C4E3B4607208C131410161201A26BC43E +477C6E5BC1456101863447517548B64E52575F4AD601586A0F111012170000000003003C +FFEE02BB0392000F001F0028000001140607062322263534363736333616073426232207 +0E011514163332373E01033736321615140F0102BB735C696D6377624F707D6B7669453C +6357353D433F5C4E3B46D3A20A1E1511AB01A26BC43E477C6E5BC1456101863447517548 +B64E52575F4AD6015AA00A150F120A6A0003003CFFEE02BB038F000F001F002600000114 +06070623222635343637363336160734262322070E011514163332373E01132327072337 +3302BB735C696D6377624F707D6B7669453C6357353D433F5C4E3B462C245289279D3101 +A26BC43E477C6E5BC1456101863447517548B64E52575F4AD601586868A900000003003C +FFEE02BB036A000F001F0033000001140607062322263534363736333616073426232207 +0E011514163332373E0113330623222726232207233E013332171633323602BB735C696D +6377624F707D6B7669453C6357353D433F5C4E3B46281C164912274718240F1D0F2E2720 +362915141701A26BC43E477C6E5BC1456101863447517548B64E52575F4AD601DC680F1D +2F3A3018131300000004003CFFEE02BB0358000F001F002B003700000114060706232226 +35343637363336160734262322070E011514163332373E01131406232226353436333216 +07140623222635343633321602BB735C696D6377624F707D6B7669453C6357353D433F5C +4E3B46311D14151C1D15131DC81D14151C1D15131D01A26BC43E477C6E5BC14561018634 +47517548B64E52575F4AD60199141D1D15141C1E13141D1D15141C1E0001005D00080246 +01F1000B0000250727072737273717371707024630C5C430C4C430C4C530C53830C5C530 +C5C430C5C530C4000003003CFF9702BB02D200190023002D0000010716171615140E0223 +2227072337263534363736333217370901262322070E0115140901163332373E01353402 +9D402B141F4874994D29354A2C565F5C527872353434FE4D016422306E532E3C0195FE9D +20306D532E3D02D2602223384653A57C4D156C7F45835BB64665164EFD7802151A7D44BC +4C3701C5FDEE1D7B45C04C3A00020066FFEE02FD0392002C0036000001150E0407030607 +06222635343F0136353426273521150E010F01061514163332363F013635342627353723 +272635343633321702FD1515140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B +54662240171E2815208C1314101612028D1005090D242A2CFEE37435345C4837A1BD200D +1414011010052234E28D2635416C7CE9551216140410596A0F1110121700000000020066 +FFEE02FD0392002C0035000001150E040703060706222635343F0136353426273521150E +010F01061514163332363F01363534262735273736321615140F0102FD1515140F0E0D53 +223F3CC47B2C3409232D011033270E3F284F3B54662240171E28B8A20A1E1511AB028D10 +05090D242A2CFEE37435345C4837A1BD200D1414011010052234E28D2635416C7CE95512 +161404105BA00A150F120A6A00020066FFEE02FD038F002C0033000001150E0407030607 +06222635343F0136353426273521150E010F01061514163332363F013635342627353723 +270723373302FD1515140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B546622 +40171E2844245289279D31028D1005090D242A2CFEE37435345C4837A1BD200D14140110 +10052234E28D2635416C7CE9551216140410596868A9000000030066FFEE02FD0358002C +00380044000001150E040703060706222635343F0136353426273521150E010F01061514 +163332363F01363534262735371406232226353436333216071406232226353436333216 +02FD1515140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B54662240171E284E +1D14151C1D15131DC81D14151C1D15131D028D1005090D242A2CFEE37435345C4837A1BD +200D1414011010052234E28D2635416C7CE95512161404109A141D1D15141C1E13141D1D +15141C1E0002004E0000027903920026002F000001150E01070307061514161F01152135 +3E013F01032E01273533150E01141F01373635342F0135273736321615140F0102791516 +1BDB241E171E24FEDF332B0D3B4C0B1929EF2C1C241F2795281D8FA20A1E1511AB028D10 +071521FEF27B6718131002031010031F2FCD010B2816061010040F227F6E2EB025180403 +105BA00A150F120A6A0000000002000000000239028D0022002C00000133321514060706 +2322270706151416171523353E01371336353426273533150E010F021633323635342322 +012E38D32C2640803A1F110C132FF32B1A0D7611192AEB1F190724471E16616686260206 +902E551B2E0A3C2A0F1A0E07101006172D01A93F11151005101004111984F8054E5A7200 +0001FF58FF3101ED02A7004A000017133E013332161514060F011516171E011514060706 +2322263534363332161514070615143332373635342726353433323634232207030E0123 +222635343633321615140706151433323617482C795E3E4D4853082B181A1F2E26364B24 +2C18141115080412281F2F511A113C4C3E573A6F1B623C232B15121117080411262F0401 +3FC0AC483B374B1F03030511123F22346C2839251D161916120E0D08030D365671680301 +0E0D659EDAFE566774221C131714100C0E0505084E00000000030011FFF501DC02980020 +002E0038000025170E0123223534370E0123222635343E013332173F0217060702151433 +32370334262322070E011514333237363723272635343633321701CF0D38351E2817385E +352C38578641430D0B033D070105590E0F25471E1A443F212C3C3B444D51208C13141016 +126F0B442A29195A54493D374C9E663A300307030411FEBC270D2901171A205C31782E4A +626FF56A0F1110121700000000030011FFF501DC02980020002E0037000025170E012322 +3534370E0123222635343E013332173F021706070215143332370334262322070E011514 +33323736273736321615140F0101CF0D38351E2817385E352C38578641430D0B033D0701 +05590E0F25471E1A443F212C3C3B444D7AA20A1E1511AB6F0B442A29195A54493D374C9E +663A300307030411FEBC270D2901171A205C31782E4A626FF7A00A150F120A6A00030011 +FFF501F102950020002E0035000025170E0123223534370E0123222635343E013332173F +021706070215143332370334262322070E011514333237363723270723373301CF0D3835 +1E2817385E352C38578641430D0B033D070105590E0F25471E1A443F212C3C3B444D8424 +5289279D316F0B442A29195A54493D374C9E663A300307030411FEBC270D2901171A205C +31782E4A626FF56868A9000000030011FFF5020902700020002E0042000025170E012322 +3534370E0123222635343E013332173F021706070215143332370334262322070E011514 +3332373613330623222726232207233E013332171633323601CF0D38351E2817385E352C +38578641430D0B033D070105590E0F25471E1A443F212C3C3B444D801C16491227471824 +0F1D0F2E272036291514176F0B442A29195A54493D374C9E663A300307030411FEBC270D +2901171A205C31782E4A626F0179680F1D2F3A301813130000040011FFF501F7025E0020 +002E003A0046000025170E0123223534370E0123222635343E013332173F021706070215 +143332370334262322070E01151433323736131406232226353436333216071406232226 +35343633321601CF0D38351E2817385E352C38578641430D0B033D070105590E0F25471E +1A443F212C3C3B444D8A1D14151C1D15131DC81D14151C1D15131D6F0B442A29195A5449 +3D374C9E663A300307030411FEBC270D2901171A205C31782E4A626F0136141D1D15141C +1E13141D1D15141C1E00000000040011FFF501DC02C50020002E00390044000025170E01 +23223534370E0123222635343E013332173F021706070215143332370334262322070E01 +1514333237361214062322263534363332163426232206151416333201CF0D38351E2817 +385E352C38578641430D0B033D070105590E0F25471E1A443F212C3C3B444D583B292B39 +3B282A19271C1A27251C1D6F0B442A29195A54493D374C9E663A300307030411FEBC270D +2901171A205C31782E4A626F0193523A3A2A283B803826271A1C260000030017FFF50280 +01B90026003100400000010736333216151407060706151416333237170E012226353437 +0E0123222635343E013332173716342322070E01073E01372734262326070E0115143332 +373E0101CB163B3F252C42348A0821253C520A2A6D5E39063B5731262E59843B360C13BA +2321201C2216444919CE1914353C252E222A26364901B73E4026203A2B2127221A372F4D +0C333D423814145C46332F4FA86B2F2D5F48231E4040142C2132171C01573680363C293A +A70000000001001AFF2701A901B900390000173726353436333216151406232235343736 +35342322070E0115141633323637170E012B010736333216151406232227371633323534 +262322074D3867B2702E3B1B15270906253F2E2A31302B254129102E573908250F0A262C +4334263711291B351A140C14605A197875B92E23141A260C110B0814302B7E40363C252C +0A3A313703251F2630151D0F291319070003001FFFF5019E02980015001F002900002517 +062322263534363332161514060706141633323627073637363534232206372327263534 +3633321701660C58743D4ABB70272B99830A332A213C9811703132232A5BEC208C131410 +16126D0C6C4B3E75C624204268111452321FBB2C1B2F30312761AB6A0F11101217000000 +0003001FFFF501AF02980015001F00280000251706232226353436333216151406070614 +16333236270736373635342322063F0136321615140F0101660C58743D4ABB70272B9983 +0A332A213C9811703132232A5B1EA20A1E1511AB6D0C6C4B3E75C624204268111452321F +BB2C1B2F30312761ADA00A150F120A6A0003001FFFF501D202950015001F002600002517 +062322263534363332161514060706141633323627073637363534232206252327072337 +3301660C58743D4ABB70272B99830A332A213C9811703132232A5B0120245289279D316D +0C6C4B3E75C624204268111452321FBB2C1B2F30312761AB6868A9000004001FFFF501DB +025E0015001F002B00370000251706232226353436333216151406070614163332362707 +363736353423220625140623222635343633321607140623222635343633321601660C58 +743D4ABB70272B99830A332A213C9811703132232A5B01291D14151C1D15131DC81D1415 +1C1D15131D6D0C6C4B3E75C624204268111452321FBB2C1B2F30312761EC141D1D15141C +1E13141D1D15141C1E0000000002002FFFF5012E0298001A0024000037170E0123223534 +3F01363534262735363717030615141633323613232726353436333217DD0E293C253218 +300916292C730560090A060C2472208C1314101612730C3F3339145AAF220B0F08011003 +1603FEAA220B06092301A56A0F111012170000000002002FFFF5013E0298001A00230000 +37170E01232235343F013635342627353637170306151416333236033736321615140F01 +DD0E293C253218300916292C730560090A060C245DA20A1E1511AB730C3F3339145AAF22 +0B0F080110031603FEAA220B06092301A7A00A150F120A6A0002002FFFF5015F0295001A +0021000037170E01232235343F0136353426273536371703061514163332361323270723 +3733DD0E293C253218300916292C730560090A060C24A3245289279D31730C3F3339145A +AF220B0F080110031603FEAA220B06092301A56868A900000003002FFFF50169025E001A +00260032000037170E01232235343F013635342627353637170306151416333236131406 +232226353436333216071406232226353436333216DD0E293C253218300916292C730560 +090A060C24AD1D14151C1D15131DC81D14151C1D15131D730C3F3339145AAF220B0F0801 +10031603FEAA220B06092301E6141D1D15141C1E13141D1D15141C1E0002001BFFF501E2 +02AB001B002A000001071615140E01232226353436333217372627072737262737161737 +033426232207061514163332373E0101E265564C8851464DAD6B3B1C020E3A75217B2F38 +23423863422720453F462B2346362027027D2D628761AA67474573C53101634B331D362C +17110F2E2BFEAD2B3359617F2A37522F840000000002000EFFF701E80270003100450000 +25170E0123222635343F01363534232206070E010723133635342627353E013717073E01 +33321615140F010615143332373613330623222726232207233E013332171633323601CC +0E333623141B102C0E181D4532211F244B60021B2627601B04434A6C311F220A380E1013 +2A070E1C164912274718240F1D0F2E27203629151417750D462B1B1B103BA236191D4549 +314E79015E0A070F0B011008110602DA7666211C1923CB320B123508020D680F1D2F3A30 +181313000003001BFFF501D40298000F001E002800000114060706232226353436373633 +3216073426232207061514163332373E013723272635343633321701D439315369464D65 +4F38403E4F542621433D4A2A243D3C222822208C1314101612012D397B31534942539E2A +1E4B282D315B71732C2F532E81E06A0F111012170003001BFFF501D40298000F001E0027 +000001140607062322263534363736333216073426232207061514163332373E01273736 +321615140F0101D439315369464D654F38403E4F542621433D4A2A243D3C2228A9A20A1E +1511AB012D397B31534942539E2A1E4B282D315B71732C2F532E81E2A00A150F120A6A00 +0003001BFFF501D40295000F001E00250000011406070623222635343637363332160734 +26232207061514163332373E013723270723373301D439315369464D654F38403E4F5426 +21433D4A2A243D3C222854245289279D31012D397B31534942539E2A1E4B282D315B7173 +2C2F532E81E06868A90000000003001BFFF501EE0270000F001E00320000011406070623 +22263534363736333216073426232207061514163332373E011333062322272623220723 +3E013332171633323601D439315369464D654F38403E4F542621433D4A2A243D3C222852 +1C164912274718240F1D0F2E27203629151417012D397B31534942539E2A1E4B282D315B +71732C2F532E810164680F1D2F3A3018131300000004001BFFF501DA025E000F001E002A +0036000001140607062322263534363736333216073426232207061514163332373E0113 +140623222635343633321607140623222635343633321601D439315369464D654F38403E +4F542621433D4A2A243D3C22285A1D14151C1D15131DC81D14151C1D15131D012D397B31 +534942539E2A1E4B282D315B71732C2F532E810121141D1D15141C1E13141D1D15141C1E +00030056FFF5024E0205000A000E00190000011406232226353436321613213521071406 +2322263534363216018A22171620202E21C4FE0801F8C422181520202E2101CC16202016 +182122FEF942F31620201618212200000003001CFF7901D5022A001A0024002E00000107 +1E0117161514060706232227072337262726353436333217370703163332373E01353403 +13263122070E01151401AC3A1F230D1444385356080C3D2340271425BA6C0B063725BE0B +0B4238202AF5BC0A342B313E022A77091A18232B3C823047027E830D15273D73C4017294 +FE78045231823632FEA60185022E3599472800000002002AFFF501DB0298002A00340000 +25170E0123223534370E012235343F01363534262335363717030615143332373E013733 +030615143332360323272635343633321701CD0E303624302D59616A1B230E1627455104 +5903142D612125284A4F130B0C2209208C13141016127709492E302D98926535126E8B3A +090C0C0E081303FE9D0907199332536EFED54806112001A66A0F1110121700000002002A +FFF501DB0298002A0033000025170E0123223534370E012235343F013635342623353637 +17030615143332373E01373303061514333236033736321615140F0101CD0E303624302D +59616A1B230E16274551045903142D612125284A4F130B0C22D6A20A1E1511AB7709492E +302D98926535126E8B3A090C0C0E081303FE9D0907199332536EFED54806112001A8A00A +150F120A6A0000000002002AFFF501DB0295002A0031000025170E0123223534370E0122 +35343F01363534262335363717030615143332373E013733030615143332361323270723 +373301CD0E303624302D59616A1B230E16274551045903142D612125284A4F130B0C2229 +245289279D317709492E302D98926535126E8B3A090C0C0E081303FE9D0907199332536E +FED54806112001A66868A9000003002AFFF501DB025E002A00360042000025170E012322 +3534370E012235343F01363534262335363717030615143332373E013733030615143332 +3613140623222635343633321607140623222635343633321601CD0E303624302D59616A +1B230E16274551045903142D612125284A4F130B0C222D1D14151C1D15131DC81D14151C +1D15131D7709492E302D98926535126E8B3A090C0C0E081303FE9D0907199332536EFED5 +4806112001E7141D1D15141C1E13141D1D15141C1E0000000002FFE8FF3201AA02980033 +003C000037173E01353427263534363332161514070E012322263534363332163332373E +013736353426272E012322073536373637333216273736321615140F01F31543371A1A17 +11161E834E932B171C1710131E0C121E0D33070A3E140E211E100F160A2942040B3D34A2 +0A1E1511ABBA6E6E7320101011161015201757DA819E181311171C210D430B140F1EFB33 +251B04110402080ABAEFA00A150F120A6A0000000002FFB5FF3301D502AB002400340000 +13173633321615140E0123222706151416331523353E0137133F01363534262735363717 +021734232206070E011514163332373E01BD02445E373D588D472121251D23CD201A0972 +440509152C4D4B0547AA3E284B14162B1B15523F1F26014F026C433B4E9860118F16110E +0F1001182201B2FA13230811090111090E05FEED5A553F33389D1A10155F2E720003FFE8 +FF3201BA025E0033003F004B000037173E01353427263534363332161514070E01232226 +3534363332163332373E013736353426272E012322073536373637333216131406232226 +353436333216071406232226353436333216F31543371A1A1711161E834E932B171C1710 +131E0C121E0D33070A3E140E211E100F160A2942040B3DD41D14151C1D15131DC81D1415 +1C1D15131DBA6E6E7320101011161015201757DA819E181311171C210D430B140F1EFB33 +251B04110402080ABA012E141D1D15141C1E13141D1D15141C1E00000003FFCD00000234 +02F50003001E00210000010721370123353E0135342F012307061514171523353E013701 +33131E0117270B0102180BFED30B0149F52D200214DC3B163EBA222B39011D1A5C0A1C28 +C32D9502F53333FD0B1002181B0812836F29191E03101007326301F0FDD73E2203F60107 +FEF9000000030011FFF501E1021F00030024003200000107213701170E0123223534370E +0123222635343E013332173F021706070215143332370334262322070E01151433323736 +01E10BFED30B011B0D38351E2817385E352C38578641430D0B033D070105590E0F25471E +1A443F212C3C3B444D021F3333FE500B442A29195A54493D374C9E663A300307030411FE +BC270D2901171A205C31782E4A626F000003FFCD00000234035E000C0027002A00000133 +0E012322263533163332361323353E0135342F012307061514171523353E01370133131E +0117270B0101F61D0B6240433D1D08632E4A4EF52D200214DC3B163EBA222B39011D1A5C +0A1C28C32D95035E435B4F4F6335FCD01002181B0812836F29191E03101007326301F0FD +D73E2203F60107FEF900000000030011FFF501E1028A000C002D003B000001330E012322 +2635331633323613170E0123223534370E0123222635343E013332173F02170607021514 +3332370334262322070E0115143332373601C41D0B6240433D1D08632E4A1B0D38351E28 +17385E352C38578641430D0B033D070105590E0F25471E1A443F212C3C3B444D028A435B +4F4F6335FE130B442A29195A54493D374C9E663A300307030411FEBC270D2901171A205C +31782E4A626F00000002FFCDFF570272029C002A002D00002123353E0135342F01230706 +1514171523353E01370133131E0117152306151416333237170623222635340B0201B172 +2D200214DC3B163EBA222B39011D1A5C0A1C28570A211A2F241134492837252D95100218 +1B0812836F29191E03101007326301F0FDD73E2203101A1B171E20124D352A2901270107 +FEF9000000020011FF57021101B9002E003C0000051706232226353437263534370E0123 +222635343E013332173F0217060702151433323F01170607061514163332033426232207 +0E0115143332373602001134492837142017385E352C38578641430D0B033D070105590E +0F251B0D411E0B211A2F6F1E1A443F212C3C3B444D4A124D352A251B0425195A54493D37 +4C9E663A300307030411FEBC270D291D0B50121A1D171E01D31A205C31782E4A626F0000 +00020042FFEE02B1036C000800290000013736321615140F01050727262322070E011514 +33323637170E012322263534363736333217163332370185A20A1E1511AB010925121283 +6D5A3537AF3E67401243814D759066556675453014151B0B02C2A00A150F120A6A2AC803 +A36138A055CD34430F4F468B7A67BB3C491107160002001EFFF501AF02980008002B0000 +133736321615140F0113170E012322263534363736333216151406222635343635342322 +0706151416333236D0A20A1E1511AB6B102E5738474C44374D5A2E3B1C28130F253F2E5B +2E2B274001EEA00A150F120A6AFE7D0A3A324E4C437D2D3D2E23141C1711091D0A143060 +89363C2600020042FFEE02B1036B00060027000001232707233733170727262322070E01 +151433323637170E012322263534363736333217163332370287245289279D3182251212 +836D5A3537AF3E67401243814D759066556675453014151B0B02C26868A9D3C803A36138 +A055CD34430F4F468B7A67BB3C491107160000000002001EFFF501AB0295000600290000 +0123270723373313170E0123222635343637363332161514062226353436353423220706 +15141633323601AB245289279D310B102E5738474C44374D5A2E3B1C28130F253F2E5B2E +2B274001EC6868A9FDD60A3A324E4C437D2D3D2E23141C1711091D0A14306089363C2600 +00020042FFEE02B10332000B002C0000011406232226353436333216170727262322070E +01151433323637170E0123222635343637363332171633323702251D14151C1D15131D8C +251212836D5A3537AF3E67401243814D759066556675453014151B0B0301141D1D15141C +1E7CC803A36138A055CD34430F4F468B7A67BB3C491107160002001EFFF501A9025E000B +002E000001140623222635343633321603170E0123222635343637363332161514062226 +35343635342322070615141633323601711D14151C1D15131D13102E5738474C44374D5A +2E3B1C28130F253F2E5B2E2B2740022D141D1D15141C1EFE2B0A3A324E4C437D2D3D2E23 +141C1711091D0A14306089363C26000000020042FFEE02B1036B00060027000001072327 +331737170727262322070E01151433323637170E01232226353436373633321716333237 +0287A2305F26598D4F251212836D5A3537AF3E67401243814D759066556675453014151B +0B036BA9A96969D3C803A36138A055CD34430F4F468B7A67BB3C4911071600000002001E +FFF501D902950006002900000107232733173703170E0123222635343637363332161514 +06222635343635342322070615141633323601D9A2305F26598D56102E5738474C44374D +5A2E3B1C28130F253F2E5B2E2B27400295A9A96969FDD60A3A324E4C437D2D3D2E23141C +1711091D0A14306089363C260003FFF8000002BC036B0006001A002B0000010723273317 +3705213216151407062321353E013713363534262717030615143332373E013534272623 +2206025BA2305F26598DFE4D0115869EA06AB9FEFF2B1B0C7A0B202CB3761644945D343A +4E2F54201F036BA9A96969DE8C81B17C531008182C01BD271415130135FE5A500F255A32 +9F4F793B230F00000003000FFFF302B902B3000E00360045000001273635342726353436 +333216151427170F010615143332363717062322263534370E0123222635343633321617 +3337363534262735360334262322070E0115141633323E010237094B14191D161921B006 +273C41120D1D280C4D4613160D2B53343239C1631F1D05012D0E162A4D591D122F2C3242 +1F1B2D5C3B01CD1132260E131618141A291E56950698DBEF0714192E0A7218151E3B493B +3D3877D81C1EA331161008021107FECE1A1F2E35933E2227629800000002FFF8000002BC +028D0018002C00001321321615140607062B01353E013F01233733373635342627130706 +15143332373E013534262322060F0133078201148B9B564A6EC1F52B1A0C415A0C5A2E0B +1E2F6C2E16459759333B6C6A2019053F990C028D8984579E38531008172BEC2AA4271914 +1104FEC9A4500F255A3398516C6F0C11E22A00000002000FFFF3024402AB002D003C0000 +010723070615143332363717062322263534370E01232226353436333216173337233733 +3635342627353637170F0134262322070E0115141633323E0102440A543A41120D1D280C +4D4613160D2B53343239C1631F1D050125B607BB0A162A4D4C061E8D1D122F2C32421F1B +2D5C3B023027D7EF0714192E0A7218151E3B493B3D3877D81C1E8A272811100802110710 +0675C71A1F2E35933E222762980000000002FFFF0000027A02F500030034000001072137 +050727363534262322060F013332363717072736353427262B0107061514333237363717 +0721353E01371336353426273502540BFED30B01531F16034A762A1A05424E422C1D1244 +140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E02F53333689A0219152D1C09 +11E9204104E8051F1518070F707F132227144E08A21008172B01BA251B14110410000000 +0003001FFFF501D2021E0003001900230000010721371317062322263534363332161514 +06070614163332362707363736353423220601D20BFED30BC10C58743D4ABB70272B9983 +0A332A213C9811703132232A5B021E3333FE4F0C6C4B3E75C624204268111452321FBB2C +1B2F3031276100000002FFFF0000027A0362000C003D000001330E012322263533163332 +36170727363534262322060F013332363717072736353427262B01070615143332373637 +170721353E013713363534262735023C1D0B6240433D1D08632E4A4E1F16034A762A1A05 +424E422C1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E0362435B4F +4F6335A79A0219152D1C0911E9204104E8051F1518070F707F132227144E08A21008172B +01BA251B141104100003001FFFF501D7028A000C0022002C000001330E01232226353316 +333236031706232226353436333216151406070614163332362707363736353423220601 +BA1D0B6240433D1D08632E4A440C58743D4ABB70272B99830A332A213C9811703132232A +5B028A435B4F4F6335FE110C6C4B3E75C624204268111452321FBB2C1B2F303127610000 +0002FFFF0000027A0332000B003C00000114062322263534363332161707273635342623 +22060F013332363717072736353427262B01070615143332373637170721353E01371336 +353426273501F31D14151C1D15131D871F16034A762A1A05424E422C1D1244140707113D +4E20244F814A2638103EFE052B1A0C7B0B1F2E0301141D1D15141C1E879A0219152D1C09 +11E9204104E8051F1518070F707F132227144E08A21008172B01BA251B14110410000000 +0003001FFFF5019C025E000B0021002B0000011406232226353436333216031706232226 +353436333216151406070614163332362707363736353423220601671D14151C1D15131D +010C58743D4ABB70272B99830A332A213C9811703132232A5B022D141D1D15141C1EFE2D +0C6C4B3E75C624204268111452321FBB2C1B2F30312761000001FFFFFF51027A028D0040 +00002901353E013713363534262735210727363534262322060F01333236371707273635 +3427262B010706151433323736371707230615141633323717062322263534012BFED42B +1A0C7B0B1F2E01F11F16034A762A1A05424E422C1D1244140707113D4E20244F814A2638 +103EA50D211A2F2411344928371008172B01BA251B141104109A0219152D1C0911E92041 +04E8051F1518070F707F132227144E08A21D1E171E20124D352A2C000002001FFF51019C +01B90024002E00000517062322263534372E013534363332161514060706141633323637 +1706070615141633320307363736353423220601571134492837173B49BB70272B99830A +332A213C360C47580A211A2F9B11703132232A5B50124D352A271E014B3D75C624204268 +111452321F2C0C58101A1A171E016C2C1B2F3031276100000002FFFF0000027A036B0006 +0037000001072327331737170727363534262322060F013332363717072736353427262B +01070615143332373637170721353E013713363534262735025BA2305F26598D441F1603 +4A762A1A05424E422C1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E +036BA9A96969DE9A0219152D1C0911E9204104E8051F1518070F707F132227144E08A210 +08172B01BA251B14110410000003001FFFF501F602950006001C00260000010723273317 +37031706232226353436333216151406070614163332362707363736353423220601F6A2 +305F26598D6B0C58743D4ABB70272B99830A332A213C9811703132232A5B0295A9A96969 +FDD80C6C4B3E75C624204268111452321FBB2C1B2F3031276100000000020034FFEE02D2 +036D0006003800000123270723373313150E010F01062322272E01353436373633321716 +3332371707272627262322070E011514163332373E01373635342F0135028D245289279D +319D31250B316F7E894C232764536874483E1A1224180F31120322305D754E32386A6047 +22090E101A2F2002C46868A9FDD210031B2ABB3E4C236A3467B63A48180A2205C503402A +3B633FA052636E22092339571A1D04031000000000040008FF3201D702950006002F003C +004B00000123270723373313231615140623222726232206151417161514062322263534 +36372635343726353436333217163B010734262322061514163332373603342627262722 +060706151433323601BF245289279D317031097346120D03040D17478E70624E592D481B +4D4F7A4F3A280B053C771B1D304A201C3223211B3F50180A072F0B147F3B4C01EC6868A9 +FED912213F6503011A0F1812225D4354373527372B101730241C504A611C083927206448 +23283B38FE87232F190704260E1921663700000000020034FFEE02D20362000C003E0000 +01330E0123222635331633323613150E010F01062322272E013534363736333217163332 +371707272627262322070E011514163332373E01373635342F013502781D0B6240433D1D +08632E4A6A31250B316F7E894C232764536874483E1A1224180F31120322305D754E3238 +6A604722090E101A2F200362435B4F4F6335FE0B10031B2ABB3E4C236A3467B63A48180A +2205C503402A3B633FA052636E22092339571A1D0403100000040008FF3201DC028A000C +003500420051000001330E01232226353316333236172316151406232227262322061514 +1716151406232226353436372635343726353436333217163B0107342623220615141633 +32373603342627262722060706151433323601BF1D0B6240433D1D08632E4A2831097346 +120D03040D17478E70624E592D481B4D4F7A4F3A280B053C771B1D304A201C3223211B3F +50180A072F0B147F3B4C028A435B4F4F6335EE12213F6503011A0F1812225D4354373527 +372B101730241C504A611C08392720644823283B38FE87232F190704260E192166370000 +00020034FFEE02D20332000B003D000001140623222635343633321613150E010F010623 +22272E013534363736333217163332371707272627262322070E011514163332373E0137 +3635342F0135021B1D14151C1D15131DB731250B316F7E894C232764536874483E1A1224 +180F31120322305D754E32386A604722090E101A2F200301141D1D15141C1EFE2B10031B +2ABB3E4C236A3467B63A48180A2205C503402A3B633FA052636E22092339571A1D040310 +00040008FF3201D7025E000B003400410050000001140623222635343633321617231615 +14062322272623220615141716151406232226353436372635343726353436333217163B +0107342623220615141633323736033426272627220607061514333236015E1D14151C1D +15131D7931097346120D03040D17478E70624E592D481B4D4F7A4F3A280B053C771B1D30 +4A201C3223211B3F50180A072F0B147F3B4C022D141D1D15141C1ED212213F6503011A0F +1812225D4354373527372B101730241C504A611C08392720644823283B38FE87232F1907 +04260E192166370000020034FEF502D2029A00310040000001150E010F01062322272E01 +3534363736333217163332371707272627262322070E011514163332373E01373635342F +013503273635342726353436333216151402D231250B316F7E894C232764536874483E1A +1224180F31120322305D754E32386A604722090E101A2F20C4094B15181D161921013F10 +031B2ABB3E4C236A3467B63A48180A2205C503402A3B633FA052636E22092339571A1D04 +0310FDB61232260B14151A141A291E5700040008FF3201D702D4000E0037004400530000 +011706151417161514062322263534132316151406232227262322061514171615140623 +2226353436372635343726353436333217163B0107342623220615141633323736033426 +2726272206070615143332360182094B14191D161921D731097346120D03040D17478E70 +624E592D481B4D4F7A4F3A280B053C771B1D304A201C3223211B3F50180A072F0B147F3B +4C02D41132270D131617141B281F57FEE212213F6503011A0F1812225D4354373527372B +101730241C504A611C08392720644823283B38FE87232F190704260E192166370002FFF8 +00000301036B0006003A00000123270723373317150E01070306151416171521353E013F +01210706151416171523353E01371336353426273521150E010F01213736353426273502 +73245289279D31E62C1C0C84031E31FEEE33290A40FEE34203192DF5291F0E760C1D3001 +0F2D2D0A36011D2E0E202802C26868A9DE1008182AFE1F0C0C1510051010042227E9F40A +0C161006101004203201AE2E12150F051010032024C6A4340B1314031000000000020013 +FFF701DE036B0006003400000123270723373313170E0123222635343F01363534232206 +070E010723133634262B0135363717033633321615140706151433323601BF245289279D +316A0D2E3E2614181437071C1B55281F241D4B920D1A0F1B4C510779826421203021100C +1D02C26868A9FD0B0D41311713114BCF1C0519503C305B6F022A32180D0F091206FE41D3 +251E2B966518121D0002FFF800000301028D0039003D000001150E010F01330723030615 +1416171521353E013F01210706151416171523353E01371323373336353426273521150E +010F01213635342627351721072103012C1C0C0C580C586C031E31FEEE33290A40FEE342 +03192DF5291F0E66670C67101D30010F2D2C0B0D011E1220282AFEE21D011D028D100818 +2A2B2CFE760C0C1510051010042227E9F40A0C161006101004203201762C3913150F0510 +100320242E3E0D13140310B16C00000000010013FFF701DE02AB0034000025170E012322 +2635343F01363534232206070E010723132337333634262B013536371707330723033633 +321615140706151433323601D10D2E3E2614181437071C1B55281F241D4B843F0F3E0D1A +0F1B4C5107219F0F9F49826421203021100C1D760D41311713114BCF1C0519503C305B6F +01F43632180D0F0912067B36FEF2D3251E2B966518121D000002FFF8000001BC03440013 +002B000001330623222726232207233E013332171633323607150E010703061514161715 +23353E01371336353426273501A01C164912274718240F1D0F2E27203629151417182A1B +0D780F192CF42A1F0B780D20280344680F1D2F3A301813139E10051A2EFE533517140E05 +1010081A2901B92E10141502100000000002001EFFF5016502700013002D000001330623 +222726232207233E013332171633323603170E01232235343F0136353426273536371703 +06151433323601491C164912274718240F1D0F2E27203629151417640E293C2532183009 +16292C73056009100C240270680F1D2F3A30181313FE1C0C3F3339145AAF220B0F080110 +031603FEAA220B0F230000000002FFF8000001B702F50003001B00000107213717150E01 +070306151416171523353E01371336353426273501B70BFED30BF62A1B0D780F192CF42A +1F0B780D202802F533336810051A2EFE533517140E051010081A2901B92E101415021000 +0002001DFFF50155021F0003001D00000107213713170E01232235343F01363534262735 +3637170306151433323601550BFED30BB50E293C253218300916292C73056009100C2402 +1F3333FE540C3F3339145AAF220B0F080110031603FEAA220B0F23000002FFF8000001C0 +0362000C0024000001330E0123222635331633323607150E01070306151416171523353E +01371336353426273501A31D0B6240433D1D08632E4A132A1B0D780F192CF42A1F0B780D +20280362435B4F4F6335A710051A2EFE533517140E051010081A2901B92E101415021000 +0002002EFFF5015B028A000C0026000001330E0123222635331633323603170E01232235 +343F0136353426273536371703061514333236013E1D0B6240433D1D08632E4A510E293C +253218300916292C73056009100C24028A435B4F4F6335FE170C3F3339145AAF220B0F08 +0110031603FEAA220B0F23000001FFF8FF570180028D002700003323353E013713363534 +26273533150E0107030615141617152306151416333237170623222635345E662A1F0B78 +0D2028F72A1B0D780F192C620A211A2F24113449283710081A2901B92E10141502101005 +1A2EFE533517140E05101A1B171E20124D352A2900020031FF57012F028E002700320000 +17232235343F013635342627353637170306151433323717060706151416333237170623 +22263534131406232226353436321666043116300917292E72045E0A0E193B0D2E1E0F21 +1A2F241134492837B51D14161A1B281E0B371D52B1200C0F080110041503FEA9220A0F4E +0B47161E21171E20124D352A23027E151E1D18171E2100000002FFF8000001800332000B +0023000001140623222635343633321617150E01070306151416171523353E0137133635 +3426273501581D14151C1D15131D282A1B0D780F192CF42A1F0B780D20280301141D1D15 +141C1E8710051A2EFE533517140E051010081A2901B92E10141502100001002FFFF500EB +01B90019000037170E01232235343F0136353426273536371703061514333236DD0E293C +253218300916292C73056009100C24730C3F3339145AAF220B0F080110031603FEAA220B +0F2300000002FFF8FFEE030F028D001D0035000001150E0107030E012322263534363216 +151406151432371336353426273523150E01070306151416171523353E01371336353426 +2735030F2B1A0D671D5452354019281F0346127F0C1E2F912A1B0D780F192CF42A1F0B78 +0D2028028D1006172DFE91686E302818201B13061104274001C62B151411041010051A2E +FE533517140E051010081A2901B92E101415021000040031FF3101F4028E000B00160035 +004E0000011406232226353436333216071406232226353436321617030E012322263534 +36333215140615143332363713363534262B0135363703170E01232235343F0136353426 +273536371703061514333201F41E14171F2015141FEC1D14161A1B281ECB681F5B41232C +1811270C121E291A4810151B1A2D7CF10D2A3A253116300917292E72045E0A0E19025614 +1E1D17161E2114151E1D18171E21B7FE667972221B121A250C0F070C4E680124410F110E +100316FEB90B4131371D52B1200C0F080110041503FEA9220A0F00000002FFFAFFEE0218 +036D0006002300000123270723373317150E0107030E0123222635343632161514061514 +32371336342627350218245289279D312B2B1A0D671D5452354019281F0346127F0C1E2F +02C46868A9E01006172DFE91686E302818201B13061104274001C62C281104100002FF84 +FF31016102950006002500000123270723373307030E0123222635343633321514061514 +3332363713363534262B013536370161245289279D3113681F5B41232C1811270C121E29 +1A4810151B1A2D7C01EC6868A9DFFE667972221B121A250C0F070C4E680124410F110E10 +0316000000020007FEF502D2028D0035004400000115220705131E011715213537363534 +2E032F010706151416171523353E01371336353426273521150E010F01373635342F0135 +01273635342726353436333216151402D21C31FED6A817222FFEED1D2E030A0610027B41 +0B1D2AF72B1A0C7C0E222D010E2A2E0B35998F2418FEDE094B15181D161921028D1025E1 +FEDB281505101003041C060E130B1C04D7ED260D171104101005182A01BD320C15140210 +10031F28C26D65240F040310FC681232260B14151A141A291E5700000002000EFEF501CD +02AB00290038000001150E010717163332373637170E012322262726270F012313363726 +2B013536371703373635342B013503273635342726353436333216151401CD24506D2537 +1A1518050A0F232E1E1623181E1E28304B8D0E040330124B5206782B8F250EC5094B1518 +1D16192101AC1002376158812A08120B3F2E232F3A5320B4021032261810091206FE3821 +6E1E1210FD491232260B14151A141A291E570000000100050000025901CB004800001337 +363736373E01333216151406222E01232207060716171E01171E0117072326272E012726 +27062307061514161707233732373637133635342E062337330706070607F32A23222526 +1824281A2E1E26170D0815102A380F0B0A23040D3C22048226130915030F09280E2B0421 +1F05F1052E11140A5004030308060E07130504F1042E10130B01140403191A3E27181C18 +121714131E4A2306100F7A09202B080F192F16520C300602A20D0E140E0110100B0B2701 +310F0D0609070403020101101002090B280000000002FFF80000022F036C000800260000 +013736321615140F01010721353E01371336353426273521150E01070306151416333236 +3736370104A20A1E1511AB01083AFE032A1A0E7A0B1F2E01112E2B0C780A2C3D4D432429 +2902C2A00A150F120A6AFDF2B410041A3001B62519161203101003202AFE53261017120E +181B550000020029FFF5015C036C000800220000133736321615140F0117030615143332 +3F01170E012322353437133635342B013536377DA20A1E1511AB779908111B28190E2D41 +29310489033112465502C2A00A150F120A6A1CFDB71D0E1239230A473638110D020C0A09 +161008130002FFF8FEF5022F028D001D002C0000250721353E0137133635342627352115 +0E01070306151416333236373637012736353427263534363332161514022F3AFE032A1A +0E7A0B1F2E01112E2B0C780A2C3D4D43242929FE85094B15181D161921B4B410041A3001 +B62519161203101003202AFE53261017120E181B55FE3B1232260B14151A141A291E5700 +00020007FEF5011702AB001900280000010306151433323F01170E012322353437133635 +342B0135363701273635342726353436333216151401179908111B28190E2D4129310489 +0331124655FEFF094B15181D16192102A6FDB71D0E1239230A473638110D020C0A091610 +0813FC4A1232260B14151A141A291E570002FFF800000253029A000E002C000001273635 +3427263534363332161514030721353E01371336353426273521150E0107030615141633 +323637363701D1094B14191D161921243AFE032A1A0E7A0B1F2E01112E2B0C780A2C3D4D +4324292901B41132260E131618141A291E56FEB7B410041A3001B6251916120310100320 +2AFE53261017120E181B550000020029FFF501C002B5000E002800000127363534272635 +34363332161514270306151433323F01170E012322353437133635342B01353637013E09 +4B14191D161921A99908111B28190E2D4129310489033112465501CF1132260E13161814 +1A291E568EFDB71D0E1239230A473638110D020C0A091610081300000002FFF80000022F +028D000A002800000114062322263534363216170721353E01371336353426273521150E +0107030615141633323637363701CB22171620202E21643AFE032A1A0E7A0B1F2E01112E +2B0C780A2C3D4D43242929015116202016182122B4B410041A3001B62519161203101003 +202AFE53261017120E181B5500020029FFF5018202AB000A002400000114062322263534 +3632160B0106151433323F01170E012322353437133635342B0135363701822217162020 +2E216B9908111B28190E2D41293104890331124655015116202016182122013EFDB71D0E +1239230A473638110D020C0A09161008130000000001FFF80000022F028D002500002507 +21353E013F01073F02363534262735211522060F01370F0206151416333236373637022F +3BFE04291E0C34630D63380B1F2F01112C2C0A338F0E8E36112E345049232A2AB4B41005 +1C2CBF3B303BC8251916100510102123B5553153C33B0F13110D171B5700000000010025 +FFF5013302AB00200000010F010306151433323F01170E01232235343F01073F01363534 +2B01353637170301330C6248070F1A2B1A0D313F27311034480C484131144953064601CC +2E32FEED180E13392209493438103BC2242E24F20E1810091206FEF50002FFECFFF102D7 +036C0008002B0000013736321615140F0105150E030703230B0106151416171523353E01 +3713262335331B013635342627350169A20A1E1511AB014B1B15190D109212E672091F27 +C62B241B75173FA0CF6A081B2A02C2A00A150F120A6A35100607232A37FE050226FE5A1F +17151303101006335F019F3610FE0D01841C101B140410000002000EFFF701DA02980008 +003A0000133736321615140F0113170E0123222635343F01363534232206070E01072313 +3635342627353E013717073E0133321615140F0106151433323736B4A20A1E1511ABF50E +333623141B102C0E181D4532211F244B60021B2627601B04434A6C311F220A380E10132A +0701EEA00A150F120A6AFE870D462B1B1B103BA236191D4549314E79015E0A070F0B0110 +08110602DA7666211C1923CB320B1235080000000002FFECFEF502D7028D002200310000 +01150E030703230B0106151416171523353E013713262335331B01363534262735012736 +35342726353436333216151402D71B15190D109212E672091F27C62B241B75173FA0CF6A +081B2AFEA5094B15181D161921028D100607232A37FE050226FE5A1F1715130310100633 +5F019F3610FE0D01841C101B140410FC681232260B14151A141A291E570000000002000E +FEF501DA01B900310040000025170E0123222635343F01363534232206070E0107231336 +35342627353E013717073E0133321615140F010615143332373601273635342726353436 +333216151401CC0E333623141B102C0E181D4532211F244B60021B2627601B04434A6C31 +1F220A380E10132A07FEAD094B15181D161921750D462B1B1B103BA236191D4549314E79 +015E0A070F0B011008110602DA7666211C1923CB320B123508FE921232260B14151A141A +291E57000002FFECFFF102D7036B0006002900000107232733173717150E030703230B01 +06151416171523353E013713262335331B01363534262735027BA2305F26598D811B1519 +0D109212E672091F27C62B241B75173FA0CF6A081B2A036BA9A96969DE100607232A37FE +050226FE5A1F17151303101006335F019F3610FE0D01841C101B1404100000000002000E +FFF701DB02950006003800000107232733173713170E0123222635343F01363534232206 +070E010723133635342627353E013717073E0133321615140F010615143332373601DBA2 +305F26598D160E333623141B102C0E181D4532211F244B60021B2627601B04434A6C311F +220A380E10132A070295A9A96969FDE00D462B1B1B103BA236191D4549314E79015E0A07 +0F0B011008110602DA7666211C1923CB320B1235080000000002003AFFF7021C02B3000E +0040000013273635342726353436333216151401170E0123222635343F01363534232206 +070E010723133635342627353E013717073E0133321615140F010615143332373643094B +14191D16192101490E333623141B102C0E181D4532211F244B60021B2627601B04434A6C +311F220A380E10132A0701CD1132260E131618141A291E56FE5F0D462B1B1B103BA23619 +1D4549314E79015E0A070F0B011008110602DA7666211C1923CB320B123508000001FFF8 +FFEE02BC029A0039000001073E0133321615140E03232226353436333216151406151432 +3E043534232207030615143B011523353E013713363534262B01350149223B6F3E545916 +364D7A4A282F2D22161F0E18202E2B27176665765A0B370EF42B1B0C7B0A1E2806028D78 +4A3B7B6039797E623F2A2024312415092204120B1C3A508350D5BBFEBE25112910100818 +2C01BE26151414100001000EFF3001BA01B90033000013073E01333216151407030E0123 +2226353436333215140615143332363713363534232206070E010723133635342627353E +0137D5434A6C311F220A4D205A41222D1811270C121E291A4811181D45322120234B6002 +1B2627601B01B7DA7666211C1923FEDC7B71231B121A250C0F070D4F68012544031D4549 +324F77015E050C0F0B011008110600000003003CFFEE02BB02F500030013002300000107 +2137011406070623222635343637363336160734262322070E011514163332373E01029A +0BFED30B014E735C696D6377624F707D6B7669453C6357353D433F5C4E3B4602F53333FE +AD6BC43E477C6E5BC1456101863447517548B64E52575F4AD60000000003001BFFF501FF +021F00030013002200000107213705140607062322263534363736333216073426232207 +061514163332373E0101FF0BFED30B010239315369464D654F38403E4F542621433D4A2A +243D3C2228021F3333F2397B31534942539E2A1E4B282D315B71732C2F532E810003003C +FFEE02C50362000C001C002C000001330E01232226353316333236131406070623222635 +343637363336160734262322070E011514163332373E0102A81D0B6240433D1D08632E4A +23735C696D6377624F707D6B7669453C6357353D433F5C4E3B460362435B4F4F6335FE6E +6BC43E477C6E5BC1456101863447517548B64E52575F4AD60003001BFFF50215028A000C +001C002B000001330E012322263533163332360314060706232226353436373633321607 +3426232207061514163332373E0101F81D0B6240433D1D08632E4A1439315369464D654F +38403E4F542621433D4A2A243D3C2228028A435B4F4F6335FED1397B31534942539E2A1E +4B282D315B71732C2F532E810004003CFFEE02D0036C0009001300230033000001373633 +321615140F0123373633321615140F010114060706232226353436373633361607342623 +22070E011514163332373E0101F1A00A110F1511ABCDA00A110F1511A9014F735C696D63 +77624F707D6B7669453C6357353D433F5C4E3B4602C2A00A150F140A68A00A150F140A68 +FEE06BC43E477C6E5BC1456101863447517548B64E52575F4AD600000004001BFFF5021D +02980009001300230032000001373633321615140F0123373633321615140F0105140607 +062322263534363736333216073426232207061514163332373E01013EA00A110F1511AB +CDA00A110F1511A9011B39315369464D654F38403E4F542621433D4A2A243D3C222801EE +A00A150F140A68A00A150F140A68C1397B31534942539E2A1E4B282D315B71732C2F532E +8100000000020031FFF803C4029A0033004400000107233427262B01220F01323E013717 +0727372E012F01070615141633323637170721220623220623222635343E013332171633 +0313363534262322070E0115143332373603C41F100C125A601C09405342281512421406 +0125393E380B202B6D733C1041FEA60C433413360862816DB766243E25118949153B2D4A +3E445790441F1F028D9641142020E30B243004E6022A22160303CC2B0C1410385306A605 +03856472CD7A0805FE3A010C4D0B222D363BD56AB227250000030014FFF4028601B90026 +0033004300002517062322270E0123222635343E01333216173E013332161514070E0107 +0607061514163332361334262322070E01073E0137362734262322070E01151416333237 +3E0102510B6B5A4523263D273F5252813F283B0D344229262B461E5F201F050A2A211C3A +2E121022241D2413412F1735ED201B2821303C241F31281D37690B6A44261D524046905C +2B252F2124203D3015250302112224252F1E013E11152720433F141614301B1E2423349D +4A2B303C2BB900000003FFF30000024C036C0008002C00370000013736321615140F0233 +32161514070607171E0133152303270706151416171523353E0137133635342726271707 +1633323635342623220121A20A1E1511ABC0FC616B42275C59112521937B42370D1A29F3 +28210C770C0E0C2DAE3F1F1459643E3A3102C2A00A150F120A6A354C414E321E15EB2E24 +10014405CB2E11171404101005222E01AF2E11120A08062CE3055046343C00000002002D +0000019C0298000800270000133736321615140F01033736373633321614062322272623 +22070E01072313363534232207353717B4A20A1E1511AB2710223C2B24151A1916131008 +081B372229244C51102608179B0301EEA00A150F120A6AFEF1234B3F2D1C2E1F1A0E5E3A +677901243B171A03111B02000003FFF3FEF5024C028D0023002E003D0000133332161514 +070607171E0133152303270706151416171523353E013713363534272627170716333236 +353426232203273635342726353436333216151484FC616B42275C59112521937B42370D +1A29F328210C770C0E0C2DAE3F1F1459643E3A3189094B15181D161921028D4C414E321E +15EB2E2410014405CB2E11171404101005222E01AF2E11120A08062CE3055046343CFC86 +1232260B14151A141A291E570002FFFEFEF5019C01B9001E002D00003F01363736333216 +1406232227262322070E0107231336353423220735371703273635342726353436333216 +1514B010223C2B24151A1916131008081B372229244C51102608179B03E0094B15181D16 +1921DF234B3F2D1C2E1F1A0E5E3A677901243B171A03111B02FD3E1232260B14151A141A +291E57000003FFF30000024C036B0006002A003500000107232733173705333216151407 +0607171E0133152303270706151416171523353E01371336353427262717071633323635 +34262322023EA2305F26598DFE6BFC616B42275C59112521937B42370D1A29F328210C77 +0C0E0C2DAE3F1F1459643E3A31036BA9A96969DE4C414E321E15EB2E2410014405CB2E11 +171404101005222E01AF2E11120A08062CE3055046343C000002002D000001AA02970006 +002500000107232733173703373637363332161406232227262322070E01072313363534 +23220735371701AAA2305F26598DD510223C2B24151A1916131008081B372229244C5110 +2608179B030297A9A96969FE48234B3F2D1C2E1F1A0E5E3A677901243B171A03111B0200 +00020011FFEE01FC036C0008003E0000133736321615140F011707273426232206151416 +171E0115140623222726232207233717061514163332363534262F012E01272E01353436 +33321716323637ECA20A1E1511ABED28123245343C2043432E73572642200F210A122214 +02513F3A491A2227031C082D1E674B34211D22120A02C2A00A150F120A6A27C803535035 +2F263343445232556E170C20E002090E495E47392135252A031F08303D294C580E0B0A10 +00020010FFF301930298000800320000133736321615140F011707232623220615141716 +15140623222726232207233733163332363534272635343633321716333237B4A20A1E15 +11AB9714100E4B1B203644513F1D1A1517140910141014502428383F42371424160E140A +01EEA00A150F120A6A348C741E1B26404F393D490A09159F8828252D464F34323A0A0712 +00020011FFEE01FC036D0006003C0000012327072337331707273426232206151416171E +0115140623222726232207233717061514163332363534262F012E01272E013534363332 +171632363701E4245289279D317028123245343C2043432E73572642200F210A12221402 +513F3A491A2227031C082D1E674B34211D22120A02C46868A9D2C8035350352F26334344 +5232556E170C20E002090E495E47392135252A031F08303D294C580E0B0A100000020010 +FFF301810295000600300000012327072337331707232623220615141716151406232227 +262322072337331633323635342726353436333217163332370181245289279D31451410 +0E4B1B203644513F1D1A1517140910141014502428383F42371424160E140A01EC6868A9 +DB8C741E1B26404F393D490A09159F8828252D464F34323A0A07120000010011FF2701FC +029B004C00001737262726232207233717061514163332363534262F012E01272E013534 +36333217163236373307273426232206151416171E011514060F01363332161514062322 +27371633323534262322079D321D35200F210A12221402513F3A491A2227031C082D1E67 +4B34211D22120A1728123245343C2043432E6A52200F0A262C4334263711291B351A140C +14604F04120C20E002090E495E47392135252A031F08303D294C580E0B0A10C803535035 +2F263343445232516C053103251F2630151D0F291319070000010010FF27016E01BA0040 +000017372627262322072337331633323635342726353436333217163332373307232623 +2206151417161514060F01363332161514062322273716333235342623220750360B1215 +17140910141014502428383F42371424160E140A0E14100E4B1B2036444A3B250F0A262C +4334263711291B351A140C146057010709159F8828252D464F34323A0A07128C741E1B26 +404F393A48043703251F2630151D0F291319070000020011FFEE0214036B0006003C0000 +010723273317371707273426232206151416171E01151406232227262322072337170615 +14163332363534262F012E01272E01353436333217163236370214A2305F26598D0D2812 +3245343C2043432E73572642200F210A12221402513F3A491A2227031C082D1E674B3421 +1D22120A036BA9A96969D0C8035350352F263343445232556E170C20E002090E495E4739 +2135252A031F08303D294C580E0B0A1000020010FFF301AA029700060030000001072327 +3317370F0123262322061514171615140623222726232207233733163332363534272635 +34363332171633323701AAA2305F26598D1714100E4B1B203644513F1D1A151714091014 +1014502428383F42371424160E140A0297A9A96969DD8C741E1B26404F393D490A09159F +8828252D464F34323A0A07120001003BFF270279028D003200003323353E013713220607 +27372107273635342B0103061514161F0115230736333216151406232227371633323534 +2623220727B372342C0B8F775324122A02142C1103653A890E161F23882C0F0A262C4334 +263711291B351A140C140910051E28020F2B51049BA402191551FE163111171103031042 +03251F2630151D0F29131907080000000002FFDAFF270128022200210039000001072303 +06151433323637170E01232235343713232734373E013736333215140F01030736333216 +1514062322273716333235342623220727370128055457020F0C20250D2E3B262E104E4B +012119521A060809011C692C0F0A262C4334263711291B351A140C14093C01AC20FEB808 +06101F30074633250A4001280612070641270908050267FE544203251F2630151D0F2913 +190708600002003B00000279036B000600210000010723273317371707273635342B0103 +061514161F011521353E01371322060727370235A2305F26598D692C1103653A890E161F +23FEE0342C0B8F775324122A036BA9A96969DEA402191551FE163111171103031010051E +28020F2B51049B0000020026FFF501C502B5000E00300000012736353427263534363332 +1615140F01230306151433323637170E01232235343713232734373E013736333215140F +010143094B14191D1619219D055457020F0C20250D2E3B262E104E4B012119521A060809 +011C01CF1132260E131618141A291E566C20FEB80806101F30074633250A400128061207 +06412709080502670001003B00000279028D002200000107273635342B01033307230706 +1514161F011521353E013F0123373313220607273702792C1103653A4C820C82310E161F +23FEE0342C0B39730C734A775324122A028DA402191551FEF12CAF311117110303101005 +1E28D42C010F2B51049B00000001001CFFF5012802220029000001072307330723070615 +1433323637170E01232235343F0123373337232734373E013736333215140F0101280554 +24620C6128020F0C20250D2E3B262E101F390C38244B012119521A060809011C01AC2086 +2C960806101F30074633250A40762C8606120706412709080502670000020066FFEE02FD +034400130040000001330623222726232207233E013332171633323617150E0407030607 +06222635343F0136353426273521150E010F01061514163332363F013635342627350271 +1C164912274718240F1D0F2E27203629151417941515140F0E0D53223F3CC47B2C340923 +2D011033270E3F284F3B54662240171E280344680F1D2F3A301813139E1005090D242A2C +FEE37435345C4837A1BD200D1414011010052234E28D2635416C7CE95512161404100000 +0002002AFFF501DB02700013003E000001330623222726232207233E0133321716333236 +13170E0123223534370E012235343F01363534262335363717030615143332373E013733 +0306151433323601BC1C164912274718240F1D0F2E27203629151417190E303624302D59 +616A1B230E16274551045903142D612125284A4F130B0C220270680F1D2F3A30181313FE +2009492E302D98926535126E8B3A090C0C0E081303FE9D0907199332536EFED548061120 +00020066FFEE02FD02F50003003000000107213705150E040703060706222635343F0136 +353426273521150E010F01061514163332363F01363534262735028B0BFED30B019F1515 +140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B54662240171E2802F5333368 +1005090D242A2CFEE37435345C4837A1BD200D1414011010052234E28D2635416C7CE955 +12161404100000000002002AFFF501DB021F0003002E00000107213701170E0123223534 +370E012235343F01363534262335363717030615143332373E0137330306151433323601 +C80BFED30B01320E303624302D59616A1B230E16274551045903142D612125284A4F130B +0C22021F3333FE5809492E302D98926535126E8B3A090C0C0E081303FE9D090719933253 +6EFED5480611200000020066FFEE02FD0362000C0039000001330E012322263533163332 +3617150E040703060706222635343F0136353426273521150E010F01061514163332363F +0136353426273502811D0B6240433D1D08632E4A8C1515140F0E0D53223F3CC47B2C3409 +232D011033270E3F284F3B54662240171E280362435B4F4F6335A71005090D242A2CFEE3 +7435345C4837A1BD200D1414011010052234E28D2635416C7CE95512161404100002002A +FFF501E0028A000C0037000001330E0123222635331633323613170E0123223534370E01 +2235343F01363534262335363717030615143332373E0137330306151433323601C31D0B +6240433D1D08632E4A1A0E303624302D59616A1B230E16274551045903142D612125284A +4F130B0C22028A435B4F4F6335FE1B09492E302D98926535126E8B3A090C0C0E081303FE +9D0907199332536EFED548061120000000030066FFEE02FD038B000A0015004200000014 +062322263534363332163426232206151416333217150E040703060706222635343F0136 +353426273521150E010F01061514163332363F01363534262735024E3B292B393A292A19 +271C1A27251C1DF71515140F0E0D53223F3CC47B2C3409232D011033270E3F284F3B5466 +2240171E280350523A3A2A283B803826271A1C26591005090D242A2CFEE37435345C4837 +A1BD200D1414011010052234E28D2635416C7CE955121614041000000003002AFFF501DB +02B3000A0015004000000014062322263534363332163426232206151416333213170E01 +23223534370E012235343F01363534262335363717030615143332373E01373303061514 +333236019A3B292B393A292A19271C1A27251C1D7B0E303624302D59616A1B230E162745 +51045903142D612125284A4F130B0C220278523A3A2A283B803826271A1C26FE6909492E +302D98926535126E8B3A090C0C0E081303FE9D0907199332536EFED54806112000030066 +FFEE02FD036C000900130040000001373633321615140F0123373633321615140F010515 +0E040703060706222635343F0136353426273521150E010F01061514163332363F013635 +3426273501CDA00A110F1511ABCDA00A110F1511A901B51515140F0E0D53223F3CC47B2C +3409232D011033270E3F284F3B54662240171E2802C2A00A150F140A68A00A150F140A68 +351005090D242A2CFEE37435345C4837A1BD200D1414011010052234E28D2635416C7CE9 +55121614041000000003002AFFF501FF029800090013003E000001373633321615140F01 +23373633321615140F0101170E0123223534370E012235343F0136353426233536371703 +0615143332373E013733030615143332360120A00A110F1511ABCDA00A110F1511A90132 +0E303624302D59616A1B230E16274551045903142D612125284A4F130B0C2201EEA00A15 +0F140A68A00A150F140A68FE8909492E302D98926535126E8B3A090C0C0E081303FE9D09 +07199332536EFED54806112000010066FF5702FD028D003D000005170623222635343706 +23222635343F0136353426273521150E010F01061514163332363F013635342627353315 +0E040703060706070614163332022411344928370F180D627B2C3409232D011033270E3F +284F3B54662240171E28C61515140F0E0D53223F1F2309211A2F4A124D352A1F1B025C48 +37A1BD200D1414011010052234E28D2635416C7CE95512161404101005090D242A2CFEE3 +74351A0C1A2E1E000001002AFF57021A01B9003900000523223534370E012235343F0136 +3534262335363717030615143332373E0137330306151433323637170607061514163332 +3717062322263534015201302D59616A1B230E16274551045903142D612125284A4F130B +0C22250E331F0E211A2F24113449283709302D98926535126E8B3A090C0C0E081303FE9D +0907199332536EFED54806112031094E181D20171E20124D352A240000020047FFEE038A +036D0006003500000123270723373305150E0107012303230323032E012726273533150E +011514171B01272E01273533150E01151416151B0136353427350283245289279D31015F +1D1B15FED3143205DE133D080A0B0A29EB291F012CAC08041B2CEC271D0228BC174302C4 +6868A9E010091A28FDBC01C5FE3B0202462D0B0A05101004141B0D07FE6C015C41261602 +1010041418030B01FE64016D2C182901100000000002000FFFEE028802950006003C0000 +0123270723373307133E0135342726353436333216151403070623222703070607062322 +262F0126272E012B013536373E02333216171617133633320219245289279D312E206342 +16151710161ED409230A08031D591720310F0704020408130711171C4F10050D08030806 +051907C006070801EC6868A9EBFEA07B69200E1514150C1320185BFEFE0B2B2301369826 +3D5E121D5BA44E1B0F0D0D040104020A1878A101300A00000002004E00000279036D0006 +002D00000123270723373317150E01070307061514161F011521353E013F01032E012735 +33150E01141F01373635342F01350219245289279D31B815161BDB241E171E24FEDF332B +0D3B4C0B1929EF2C1C241F2795281D02C46868A9E010071521FEF27B6718131002031010 +031F2FCD010B2816061010040F227F6E2EB02518040310000002FFE8FF3201AA02950006 +003A00000123270723373303173E01353427263534363332161514070E01232226353436 +3332163332373E013736353426272E012322073536373637333216018E245289279D3143 +1543371A1A1711161E834E932B171C1710131E0C121E0D33070A3E140E211E100F160A29 +42040B3D01EC6868A9FE256E6E7320101011161015201757DA819E181311171C210D430B +140F1EFB33251B04110402080ABA00000003004E000002790332000B0017003E00000114 +0623222635343633321607140623222635343633321605150E01070307061514161F0115 +21353E013F01032E01273533150E01141F01373635342F0135021B1D14151C1D15131DC8 +1D14151C1D15131D012615161BDB241E171E24FEDF332B0D3B4C0B1929EF2C1C241F2795 +281D0301141D1D15141C1E13141D1D15141C1E8710071521FEF27B671813100203101003 +1F2FCD010B2816061010040F227F6E2EB0251804031000000002FFFA0000025E036C0008 +001E0000013736321615140F01050133323637363717072135012322070E010727372101 +29A20A1E1511AB0112FE1DA1474B1B29241336FE0701DFAB6628161517132D01E602C2A0 +0A150F120A6A43FDA50C141E4A03A90E025B1B0F1F2B05930002FFFEFFAF018602980008 +002D0000133736321615140F0117011E011716333235342726353E013332161514062322 +2726232207270123220607273721A7A20A1E1511ABB2FEDF2E2D1D22241B050801140C10 +1334292A47442517160901368828240F1020011B01EEA00A150F120A6A4DFEA80A1E2B32 +0F050A100C0D1514111F2D2C2A12090174172504740000000002FFFA0000025E0332000B +00210000011406232226353436333216170133323637363717072135012322070E010727 +3721018F1D14151C1D15131DCFFE1DA1474B1B29241336FE0701DFAB6628161517132D01 +E60301141D1D15141C1E95FDA50C141E4A03A90E025B1B0F1F2B05930002FFFEFFAF017C +025E000B0030000001140623222635343633321617011E011716333235342726353E0133 +32161514062322272623220727012322060727372101211D14151C1D15131D5BFEDF2E2D +1D22241B050801140C101334292A47442517160901368828240F1020011B022D141D1D15 +141C1E9FFEA80A1E2B320F050A100C0D1514111F2D2C2A1209017417250474000002FFFA +0000025E036B0006001C000001072327331737170133323637363717072135012322070E +01072737210201A2305F26598D82FE1DA1474B1B29241336FE0701DFAB6628161517132D +01E6036BA9A96969ECFDA50C141E4A03A90E025B1B0F1F2B059300000002FFFEFFAF01AA +02970006002B00000107232733173707011E011716333235342726353E01333216151406 +2322272623220727012322060727372101AAA2305F26598D09FEDF2E2D1D22241B050801 +140C101334292A47442517160901368828240F1020011B0297A9A96969F6FEA80A1E2B32 +0F050A100C0D1514111F2D2C2A12090174172504740000000001000D0000020102AB0021 +000013333637363332161514062322272623220703061514171617072137363736371323 +A41B0C103F7F284018101C11181D39136403090D3A03FEFC0332131509451B01C239258B +1F200F19212A5BFE24100D14090D020F0F030D0F2C01480000020017FFF501D902AB0022 +0030000013073E0133321615140E012322263D01132337333736342627353637170E010F +01330717342322070E0115143332373E01DF3C33593634405E974B2E547D7B0D76090913 +2D4B4E05030E0410A2080346454420282E42392E4001FDDB534440384D9C6320150601CD +1F1F1F20070211090E050B3A0D381FCB567336832E223B30850000000001001EFFF50241 +02240033000025170E0123222635343637363332173E01333216151423222E0223220F01 +161514062226353436353423220706151416333236015E102E5738474C44374D5A1C150F +49321B2B190E1006110E29140A0B1C28130F253F2E5B2E2B27406B0A3A324E4C437D2D3D +09353F201A1C131713532B1015141C1711091D0A14306089363C260000010042FFFA029F +02AC003A000025170E0223222E01353436372E0135343E023332171E011514062322272E +0123220615141633323633321615140623222623220615143332360233220F65783E3B63 +4B714A312E3C5F69323634253C1A152D13073E17547A283709530B101F35150842064A6D +A5595EA809364E211841324A840D1A2D263758341C100B3D211725511A1E695825210B0F +0E121805634C5D2B0001FFC2FF6101EE02C2003300001333123332161514062322263436 +353423220706070E010733072306020706232226353436333216151406151433323F0136 +3723646B458D202D1E140E1511131B112317020A037E0680155B1A374D202A1E170E1213 +1244211B131C6701B5010D22191521121819050C12276B09340F1F76FECE2C61211A181F +160E1316010CAE9E636E000000010013FFF6028E02AB003F0000010706151433323E0135 +34272E01353436333216171615140E0223222E0235343F01363534232206070E01072313 +3634262B01353637170336333216151401BC4509113E6D3B05022F160E161E05022A4A72 +411012190C1437071C1B55281F241D4B920D1A0F1B4C510779826421200157F71F071456 +7C3C100D052B1610121B1709153F836C450207130F114BCF1C0519503C305B6F022A3218 +0D0F091206FE41D3251E0F000001000EFFF501EA02AB002F000033133E01333216151406 +2322262322060703373635342B013533150E010717163332373637170E01232226272627 +0F010E7C188E5D2637190F16321126530E4F2B8F250EB324506D25371A1518050A0F232E +1E1623181E1E283001D15B7F1E1A10163C4A36FED4216E1E12101002376158812A08120B +3F2E232F3A5320B400010029FFF5011702AB0021000001033307230706151433323F0117 +0E01232235343713233733373635342B0135363701174F4E084E4208111B28190E2D4129 +3104474A084A3A033112465502A6FED41FFE1D0E1239230A473638110D01101FDD0A0916 +100813000001001E000001DE029C001C0000010713230B01231336373427073537262322 +062322263534363332173701DE663A2C22E85EF5310C01ACA5111C0A2D12141C2620691F +6E023F4BFE0C014DFEB3015543181C0B7F36774314181619217C55000001000EFF1701BA +01B90022000013073E0133321615140703231336353426232206070E0107231336353426 +27353E0137D5434A6C311F220A995195110F0C1D4631211F244B60021B2627601B01B7DA +7666211C1923FDD702033C130D0F4449314E79015E0A070F0B011008110600000002003C +FFEE030F02B3002400340000011407062322271615140607062322263534363736333217 +1633323635342E0134363332160734262322070E011514163332373E01030F4218090B09 +23735C696D6377624F707F693B151A111311121B13151CBD453C6357353D433F5C4E3B46 +027B370D03023D576BC43E477C6E5BC1456143120C09010B161E191EB447517548B64E52 +575F4AD60002001BFFF5024701D300240033000001140706222716151406070623222635 +343637363332161716333235272E01353436333216073426232207061514163332373E01 +02474218140A0539315369464D654F38402D43100F11240909111B13151CC72621433D4A +2A243D3C2228019B370D03021613397B31534942539E2A1E282507160606160E0F191E6F +2D315B71732C2F532E8100000002FFB5FF3301D8029D0028003800001307363332161514 +062322270706151416331523353E0137133E043332161514062322262322133426232206 +070E0115141633323736ED334B60373CBB6E242124041B23CB201B09940B1423293E2526 +37190F16321032781D21274E12162B1B154E43450217CF71423B7BCC1187100F100D1010 +011A24022E283D422A1C1E1A10163CFEBE2C29442E399C1911155F600002001FFF17013F +02AC0020002D000037130623222635343633361615140703061514333236333216151406 +232226353413342322061514163332363736419E2328262F5934393A08B81C2413321610 +183B21434CD82E2840221A172E0E072D01BA1830243455014B392516FDED5227293C1610 +1D1B45393A028635362A19201B1528000001FFCAFF260128022200340000010723030615 +143332363717060F01062322263534363332163332363F01363706232235343713232734 +373E013736333215140F010128055457020F0C20250D230C1D1B552637190F1632110C1B +071E0307222B2E104E4B012119521A060809011C01AC20FEB80806101F3007572E665D1E +1A10163C2117630B2922250A4001280612070641270908050267000000010026FFF501C4 +02AB002A0000132327343736373E0133321615140623222623220E010F01330723030615 +1433323637170E012322353437844B012116241B6257223B180F16321115210D08215005 +5457020F0C20250D2E3B262E10018C061207051B6C741B1D0F173C24231D7920FEB80806 +101F30074633250A4000000000010066FFEE037102FD003B00000133323E0135342E0134 +36333216151406070E01070E010703060706222635343F0136353426273521150E010F01 +061514163332363F0136353426270237C7151A0711111A13151D241F07290C23201A5322 +3F3CC47B2C3409232D011033270E3F284F3B54662240171E28028D090905020A161E191E +191B250601040104325CFEE37435345C4837A1BD200D1414011010052234E28D2635416C +7CE95512161404000001002AFFF5025F021F003B000001333235342E0134363332161514 +0607062B010306151433323637170E0123223534370E012235343F013635342623353637 +17030615143332373E0101874A5211121A13141E241E2129074A130B0C22250E30362430 +2D59616A1B230E16274551045903142D61212501B015020B161E1920191B240506FEE948 +0611203109492E302D98926535126E8B3A090C0C0E081303FE9D09071993325300010008 +FF1601CE01C20031000001073633321615140F0106151433323637363332161406070623 +22263534363F01363534232206072627252322060723372101CEED291D2E345CC536411C +2B050D27161A1B1C363B3043392A96384B2632240B050108A22E2413123D014001ADBA11 +30285D30651C37361E19401A242E0D19352C2E4A164C1D2C32141C0B07D4263096000000 +0001000C000001F402A40024000001072306071533323637170721353736372337213635 +3426232207273E013332161514060701F409941DC7B52B2A121132FE9ED2380AF8090112 +2B3F386032151B6A4D485A232201682E20C9051B26078611E03D0C2E403A384160074B57 +61482942280000000001002FFFF401C5021B002400000107363332161514062322263534 +3633321E023332363534262B0137233733373307330701290C0710444D916C32671B1317 +1A0A221D3354453414254D084D0F2F0C4E0801BF3E0157426F86392E13192830288A342F +36951F3D3D1F00000001000F0000010202E000030000010323130102C033C002E0FD2002 +E00000000002000F0000017B02E00003000700000103231323032313017BC133C145C133 +C102E0FD2002E0FD2002E0000001000F000001AD02E00013000001072307330723032313 +2337333723373313330301AD0D9D169D0D9D4A33499D0D9E169E0D9F47334801D0335A33 +FEF00110335A330110FEF00000020027FFF50130029B000B00170000372736373E013332 +15140706031406232226353436333216891131160A1F1C2C2738402117161C2115171DB1 +05E38E403433285378FEB4151F1E18151F2000000002FF84FF31018D0295000600250000 +0107232733173707030E01232226353436333215140615143332363713363534262B0135 +3637018DA2305F26598D72681F5B41232C1811270C121E291A4810151B1A2D7C0295A9A9 +6969DFFE667972221B121A250C0F070C4E680124410F110E100316000004FFCD00000234 +03B6002500280034003E000001131E01171523353E0135342F012307061514171523353E +0137012E013534363332161514060B0201342623220615141633323627373E0133321514 +0F01018C5A0A1C28F52D200214DC3B163EBA222B390117181F2F22202E28392D9501021C +14111C1B12151B958D0E0D0D19208D0290FDE33E2203101002181B0812836F29191E0310 +1007326301E6082A1B212E2F211D2DFE720107FEF901DA131A1B12141C1C5E790C071A10 +134F000000050011FFF501DC035C00080013001E003F004D0000133736321615140F0116 +14062322263534363332163426232206151416333213170E0123223534370E0123222635 +343E013332173F021706070215143332370334262322070E01151433323736D1A20A1E15 +11ABB03B292B393A292A19271C1A27251C1D730D38351E2817385E352C38578641430D0B +033D070105590E0F25471E1A443F212C3C3B444D02B2A00A150F120A6A3A523A3A2A283B +803826271A1C26FE610B442A29195A54493D374C9E663A300307030411FEBC270D290117 +1A205C31782E4A626F0000000003FFE50000038F036C00080049004C0000013736321615 +140F01050727363534262322060F01323E0337170727363534262B01070615143B013236 +37170721353E013F01230706151416171523353E0137013E0135342F0135170133022FA2 +0A1E1511AB013D211101457B2316073D36342F19130E124511062D49283E09331E706F39 +1240FE0E2C1D0731BE65181828BB131D2401711709261A7BFEF4AD02C2A00A150F120A6A +3599020D17351D0D18DE0408191D1F04E80424101F13D920111D365205A410041319AA83 +20180F0B051010041B2D01D11D0F091901011028FEAA000000040017FFF5028002980008 +002F003A00490000013736321615140F0117073633321615140706070615141633323717 +0E0122263534370E0123222635343E013332173716342322070E01073E01372734262326 +070E0115143332373E010133A20A1E1511AB75163B3F252C42348A0821253C520A2A6D5E +39063B5731262E59843B360C13BA2321201C2216444919CE1914353C252E222A26364901 +EEA00A150F120A6A373E4026203A2B2127221A372F4D0C333D423814145C46332F4FA86B +2F2D5F48231E4040142C2132171C01573680363C293AA7000004003CFF9702BB036C0008 +0020002900330000013736321615140F0125071E0115140E012322270723372635343E02 +3332173709012623220E01151409011633323E0235340163A20A1E1511AB01173D2F2C7E +C0603A2E442C5059446E9A50353331FE4B016522324E8D4D0196FE9A20324377482902C2 +A00A150F120A6A105E24654D6DCB7812697C3C8849A18356144CFD7802151A9ACC554901 +CAFDED1D6593993B3D0000000004001CFF7901D5029800080021002B0035000013373632 +1615140F0137071E01151406070623222707233726272635343633321737070316333237 +3E0135340313263122070E011514B4A20A1E1511ABD53A303344385356080C3D23402714 +25BA6C0B063725BE0B0B4238202AF5BC0A342B313E01EEA00A150F120A6A3C770B4B333C +823047027E830D15273D73C4017294FE78045231823632FEA60185022E35994728000000 +0001FF84FF3100F601B9001E000013030E01232226353436333215140615143332363713 +363534262B01353637F6681F5B41232C1811270C121E291A4810151B1A2D7C01B6FE6679 +72221B121A250C0F070C4E680124410F110E10031600000000020013FFF601A501CC0030 +003A00001333363332161514070607061514333236353426353436333216151407062322 +2635343F013635342322073736333215140F013637363534232206AD015351272C673891 +123C2137061B13111A41354738400C300514151A0731352F1024632E3936274101834926 +23583C20314A0D391C1509160B0F1A1A142E231D2C2C162EBF170D1D121C303010498D20 +222A3E322E00000000020011FFF601E701CC00180027000001030615143332370F012737 +0623222E023534363332173703373635342623220615141633323601E75706290B07049F +040F3E5411263320AF6938274A993A022E22417D3E2C224001CCFEA61A0718011034033D +400A1A3E2D77D02B2BFE9AE6070D262AA0683F3F2200000000020011FFF601E701CC0018 +0027000013073633321E021514062322270723133635342322073F010F01061514163332 +36353426232206DA0F3E5411263320AF6938274A155706290B07049F173A022E22417D3E +2C224001C93D400A1A3E2D77D02B2B015A1A071801103470E6070D262AA0683F3F220000 +00020017FFF501E802AB001F002D000037133E013332161514062322262322060F01333E +0133321615140E012322263501342322070E0115143332373E0117721987652337190F16 +321026520F3E0132593634405E974B2E54016D46454420282E42392E403001A55D791E1A +10163C4C37E4534440384D9C6320150108567336832E223B308500000001001EFFF501A9 +01B90022000013273E013332161514060706232226353436321615140615143332373635 +342623220669102E5738474C44374D5A2E3B1C28130F253F2E5B2E2B274001430A3A324E +4C437D2D3D2E23141C1711091D0A14306089363C260000000002FFFDFF6001A901B90029 +00380000073726353436373633321615140622263534363534232207061514173E023332 +161514062B0122270725342623220E04071633323603775644374D5A2E3B1C28130F253F +2E5B222322341C22324843162A1D76013D20150C150F170B1E07222C3529A09E1F72437D +2D3D2E23141C1711091D0A14306089451A28231E251B31340797F00F150707150B21080C +260000000002000FFF17020F02AB002B003A000001170703061516333236333216151406 +23222635343F010E01232226353436333216173337363534262735360334262322070E01 +15141633323E0102090627A60A022A11321512173C2243480D232B53343239C1631F1D05 +012D0E162A4D591D122F2C32421F1B2D5C3B02AB0698FDA22719363C170F1E1A413B2734 +8B493B3D3877D81C1EA331161008021107FECE1A1F2E35933E222762980000000002000F +FFF302EC02AB002A0039000001373E013332161514062322262322060702151433323637 +17062322263534370E012322263534363332160734262322070E0115141633323E010174 +1D1987612337190F16321025520E7C120D1D280C4D4613160D2B53343239C1631F1D0B1D +122F2C32421F1B2D5C3B017F6557701E1A10163C4936FE390714192E0A7218151E3B493B +3D3877D81C341A1F2E35933E222762980002001FFFF501A001B90014002100003F011E01 +333236372E0135343633321615140623220134262322061514171E0117361F12202C213A +5E0762684A364F5E8B607301072B241F26100F363807610C2C1F5C3D0D493233436A5065 +A50128394C3322171C1B1F10400000000002001FFFF5019C01B90015001F000013273633 +32161514062322263534363736342623220617370607061514333236550C58743D4ABB70 +272B99830A332A213C9811703132232A5B01410C6C4B3E75C624204268111452321FBB2C +1B2F3031276100000002001FFFF5027F01B9002C0038000001170E01232235343F010716 +15140623222635343E023F01262322060727363332161737170706151416333225070E01 +151416333236373602641B10381D410A105B04BB70272B1D342E1E751138213C360C5874 +2D410F86052006161524FEEC622A450F142A591C0F01250C1D2A3A0E2A3D2F0F1375C624 +20243F30201040501F2C0C6C2A25440477180218113139185B4115135F4726000001001F +FFF201D301DB002E000037352634363332171E011514062322272E012322061514163B01 +152322061514163332371706232227263534363736D15D74503B2E161C1A16230E0A1214 +2E482E26060B3F5F2D274F5D095C6C4C2F24312821F902157C4F1A0C2B13121A34241743 +2D21221E4D38242A3E0C562219302849131000000001001FFFF201BF01DB002E00003735 +363736353426232206070E01232226353436373633321615140715161514060706232227 +371633323635342623DF341C38282112120A051912161A18182E3E41528E7135253F4C6D +310F3E4E36542E2CE91E011426351E251B23131E1A12102C0D1B342B621F020E58244714 +22560C3E503723290001001FFFF2029A01DB0043000001170E01232235343F0107060715 +1E01151406070623222737163332363534262B0135363736353426232206070623222635 +343637363332161D01371707061514163332027F1B12371C410B0F551771343D35253F4C +6D310F3E4E36542E2C0B341C3828201312090E25131B1A182E3D40527405240215162201 +250C1E293B0D2E392C42180206342C24471422560C3E503723291E011426351E251A2035 +1911142B0D1A352A093B0485080518100002001EFFF201CA01DB0013002B000025151615 +1406070623222635343E013332161514073532373E0135342623220E0115141633323736 +35342726013C71321F415057564484533958E027181E2B281F3B602D2E2F3626333312FB +020E5923431327544B549660303160311E0B0E371F1E26667E353A51202A3B3910050000 +0001FF9CFF31015401B90027000001150E010F01330723070E0123222635343633321514 +0615143332363F012337333736353426233501542F220C1C4107422D1F5B41232C181127 +0C121E291A338A078B1E041F2401B910021E2D701FB17972221B121A250C0F070C4E68D0 +1F7B12081711100000020008FF2C031F02AB002A00390000013736333216151406232226 +23220607030E01232226353436321E023332363F01062322263534363332033736353426 +23220E01151416333201E40838A02536190F16321022211286179D5D337815201E183220 +3E5C1017514D464EBD72382C3804302A365C2F3C383501B21FDA1F1A10153C404BFDE75A +5F272810151C201C523E5D43664973C7FE7DE10F122938557132405700020008FF2C0243 +01E200220030000001030E01232226353436321E023332363F0106232226353436333217 +3E0437033736353426232206151416333202437F179D5D337815201E1832203E5C101751 +4D464EBD723B30080C0C051003973804302A50713A3A3501E2FE03596027280F161C201C +523E5D43664973C73306090D061503FE76E10F122938A9503E58000000010034FFF50232 +01B90030000025150E010F010E0123222635343637363332171E02333237330723262726 +2322070E0114163332363F0136353426273502321F1B0720039F216278463D5568223A05 +120C02190C101D100E1421424F352E3E4F472348041A05181FE70B02151F820D22525446 +7928370D010403158B35172529227E7E49141268170C100B010B00000002000FFF1601AA +01B90034003E000037173E01353427263534363332161514070607171615140623222635 +343E05372E01272E01232207353E02373633321603270615143332363534F31543371A1A +1711161E2C28450D083E372323030A0A140F200B053D130E211E100F0F27281D080C0B3D +05094B1E1A20BA6E6E73201010111610152017325E566F492B233D43211C060E14101F15 +2D1034F430251B04110309070302BAFEAD2D621B25271E1B00020004FFF601DB01C20032 +0040000013373E0237363332161514072E0123220E040F011E03151406232235343F0127 +2E01232207343633321E02170F010615141633323635342E02E83F07251B0E1813181C0B +06251708110B120612014903110708552F4E2F393B071E112B0F3929131B150B08032E21 +1911182606050D010746092B1E0D1623181D28172006071006160152082E1725112F453E +2B313C881013352B550C1C1514C232251A101128180A180E1F00000000010013FF0E01DE +01C2002E000001030614163B011506072713062322263534373635342623220607273E01 +33321615140F01061514333236373E013701DE920D1A0F1B4C5107798264212030220A07 +0C1D290D2E3E2614181437071C1B55281F241D01B9FDD632180D0F09120601BFD3251E2B +966815070B1D330D41311713114BCF1C0519503C305B6F0000010013FFF701EE02AB0034 +000033133E01333216151406232226232206070336333216151407061514163332363717 +0E0123222635343F01363534232206070E0107137C1987652337190F16321025530E4F82 +64212030220A070C1D290D2E3E2614181437071C1B55281F241D01D65D781E1A10163C49 +36FEDCD3251E2B966815070B1D330D41311713114BCF1C0519503C305B6F00000001FFFA +FF1701EE02AB0033000033133E0133321615140623222623220607033633321615140703 +0E012322263534363332163332363713363534232206070E0107137C1987652337190F16 +321025530E4F82642120095E1987652337190F16321025530E6B071C1B55281F241D01D6 +5D781E1A10163C4936FEDCD3251E0F21FEA65D781E1A10163C493601941C0519503C305B +6F00000000020010FFF50108028E000A002B000001140623222635343632160307230706 +1514333237170E01232235343F01233733373635342627353637170701081D14161A1B28 +1E0B084A250A0E193B0D2A3A253116144B094B130917292E7204310257151E1D18171E21 +FE981F87220A0F4E0B4131371D524B1F47200C0F080110041503B10000010033FFF6010A +01C6001D0000371706232235343F01363534262322073537363332151407030615143332 +FD0D4E41480C38081010110E75200A050654042C20500C4E41172ED11A0B0F0904132409 +0B0515FECC1009280001FFF80000012A01B90017000001150E0107030615141617152335 +3E013713363534262735012A2C1B0B4106171DCD2C1C0A4208181E01B911021C2AFEFD18 +101211011111011D29010D20060E0D021100000000010004FFF5014B02AB002E00000133 +062322270706151433323F01170E0123223534371326232207233E01333217373635342B +013536371703163236012F1C16490F213E08111B28190E2D4129310449160C250E1D0F2E +2709162F0331124655064B1C281701A7680BED1D0E1239230A473638110D0119062E3A30 +04B50A091610081305FEE10C130000000002000CFFF5016E02AB002D003800000117060F +0206151433323F01170E01232235343F0106232226353436333217373635342B01353637 +1703373E0107372623220615141633320166083A5F092A08111B28190E2D412931042B10 +0D252E56320A142803311246550666082152CB2508081E3A1C160D01680D3A2003A11D0E +1239230A473638110DA4022D233451059A0A091610081305FE7A02082C438E03391E1823 +00010008FF17011702AB001C000001030615143332363332161514062322263534371336 +35342B013536370117C10F271132160F193425543A0BA3033112465502A6FD1E3C1D323C +1610191F423F242C026F0A091610081300020029FF17021902AB00340040000001073633 +32161514062322272E013534363332171E0133323635342E0223220F0106232226353437 +133635342B01353637170721072322060F010615143332370219CE0B0D3645AE74392F14 +171A17240D091B263C7A162423130D18581D241717077910370C4853063C013833CA2F27 +0C310D0E080D0190CC03463F85A61F0D2811121A3A261480691E2D150A1B5A1D2015131B +01CE3D051810091206E340282EBB3405100D00000001000CFFF702C001B9004200000103 +0615143B0115072737062322263534370E01070623222635343F0136353423220F012737 +36333215140F01061514163332373E01373303061514333236373E013702C0580A2919A2 +033A825A1D1F28354724201F1B1F03440A0C162B150F054D432A0D3C080D082F54252A1D +4B56081519522A19232401B0FEC2261016101F02D1D3201C267254551714231C0D0FFB25 +0810351A0C076B2E0F33E32006090C75346973FEC21E10194F402758770000000001000C +FF1702C001B9003B000001032313062322263534370E01070623222635343F0136353423 +220F01273736333215140F01061514163332373E01373307061514333236373E013702C0 +B05479825A1D1F28354724201F1B1F03440A0C162B150F054D432A0D3D070D082F54252A +1D4B1B431519522A19232401B0FD6701B3D3201C267254551714231C0D0FFB260710351A +0C076B2E0F33E31C0A090C753469735DE728194F402758770001000CFF1702A001B90043 +00001307363332161514073E013736333216151407030E01232226353436333216333236 +371336353426232206070E01072337363534232206070E010723133635342B013537D13A +825A1D1F28354724201F1B1F03721474512537190F1632132335106A070C08164826252A +1D4B1B431519522A1923244B5A082919A201B7D1D3201C267254551714231C0D0FFE5949 +571E1A10163C463C018F1C0A090C40353469735DE728194F4027587701451E1116101F00 +0001FF92FF17024401B90043000025170E0123222635343F013635342623220607060F01 +0623222635343633321633323E0437133635342627353E013717073E0133321615140F01 +0615143332373602360E333623151D102F110F0C1E4A332D29182D982536190F1632100C +13100A0E060871021B2627601B04434A6C311F220A380E10132A07750D462B1B1B1239A2 +3C130D0F4C4B429558A81F1910163C0716102A141D019D0A070F0B011008110602DA7666 +211C1923CB320B12350800000001000EFF1701E701B90032000001030615143332363332 +1615140623222635343713363534232206070E010723133635342627353E013717073E01 +333216151401B0690D2C1132160F19372545480A5311191E4731211F244B60021B262760 +1B04434A6C311F220140FE85311F3C3C16101A1E4A3C3B2101213C111E4449314E79015E +0A070F0B011008110602DA7666211C190001FFECFFF8025701B900230000011506070607 +0323030706151416171523353E0137132E01233533133736353427262735025725101B0E +540EEA3F08181FB132220D4710161C86CC360705082E01B9110408143BFEAB016AFB220E +140F031111041E34011D160E11FEC1DA1B120B0A0E0411000003001BFFF501D401B9000F +001900230000011406070623222635343637363332160533363534262322070617230615 +14163332373601D439315369464D654F38403E4FFEB6EB0B2621433D1FD3EE102A243D3C +23012D397B31534942539E2A1E4B7E2E282D315B2F4B33372C2F533000020031FFFA02E2 +01B90032003E0000010723363534262B01220F01333236373307233534262B0107061514 +3B0132363733072122062322263534363736333217163303133635342322061514333202 +E21812012B42293506244C3D2F101027101D266527022659274126123CFEF7196511515D +564443570B241C3D9D40043A577F6F4C01B55E060C2013179419289B121B149D05091626 +2F6E04514A508B25240202FEA101040F0D2AA66F780000000002001EFFFC027E01DB0011 +00340000011406232226270623222635343E013332160734262322061514333236373635 +343E03333216151406070615141633323E02027E8C63283006394F424978AB5363875952 +4467AA3D2038070502080E1D14171C3F0402211A263C2111010665A52D2754503F5E9F53 +753B4D4FBF8A582C201A39151C2718111A1223770E070C19203C57530003001EFF170276 +02AB0027002E0035000001071E0115140E010F0106151416171523353E013F012E013534 +3E013F0136353426273533150E0107033E0137362603130E0115141601D82B577261904D +27061C23EC2E270B26576F648C4B2B051C22EC2F253E665D74020237F2665679350257A4 +0852494B7E450592150D130E020F0F031C298F055D494E7B3D05A1160B1411020F0F0219 +EAFE820E9B5E343FFE86017E04965D36490000000001FFD30000014201B9001E00000103 +06151433323715072737070607062322263436333217163332373E013701425110260916 +9B033710223C2B24151A1916131008081B3722292401B9FEDC3B171A03111B02D8234B3F +2D1C2E1F1A0E5E3A677900000001FFD30000018002AB0027000001030615141716171523 +37070607062322263436333217163332373637363F013635342B0135363701809F022309 +1B9E3710223C2B24151A1916131008081B372B16230C1E0D3112445702A6FDA00A081A07 +02020FDA234B3F2D1C2E1F1A0E5E4841672E743006191008130000000001FFD3FF170156 +01B90028000001030615141633323633321615140623222635343F010706070623222634 +36333217163332373E013701427B1E1814113215111838263F4C223010223C2B24151A19 +16131008081B3722292401B9FE456A221C1D3C16111B1C3E3D1C7FAD234B3F2D1C2E1F1A +0E5E3A67790000000001FFECFF17019C01B9001F00003F01363736333216140623222726 +232206070607032313363534232207353717B010223C2B24151A191613100809185A121B +14514992102608179B03DF234B3F2D1C2E1F1A0E932F4743FEEB020D3B171A03111B0200 +00010018FF17019C01B9002F00003F013637363332161406232227262322070607061514 +1633323633321615140623222635343713363534232207353717B010223C2B24151A1916 +131008081A38251D521814113215111838263F4C1551102608179B03DF234B3F2D1C2E1F +1A0E5E3E4EDD3F1C1D3C16111B1C3E3D244A01243B171A03111B02000001002D000001A8 +01D60011000033373E0133321615140623222623220607032D451E615A2637190F163210 +22281451F96A731E1A10163C4348FED7000100420000012501D600140000331336353423 +220623222635343633321615140F0186530D231132160F19372644420E4501292D312D3C +16101A1E463E2932F700000000020019000001F501D0001F002900001333321615140717 +163B01152327230706151416171523353E013713363534271733323635342623220784E3 +424C9A480E1F0A7C5C251A07191CD82A220B41083561113F632B28151D01D0342C652DAA +2212D16A1E0D161301121204202C01011D112805CC44391E230700000002001900000245 +01D0001E002800000123220F01161514062B01353637363713363534273533150E010F01 +3337330507163332363534262302450A1F1F9D5E7853DA2615130C400736D827270B1A25 +C473FE9B2D1915354B403001BE22AA1D424053120414113201011B0D2602121202242F6A +D1F2B7073F31292500010009FF26017801BA003A00000107232623220615141716151406 +2322272623220F0106151433323633321615140623223534371333163332363534272635 +343633321716333237017814100E4B1B203644513F1D1A1517030813011A1132160F1937 +265D02231014502428383F42371424160E140A01BA8C741E1B26404F393D490A09028A08 +0C203C16101A1E410A0A01178828252D464F34323A0A07120001FF92FF17024102AB001D +000017133E01333216151406232226232207030E012322263534363332163332708C187B +592534190F1632103E2488187A592436190F1632103C4802195E7C1F1910163C8CFDF45E +7C1F1811163C00000001FF92FF17025302AB0025000037133E0133321615140623222623 +220607033307230E012322263534363332163332363723378974148B5D2337190F163210 +2847107F5005541F754B2338180F1732101B3E1750071F01D1506B1E1A10163C353FFE0A +1F6E7B1E1911163C6C5B1F000001004FFF17016301D60023000025030615143332363332 +161514062322353437133635342322062322263534363332151401294E171E1132160F19 +3726810D4E12221132160F19372685FCFEC85C101F3C16101A1E8127320138481B283C16 +101A1E86360000000002FFC2FF0D025A02AB002700340000371336373633321615140623 +222623220E0207031615140723363534270E012322263534363332172623220615141633 +323637369F741E473B4E2039180F17321016221B0D0A832C171A141A275549263572401A +0B1718305F2A201B2B090D0E01BA753C321C1D0F163C1E3D2A26FE072C493B2838253624 +5A523324445F1F0652341C26251C250000010026FF9F012801CC0021000013273E013332 +15140703331714070E010706232235343F0123373313363534232206780D2E3B262E104E +4B012119521A060809011C50055457020F0C20014C074633250A40FED806120706412709 +080502672001480806101F0000010006FF17013402220024000001072303061514333236 +33321615140623222635343713232734373E013736333215140F01013405548A07161232 +161018382938430C7E4B012119521A060809011C01AC20FDF81A10213C16111A1D312215 +2E01DF0612070641270908050267000000020009FFF501DF01B9002D0036000001072307 +06151433323637170E0123223534370E01232235343F0123373337363534262335363717 +07333637330F01230706151433323701DF053F19130B0C22250E303624302B586134351B +0F4B074C0C0E16274551042D9D152F4A2E70942403132D6101051F61480611203109492E +302D928E6335126E3C1F303A090C0C0E081303B12A81AB1F930907199300000000010031 +FFF6022801C200280000012322061514161514062322263534373637363534262B013533 +060706151416333236373635342733022826151F27B4644D5F06145512130D2691541B0D +332D465B1411068701AE2013074A2C67A160481A19514910150D11148B6B34262D3B744F +46492E3800010034FFF601DB01B90029000013030615141633323E013534272E01353436 +333216171615140E02232235343F013635342623353637D34B062C203D6D3A05022F160E +161E0502294A73427F0735061627455101B6FEC918081C245D7F39100D052B1610121B17 +09153F816E455A161CD4180A0C0C0E081300000000010014FFEE01AA01B9002D00002515 +060706232226272627070E011514171615140623222635343736373E0633321D0114171E +01333201AA37390B0906071112071F36531219161018203F3740091A0C13090C08040918 +0A171C14140D090C041A585BA620378D230C1116170F1325163A615544091C0E13090A03 +1B188A8C392400000001000FFFEE028801B900350000251506070E022322262726270306 +232235030E0115141716151406232226353413373633321713373637363332161F011617 +1E013302884F10050D08030806051907C006070820634216151710161ED409230A08031D +591720310F070402040813071117130D0D040104020A1878A1FECF0A0E01617B69200E15 +14150C1320185B01020B2B23FECA98263D5E121D5BA44E1B0F0000000001000A000001CC +02870033000013270E01151417161514062322263534373E013332161514062322262322 +070E010706151416171E013332371506070607232226C11543371A1A1711161E834E932B +171C1710131E0C121E0D33070A3E140E211E100F160A2942040B3D00FF6E6E7320101011 +161015201757DA819E181311171C210D430B140F1EFB33251B04110402080ABA0001003E +0000025B01D00026000001150E010F0206151433152335323E023F01272E01273533150E +0115141F01373635342735025B2428339E1B0735D817211308041B4624241FD222140B4E +99162501D012031A329B6A1C112B12120E1A16106E854524021212010A0B071697971612 +0A01120000010011FF2601AD01AC00240000250706151416333236333216151406232226 +35343F01213501232206072737211501333237015C3E09090E1132160F1937262D291019 +FEEE01238828240F1020011BFED4D1191260CE1D0F100E3C16101A1E251C0F365412015E +172504740BFE9B2800020011FFD1017C01AC001A002300000901333633321615142B0106 +07233E013723350123220607273721033332363534262322017CFED47432371F26774212 +07180113059001238828240F1020011B9F4A16191A0F2401A1FE9B54201C54200F012608 +12015E17250474FE90100B0F1500000000010015FF17020501C200280000010736333216 +1514062322272E013534363332171E01333236353426232207270123220E010723372102 +05D710113645AE74392F14171A17240D091B263C7A40370B2E0B0116C7232A100C123D01 +380190CC03463F85A61F0D2811121A3A261480693634130B0103181D2196000000020007 +FECF022001C2002900340000010703323633321615140607172327062322263534363332 +16173E013534262322072701232206072337132E012322061514163332022004F4020804 +345677501925181F2F3A463E312B321A314442360B310B01088A32261212210719211713 +1C341D2301C20FFEF40141445C841B594E062924293337441B6C443D3A130B0125273176 +FD753E292015191C00010037000001FD02AB002500003F013E0135342322060706232226 +35343637363332161514060F01061514161715233536373698444F733B1426051027121C +171A303E554A894A3E091C25ED2D111466FE0189525A2418441A10112C0F1B47365A7F05 +EA2012160D020F0F030C0E0000010037000001EF02AB002800003F012E0135343E023332 +161716151406232226272E01232206151416330706151416171523353E019B3E33451B37 +623F1F4513241A16131B0504241A3258423043081A23ED312566EA0A3C37264B432A150F +1C25121A211B172D87393145FE1C151311020F0F021E00000001FFE7FFF2019D02960028 +000001150E010F011E0115140E022322262726353436333216171E013332363534262337 +363534262735019D31250E3C33451B37623F1F4513241A16131B0504241A325842304108 +1A2302960F021E37E30A3C37264B432A150F1D24121A211B172D87393145F7220F131102 +0F00000000010018FF1201CB01B9002A000017133E013332161514062322263534363534 +2623220607030615141633323637363733070E0123222635341C5C127C47354915130B12 +0E271A26510961032F26364B0D02021909106E4D374C54016F475743321627110B092008 +1D253926FE7D0D0F26324534081026415846341100030016FFEF02C002A7000A00180027 +0000011406232226353436321625140E0123222635343E0133321607342623220E021514 +1633323E0101A5231C1827253425011B72C672738D78C77077845D57543C775A38555550 +995D014C1B25241B1A25244A73CF7F877276CF7A88625E704977A1535C6B81C800030013 +000001F901D00016001F002D000013333215140607161514062B01353236371336353426 +2717333235342B01220F0206151433323635342E02237ED0AB4B3C6D815FEC2C29094604 +1B226F3588701012062E28022B445C152B2C2001D05C383F081548465212212601190F0E +151604BA6C4016B8A20804143C40171D0E04000000020014FFF201D601DB0015002B0000 +373526353436373633321615140623222635343637361723220615141633323E01353426 +2322061514163B01C65D302734355459AA7A3965322923890D3A622E273962322D312D48 +2E2507F902153E273D12175B4C83BF32372D4614110A4B39252A698232394E432E1F2300 +0001001DFFF5028A02030037000025070E0123222635343633321E02173E013332161423 +222E0223220F01232E012322061514163332363F013635342E01273533150E0101D92003 +A21B6676C4791127182E080D3C281623140C0E050D0B220F1A100A413C63894E471D4D05 +1A050E1217BD1F1BA7830E2152547AA405040A022A351A2C0F130F437C3937A166404912 +156A16070C0C03020D0D0213000100190000029F01D00033000001150E01070306151416 +171523353E013F01230706151416171523353E01371336353426273533150E010F013337 +363534262735029F2D1E0A4805191ED82E1C0C20F22006181ED72E1D0C46061820D72B1F +0A1FF21F06172101D012031828FEDF160C1411011212021B2F7E7E170F1312011212031C +30011A170D110C0212120317297E7E180D100B03120000000003FFB0FF17018A028C000B +002800360000011406232226353436333216070306071617232627062322263534363332 +1713363534262B01353637032E0123220615141633323E02018A1E14171F2015141F2168 +0D0F362E28183345792C3C663835264D10151B1A2D7CC4103A0E284E33261A261B0E0256 +141E1D17161E21B5FE663820436A3E4B8935293A51170138410F110E100316FE10080C3D +291F1E142F2600000001001AFF0101D901B70029000001030615143B0115060727130706 +15143B011523353E013727262322070607273E013332161716173F0101D98D1233124B52 +06782B8F250EB324506D25371A1518050A0F232E1E1623181E1E283001ACFDF042161810 +09120601C8216E1E12101002376158812A08120B3F2E232F3A5320B400010019000001D9 +01D0001D0000250721353E01371336353426273533150E010703061514163B0132373637 +01D943FE83261E094907151DC12A1A094C03182232582E0E1D7F7F12011B2501261C0B0F +0D021212021D27FECD09090C072B0F2500020019FF2F029A0246002A0039000001373E01 +333217161514062322272623220607030615143B011523353E0137130E0123222635343E +0133320734262322070E0115143332373E01018B1B12432835202211101F10101613170F +AC02390FED2E24095A3B5A3B2F355989433F012219293433423529293353017B54383F12 +13220C151E1F2037FDA508031F101003181D01225E463E364D9E6552192331318C3E4D24 +2CAF000000010037000001FD02AB002C00003F013E013534232206070623222635343637 +363332161514060F0133072307061416171523353637363F012337B02C4F733B14260510 +27121C171A303E554A894A274D084D10081B25EC2C11150F104E08C1A30189525A241844 +1A10112C0F1B47365A7F058F1F3C1C2C0D020F0F030C0F393C1F000000010037000001EF +02AB003000003F012E0135343E023332161716151406232226272E012322061514163307 +3307230706151416171523353E013F012337B32633451B37623F1F4513241A16131B0504 +241A325842302B4B084B10081A23ED31250E104F08C18F0A3C37264B432A150F1C25121A +211B172D87393145A31F3C1C151311020F0F021E373C1F000003000FFFF302E502AB0027 +0034004300000901333237170721062322263534370E0123222635343633321617373635 +342627353637170F0121072322060727070615143332370334262322070E011514163332 +3E0102E5FED4D11A11101DFED2181A13160D2B53343239C1631F1C072D0E162A4D4C0627 +1B0118488828240F0C3C07120F0A321D122F2C32421F1B2D5C3B01A1FE9B2804600D1815 +1E3B493B3D3877D81A20A331161008021107100697623C172503D81A09140C01351A1F2E +35933E22276298000003000FFF17030C02AB003F004B005A000001073633321615140623 +22272E013534363332171E0133323635342623220F01062322263534370E012322263534 +363332161733373635342627353637170721072322060F01061514333237033426232207 +0E0115141633323E01030CCE0B0D3645AE74392F14171A17240D091B263C7A442C0D1858 +1E2B13160D2B53343239C1631F1D05012D0E162A4D4C063B013833CA2F290D360912080D +2E1D122F2C32421F1B2D5C3B0190CC03463F85A61F0D2811121A3A2614806939311B5A1F +18151E3B493B3D3877D81C1EA3311610080211071006E3402A2EC32407140D01341A1F2E +35933E22276298000004000FFFD102E502AB0031003A0047005600000901333633321615 +142B010607233E013723062322263534370E012322263534363332161737363534262735 +3637170F0121033332363534262322372322060727070615143332370334262322070E01 +15141633323E0102E5FED47432371F267742120718011305901E1413160D2B53343239C1 +631F1E052D0E162A4D4C06271B01189F4A16191A0F242B8828240F0C3C07120E0D341D12 +2F2C32421F1B2D5C3B01A1FE9B54201C54200F0126080D18151E3B493B3D3877D81C1EA3 +3116100802110710069861FE90100B0F15F5172503D7180C140F01321A1F2E35933E2227 +6298000000020026FFF5020B022200310048000001072326232206151417161514062322 +2726270E01232235343713232734373E013736333215140F013336333217163332370117 +060716333236353427263534372303061514333236020B14100E4B1B203644513F1D1A2D +19133E1B2E104E4B012119521A060809011C6B1D231424160E140AFED90D06031A412329 +383F134E57020F0C2001BA8C741E1B26404F393D490A12231D22250A4001280612070641 +2709080502670D0A0712FEBB070A045628252D464F34241BFEB80806101F00000002FFE0 +FF17028F02AB0035003E000001373E01333216151406232226232207030E012322263534 +3633321633323F010E01232235343713232734373E013736333215140F01172303061514 +33323701400A197A592534190F1632103E2488187A592436190F1632103C2226323F1F2E +104E4B012119521A060809011C606957020F155501AC255E7C1F1910163C8CFDF45E7C1F +1811163C7F922E27250A400128061207064127090805026720FEB80806104F0000020026 +FFF002640222004400500000130306151433323726353436373633321615140622263534 +3635342322070615141736333216151406232226270623222635343713232734373E0137 +36333215140F0133071334262322071E02333236CF57021E20340F44374D5A2E3B1C2813 +0F253F2E5B04674B2233444958500F6043111C104E4B012119521A060809011C5005F620 +194A5B093D28122F2F018CFEB808090E192130437D2D3D2E23141C1711091D0A14306089 +1A0E32251C2D371E183111140A400128061207064127090805026720FEC311142D171B04 +2300000000010007014601870346002F000001170E0123222635343F0136353423220607 +0E01072313363534262B0135363717033633321615140F01061514333236017C0B263520 +1014112E06191548211A1E183F780D160D16434106666C541B1C043C050E0A1A01A40931 +24110E0C389A1602133C2C2343530194290B080A0B080C04FEB59C1B160B0CBC12070D16 +000100070146019E0346003A00001B013E0133321615140623222E0427262322060F0136 +33321615140F010615141633323637170E01232235343F01363534232206070E01070770 +115D39354B100C05090509030A021A1C21390B456C541C1B043C0509050A19220B263420 +25112E06181648211A1E18014D017937491F180B10030208040D021F3724E19C1B160B0C +BC0F0A050815260931241F0C389A1602133C2C23435300000002002C00C7015E0353000A +00290000001406232226353436333207030E01232226353436333215140615143332363F +01363534262B01353637015E16101217171110024F1745321A22120D1E090D171F14370C +1015131F61033A201616121017A3FEC95C561A140E141C090C05083A4FDD310C0D0A0D01 +11000000000100020159014002B2001F000013373637363332161406232227262322070E +0107233736353423220735363717740D1A38251F13161613110D0707182F1D232042460E +210E0D1B6C0202071C373523162418140B492D5060E42C1514020D041102000000010000 +0159013E02B2001F00000107061514333237150607273707060706232226343633321716 +3332373E0137013E460E21011A1B6C022F0D1A38251F13161613110D0707182F1D232002 +B2E42C1514020D041102A91C373523162418140B492D50600001000000A3014F02B20028 +000001030615141633323633321615140623222635343F01070607062322263436333217 +163332373E0137013E6B1A15110F2B120F15302138411E290E2031251F131616130F0F07 +0717301E231F02B2FEA5521B16172F100E151630301663871B3D2F23162418140B492D50 +6000000000020006015901CE02AC001D002800000123220F01161514062B01353E013F01 +363534273533150E010F01333733050716333236353427262301CE09191A7F526344B823 +1C0A2F052FB6211F09141FA161FED92114142C3C20152B029F197C15332F3A0D041A25BB +16061C030D0D021A224E99B186052D241E110B000001000F0147020302B2003200000113 +3E01353427263534363332161514070623222F010706070623222726272E012B01353637 +3E023332161716173736333201411A4E341111120D1118A82109060317462B01260C0901 +0A0F050E12162D1E040A070206040513069805050602A6FEEA6153190B1110100A0F1A13 +40D42A1BF6794C024A25CF38160C09080701020208134E90F00800000001001000CA0165 +02B5003100001317363534263534363332161514070E012322263534363332163332373E +013736353426272E012322073536373236333216DA105D28110D1117633C6F211115110C +0E18090E160A2705072F0F0B19160B0D411D010A04082E01F3539B280B1A110C10191143 +A56277120E0D1216190B3308100A17BF261C15030C0D05018D0000000001004F01BB00EC +02AE0017000013170E01151433323633321615140623222E013534373E01E80433330A02 +1106151D30180D1C1B040F6302AE131C41110A0419191D1D081E19140D3754000001001E +0127013302B2001C00001333323635342322060706232226353436373633321514062322 +270723430C44552F111D040C1E1115141426327C794B0D051A2501BE5A424B1E1137150E +0D230B156353840152000000000100170127015702B2001E00001337062322263D013E01 +333217161514062322272E012322061514163B01078D0E050D3141045E6334252215111E +0B041E11294A3B2E0C2501275201403206536F1916210E1534131F692D232E970001005B +01EC0181029500060000012327072337330181245289279D3101EC6868A9000000010079 +01EC01AA0295000600000107232733173701AAA2305F26598D0295A9A969690000010075 +01EC01A2028A000C000001330E0123222635331633323601851D0B6240433D1D08632E4A +028A435B4F4F6335000100CF01FC0131025E000B00000114062322263534363332160131 +1D14151C1D15131D022D141D1D15141C1E0000000002009B01FC016302C3000A00150000 +0014062322263534363332163426232206151416333201633B292B393B282A19271C1A27 +251C1D0288523A3A2A283B803826271A1C2600000001FFECFF5700C80028000F00001F01 +0623222635343733061514163332B71134492837461821211A2F4A124D352A3D352D3017 +1E00000000010064020501AB02700013000001330623222726232207233E013332171633 +3236018F1C164912274718240F1D0F2E272036291514170270680F1D2F3A301813130000 +0002005D01EE01E6029800090013000001373633321615140F0123373633321615140F01 +0107A00A110F1511ABCDA00A110F1511A901EEA00A150F140A68A00A150F140A68000000 +0002001700DA014F02AC0029003500000115220E040F01171615140623222635343F0127 +26272623353315230615141F01373635342335030706151416333236353427014F070D08 +0C040C017D0E042E281A2415381A080B0619850B1A031868111B862C0C0E0C12190502AC +0B07050F051401BF4B160C2C3A1916201C499D3627190B0B031A0D159BA31C0A110BFEC8 +3D18130C101B140D1900000000010029014D00D6034500190000130306151433323F0117 +0E012322353437133635342B01353637D66F060C141D120A20301E23036302230D2C4403 +41FE58150B0D2A19073427290A0B017D0707100B050F000000010010014F012202B30029 +000001072326232206151417161514062322272623220723373316333236353427263534 +36333217163332370122100D0A3B151A2B353F32161511110F080C0F0D103E1C1F2B3234 +2B111B13090F0902B36E5B18151D323F2C30390807107C691F1C24363E29262E08050E00 +00010004014D017B02B3003C000013173E013332161406232226232207141F0116333237 +3E0237170E012322262F01070E0123223534363332171633323F01272E01232207273633 +3216D90A29341A0E130F0C07180A1737061A07100F17030704020C232716131408174619 +1E14270F0C0A0F0E060D1C4117080D0F092402471E0F10026F2D4031101A100E600D176A +2020040A0602073625181E5E5E2214220C100907275C6224120A0D1A1B00000000010008 +014D0159034F00230000133726353436333217161514062322272E012322061514163307 +06151416171523353E0157295D5F5E2D252715111E0A051D11274332252D07141BB8271D +019CA6134D426B1517220E1532151D692D2635B520070F0B020C0C02160000000001000F +FF6D0131004600060000250723273317370131841A84454C4C46D9D9787800000002000A +01FB018B02990003000700000121352115213521018BFE7F0181FE7F01810263369E3600 +000100A001EE014202890009000013373633321615140F01A05E111A0910285801EE8417 +0C0A0F25510000000003004601EE01830289000B00150021000001140623222635343633 +321607373633321615140F0127140623222635343633321601831D14151C1D15131DE35E +111A091028581A1D14151C1D15131D021F141D1D15141C1E4484170C0A0F255131141D1D +15141C1E0003FFCD0000023402A6000900240027000013373633321615140F010123353E +0135342F012307061514171523353E01370133131E0117270B018B5E111A091028580187 +F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D95020B84170C0A0F2551FDF51002 +181B0812836F29191E03101007326301F0FDD73E2203F60107FEF90000010096014A0105 +01B9000A00000114062322263534363216010521181620202E210180171F201618212100 +00020007000002A702A60009003A000013373633321615140F0125072736353426232206 +0F013332363717072736353427262B01070615143332373637170721353E013713363534 +262735075E111A09102858027E1F16034A762A1A05424E422C1D1244140707113D4E2024 +4F814A2638103EFE052B1A0C7B0B1F2E020B84170C0A0F2551829A0219152D1C0911E920 +4104E8051F1518070F707F132227144E08A21008172B01BA251B14110410000000020004 +0000033502A60033003D000001150E01070306151416171521353E013F01210706151416 +171523353E01371336353426273521150E010F0121373635342627350537363332161514 +0F0103352C1C0C84031E31FEEE33290A40FEE34203192DF5291F0E760C1D30010F2D2D0A +36011D2E0E2028FDC75E111A09102858028D1008182AFE1F0C0C1510051010042227E9F4 +0A0C161006101004203201AE2E12150F051010032024C6A4340B131403108284170C0A0F +2551000000020003000001AD02A600090021000013373633321615140F0125150E010703 +06151416171523353E013713363534262735035E111A0910285801882A1B0D780F192CF4 +2A1F0B780D2028020B84170C0A0F25518210051A2EFE533517140E051010081A2901B92E +10141502100000000003003AFFEE02BB02A6000900190029000013373633321615140F01 +051406070623222635343637363336160734262322070E011514163332373E013A5E111A +09102858025F735C696D6377624F707D6B7669453C6357353D433F5C4E3B46020B84170C +0A0F2551696BC43E477C6E5BC1456101863447517548B64E52575F4AD600000000020008 +000002D502A6002D0037000001072623220E020F01061514161F011521353E013F013635 +3426232207273E01333215140733353E013332171605373633321615140F0102D5101117 +2F5B432C0F280A171E24FEDF332B0D380A313C141A072433208F0203257F3A1C1412FD3B +5E111A09102858025C0719517F6F348C221513100203101003202FC5423E69580B0E1A17 +CF1E100165950E0E7384170C0A0F25510002FFFA000002E302A600090039000013373633 +321615140F0113372635343637363332161514060F01333E0437170721373E0135342322 +070615141617072137170615141633325E111A091028589E0593473D6DA4767499810780 +1814221317081238FEE82857778C6C54612A2A18FEF12A1104201E020B84170C0A0F2551 +FE5325309B418032597C6A6BAC1A2502030B101F1605AEA613A962B55766723D590EA6B3 +0514101A1200000000040031FFF501830289000B00150021003A00000114062322263534 +3633321607373633321615140F0127140623222635343633321613170E01232235343F01 +36353426273536371703061514333201831D14151C1D15131DE35E111A091028581A1D14 +151C1D15131D360D2A3A253116300917292E72045E0A0E19021F141D1D15141C1E448417 +0C0A0F255131141D1D15141C1EFE400B4131371D52B1200C0F080110041503FEA9220A0F +0002FFCD00000234029C001A001D00002123353E0135342F012307061514171523353E01 +370133131E0117270B010234F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D9510 +02181B0812836F29191E03101007326301F0FDD73E2203F60107FEF90003FFF80000024C +028D0018002400310000133332151407060715161514062321353E013713363534262713 +33323635342623220E01070307061514333236353426272682FAD040224D7E957EFEF029 +1D0D790B1E2F76266267383D17110E05492023435869261D22028D96462B171001267062 +6610061C2E01B42719160F04FEEE4E4A3834030F12FF007785072A58522D400A0C000000 +0001000800000285028D001E00000107273635342E01232206070306151416171523353E +01371336353426273502852015031C505B231C05840E222BFB291E0C7B0A1B31028D9A02 +160B27210E0D13FE27320A121203101003202B01BA2419150F0410000002FFE00000020E +029C00030006000029010133130B01020EFDD201901A1157FF029CFDC201B0FE50000000 +0001FFFF0000027A028D00300000010727363534262322060F0133323637170727363534 +27262B01070615143332373637170721353E013713363534262735027A1F16034A762A1A +05424E422C1D1244140707113D4E20244F814A2638103EFE052B1A0C7B0B1F2E028D9A02 +19152D1C0911E9204104E8051F1518070F707F132227144E08A21008172B01BA251B1411 +041000000001FFFA0000025E028D00150000090133323637363717072135012322070E01 +07273721025EFE1DA1474B1B29241336FE0701DFAB6628161517132D01E6027FFDA50C14 +1E4A03A90E025B1B0F1F2B05930000000001FFF800000301028D0033000001150E010703 +06151416171521353E013F01210706151416171523353E01371336353426273521150E01 +0F01213736353426273503012C1C0C84031E31FEEE33290A40FEE34203192DF5291F0E76 +0C1D30010F2D2D0A36011D2E0E2028028D1008182AFE1F0C0C1510051010042227E9F40A +0C161006101004203201AE2E12150F051010032024C6A4340B131403100000000003003C +FFEE02BB029A0014002400340000013306152334262B012207233635331E013B01323637 +1406070623222635343637363336160734262322070E011514163332373E01020C133513 +151A693C1813351306141A621D32B6735C69716373624F707D6B7669453C6357353D433F +5C4E3B4601AD8948201A3A815023161E106BC43E477B6F5BC1456101863447517548B64E +52575F4AD60000000001FFF800000180028D0017000001150E0107030615141617152335 +3E01371336353426273501802A1B0D780F192CF42A1F0B780D2028028D10051A2EFE5335 +17140E051010081A2901B92E101415021000000000010007000002D2028D003500000115 +220705131E0117152135373635342E032F010706151416171523353E0137133635342627 +3521150E010F01373635342F013502D21C31FED6A817222FFEED1D2E030A0610027B410B +1D2AF72B1A0C7C0E222D010E2A2E0B35998F2418028D1025E1FEDB281505101003041C06 +0E130B1C04D7ED260D171104101005182A01BD320C1514021010031F28C26D65240F0403 +100000000001FFCD00000234029C001900002123353E013534270B01061514171523353E +01370133131E01170234F52D200246E5163EBA222B39011D1A5C0A1C281002181B081201 +AEFE6629191E03101007326301F0FDD73E22030000><0001FFEE00000368028D00280000 +01150E01070306151416170721353E01371301230B01061514171523353E013713363534 +27353313010368291D0B7B0E242D01FEF131280D7EFE88113E760946C5282517720C4AB5 +38014F028D10071B28FE45320C141501101005222F01CBFDCF0222FE5420162B05101005 +2F520191290B1E0410FE1401EC000001FFECFFF102D7028D0022000001150E030703230B +0106151416171523353E013713262335331B0136353426273502D71B15190D109212E672 +091F27C62B241B75173FA0CF6A081B2A028D100607232A37FE050226FE5A1F1715130310 +1006335F019F3610FE0D01841C101B1404100003FFFA000002A8028D000D002600330000 +0107273635342623212206072737050727363534262B01220607273717061514163B0132 +3E01371307213717061514332132363702A82C11021C2CFEE3292F0F122A01703B110215 +289621290F123813031D218F21250E0D3438FDE72C11043E013F344015028DA40212051C +151D28049BC5DA020C0F1510182504D1040B12160B11161EFEE3AEB30513112C20350002 +003CFFEE02BB029A000F001F000001140607062322263534363736333616073426232207 +0E011514163332373E0102BB735C696D6377624F707D6B7669453C6357353D433F5C4E3B +4601A26BC43E477C6E5BC1456101863447517548B64E52575F4AD60000000001FFF80000 +0301028D0025000001150E01070306151416171521353E01371321030615141617152335 +3E01371336353426273503012C1C0C84031E31FEEE32290B91FEE39303192DF5291F0E76 +0C1D30028D1008182AFE1F0C0C15100510100421280208FDED0A0C161006101004203201 +AE2E12150F0510000000000200000000025D028D001C0027000013333215140607062322 +270706151416171523353E0137133635342627170716333237363534232292F1DA251F45 +883723350E1E27F42B1B0E74111C2BAE451D17572947822C028D94284D1A3908C1360912 +13041010061B31019E3D161411052DF50518285E7B000001FFFA00000293028D00150000 +010727363534262B011301213236371707213501033502931F1603415AB2A0FED4013139 +421B1342FDDA015EC1028D9A020F1C2D1CFEFAFEFB2F3A03C40F0136013A0E0000000001 +003B00000279028D001A00000107273635342B0103061514161F011521353E0137132206 +07273702792C1103653A890E161F23FEE0342C0B8F775324122A028DA402191551FE1631 +11171103031010051E28020F2B51049B00000001004E00000288029C002D000001072623 +220E020F01061514161F011521353E013F0136353426232207273E01333215140733353E +013332171602881011172F5B432C0F280A171E24FEDF332B0D380A313C141A072433208F +0203257F3A1C1412025C0719517F6F348C221513100203101003202FC5423E69580B0E1A +17CF1E100165950E0E0000030032000002DB028D0025002D0035000001071E0115140706 +070615141633152135333236372E01353437363736353426233521152322070332373635 +3426011322070E01151401FE0C65845B55AF0B2A33FED7092E300C62845856AF0E192D01 +1F11582563783B3C47FEF6637143201902462A045659604D48031D1B1712101029380159 +585F4C4A04310F130E10107FFE973F405A3F4BFE9D0169462245318100000001FFE30000 +028F028D0031000001150E010F01171E01171521353E0135342F0107061514331523353E +013F01032E01273521150E0115141F01373635342735028F213725A965122433FEF12B22 +0E43971742D528486C53670F252B0107291E103B91153D028D100B292BC2FF2D1A061010 +0111131023A5AC1A1621101007437E6100FF251C041010051113112793A718131F031000 +00000001004D0000030A029B0043000001150E04070E032307061514163315213532363F +0122353436353423353332161514061514163337363534262B0135211523220F013E0337 +3E0333030A11190F070A021346595C343B062535FEDF312F0B3BC5222A1D2F35153A313B +0A16270B0116114D10430F303E360B0C1529412E029B1004101C132908415D3316D21808 +181610102228D6921E660F361038361152132F33D92509130E101037F101101F3D272B34 +381B0001FFFA000002E3029A002F00003F012635343637363332161514060F01333E0437 +170721373E0135342322070615141617072137170615141633F20593473D6DA476749981 +07801814221317081238FEE82857778C6C54612A2A18FEF12A1104201E5E25309B418032 +597C6A6BAC1A2502030B101F1605AEA613A962B55766723D590EA6B30514101A12000003 +FFF8000001CC0358000B0017002F00000114062322263534363332160714062322263534 +3633321617150E01070306151416171523353E01371336353426273501CC1D14151C1D15 +131DC81D14151C1D15131D7C2A1B0D780F192CF42A1F0B780D20280327141D1D15141C1E +13141D1D15141C1EAD10051A2EFE533517140E051010081A2901B92E1014150210000003 +004E000002880358000B0017004500000114062322263534363332160714062322263534 +3633321605072623220E020F01061514161F011521353E013F0136353426232207273E01 +333215140733353E013332171602401D14151C1D15131DC81D14151C1D15131D01101011 +172F5B432C0F280A171E24FEDF332B0D380A313C141A072433208F0203257F3A1C141203 +27141D1D15141C1E13141D1D15141C1EDE0719517F6F348C221513100203101003202FC5 +423E69580B0E1A17CF1E100165950E0E00000003001BFFF5022502890009002500330000 +01373633321615140F011703151416333237170623222706232226353436373E01333216 +153707342322061514163332373E02012C5E111A09102858D78E1210171C102540310358 +6A4541312A225D2E3C422E684D34792223495C01090601EE84170C0A0F255142FEFB1227 +3C4A077E696B54443D772B222B47306A9C94B86F3043BF061B1A0002001EFFF501A90289 +0009003D000013373633321615140F011317062322353436372E01353E01333216151406 +2322263534363534232206151416333236321615142322262322061514163332F85E111A +091028584E104E79934232191C017B64303C201012150F253558292103241C142D091A0B +2C5534255701EE84170C0A0F2551FE740A636F2D480D0425173C572F24141A16120A1D09 +143C3E1A1B070C091B0938332A270002000EFF3301BA02890009003B0000133736333216 +15140F0103173633321615140703061514172326353437133635342322060F0123133635 +3423220E05072736373633321514E75E111A09102858750285581F280A571E08490B214B +0E1928921D204B520E0E04090A090B070B020C0D1031302801EE84170C0A0F2551FEF601 +D61D1C1827FEBF6D3E0F130D1F327801122F1923BB616A0131320C110409080D090D020A +16133D2D290000020031FFF50120028900090022000013373633321615140F0113170E01 +232235343F013635342627353637170306151433327E5E111A091028583E0D2A3A253116 +300917292E72045E0A0E1901EE84170C0A0F2551FE840B4131371D52B1200C0F08011004 +1503FEA9220A0F00000000040013FFF601BE0289000B0015002100480000011406232226 +35343633321607373633321615140F012714062322263534363332161735321615140E02 +232235343F013635342322072736333215140F0106151433323E0135342601B21D14151C +1D15131DE35E111A091028581A1D14151C1D15131D3D55552B46653586201C080C162F0D +463D2C08320B3932572B28021F141D1D15141C1E4484170C0A0F255131141D1D15141C1E +880F3B4F2F6D5E3F682A685D1D0A0E3C096A2D191BB527234E77903536280002001BFFF5 +022501B9001B002900000103151416333237170623222706232226353436373E01333216 +153707342322061514163332373E0202258E1210171C1025403103586A4541312A225D2E +3C422E684D34792223495C01090601ACFEFB12273C4A077E696B54443D772B222B47306A +9C94B86F3043BF061B1A0002FFD8FF33020202A6001D003A000007133E04333216151406 +07151E011514060706232227070607233601031416333236353423220623222635343332 +1633323635342623220609890D1D2E364E2D2F4A4F3121376A4735471D25260E114E1001 +01721B155288280921080D112C0320081F3B241F32497F01EF304D573B27313439661601 +033B31459D271E1185311D180292FE501015CC693B080D091806702E35266A0000000001 +0013FF3201B601B30020000009010E05232235343E013736353423220607233633321615 +140E01071301B6FEEE0202080C131D132621360A0F300F240D10253E241C060B01B201AC +FE7B222147272F1532184A62168C298126268B3D391C44530B012D00000000020018FFF5 +01CC029C00260033000001140623222E03232206141E01171E0115140623222635343637 +2E0335343E0133321603342623220615141633323E0101CC131412160B0E1F19203E345B +17231E9772474D9860083314163C49242C4C68261D42732B232B522D025C131D0F15150F +2034303B16213D3669AD4A4460A50908301729132D3D1621FE8F3C36B067293260760001 +001EFFF501A901B9003300002517062322353436372E01353E0133321615140623222635 +343635342322061514163332363216151423222623220615141633320168104E79934232 +191C017B64303C201012150F253558292103241C142D091A0B2C55342557620A636F2D48 +0D0425173C572F24141A16120A1D09143C3E1A1B070C091B0938332A27000001001EFF47 +01DB02AB003A000001170E011514173E013332151406070E011514163332363332161514 +070E01232226353436333216333236353423220623223534372722263534360122052326 +301E6C241F7B4347853736071C033F441C164E221C27181310250C21304C0D3F0F7AD401 +1A2C3D02AB0F0D2613280824471921490630D95C312C01322A2D28222D1A1A11191D271E +320A76D0CF011F1B20370001000EFF3301BA01B900310000371736333216151407030615 +14172326353437133635342322060F01231336353423220E050727363736333215149402 +85581F280A571E08490B214B0E1928921D204B520E0E04090A090B070B020C0D10313028 +E401D61D1C1827FEBF6D3E0F130D1F327801122F1923BB616A0131320C110409080D090D +020A16133D2D290000000003001BFFF501EE02A6000F0018002200000114060706232226 +3534123736333216053336353423220E01172306151433323E0201EE4A3C58753C44835B +38423843FEA5E91E35285E3DD3E91D382246352701FF73D250755C509700FF452A52F966 +67697A87554C598C42646200000000010031FFF500EB01B90018000037170E0123223534 +3F01363534262735363717030615143332DE0D2A3A253116300917292E72045E0A0E1972 +0B4131371D52B1200C0F080110041503FEA9220A0F000001000EFFF301D101B900270000 +3F0136373633321615140623222623220607171E0133150623222F010723133635342B01 +35363717990385600D0D1A1C1A140C200A1A383E62192220271E3E245D3C4C560E211C35 +6203E101AB27051B141519132D41AC2C19100D40A0D30135350E180E0714030000000001 +FFF4FFF001AF02A60021000025330623222635343E013703230136353423220607233633 +32161514021514163332019F101F46171E050902D160013604310E260D1021441C241413 +161E7B8B2F2F1B446A24FEC501AC242B6C242689464144FEF73F352F00000001FFDFFF33 +01E301AC002B000025170E0123222635343F01230E012322270E04072336371333030615 +1432363F01330306151433323601D50E2E3922151C0F2101445B2C1C080915090C0A0647 +1814834B4B0450820F214D4F130B0C217509473018151F2D6879661C245325281408234D +0209FED6120930C13A7AFED44C010F1F000000010014FFEE01CB01B90016000001170E04 +0722353437133635342627353717033601BB100B244357834F1C035C021E23A30369E801 +B204407478573A03160607014D0A07100A01101F02FE861600000001001EFF4701BE02AB +004B000001170615141736333215140623220615141736333215140E022322270E010714 +163332363332161514070E0123222635343633321633323635342322062322353E013726 +35343722263534012804491D5B342F4F4D25301A3B4F351A28220F1D2B3663013736071C +033F441C164E221C27181310250C21304C0D3F0F7A01614D253A0D2202AB10183018053D +1F1E23441B2213291E12190C050D127234312C01322A2D28222D1A1A11191D271E320A76 +528A2619263B301B12490002001BFFF501D401B9000F001E000001140607062322263534 +363736333216073426232207061514163332373E0101D439315369464D654F38403E4F54 +2621433D4A2A243D3C2228012D397B31534942539E2A1E4B282D315B71732C2F532E8100 +000000010013FFEE021801AC002B00000107230E021514333237330E0123222635343F01 +230E020706232226353437363F0123220607233E01330218156603261A1F2C1B100D4933 +1E1D114C680C3434241113141E1E321652062D301E102A5B4A01AC4A0B815D092A3D3757 +2B20222ED220B9721D0C1A1229080D39D11225463B000002FFD8FF3301D701B900140023 +000007133E0133321E0315140623222707060723360134262322060706151416333E0209 +4B228A51171C311D17BF6B261E240A154E140192231F2B56162E1C153E663287011E849E +020E1B372773C8118D291D1F01E62E3E664FA61C1015016889000001001EFF4701C501B9 +002F000001142322262322061514163332363332161514070E0123222635343633321633 +32363534232206232235343E0133321601C5320C40115A8B3736071C033F441C164E221C +27181310250C21304C0D3F0F7A6A9C4C1F360182360B674D312C01322A2D28222D1A1A11 +191D271E320A76589D591C0000000002001BFFF5021301AC0010001C00000107231E0215 +140623222635343E0133173426272206151416333236021312AA0F341DA367464C4A925A +1221193A802A24436301AC4A1829272154904B4245875ECF2E480F9B632C2E9000000001 +000CFFF501AA01AC0018000001072306151433323637330E012322353437232207233E01 +3301AA15854624122C0D1012512E40623D50311019684301AC4A9F4F33291C3D54474FD7 +583D6500000000010013FFF601BE01B9002600000135321615140E02232235343F013635 +342322072736333215140F0106151433323E01353426011455552B46653586201C080C16 +2F0D463D2C08320B3932572B2801AA0F3B4F2F6D5E3F682A685D1D0A0E3C096A2D191BB5 +27234E779035362800000002001BFF33024E01B90021002E00003F013E03333215140E01 +070623072337222726353436373633150E02151416370732373E0135342E01232206DB2F +0E202F42277E2F6643272E354D36531D4A6349392A33583038C040291C38560E100D2131 +0AA7334F502C7F35776D160CC2C20B1D624DA32A20100D6F8B3E3228EFEF0F1EC1502426 +08510001FF94FF3101F201B9002900000901061514163332363733062322263534370723 +0135342623220607233E0333321615140E01073701F2FEE30115180C200D101B42201F08 +C55D011F131A0D230D10080E1720151D1D010201C401ACFE9F1A2B593D25258949374A32 +F40159594C4323261E282C1640500314392BFE0000000001000FFF33029C01B9002D0000 +01150E040706230723372322272635343635342737333215140615141633133303323736 +373E0433029C1C2C1A18090932D1364C3609451D352638013553232929734C73251F4B1E +0A0E1F26402901B910052224442426DBC2C2152657258221540610661CA21D3A3401A2FE +5E1535862B3243241B000001001BFFF5028E01B70041000001333217161514070E012322 +2E01270E012322263534373E013B011522070E01151433323637263534373E0133321514 +070607061514163332373E013534272E012301D81250272D2B2668342431130A1F413335 +4C0A1A9665125135263139214A0F030C0B2C1A232A120D02231E462F161A0508302501B7 +21265349514648171A16252248452A27608410412F913E5E452A161D292D2A3A2E49491F +14070E2232582A75332A1320160000030031FFF50167025E000B00170030000001140623 +222635343633321607140623222635343633321613170E01232235343F01363534262735 +36371703061514333201671D14151C1D15131DC81D14151C1D15131D3F0D2A3A25311630 +0917292E72045E0A0E19022D141D1D15141C1E13141D1D15141C1EFE320B4131371D52B1 +200C0F080110041503FEA9220A0F00030013FFF601BE025E000B0017003E000001140623 +22263534363332160714062322263534363332161735321615140E02232235343F013635 +342322072736333215140F0106151433323E0135342601A51D14151C1D15131DC81D1415 +1C1D15131D3755552B46653586201C080C162F0D463D2C08320B3932572B28022D141D1D +15141C1E13141D1D15141C1E960F3B4F2F6D5E3F682A685D1D0A0E3C096A2D191BB52723 +4E77903536280003001BFFF501D40289000900190028000001373633321615140F011714 +0607062322263534363736333216073426232207061514163332373E0101075E111A0910 +2858AB39315369464D654F38403E4F542621433D4A2A243D3C222801EE84170C0A0F2551 +C1397B31534942539E2A1E4B282D315B71732C2F532E8100000000020013FFF601BE0289 +00090030000013373633321615140F011735321615140E02232235343F01363534232207 +2736333215140F0106151433323E01353426D85E111A091028581A55552B46653586201C +080C162F0D463D2C08320B3932572B2801EE84170C0A0F2551440F3B4F2F6D5E3F682A68 +5D1D0A0E3C096A2D191BB527234E77903536280000000002001BFFF5028E02890009004B +000001373633321615140F0117333217161514070E0123222E01270E012322263534373E +013B011522070E01151433323637263534373E0133321514070607061514163332373E01 +3534272E012301635E111A09102858531250272D2B2668342431130A1F4133354C0A1A96 +65125135263139214A0F030C0B2C1A232A120D02231E462F161A0508302501EE84170C0A +0F25513721265349514648171A16252248452A27608410412F913E5E452A161D292D2A3A +2E49491F14070E2232582A75332A132016000003002DFFF601B402B6001F002B00380000 +011406070623222706073E0233321615140623222E023534363736333216073332363736 +35342623220613342623220E0115141633323601B42C21386A14093E221D2A4C2B3C4070 +532B3C1E0D595E3C2C2A3EFD073C4B2327120E306782221D265939201C3586026921360F +1A01579C2B332D654056912844472884F0452C268D132225230E1466FED01E2D576E251E +31B600020013FFF6020E02A6002D00380000250726270E01232235343F01363534232207 +2736333215140F010615143332372E013534373633321E0115140716033423220615141E +011736020E052522277850860715080C162F0D463D2C08120B396B3D5C7437314936491C +2F2A4F4F292C1240341ECE1303095C75681E1741180F0E3C096A2D191B3C27234ED42890 +5558342E3D553381800E0112A1573C264C611F7900000001004E000002B5029C00330000 +013312333216151423222635343E01353423220E020F01061514161F011521353E013F01 +36353426232207273E013332161514015B036F94262E3E141A1312142F5E47300E290917 +1E24FEDF332B0D380A313C141A072433204A45019801042D204C1914121607020D537F70 +318C1E1913100203101003202FC5423E69580B0E1A1762641F000003001BFF33025302AB +001A0024002D0000010732171615140E032307233722272635343E033B0137130332373E +01353427260313220E011514171601E143461F501934476539364E3649224D1A33456134 +0F4304712520395A2D0CED7238683C301502ABF20C1F5F245254452BC2C20B19671F5055 +472EF2FEF9FE66101DCA463A1C07FE66019A659446401308000000020011FFF5034001AC +001E003B0000010723161514070E01232226270E01232226353437363723220607233E01 +3305210E0115143332363726343633321514070615141633323E02353403401640082B26 +6834332F101F4034354C0A1539232E42231018714001BEFE8E233039234D0A022E212039 +02231E2C45231101AC4A2025495146482424272148452A274C43332F3C704A2F903B5E4A +250F58542E3B58070E223243645D261400000002003CFF3302BB029A001C002C00001737 +2E01353436373633361615140607060F0106151416171523353E010134262322070E0115 +14163332373E01D21B525F624F707D6B76735C4C53110F192CF42A1F018B453C6357353D +433F5C4E3B4672630C79625BC145610186736BC43E330F3D3517140E051010081A027C47 +517548B64E52575F4AD60002001BFF3301D401B900120021000017372E01353436373633 +321615140607060F01133426232207061514163332373E015C36393E654F38403E4F3931 +3D4A38D52621433D4A2A243D3C2228CDC407473B539E2A1E4B41397B313D10C802132D31 +5B71732C2F532E81000000010037FF310299029A0030000017371E013332363534272627 +2E0335343E02333215140623222E0223220E0115141E03171E0115140623228D12125428 +4A65202A842F454122598AA84F881B1A111B122C204B966628494B622034319666767C10 +191D48392C1A22100513243E2B549D6C4145191C1418144C8A4F2834180D120D1448286E +78000001001EFF4701E201CA002E0000013306070623220615143332363332161514070E +01232226353436333216333236353423220623222635343736373601D30F0E5626624E57 +4D0B3D0E3C371C164E221C27181310250C21304C0D3F0F304A4048848501CAA62F142636 +41123D2E2D28222D1A1A11191D271E320A3636513B42090900000001000800000285028D +002200000107273635342E012322060F013307230706151416171523353E013713363534 +26273502852015031C505B231C0540CD0BCC3A0E222BFB291E0C7B0A1B31028D9A02160B +27210E0D13E324D2320A121203101003202B01BA2419150F041000010020FF4201D801B1 +000900000107230733072303231301D80DDB3CB409B4404B9201B138FF25FEED026F0001 +0013FFEE02A3030500270000090106151433323717062322263534363F01052701363534 +26232207273E013332161514060F012502A3FEF52623302B08553D28282440A2FE010A01 +1626140F302B082E422229272A47A2020301F2FE9E331625230F482A1D223B45AC900D01 +632E1D1013230F2622261C203D4BAC9100000001001F000001BD02AB0007000009012301 +2101330101BDFEDA640122FECA012664FEDE017DFE8301330178FED2000000010007FF31 +029C029A0025000013273E0133321615140007353E023534270E0307233E03372E012322 +070323130617105FAC6181A8FEF8BC61A0540D396648300D5E0E415D85461A814F18154B +2B486301F7124E43B293D7FEC41117159BCB643F390A5C827D3A3B8A88670F475404FEE8 +010E1A0000000001005DFF2E01C00228001900001337041716151407273E023736353427 +0727372627072737265D0A010540146912040E0904150BB409B70A16B809B5430214144F +D44148BA940B0A21190D4E4E343F5A1B5C30385C1C5A8F0000000001FFF0FFF3022F01B9 +002A000001070E011514333216151406232226353437270723373E0135342623222E0235 +34363332161514071725022F9C142B31191A201B23303001FC6D940D2A0C0E0C0E140922 +131F252D01010C01AC911F6B20291610131C2F283C5E01E588168226110B0207120F1419 +2825486D01F60002001BFF3301E401B90020002F000005233635342E033534373E013332 +161514060706232227071E041514133426232207061514163332373E0101391003385051 +3856297D403E4F393153694D2102043B4D4C34362621433D4A2A243D3C2228CD06061118 +1A27533B957036474B41397B315339012F34100B1F1F2101F62D315B71732C2F532E8100 +00000003003CFFEE02BB029A000F00190023000001140607062322263534363736333616 +052136353426232207060521061514163332373602BB735C696D6377624F707D6B76FE0A +017D10453C6357310151FE8113433F5C4E4001A26BC43E477C6E5BC145610186AE403A47 +51754186454052575F4F0001001EFFF501A401B900220000010723262322060733072306 +15141633323637170E0123222635343E0133321633323701A412101053305D0DB306B40A +2E2B283D2E102F53464248477C441B340B0F0601B9816B733B201F253840242E0A3A3256 +41478B5B111100010018FFF5019E01B90022000013273E0133321615140E012322262322 +072337331633323637233733363534262322065C102F53464248477C441B340B0F061012 +101053305D0DB306B40A2E2B283D01430A3A325641478B5B1111816B733B201F25384024 +000000030001000002770358000B00170046000001140623222635343633321607140623 +2226353436333216170727363534262322060F0133323637170727363534262B01070615 +143332373637170721353E013713363426273502491D14151C1D15131DC81D14151C1D15 +131DF61F16034B762A1A053F4E422C1D1244140723324E1F234F7F4A2638103EFE082B1A +0C770B1F2E0327141D1D15141C1E13141D1D15141C1EAD9A0219152D1C0911E9204104E8 +051F15200E707E142227144E08A21008172B01BA2C281104100000010046FF300297028D +003C000001333633321514020706232226353436333216151406151433323E0135342623 +22060F01061514171521353E0137132322060727372107273635342B010155015E667D65 +3E3C412734201E151E1C171F54381A27284B362D1046FEF4362A0B8C334D4420122A0200 +2B120366320150586C61FED5413F3121202A1A17121D0413CBDA252931272CA63E082901 +1010051C2A020E3249049B9F0211185000000002FFDC0000025B03920008002300000137 +36321615140F011707273635342B010306151416171523353E0137133635342623350163 +A20A1E1511ABD52C1103659E840F212BFB2A1D0C7B0A1B2202E8A00A150F120A6A5BA402 +121E4EFE173A0F1212031010031F2C01BA241915131000010043FFEE02A8029A002F0000 +0107272E0123220706072107210615141633323635342635343633321615140706232226 +35343637363332171633323702A825120942406D5A421B010A0DFEFB07624D33490B241D +161C2844927590665566753B3014151B0B0298C60153506146692A272D65701B1B05180D +1E241E132C26448B7A67BB3C49110716000000010007FFEE01F2029B0035000001072734 +26232206151416171E0115140623222726232207233717061514163332363534262F012E +01272E013534363332171632363701F228123245343C2043432E73572642200F210A1222 +1402513F3A491A2227031C082D1E674B34211D22120A029BC8035350352F263343445232 +556E170C20E002090E495E47392135252A031F08303D294C580E0B0A10000001FFF90000 +017E028D0017000001150E01070306151416171523353E013713363534262735017E2A1B +0D740F192CF52A1F0B750D2028028D10051A2EFE533517140E051010081A2901B92E1014 +1502100000000003FFE1000001B10358000B0017002F0000011406232226353436333216 +07140623222635343633321617150E01070306151416171523353E013713363534262735 +01B11D14151C1D15131DC81D14151C1D15131D7D2A1B0D740F192CF52A1F0B750D202803 +27141D1D15141C1E13141D1D15141C1EAD10051A2EFE533517140E051010081A2901B92E +1014150210000001FFDEFFEE01CF028D001C000001150E0107030E012322263534363216 +1514061514323713363426273501CF2B1A0D671D5452354019281F0346127F0C1E2F028D +1006172DFE91686E302818201B13061104274001C62C281104100002FFDDFFF00385028D +0031003C00000133321514062321353E0137133635342B01220607030607062322263534 +33321E0233323637133635342627372115060F02061514333236353423024C57E2A278FE +E2291E0C81041A77141305592F362C462437351316061311293C2346081E210501D03F0C +44320F435D748C016C84688010061D2D01E01006131115FEBDAB372E2D2137181E187680 +0103200F1A13011010082FFEBA2D132A5B63660000000002FFE40000038A028D00350040 +0000013332171E011514062321353E013F01210706151416171523353E01371336353426 +273521150E010F0121373635342627353315060F02061514333236353423024F59702C1F +27A278FEE2291E0C3FFEF54303192DF529200E750C1D30010F2D2D0A37010B2C0C1C2BF7 +3F0D43330E425D758A016C150F3C28677D10061D2DE8F60A0C161006101004213101AE2E +12150F051010032024CAA82C121511051010082FFEBA2A162A5B63660000000100460000 +02BD028D003700002901353E013F013635342322060F0106151416171521353E01371323 +22060727372107273635342B0103333633321615140F01061514161702BDFEF332260B15 +164C284B36330A1F27FEF435280C8F334E4420122A02002B120365324B015A6A3E4A0C22 +06202F100421284D502659272CB7260E1612031010051B2B020E3249049B9F02111850FE +E7583736312C80180F12100500000002FFE4000002910392000800520000013736321615 +140F010333323637363736333216151423222623220E04070E0107151E011F011E011715 +23272E082726230706151416171523353E01371336353426273521150E01070154A20A1E +1511AB961C4D5428201B2331172532121F06070E0B0D0709012A46371E21154317222FA5 +72030903080307040808050E0D390B1D2AF52B1C0A760E222D010E2A2E0B02E8A00A150F +120A6AFE8A4E52421C251F1436280A0E150E1401504E1002122B2E9728150510F8061507 +0F050A0306030103D92C071711041010051A2801BD320C1514021010031F280000000002 +006EFFF202CC0377001B0045000001140623222635343633321614061514163236353426 +35343633321617150E0107030623222635343633321E0233323637032E0127353315070E +011514171B01363534233502755A35345C2213101811234222111A11131F57283524DD51 +5C2231211415180406071C4A1E541E1C29EF1A1B16054F91143C033F2D37372D1C1C1522 +1406111A191206151110151CCE10042A41FE7692241E1F1E181E1862440119662C091010 +030310130F12FEFB0106260A19100001FFE7FF4D02EB028D0036000001150E0107030615 +141617072322070607233635342B01353E01371336353426273521150E0107030615143B +013236371336353426273502EB2C1C0B750E1C2B04B648361515130954B52A1F0D730C1C +31010F2E2C0A83041CD614130784031F28028D1008182AFE46310E151005104B1D4B2927 +631004213101AE2D13150F051010041F24FE15120312101801EF0B0B1415031000000002 +FFCF00000236029C001A001D00002123353E0135342F012307061514171523353E013701 +33131E0117270B010236F52D200214DC3B163EBA222B39011D1A5C0A1C28C32D95100218 +1B0812836F29191E03101007326301F0FDD73E2203F60107FEF90002FFE40000025B028D +001D002A00000107273635342B0122060F0133321514062321353E013713363534262735 +13070615143332363736353423025B2C1103675D201A063A57E2A278FEE229210C750C1B +2B64330D42565D17078C028DA503210E4F0C17DA84688010061F2C01B32C1215110510FE +BBBB27182A39541918660003FFE90000023B028D00180023003000001333321615140607 +15161514062321353E013713363534262713333235342623220E010F0206151433323635 +3426272671FA666A634C80957EFEF02B200C730B1E2F7725C9373E171210054520204359 +66271E1D028D453D4A550D01276F626610061D2D01B42719160F04FEF096372F030F12FC +7574122A53512D400A0A0001FFDC0000025B028D001A00000107273635342B0103061514 +16171523353E013713363534262335025B2C1103659E840F212BFB2A1D0C7B0A1B22028D +A402121E4EFE173A0F1212031010031F2C01BA241915131000000002FF99FF4D02B8028D +001F002E000001150E010703061514330723363534262321220723373332361336353426 +27371B013635342B012206070E0107333202B82B1D0C780943341309332EFECE63461335 +134C7C50081E2205BD86031C97141106435D36FB24028D1006192CFE4024102EC33F113A +29B3C3EC01241B141A130110FDC401F50A08111013F3F43B00000001000100000277028D +002E0000010727363534262322060F0133323637170727363534262B0107061514333237 +3637170721353E013713363426273502771F16034B762A1A053F4E422C1D124414072332 +4E1F234F7F4A2638103EFE082B1A0C770B1F2E028D9A0219152D1C0911E9204104E8051F +15200E707E142227144E08A21008172B01BA2C281104100000000001FFC9000003CC0295 +0067000001333237363736373E01333216151423222623220E01070607151E011F011E01 +171523272E012726230706151416171523353E013F0122060F0123353E013F0136373526 +353436353423220623222635343633321514061514163B013736353426273521150E0107 +0224113E2E3B312414131E1A17252E1217061325381F2F321E21154317222FA572141415 +0E02390B1728FB2E250A41233528CCA3311E2492404C490513062A121617302158053F41 +0A2C0E222D010E2A2E0B01721A23634A1514101F14362841661D2B0F02122B2E97281505 +10F82C1B0603D92C071810041010051B27F11D31FA1005112CB44E030224530D380F352C +1C111724600D440B3D2AA2320C1514021010031F280000010009FFF00224029C003D0000 +131514333237363332161514070607151E0115140607062322263534363332161514070E +021514163332363534262B01373332363534262322060F0137B82211242E365C552F373D +2A4C5C494057565C1D1E1A1A10030D05402D5B70413A2D09284C5F362D4960241225029B +0312090D553F3D2F37060206413A4D6E1A17483F1F291C161115040D0805161E70593338 +2457462D414E5701C9000001FFE7000002ED028D0031000001150E010703061514161715 +21353E0137130106151416171523353E01371336353426273521150E0107030136353426 +273502ED2B1D0C790A1A33FEF334260B6DFE71062125F22F1A126E0C1C31010B2B2F0E65 +018C061E24028D1007192AFE3B220A1D100510100322280195FE6D130E17150210100720 +42019C2E11160E051010032536FE830191180D121003100000000002FFE7000002ED0377 +001B004D0000011406232226353436333216140615141632363534263534363332161715 +0E01070306151416171521353E0137130106151416171523353E01371336353426273521 +150E0107030136353426273502655A35345C2213101811234222111A11131F882B1D0C79 +0A1A33FEF334260B6DFE71062125F22F1A126E0C1C31010B2B2F0E65018C061E24033F2D +37372D1C1C15221406111A191206151110151CCE1007192AFE3B220A1D10051010032228 +0195FE6D130E1715021010072042019C2E11160E051010032536FE830191180D12100310 +00000001FFE4000002910295004900001333323637363736333216151423222623220E04 +070E0107151E011F011E01171523272E082726230706151416171523353E013713363534 +26273521150E0107E11C4D5428201B2331172532121F06070E0B0D0709012A46371E2115 +4317222FA572030903080307040808050E0D390B1D2AF52B1C0A760E222D010E2A2E0B01 +724E52421C251F1436280A0E150E1401504E1002122B2E9728150510F80615070F050A03 +06030103D92C071711041010051A2801BD320C1514021010031F280000000001FFDDFFF0 +02E4028D0033000001150E01070306151416171521353E0137133635342B012206070306 +070623222635343633321E02333236371336353426273702E42C1C0C81032629FEF13826 +1179031A89141305592F362C4626351D18121708130F293D2246081E2105028D1007182B +FE1F0C0C151203101004274101C70B08131115FEBDAB372E28231B1F191F197B7E010320 +0F1A130110000001FFDF00000357028D0028000001150E01070306151416170721353E01 +371301230B01061514171523353E01371336353427353313010357291D0B760E242D01FE +F231280D7BFE87113E760946C5282517720C4AB538014F028D10071B28FE45320C141501 +101005222F01CBFDCF0222FE5420162B051010052F520191290B1E0410FE1401EC000001 +FFE6000002ED028D0033000001150E01070306151416171521353E013F01210706151416 +171523373E01371336353426273521150E010F01213736353426273502ED2C1D0B80031E +31FEF033290A3FFEE14203192DF6022A1F0D730C1C30010F2D2F0A34011E2D0E2028028D +1008182AFE1F0C0C1510051010042227E9F40A0C161006101004213101AE2E12160E0510 +10032024C6A4340B1314031000000002003CFFEE02BB029A000F001F0000011406070623 +222635343637363336160734262322070E011514163332373E0102BB735C696D6377624F +707D6B7669453C6357353D433F5C4E3B4601A26BC43E477C6E5BC1456101863447517548 +B64E52575F4AD60000000001FFE3000002E9028D002C000001150E010703061514161715 +21353E0137133635342B012206070306151416171523353E01371336353426273502E92C +1D0B80031E31FEF133290A80041CD71513078503192DF52A210D720C1D30028D1008182A +FE1F0C0C151005101004222701E51203121018FE110A0C161006101004213101AE2E1215 +0F05100000000002FFE800000242028D001B002700001333321514060706232227070615 +1416171523353E0137133634262717071633323736353426232277F1DA251F4588322733 +0E1E27F42B1B0E71111C2BAC421D16552B4743412C028D94284D1A3909C2360912130410 +10061B31019E3F28110533F00519285E403500010043FFEE02B2029A0020000001072726 +2322070E01151433323637170E0123222635343637363332171633323702B2251212836D +5A3537AF3E67401243814D759066556675453014151B0B0298C803A36138A055CD34430F +4F468B7A67BB3C491107160000000001004600000284028D001A00000107273635342B01 +0306151416331521353E01371323220607273702842B1203653E820E2236FEE0342D0A8C +3C4E4420122A028D9F02111850FE17311119151010051E28020E3249049B0001006EFFF2 +02CC028D0029000001150E0107030623222635343633321E0233323637032E0127353315 +070E011514171B01363534233502CC283524DD515C2231211415180406071C481E521D1D +29EF1A1B16054D93143C028D10042A41FE7692241E1F1E181E1861450119652D09101003 +0310130F12FEFB0106260A19100000030049000002F6028D0025002C0034000001071E01 +15140706070615141633152135333236372E013534373637363534262335211523220703 +3237363534011322070E01151402130C66895B55B10B2B32FED7092F310C63875856B10E +222E01190558255F783B3CFEAF60743D201902462A045758604D48031D16181610102938 +0159585F4C4A04310F120F101085FE9D3F405A7EFEA901634022453181000001FFBD0000 +0269028D0031000001150E010F01171E01171521353E0135342F0107061514331523353E +013F01032E01273521150E0115141F013736353427350269213725A965122433FEF12B22 +0E43971742D528486C53670F252B0107291E103B91153D028D100B292BC2FF2D1A061010 +0111131023A5AC1A1621101007437E6100FF251C041010051113112793A718131F031000 +00000001FFE7FF4D02EB028D0033000001150E010703061514161707233E013534262321 +353E01371336353426273521150E0107030615143B013236371336353426273502EB2C1D +0C760E1E2C34130207312DFE272A1F0D730C1C31010F2E2C0A83041CD614130784031F28 +028D1008182AFE46310E141105C3063D0C3A2A1004213101AE2D13150F051010041F24FE +15120312101801EF0B0B141503100001003600000297028D0032000001150E0107030615 +1416171521353E013F010623222635343F0136353426273521150E010F0106151433323F +0136353426273502972C1D0B80031D31FEF033290A3C5C62454F0A24081E30010F2D2D0A +250A66474A320D1E28028D1008182AFE1F0C0C1510051010042227DB2E25331C2586200B +150F0510100220258A26133D25B9320D1314031000000001FFF2000003D1028D00400000 +01150E01070306151416170721353E01371336353426273521150E0107030615143B0132 +371336353426273533150E0107030615143B013236371336353426273503D12C1D0B750E +222D01FCAA2A1F0D73081A3001052D230A83041C8A200D83031526E82B1A0C84041D8414 +140784031D28028D1008182AFE46310E141006101004213101AE1E22150F051010041D26 +FE170C09143001E70B0B161303101008172BFE1A0C0914121801ED0B0B15140310000001 +FFF2FF4D03D1028D0047000001150E010703061514161707233E013534262321353E0137 +1336353426273521150E0107030615143B0132371336353426273533150E010703061514 +3B013236371336353426273503D12C1D0B750E1E2C34130207312DFD4A2A1F0D73081A30 +01052D230A83041C8A200D83031526E82B1A0C84041D8414140784031D28028D1008182A +FE46310E141105C3063D0C3A2A1004213101AE1E22150F051010041D26FE170C09143001 +E70B0B161303101008172BFE1A0C0914121801ED0B0B151403100002003F0000028C028D +001A002500000133321514062321353E01371336353423220607273721150E010F020615 +14333236353423015357E2A278FEE6291E0C7B022A4E3C23122A016D1F230744330E435D +748C016C84688010061D2D01D00811212E4E049B1004181BFEBA2A162A5B636600000003 +FFE40000037D028D0017002E0039000001150E01070306151416171523353E0137133635 +342627350133321514062321353E0137133635342627353315060F020615143332363534 +23037D2A1C0C720F192CF62A1F0B750D2028FE5857E2A278FEE6291E0C740C1C2BF23D0C +44310F435D738C028D10051B2DFE533517140E051010081A2901B92E1014150210FEDF84 +688010061D2D01B42C121511051010082FFEBA2D132A5B6366000002FFE400000219028D +00180023000013333217161514062321353E0137133635342627353315060F0206151433 +3236353423E0577D3332A377FEE5291E0C750C1C2BF43D0D45310F435D738C016C272541 +617E10061D2D01B42C121511051010082FFEBA2D132A5B6366000001000FFFEE027C029A +002B00001333163332373633321615140706232226353436333216140615141633323736 +372137213635342322060F019D1604192228285462848C73B055691D1E191B254134785A +4919FEF10B010A049B547023120299160C0B9073B0897044441E291D2A2807161E5F4E7C +2A1B21D44E55010000000002FFE0FFEE0352029A002B003A000013333637363332161514 +060706232226353437230706151416171523353E01371336353426273521150E01070534 +262322070E0115143332373E01DE552E5966705E646D575F61576512524003192DF52A20 +0D720C1D3001102D2D0A01E039345752333B6D544738440170755461787173CA3F477669 +3D3CF40A0C1610061010051E3301AE2E12150F0510100320244C434C754AB8569D5F4BD9 +00000002FFCF000002A4028D0020002E000001150E01070306151416171521353E013F01 +23012335363F013522263534373633033736353426232206151416333202A42C1D0B750B +222BFEEE33290A3A1FFEDB98322EF0425E3551C212330D0F1563884D4233028D0F09182A +FE4628121716021010042227D4FECF10032EF5024F3C4A334DFECEB92B140F0D575B3137 +000000020017FFF501E201B90021002F000025170E0123223534370E0123222635343637 +363332173F021706070215143332370334262322070E0115143332373601D50D38351E28 +17385E352C3844375350430D0B033D070105590E0F25471E1A443F212C3C3B444D6F0B44 +2A29195A54493D37438B344E3A300307030411FEBC270D2901171A205C31782E4A626F00 +000000020024FFF5021702AB002600350000013306070E01070E02071736373633321615 +140607062322263534373E053B013236033426232207061514163332373E010207101C42 +15570B2E583A11023B2A38403E4F39315369464D0F0B18293540552F3C2422712621433D +4A2A243D3C222802AB5018080101064F63340139161E4B41397B315349423A3927486350 +48280DFEB42D315B71732C2F532E810000000003001FFFF501A701B900170021002C0000 +2515321E0315140623222635343E023332161514063734232206073E0307342322070615 +1433323601141217281813845157451F3F734A303D52103F30631831464B282C4237490C +503945F502010A12251A426054492D605E3C282B2D39683C5D680A14212DB7491A213266 +550000010001FFF5018001B90023000013273633321615140E03151433323637170E0123 +222635343E03353426232206650A5462323D3E58583E4F223C2D0C38512F3D443E59583E +26212739015C0D50322926392525362337131F0C3323302B223B2B28331C192114000002 +001EFFF501D602AB00130022000013371E0115140E01232226353436333217372E011334 +26232207061514163332373E01D30D6B8B4C8851464DAD6B3B1C020A65632720453F462B +2346362027029A1118BB7161AA67474573C5310153A3FEC72B3359617F2A37522F840002 +0022FFF501A601B90014001F000025170623222635343633321615140607061416333227 +173E013736353423220601700C58743E50C171272BA1850A332A4A9701444C1B32232977 +6D0C6C4C3D74C72420416911145232AF011120193031278A000000010000FFF5031701B9 +004D000025333E0133321615140623222E022322070E01151416333237170E0123222635 +343723072337230E01232227263534363332160616333236373E01353426232207273E01 +33321615140733373301B63A1B794B2523160E1313030E0E191F242C2720372F0D253E2E +323E0A383D493D3918754D27131B1B0D120F020A0F0E260E242A2720372F0D253E2E333D +043A3449F252751F18151414191422288F36383B46093729504A2825DCDC588F0B101B14 +1B161A16130F279036383B4609372945492613BE00000001FFEEFFF5016501B900360000 +1333143332363332161514060F011E011514070623222635343633321615140615141633 +32363534262322073733323635342623220723650E0D0A3C1C453E503401303223377240 +481A141215101C1841432E280E08071E3144241F55301001B91111372A323A07010B3223 +32233A31281A1A17130B1A0C0A134A35262B0117422724276C000001001DFFF501EF01B9 +002E000025170E0123223534370E012235343F01363534232207273E01333215140F0106 +15143332373E01373303061433323601E10E303624302D59616A1B23110A1A3E0E2F3B25 +2E163603142D612125284A530F0B0C2277094A2D302D989265351E6281410D11520A482F +30214FC60907199332536EFED139222000000002001DFFF501EF029B0014004300000133 +0E01232226353436333216140615141633323613170E0123223534370E012235343F0136 +3534232207273E01333215140F010615143332373E01373303061433323601CA12084F3F +30511F12101811241D303E250E303624302D59616A1B23110A1A3E0E2F3B252E16360314 +2D612125284A530F0B0C22029B3D5F392E191C15221406101B45FE13094A2D302D989265 +351E6281410D11520A482F30214FC60907199332536EFED139222000000000010012FFF5 +01E501B9003A00003733323E0333321615140623222623220E02071516171E0233323736 +37170E01232226272E0427072313363534262B01353637179F0B23392727372217211B13 +111E100C201E331C391F0B120D0A1918050A0F1E361F26260E06060F0F1A103C4B4E1318 +1E0B5B4504F2293B3A291F121718252833340902053A1642242A08120B3C313732161526 +141504DC012449080F0C0F0D0D020001FFD4FFF401BA01B9003C000025170E0123222635 +343F01363534232207060F01140E0407062322263534363332171E0233323E023F013E01 +33321615140F01061433323701AC0E313C25141B102C0F1D1B1F221E120D050E0A11081D +2C19221A12170B030202040E17170F0E0E21633C1D230A380C0F1929730D432C1B1B103B +A23A19211D21653D012C0E28141B091F1A1917171806140B16362F2F307255221B1923CB +2B24330000000001FFD3FFF4025901B0002E000025170E01232226353437032303070607 +0E012322263534363332171E023332363F0133133313330E011514333237024B0E333623 +141B3AD3082730231E13232019231912170B030102041B2F1A4B3B2702CD3C1C4110122B +740D462B1B1B59B3FEC80147966D241715191917171706140B5753EEFECC013455FC2713 +350000010014FFF701D801B90023000025170E0123222635343F01230723133635342627 +3536371707333733030615143332373601CA0E333623141B1018AE3A495D021B264F5204 +3AAD3649530E10132A07750D462B1B1B103B5BD3015E0A0D0F0B010F091102CEC7FEC538 +0512350800000002001DFFF501D601B9000F001E00000114060706232226353436373633 +3216073426232207061514163332373E0101D639315369464D654F38403E4F542621433D +4A2A243D3C2228012D397B31534942539E2A1E4B282D315B71732C2F532E810000000001 +0013FFF701DF01B90030000025170E0123222635343F01363534232206070E0107231336 +3534262735363717073E0133321615140F010615143332373601D10E333623141B102C0E +181D4532211F244B60021B26683A04434A6C311F220A380E10132A07750D462B1B1B103B +A236191D4549314E7901610A0A0F0B010F0E0C02DA7666211C1923CB320B123508000002 +FFB3FF3301D001B9001E002E0000130736333215140E0123222706151416331523353E01 +37133635342B013537173426232206070E0115141633323736D71F4B606D548847242128 +1B23CB201B09730F2C189DAA1D21274E12162B1B154E434501B56D717D51985E11A50110 +0D1010011A2401B0370C1B0F1A802C29442E399C1911155F60000001001BFFF501A601B9 +0022000025170E0123222635343637363332161514062226353436353423220706151416 +333236015B102E5738474C44374D5A2E3B1C28130F253F2E5B2E2B27406B0A3A324E4C43 +7D2D3D2E23141C1711091D0A14306089363C2600000000010011FFF702C501B900430000 +250706232235343F01363534262322070E01072337363534232206070E01072313363534 +2B013536371707363332161514073E01373633321615140F0106151433323F0102C5054D +43280D3A070C082F54252A1D4B1B441619522A1923244B491929196939033A825A1D1F28 +354724201F1B1F0D3A0A0C162B1569076B2E0F33E31C0A090C753469735DEB24194F4027 +587701065B19160F0F0B02D1D3201C267254551714211A1330D8260710351A0000000001 +FFC3FF32018501B90031000037173E01353427263534363332161514070E012322263534 +363332163332363736353426272E012322073536373637333216CE1543371A1A1711161E +834E932B171C171013190C194C170A3E140E211E100F160A2942040B3DBA6E6E73201010 +11161015201757DA819E181311171C5626140F1EFB33251B04110402080ABA0000000003 +001DFF3302A502AB003100410051000001333E0133321615140E01232226270706151416 +331523353E013F01230E0123222635343E013332173635262B0135363717133426232206 +070615141633323E022535342623220E021514163332363701A6022240322C3D3B6D412E +2F0229101B23CB201B0930011F3925353B3B6E4047153C01281B603D0760272124420B36 +2D182C462513FEF52E18294427152720254409015F32284E3A4A9062361D9F3C0D100D10 +10011A24B6281B5A3F43886045DC161B0F0D0E06FE892C3C40209C2F242F3B5A5A380424 +2F3C585A242D3F411F000001FFDDFFF501B701B9003C000013173E013332161406232226 +232207141F011633323F01170E012322262F01070E012322263534363332171633323F01 +272E0123220F01273736333216EB0C3541211217130F091E0D1D4508200A14101F150F2B +321D181A091D581F2719171B130F0C140F0B1122521C0A11130C1B12030B502513160163 +39513E15201411790D208629281C09432F1E2677772A1A16140F150B0931747C2C180705 +10041D2400000001001DFF4A01EF01B90040000025070615141615140607273E01353426 +353437270623223534370E012235343F01363534232207273E01333215140F0106151433 +32373E0137330306143332363701EF221707243C0917120329011A23302D59616A1B2311 +0A1A3E0E2F3B252E163603142D612125284A530F0B0C22256E3423210D250B23321A120B +1919041A0222300115302D989265351E6281410D11520A482F30214FC60907199332536E +FED1392220310001002AFFF701C201B90028000025170E01232235343F01062322353437 +363534262335363717070615141633323F013303061514333201B40E303A242F160D4537 +681C0716274254042F041B222C353B4A4F130B177A094931300E56322048106619040C0C +0F071303AC101217161CD9FED549051100000001001FFFF502F101B90044000025170E01 +2322353437230E012235343F0123070E012235343F01363534232207273E01333215140F +010615143332373E013733030615143332373E0137330306151433323602E30E30362430 +2F0259616A1F1D02155A616A1B23110A1A3E0E2F3B252E163603142D612226274A580314 +2D612125284A4F130B0C227709492E3024A1926535186B61229265351E6281410D11520A +482F30214FC60907199333556BFEA30907199332536EFED54806112000000001001FFF4A +02F101B9005600002517070615141615140607273E013534263534372706232235343723 +0E012235343F0123070E012235343F01363534232207273E01333215140F010615143332 +373E013733030615143332373E0137330306151433323602E30E221707243C0917120329 +011924302F0259616A1F1D02155A616A1B23110A1A3E0E2F3B252E163603142D61222627 +4A5803142D612125284A4F130B0C22770934261C0D270B23321A120B1919041A02223001 +153024A1926535186B61229265351E6281410D11520A482F30214FC60907199333556BFE +A30907199332536EFED5480611200002000CFFF5021001B90023002F0000373336333216 +151406232226353436372706232226232207273E01333216333237170E01173423220706 +151416333236F5034C513D3E93634C415137011F2B1041192B1D0C1B2F301B521C2C1B0A +2C3CC2483134332520415AD548392D4D75403C459630020D0E35063B2E1717092B894944 +292765171E7000030032FFF5029101B90013002C0038000025170E012322263534371333 +030615143332363725173633321615140623222635343F01363534262335363717133423 +22070615141633323602830E2B3C25141B105249520E100B1A18FE29014C513D3F94634C +3C11230E16274E48048A493134332520415B750945301B1B103B0138FEC5380512171E7B +0148392D4D752E2A19448B3A090C0C0F0A1003FEFD44292765171E70000000020032FFF5 +01B101B900180024000037173633321615140623222635343F0136353426233536371713 +342322070615141633323697014C513D3F94634C3C11230E16274E48048A493134332520 +415BD60148392D4D752E2A17468B3A090C0C0F0A1003FEFD44292765171E700000000001 +0007FFF5018701B9002A0000133314333236333216151406232226353436333216151406 +15141633323736372337333635342623220723690E0D0B3C1B4F52A06F343D1A14121510 +1A1353371B0C8905870426275A341001B91111594A77AA32251B1B17130B1A0C0D10582E +47161C1A3B466C00000000020015FFF5028F01B90020002F000037333E01373633321615 +14060706232226353437230723133635342B013536371705342623220706151416333237 +3E01A04B17533538403E4F39315369464D0D483A4C4F13291969390301612621433D4A2A +243D3C2228E9385E1C1E4B41397B315349422A29D30124460A1C0F0F0B02712D315B7173 +2C2F532E81000002FFE7FFF701C101B00032003D000025170E01232235343723220E0207 +0E012322263534373306151416333236373E013F012E0135343E013B0103061514333236 +273736353423221514163301B30E30362430241F0E162223101332261C25081004120E12 +170D103629012F37485B388150120B0C225E29021797392C7709492E30336F0615362931 +2726291711140818191F29332C0502042D20374415FED648071120999D08031376202500 +000000040022FFF501DB025E000B0017002C003700000114062322263534363332160714 +0623222635343633321613170623222635343633321615140607061416333227173E0137 +36353423220601DB1D14151C1D15131DC81D14151C1D15131D5D0C58743E50C171272BA1 +850A332A4A9701444C1B32232977022D141D1D15141C1E13141D1D15141C1EFE2D0C6C4C +3D74C72420416911145232AF011120193031278A000000010014FF3001C002AB003C0000 +37333E01333216151407030E012322263534363332151406151433323637133635342322 +06070E0107231323373337363534262B0135363717073307239A01476D301F220A4D205A +41222D1811270C121E291A4811181A58271E1C254B81670C65070D1A0F1B4C510728A90C +A8E77062211C1923FEDC7B71231B121A250C0F070D4F68012544031D513D2F4C7D01EB26 +192D110C0D0F091206942600000000020001FFF501C702980008002C0000133736321615 +140F02273633321615140E03151433323637170E0123222635343E03353426232206E8A2 +0A1E1511ABA60A5462323D3E58583E4F223C2D0C38512F3D443E59583E2621273901EEA0 +0A150F120A6A920D50322926392525362337131F0C3323302B223B2B28331C1921140001 +001AFFF501B901B900260000010723342322060733072306151416333235343633321615 +14070623222635343633321633323701B924103F397719A105A1082D2A431E180F143F36 +3B4950B76F1B2E0B0F0601B9816C6A4D161D2B3F4644171D150D2E211C4A4B70BF111100 +00000001FFF7FFF3015501BA002900000107232623220615141716151406232227262322 +07233733163332363534272635343633321716333237015514100E4B1B203644513F1D1A +1517140910141014502329383F42371424160E140A01BA8C741E1B26404F393D490A0915 +9F8828252D464F34323A0A0712000002002BFFF50102028E000A00230000011406232226 +353436321603170E01232235343F0136353426273536371703061514333201021D14161A +1B281E2A0D2A3A25311630091729415F045E0A0E190257151E1D18171E21FE050B413137 +1D52B1200C0F08010F051503FEA9220A0F000003002BFFF50165025E000B001700320000 +01140623222635343633321607140623222635343633321613170E01232235343F013635 +34262735363717030615141633323601651D14151C1D15131DC81D14151C1D15131D3C0E +293C2532183009162937680560090A060C24022D141D1D15141C1E13141D1D15141C1EFE +330C3F3339145AAF220B0F08010F041603FEAA220B06092300000002FF54FF3100E7028C +000B002A000013140623222635343633321607030E012322263534363332151406151433 +32363713363534262B01353637E71E14171F2015141F21681F5B41232C1811270C121E29 +1A4810151B1A24850256141E1D17161E21B5FE667972221B121A250C0F070C4E68012441 +0F110E0F02180002FFD4FFF4027701B9003B004700002533363332161514062322263534 +3F01363534232207060F01140E0407062322263534363332171E0233323E023F013E0133 +3216151407173423220706151416333236016C01444F3D3A8D5B4C3C111D0F1D1B1F211F +120D050E0A11081D2C19221A12170B030202040E17170F0E0E21633C1D230A9744322933 +1F1F4153D548382E4D752E2A1F3E703A19211D20663D012C0E28141B091F1A1917171806 +140B16362F2F307255221B19238D44293359171E6F0000020015FFF5028901B900280034 +000001073633321615140623222635343F01230723133635342627353637170733373635 +3427262335363713342322070615141633323601BA3B444F3D3A8D5B4C3C110A9A394C5F +011B264F52043A9A160E0B0D2552447D443229331F1F415301B6E148382E4D752E2A1746 +29D3015E05120F0B010F091102CE4C32110C06060F0A10FEFA44293359171E6F00000001 +0014FFF701DF02AB0036000025170E0123222635343F01363534232206070E0107231323 +373337363534262B013536371707330723033633321615140706151433323601D20D2E3E +2614181437071C1B55281F241D4B81670C65070D1A0F1B4C510728A90CA8468264212030 +21100C1D760D41311713114BCF1C0519503C305B6F01EB26192D110C0D0F0912069426FE +FBD3251E2B966518121D00020012FFF501E50298000800430000133736321615140F0233 +323E0333321615140623222623220E02071516171E023332373637170E01232226272E04 +27072313363534262B0135363717BDA20A1E1511AB410B23392727372217211B13111E10 +0C201E331C391F0B120D0A1918050A0F1E361F26260E06060F0F1A103C4B4E13181E0B5B +450401EEA00A150F120A6AFC293B3A291F121718252833340902053A1642242A08120B3C +313732161526141504DC012449080F0C0F0D0D0200000002FFC3FF3201A1029B00140048 +000001330E01232226353436333216140615141633323603173E01353427263534363332 +161514070E012322263534363332163332373E013736353426272E012322073536373637 +333216018F12084F3F30511F12101811241D303EB31543371A1A1711161E834E932B171C +1710131E0C121E0D33070A3E140E211E100F160A2942040B3D029B3D5F392E191C152214 +06101B45FE566E6E7320101011161015201757DA819E181311171C210D430B140F1EFB33 +251B04110402080ABA000001001DFF4A01EF01B90046000025170E012322353437060706 +15141615140607273E0135342635343E03373506232235343F01363534232207273E0133 +3215140F010615143332373E01373303061433323601E10E303624302D63330D08253B09 +171203040A0512011A1D351B23110A1A3E0E2F3B252E163603142D612125284A530F0B0C +2277094A2D302D98A22F250E0D1A0B1F331A120B1919041902090F12081502010D351E62 +81410D11520A482F30214FC60907199332536EFED13922200000000200130000026D028D +002A003500000133321514062321373E01371323220E0307273733363534262735331506 +0F01330727363534262B010F01061514333236353423013457E2A278FEE203291E0C6E2C +211E29161C0E1220D00A1C2BF33D0D0CCC25120336353630320F425D758C016C84688010 +061D2D0197020916271F0487280F1511051010082F2B8B021A0A251CAFBA2D132A5B6366 +00000002000DFFF501F802AB002B0038000037173633321615140623222635343F012322 +060723373337363534262B013536371707330723363534262B0117342322070E01151416 +333236DD014C523D3F91644C3C113D3739211A111FA5200E190F1C54490744A51A120125 +2C318F4932341A1625204258CC0148392D4D6B2E2A1C41E815336677360A0C0E0F0A1106 +F5660B0A1C17E94429143836171E670000000002FFC90000037D028D0032003500002901 +35363534272E01270706151416171523353E013F010E0107061514171521353E01373E03 +3703210316171E0317032113037DFEE8470C18492C340B1227E428180C3C307835362BFF +0025331E283A586B415501E1FC5F431220163529A0FEDF4710044B1B23424E03C1260D19 +0F04101005182AD903453F40391D031010032A2A39444D26030133FECD045D1958403503 +0253FEF8000000020000FFF502AE01B00035003800002517062322272E0427072337230E +0423222635343633321E023332373E02373637272107151E011514061514333203231702 +A10D35543F0E0108060E171137473701213220243B2B25291B0E1212020D0E120B081619 +152746600185BD393F021F354EDB4C5E09604D043610241306C9C90C363936231F171417 +151815150F392D152705D1CF02064131031A042E017A9E0000000003003CFFEE02BB029A +000F0022003400000114060706232226353436373633361607342623220706073E013332 +1E0233323736070623222E02232207061514163332373602BB735C696D6377624F707D6B +7669453C6357321E2E3823162B1E2E181F2A14233D47192F1E2C171E2C13433F5C4E3801 +A26BC43E477C6E5BC1456101863447517544551F122025201C50802D2025201E45405257 +5F440003001DFFF501D601B9000F001E0030000001140607062322263534363736333216 +063426232207060736333216333237070623222E02232207061514163332373601D63931 +5369464D654F383F3E50542621433D201232221B3A16131808312B131C0E160E151A0E2A +243D3C21012D397B31534942539E2A1E4B555A315B3130263F111D261417141333312C2F +532E0001004CFFEE02E602960020000025133E033332161514232226232206070123032E +01273533150E01151417130132D51218272E1C242032111F090E150FFEBA135211172AF0 +2F1D013F8D018220252D1520133628111AFDC401F7692A05101005141A0A05FE52000001 +0022FFEE021001B9002600003733363736333216151423222623220E0307062322352627 +2E012B01353637363332171E01ED0146582E2A121A300B1707193D3033170A0913100A24 +11181A1E4825140304061B2073C3562D19122E134457773D1D191CAC7D38260F0B090510 +44AF0001FFE20000026E030F001B00000107210306151416171523353E01371336353426 +233521323E0237026E31FEE68310222BFB2A1D0C770A1B22013C20272F220C030FA6FE17 +3E0B1212031010031F2C01BA24191513100618372D000001002AFFF5019401FB001E0000 +010723220703061514333237170E01232235343F013635342627353332363701941D861B +0750080C193B0D2A3A2531163A041520BA42281601FB6119FEDE1E0E0F4E0B4131371D52 +D70D0B0D08020F16350000020047FFEE038A0370002E00380000052303230323032E0127 +26273533150E011514171B01272E01273533150E01151416151B01363534273533150E01 +0725272635343633321F010210143205DE133D080A0B0A29EB291F012CAC08041B2CEC27 +1D0228BC1743B81D1B15FEE38C1314101612731201C5FE3B0202462D0B0A05101004141B +0D07FE6C015C412616021010041418030B01FE64016D2C1829011010091A28926A0F1110 +1217950000000002000FFFEE028802980035003F0000133536373E023332161716171336 +333215133E0135342726353436333216151403070623222703070607062322262F012627 +2E012325272635343633321F010F4F10050D08030806051907C006070820634216151710 +161ED409230A08031D591720310F07040204081307111701878C13141016127301940D0D +040104020A1878A101300A0EFEA07B69200E1514150C1320185BFEFE0B2B23013698263D +5E121D5BA44E1B0F586A0F1110121795000000020047FFEE038A036C002E003700000523 +03230323032E012726273533150E011514171B01272E01273533150E01151416151B0136 +3534273533150E0107253736321615140F010210143205DE133D080A0B0A29EB291F012C +AC08041B2CEC271D0228BC1743B81D1B15FE48A20A1E1511AB1201C5FE3B0202462D0B0A +05101004141B0D07FE6C015C412616021010041418030B01FE64016D2C1829011010091A +2890A00A150F120A6A000002000FFFEE028802980035003E0000133536373E0233321617 +16171336333215133E013534272635343633321615140307062322270307060706232226 +2F0126272E01233F0136321615140F010F4F10050D08030806051907C006070820634216 +151710161ED409230A08031D591720310F070402040813071117E2A20A1E1511AB01940D +0D040104020A1878A101300A0EFEA07B69200E1514150C1320185BFEFE0B2B2301369826 +3D5E121D5BA44E1B0F5AA00A150F120A6A0000030047FFEE038A0332002E003A00460000 +052303230323032E012726273533150E011514171B01272E01273533150E01151416151B +01363534273533150E010701321615140623222635343633321615140623222635343602 +10143205DE133D080A0B0A29EB291F012CAC08041B2CEC271D0228BC1743B81D1B15FE4A +131D1D14151C1DDD131D1D14151C1D1201C5FE3B0202462D0B0A05101004141B0D07FE6C +015C412616021010041418030B01FE64016D2C1829011010091A2801001E13141D1D1514 +1C1E13141D1D15141C000003000FFFEE0288025E00350041004D0000133536373E023332 +161716171336333215133E01353427263534363332161514030706232227030706070623 +22262F0126272E01233732161514062322263534363332161514062322263534360F4F10 +050D08030806051907C006070820634216151710161ED409230A08031D591720310F0704 +02040813071117EE131D1D14151C1DDD131D1D14151C1D01940D0D040104020A1878A101 +300A0EFEA07B69200E1514150C1320185BFEFE0B2B23013698263D5E121D5BA44E1B0FCA +1E13141D1D15141C1E13141D1D15141C00000002004E0000027903700026003000000115 +0E01141F01373635342F013533150E01070307061514161F011521353E013F01032E0127 +3525272635343633321F01014A2C1C241F2795281DBD15161BDB241E171E24FEDF332B0D +3B4C0B192901578C131410161273028D10040F227F6E2EB0251804031010071521FEF27B +6718131002031010031F2FCD010B28160610376A0F11101217950002FFE8FF3201AA0298 +0033003D00001335363736373332161F013E01353427263534363332161514070E012322 +263534363332163332373E013736353426272E01232237272635343633321F010F160A29 +42040B3D0D1543371A1A1711161E834E932B171C1710131E0C121E0D33070A3E140E211E +10F98C1314101612730190110402080ABA456E6E7320101011161015201757DA819E1813 +11171C210D430B140F1EFB33251B586A0F11101217950001003100BF011A010100030000 +01072337011A0DDC0E01014242000001003100BF011A01010003000001072337011A0DDC +0E01014242000001FFF800C001FC0102000300000107213701FC08FE0408010242420001 +FFFA00C501F900F3000300002507213701F908FE0908F32E2E000001FFFA00C5037E00F3 +0003000025072137037E08FC8408F32E2E00000100AB01B40136029A000E000001170615 +1417161514062322263534012D094B14191D161921029A1132270D131617141B281F5700 +00000001009701B40122029A000E0000132736353427263534363332161514A0094B1419 +1D16192101B41132260E131618141A291E560001002CFF7F00B70065000E000017273635 +342726353436333216151435094B15181D161921811232260B14151A141A291E57000001 +00A901B40122029A00100000010726353436333216151406070E011514010C0D56262219 +181A15111101C7132F54273C1B14131B0101130B2C00000200A601B40202029A000E001D +000001170615141716151406232226353427170615141716151406232226353401F9094B +14191D1619214F094B14191D161921029A1132270D131617141B281F57481132270D1316 +17141B281F570002009701B401F3029A000E001D00000127363534272635343633321615 +140527363534272635343633321615140171094B14191D161921FEAD094B14191D161921 +01B41132260E131618141A291E56491132260E131618141A291E5600000000020039FF7F +01950065000E001D00000527363534272635343633321615140527363534272635343633 +321615140113094B14191D161921FEAD094B14191D161921811132270D131618141A291E +57481132270D131618141A291E57000200A901B401F3029A001000210000010726353436 +333216151406070E0115140F0126353436333216151406070E01151401DD0D5626221918 +1A151111960D56262219181A15111101C7132F54273C1B14131B0101130B2C2A132F5427 +3C1B14131B0101130B2C00010065FF6101E8029A00380000172336373635342726353637 +220706232235343332171617363534272635343332151407060736373633321514232227 +2623061514170E0107B316070D22030238122F31180F2C30111D2A300A05042F2A18230C +302A1C132F2C0E183138091014310C9F283DAC500D1C100B3E66140B2A280C1101371F11 +18170C392D13293C3601110C282A0A152D26361F1D7D3500000000010016FF7101EB029A +006800000123220706232226353436333217161736353427263534363332161514070607 +363736333216151406232227262B01061514170E01073332373633321614062322272627 +061514171615140622263534373637060706232226353436333217163B013635342F013E +01011A1219272211151B191510202D2E0906031D1312171A23082E2D211014191B151122 +27191A0B101C220E1319272211151B19160F202C2F0A06031D26161A23082F2C1E131419 +1B15112227191A0B0E021C2201AB110E171312160C11012E1E1C22110916201914182838 +3501110C161213170E1132231B381B493F110E1824160C11012E1E1B23100A1620191518 +28383401110C161213170E1134220C3D091B4900000000010046003B01C701BC000B0000 +25140623222635343633321601C7724F5070714F546DFC4F7271504E727000030039FFF5 +02FA0064000A001500200000251406232226353436321605140623222635343632160514 +06232226353436321602FA22181520202E21FED722181520202E21FED722181520202E21 +2B1620201618212217162020161821221716202016182122000000060050FFED042B02C2 +000B0019002500330050006000002514062322263534363332160734262322070E011514 +163332362514062322263534363332160734262322070E01151416333236030123010E01 +23222716151406070623222635343633321716333236370534262726270E010706151416 +333236042B6340344675482E3217231C231F18251F193452FEAD6440334675482E321723 +1C1D14212D201834522AFE522F018A1C35282329042C2426303244744D1A222F312E3445 +FF00091017151F1A0F301F1A354FD55B8D503B50823D3627302B206D281F277F4E5B8D50 +3A50833D362730172375331D277F023BFD2C029220190E1715346822254F3A55801C271E +43891610080B170F17194C571F278200000000080050FFED059502C2000B001900250033 +003F004D006A007A00002514062322263534363332160734262322070E01151416333236 +2514062322263534363332160734262322070E0115141633323625140623222635343633 +32160734262322070E01151416333236030123010E012322271615140607062322263534 +3633321716333236370534262726270E01070615141633323605956340344675482E3217 +231C231F18251F193452FEAD6340344675482E3217231C231F18251F193452FEAD644033 +4675482E3217231C1D14212D201834522AFE522F018A1C35282329042C2426303244744D +1A222F312E3445FF00091017151F1A0F301F1A354FD55B8D503B50823D3627302B206D28 +1F277F4E5B8D503B50823D3627302B206D281F277F4E5B8D503A50833D36273017237533 +1D277F023BFD2C029220190E1715346822254F3A55801C271E43891610080B170F17194C +571F278200000001003300250119019300180000373536373633321514070E0107161716 +15142322272E0127263367185809061B0F5F13060E3E070413144D0C10D80950154D0711 +1E105C180E1A7411071516630E14000100340025011A0193001500002507060706232235 +343F012726353433321716171617011A242F2C570A065B41143E07102D080D3112D71B23 +274D07125E4328721307440C0F391B00000000010000021401F402460003000001213521 +01F4FE0C01F4021432000001FF57FFF6015102A400030000090123010151FE393301C702 +A4FD5202AE000001000800000285028D002E000013333723373337363534262735210727 +3635342E012322060F01330723073307230706151416171523353E013F01233359195908 +59290A1B3101F12015031C505B231C0537A808A919A908A9230E222BFB291F0B2858010E +5A1E922419150F04109A02160B27210E0D13C61E5A1E7D320A121203101003202B920002 +000AFFF80205029E00370042000013333637233733363736333216151423222623220607 +330723060733072306071633323F011706232226270E0123222635343633321737231726 +2322061514163332363C7808056D086B1B3D39502934271E0C22272D1D84088201148008 +8010295A4627280A0B29652539321C291B1F223028221C117D5E231A171E19121428011D +41191E7C49442A2229585D8F1E03571E3C5B261C0709641A27251A221E1C280B8CB61919 +14101520000000040000FFF30466028D001C002F0059007B000013070615141617152335 +3E013713363534262735333216151407062322130716333236373635342623220E050133 +163332363534272635343633321716333237330723262322061514171615140623222726 +232207231307230306151433323637170E01232235343713232734373E01373633321514 +0F01F2350E1E27F42B1B0E74111C2BDD6C614145742D2B4516142C271944373E06080703 +03010301DB1014502428383F42371424160E140A0E14100E4B1B203644513F1D1A151714 +091031055457020F0C20250D2E3B262E104E4B012119521A060809011C0139C136091213 +041010061B31019E3D16141105104A4A593639011FF505090F275F423901030207030CFE +3F8828252D464F34323A0A07128C741E1B26404F393D490A091501B920FEB80806101F30 +074633250A400128061207064127090805026700000000010010FFF4021A0298002F0000 +133336372337333E01333216170723373534262322060733072306073307230615143332 +3637170E012322263534372318210A0F22082631A3552957131E0F013D31346829D008D2 +0F0BD508D40B4D30453B0A405D38424C0A2301222F2B1E718D1B1A6F0F0A313F796A1E30 +2A1E392A8529410A4D3B5E52263A0001FE3B0273FFEF02F8000800000321371706151433 +2111FE4C841224290119027385081E1613000001FE560273000A02F80008000013213521 +32353427370AFE4C011929241202733613161E0800000001FED4FF64FF16029600030000 +07231133EA42429C03320001FE3B0224FFEF02F80011000003212215141E031707273717 +0614332111FEE231040B091406128584123131011E02730F03070B0A1306086A6A08291E +00000001FE3B0224FFEF02F80011000001213235342E03273717072736342321FE3B011E +31040B091406128584123131FEE202A90F03070B0A1306086A6A08291E000003FE3B020B +002C026E00090013001D0000023216140623222635343632161406232226353424321614 +062322263534E1281E1E15141CE4281E1E15141CFE8F281E1E15141C026E1E281D1D1514 +1D1E281D1D15141D1E281D1D15140004FDBA020B0072026E00090013001D002700001232 +161406232226353426321614062322263534263216140623222635342632161406232226 +35342C281E1E15141CAA281E1E15141CAA281E1E15141CAA281E1E15141C026E1E281D1D +15141D1E281D1D15141D1E281D1D15141D1E281D1D15140000000002FD2DFF2300DF02D5 +000B0013000024103E01201E01100E0120260210162036102620FD2D7FDA0100DA7F7FDA +FF00DA3DEE0152EEEEFEAE7C0100DA7F7FDAFF00DA7F7F0203FEAEEEEE0152EE00000001 +FE3B0224001902F8001F0000032736342B012215141E03170727371706143B013235342E +032737176B123131B132040B091406128584123131B231040B0914061285022408291E0F +03070B0A1306086A6A08291E0F03070B0A1306086A000002FC36FF6501EA03FF00020005 +000005210901210101EAFA4C02D1FDB204AEFDA09B049AFBA803C40000000001FE52FF64 +FFE8029600030000072301331847FEB1479C033200000002FEA1FF64FFAA029600030007 +00000523113313231133FEE34242C742429C0332FCCE033200000001FDADFF4E00DD02D5 +00050000013521112311FDAD033042029342FC7903450003FE32FF260023FF8900090013 +001D0000063216140623222635343632161406232226353424321614062322263534EA28 +1E1E15141CE4281E1E15141CFE8F281E1E15141C771E281D1D15141D1E281D1D15141D1E +281D1D1514000001FE22021A003702A9000700000135211523352115FE22021536FE5702 +1A8F8F5959000001FCE70057009901A3001D000001211521221514171617072E0127353E +0137170E050706151416FDA202F7FD0A1A0E151F1436574343573614030C0409070B070E +12011E42140E0E162C133F451D0A1C463F130512060C080B070E0B070F000001FE3B0224 +FFEF02A9000800000307273635342321351184122429FEE702A985081E16133600000001 +FE3B0224FFEF02A900080000012115212215141707FE3B01B4FEE729241202A93613161E +08000001FE3BFF04FFEFFFD80011000007212215141E0317072737170614332111FEE231 +040B091406128584123131011EAD0F03070B0A1306086A6A08291E0000000001FE3BFF04 +FFEFFFD80011000007213521323427371707273E043534A7FEE2011E3131128485120614 +090B04AD361E29086A6A0806130A0B07030F00020023FFEE02BE029A001E002400000107 +2E06232207031633323E03371706232226353412333201130E01151402BE1909280F2017 +25291A764484465B2B462D411D2B1577CB768AEAB5A6FE027B4A53021215092A0E1C090E +0434FE0B390D0E2C18241A8E8175B700FFFDCD01CB3AA95D5F000004002FFFF2032802A4 +000F001B001F004100002514060706232226353436373633321607342322070615143332 +37360301230901170E012322263534373633321615140622263534363534232207061514 +1633323603282D274353393D513F2D33313F46363530393C2C303C5CFE3E3101C5FED60C +24452C383E4F414D28361620100C1D332446281D2033EA2D6227423A35427E22173C264C +4856564841530208FD4E02B2FEF7092E28453E62483B2B1C1017130E0717081026496528 +331A0003001EFF2502A601B90030003D0048000009011633323717062322270623222635 +34373633321737270E012322263534370623222737163332373E01333216173337073423 +22070615141633323E01032726232206151416333202A6FEF32C2F3B290C363F3D294D7E +2F33362C473B205701366138262E171E28271F0A1F1A404139953C243004021C3C365F6E +5618113D9360D5011D3739451F195D01A8FE0E171E1A2216722B1C30201A0D9C013A373A +2F303314111A0D4F455A2120303C2A9F7D3E131681A3FE73010D2820121700030035FFF1 +03DF02AF0055005C00630000010706073E013F013E01333215140E020706151416333237 +1706232226353437070E0107062322263534253E06370623222726232206151416333236 +37170623222635343633321617163332373E013705342322060736010E011514173202CA +127C6C16500D2164AF2F39335D613FAE130F37701578522730686B3996461E1A1920011B +04200D2019252B19070D2C503F272F452B2D26381B1236573B44644B1D3A373C1A29270E +39040100181B7546EEFD756974184302980E60D60919052B7D8C32274A3F3119FB530F14 +810D92302965A1296FA41B0B211B80770734152F1F2C2A16012C223D282A342B2B096B46 +383B5C151C1F13072A032B167E6169FED6306035190400030011000002FF028D000B000F +00130000010323132103231333032113172303330123033302FFAF9A53FEF6549AAF9A51 +010A526A4E9B4EFEF64E9B4E028DFD730135FECB028DFECD013325FDBD0243FDBD000001 +002DFFF501E3029C0031000025170E0422263437363534220607060F0123133635342322 +07273703333E0333321615140F011407061514333201D50E022113201F20194007304E20 +3A09204E98021F0D10029F6D040C3D2C43201D1C0B3A01010D166A0B0329121E0E142CF5 +181423553058227A0258080216020E18FE600E4E342D281E122EE6020303020E00000002 +001AFFF1037802A3003200400000372314163332363736372E0135343736333217363332 +161514230E0107363717060706070623222635343633321615140623220135262322070E +01151416173E013F0247316690420F0C3844564176334328240C116D24422C4A3310336A +4F3675A84D602219161D17141702930F266436243132312D595F17386B9423180C48395B +3628081B0B09341A725F144F0A6418AA397947331F291B15141C0206020118103F27223C +06577000000000030021FFF103A302AF003E004800510000010726232206151416333237 +3E02373E01333216151406070E010716333237170623222706232235343633321E02173E +01370623222E02353436333205342322060736373E0101352623221514333202600C375A +5688856D19260B25190D2A81392A29AE822F904045228D7C138396545046557E3B311532 +1C41092A732F1A1B325D5333936C63015C31376B242D3B3E51FD904A3343574202831321 +54494A540514492E143F642D2544972366A719178B0E9C1E183D17220B0A1A031B8E5703 +152A4E334E69632CA0670C21225CFE07012B1921000000020030FFF5023B02AF001E0028 +0000133716333E0133321514060706151433323637170E0123222635343736372637173E +013534232207064D0E243A55B33A40C1818F2421574F1555612D25324D0C1C319801759B +23252D3A01851A197DAC4351A013E7432B4C5B1163512D2A5486162C0222011A83481F2F +3B0000020019000002F3028D000B00110000010323012303231333013313030123070133 +02F3AE6FFEFA048E25AF700106028E9DFEF13F0701113B028DFD730210FDF0028DFDF102 +0FFDB902221CFDD9000000040013FFF10407029C000B0019001D004D0000011406232226 +35343633321607342623220E0115141633323E010307213727133E01333216151406222E +0123220E050703230323030623222635343633321E0233323713260737331304077D5534 +397F53313C3D181729492418182748261911FEE813836B0C34271E2C1C2A170D07050808 +05050403019B13A2027B224E1F2D1B161418050807181D7F173F05A38C0163568A483553 +86440A181D596B28191E5B6DFECC3C3C8601822A39261E151B1919060A0B100A0E02FDDC +0226FE53792D1F161C1419146501C1290212FE2A000000040011000002AE028D000B0011 +0019001D000013213216151407062B01072301033E0135340313262B0103333203230333 +C0014D4958384897AA429A024C513944A3551723B1599F35A14E9B4E028D59495F4154F7 +0253FED811703E42FEF901390AFEB4014CFDBD00000000050023FFB902C9029A0017001D +002D0033003B00000517062322262706232226343E013332161514060716333213033E01 +353403132E0123220703161736333216173625130E0115140526232207163332025E0825 +2D213C113C40798E67C078798E86771E2F2543794B51C7881C592A6F4789171F33502933 +1B15FE9F794B51014720323624232331132113281F127FEEC8777F7786DE3230023FFE3A +36AA5F49FE5D02001B1E28FDFE160C3E1B22083A01C638A95F508424260900020022FFF1 +036C02AF0052006200000117060716151406071516151406151433323717062322263534 +3E0135342322270E01232226353436333216151406232227231416333236373E01372623 +2206151416333237330E01232226353436333217361734270E070732173E010354072220 +536153364C20345D11654B25382A2A211E1152C1794D602219161D171417070246325D77 +554E6B434C7495CA3C2B9F2E14077B613F4FDEAB7D60240A300B1410150B16091A04221A +3947029D130E183556405E0302282C257811226510732F23213D371A300BA99648331F28 +1B15141C0C1C3368978B96313096632937F56BA8473B6BA83B17A540260A1A1423132C12 +35091106580000050011000002AE028D000D0013001B0023002700001321321615140607 +13230323032301033E0135340713262B01033332132706232226231703230333C0014D49 +58675B89AC7D5A479A024B4A3B3C9E4F1F18B3549F3369720B17081F076BB64E9B4E028D +59495C7310FEF40108FEF80255FEE7135C3F46FB01280BFEC5FEF8E40201E30243FDBD00 +00000002001E00F703BD028D0023003B000001150E011D011416171523353E013D010323 +03151416171523353E013D01262735331B0105232E012B01111416171523353E01351123 +22060723352103BD20180D25AA250C9A0B9715208320141530799AA0FE21140B1E1A320D +25AB250D311B1F0A140159028D1402181DFE240F041414040F24EBFEC80136DF24190414 +14041924F9300214FECB013558251FFECB240F041414040F2401351F2558000200070000 +02EE028D000B000F00000901152107213501352137210723013302EEFDEA01AD09FD8B02 +1BFE6C0902573464FDE7670268FDC00325250241022525FDBD0000020022FFF1038602AF +005E006A0000011706071615140607151E01151406070623222635343637170E01151416 +3332373E013534270623222702212226353436333216151406232227231416333236373E +01372623220E011514163332363733140623222635343E02333217360734270E01073633 +32173E01037F072A1E4859402D2D332D313F2C324842042A3F1D152419242A2E18192609 +9DFEFF4D602219161D171417070246325F8554477F4D51815DA96E3C3354780514806A3F +54507D8C4290642201282D492C07101B1C3C4002941313153B4F325708021348272C5A1E +21301E32470B17053821141C0F155530451A0613FEBA47331F291B15141C0C1C336B947D +9C31383F7A4C2F3B96797DB04A454270442643149B352A266D55040E05580002001EFFF5 +022A01B9001E0027000025170E012322263534372E01273716173E013332161514060706 +151416333227173E0135342322060216145C9C4C343612223E10171D482CB1582027AB8B +2A1E18776E016999242F76C4145D5E3B342E2E04271D1139055B831C1B387515492D1A1D +D0010F632C1B5D00000000020064FFF102DE02AF0045004E000001072623220615141633 +3E02333215140607161716142322270E011514163332373635342623220607273E013332 +1615140623222635343637352E02232E013534363332053426232206073E0101A50A2C42 +48586E510664853F64C38801034B2A1C19586B4E425E37233027355D1215146D4942449C +665B7D7E6F020403026B8169544B01491E16489C0A76AC029212113E31353D416A37493E +710A060603380E17784231463C262F22284F3F0652603F34446C59504F7B1702020A0802 +4F433951630F127C4307650000000002002B0000038B02A8001D00570000011706232226 +23220615141633323637170623222635343736333216333207170E010736373E01333215 +1406070615141707353437060702232226353436333216151406232227231E0133323637 +06072736373E01373E0103800B595739F72D5D6E3F2C47571C1427AA3B56404C9738F931 +2FAD0E222E1B4534122C1017291F08033B0B3E3569F93B5A211B141E1814150802043B2B +4C714061500D3A9E030A011E5402960F493C4E413838575901CD4A3D4E364038380E2164 +540E011B1E160F2D0F16260C0E141221250309FEE6393620251D14151D0D1B2460811F36 +0F46360617024566000000010026FFF1042002A200590000011706021514333237170623 +2226353401270E01070607062322263534373637270E01070E0823222635343633321615 +14062322272316333236373E0133321F01060207061514333236373E0537320418088EB1 +2137731378532531013401429D6B51202E36191F8F7E4201358337153D21301E281E2524 +13222E2318161E1813080703031C32604F91BB56070A0F64B727110D12251F145038594C +61301002A00DE1FEB02D238410922C2695019E0219BEBD8F2636262058DBC44A01168A57 +2263354B293219190A2D211C252014121A04156685F2B6020C8DFEC8592D10122E312089 +607D513F06000002001EFFF502A801B9001F003000002517062322270623222635343706 +232227371633323E01333216151407163332272623220E01151416333236372635343602 +9C0C1F2C291C7796333A121D35211F0A1F1A446E97472730221922245B0532358A5C1C1C +387C361026DF191512CE3B3A2E2D18111A0D905E31263E441194337D9A2C1B255C641922 +1D3400030028FFF4027601AC0023002D0031000001230306151417323E0237170E082322 +26353437132303231323352107230706151417373437032303330276774A0E041728142B +081B0522071B0918101B1B112C3C0947546A8D6C5F02419B44410E38020D934263420187 +FEF4341B0B08101028071A041F0616030E030601332B161F0100FE7901872525F1331131 +08022A2F0113FE9E00000003001E0000030C028D0007000B000F00000103231321032313 +0523033301230333030CAD9BA5FEF6A69BB0020F4F9A4FFEF54E9C50028DFD730268FD98 +028D25FDBD0243FDBD0000040011000002BF028D0008000E0016001A0000133332161514 +06232101033E0135340313262B0103333203230333C0FB7490EAB7FEF3026672474EC481 +2E6E6C996E5F594E9C50028D8073B7E3021BFE5229A75856FE6B01D939FDBD0243FDBD00 +000000040028FFF5027A02AB00110015002300290000010323370623222635343E013332 +1617331317230333273736353426232207031633323607130E011514027ABC890A365641 +4A37734A25480F02535E43A843491D0A2E252B16620E2A3356E15A393F02ABFD55303B56 +4248875D1E1E012E25FD9F71662315263A0AFE9C0C4A2C01461F8546250000040028FFF5 +01E501B90016001C00230029000025170E0123222635343633321615140721071633323E +0127333635342707372623220F02130E01151401981C2E64465460A172466416FEE6251E +2F2D442405470C284F2F241E2A1E2F5159324880153C3A585272A850442E408C111C2185 +2221381E99A90F0DABA40144178D3B3100000004001B0000015A028D00030007000B000F +000001072337172307331703231317230333015A248E255E43114402768C755E43614302 +8D8787253D7FFE5401AC25FE9E000004FF98FF27018A028D000300070014001A00000107 +23371723073307030E012322273716333237131723033E0137018A248E255E431144027D +157E522B241922231B0FA0583C981D3308028D8787253D7FFE2B4F6110210D07025A25FD +C80E3C1E000000020028FFF501D7029C001900260000132736333211140E032322263534 +3633321736353426232213342623220615143332363736C30A263CBC102A406A43454397 +6A451B073D46218325184970433657170F02711417FEFC3168745B3B5C466DAF30271F4D +68FEC12C2AB56B627654370000000001005600DC024E011E0003000025213521024EFE08 +01F8DC42000000010028FF8801CC001000070000052135331521353301CCFE5C1E01681E +78884C4C000000030000FFF202AC02A4001A00260030000001353E023332151407030615 +143B011523353E0137133734232205140623222635343633321607342622061514163236 +01301A39240107074C07220FAD231B044B041405015BC68F90C7C9938BC52AACFAB2B2FA +AC01D9190109090B021CFEF61C02131414020B0D0103140A9291C7C89092C8CB8F7FB1B2 +7E7DB1B0000000030000FFF202AC02A4001B0027003100001327363332161514060F0133 +32363717072335373E023534262322051406232226353436333216073426220615141632 +36FB14245E2D3B2633655F1A190B1320DA7C1E1A1A24213A0194C68F90C7C9938BC52AAC +FAB2B2FAAC019D0662392B1D352F5D0E150653137A1E1B2D151B218791C7C89092C8CB8F +7FB1B27E7DB1B000000000030000FFF202AC02A400280034003E00000127363332161514 +071615140623222734363332171633323635342B01353E05353426232205140623222635 +343633321607342622061514163236012214204D25314E2E644546020F0D121814162328 +61120A2C1722120D1D1720016AC68F90C7C9938BC52AACFAB2B2FAAC01BB064429204113 +1738444F260D10120F2F2354150209060C0E180F14189791C7C89092C8CB8F7FB1B27E7D +B1B000040000FFF202AC02A4000A000D0019002300000107330723072337233F010F0133 +2514062322263534363332160734262206151416323601CE443A0A3E19361A9F0FF92EA4 +74015AC68F90C7C9938BC52AACFAB2B2FAAC0205EA305F5F30EA4E9C2F91C7C89092C8CB +8F7FB1B27E7DB1B0000000030000FFF202AC02A4001C002800320000010723071E011514 +062322263534363332171633323635342726273537051406232226353436333216073426 +2206151416323601E7138E113F496A4D1F260F0B0E181312283F22204940016AC68F90C7 +C9938BC52AACFAB2B2FAAC020534250A4137475D14130C11110F412A2C161709137BBB91 +C7C89092C8CB8F7FB1B27E7DB1B000040000FFF202AC02A400140020002C003600000115 +0E010736333216151406232226353436373E010734262322061514163332362514062322 +263534363332160734262206151416323601DF346520131C2D3F5A41373C39332A511726 +1F2C2921192C340129C68F90C7C9938BC52AACFAB2B2FAAC020C15073E2E063B2E415A44 +3C3967251E1BDB1E204C342125575291C7C89092C8CB8F7FB1B27E7DB1B000030000FFF2 +02AC02A4000A001600200000010323132322060727373317140623222635343633321607 +34262206151416323601EFEB36DA7B1D22161236E9C7C68F90C7C9938BC52AACFAB2B2FA +AC01FAFE8C014B11180954BB91C7C89092C8CB8F7FB1B27E7DB1B000000000050000FFF2 +02AC02A400160022002C003800420000011406071E011514062322263534372E01353436 +333216073426232206151416173E01073426270E01141632362514062322263534363332 +160734262206151416323601E42931231D4939354A6C1B153C33323E30251C1B1E172125 +1D1F1830262E3242280117C68F90C7C9938BC52AACFAB2B2FAAC01B422290E1D301E303B +33314E201B24162B2E2D2A1B231C1710201B0F1EBB16222B0D343E26268691C7C89092C8 +CB8F7FB1B27E7DB1B00000040000FFF202AC02A40015001E002A0034000037353E013736 +37062322263534363332161514070E011334232206151433322514062322263534363332 +1607342622061514163236C82433212E181F2A2939553D363F712347A53A2A2F3A590100 +C68F90C7C9938BC52AACFAB2B2FAAC86150619192428153B2C3E56443975511A18011743 +462C4B1D91C7C89092C8CB8F7FB1B27E7DB1B000000000040000FFF202AC02A40019001C +0028003200002523353635342F012307061514171523353E01371333131E01172F010725 +1406232226353436333216073426220615141632360220A432021085220F287C171D1FA9 +134B0714198C1E4B0181C68F90C7C9938BC52AACFAB2B2FAAC8D160311050A43381A080B +011616041F320114FECA1E1401887C7C1F91C7C89092C8CB8F7FB1B27E7DB1B000000005 +0000FFF202AC02A40015001F002A003600400000133332151407161514062B01353E013F +0136353426271733323534262322060F0314333236353427262514062322263534363332 +1607342622061514163236EE9882503A584BA51A10063F05131D5412722124110B032912 +0E27323A290F0138C68F90C7C9938BC52AACFAB2B2FAAC02055C40141A3B393A16050D16 +F211100807028A481F1B040B954B3C112B29300F050791C7C89092C8CB8F7FB1B27E7DB1 +B00000030000FFF202AC02A4001B00270031000001072326232206151433323637170623 +222635343633321716333237171406232226353436333216073426220615141632360220 +151409554F6A7328422712565D4E639B642C1D100C0E099FC68F90C7C9938BC52AACFAB2 +B2FAAC020C7D5C7C50731D250E5A5349638709050EC291C7C89092C8CB8F7FB1B27E7DB1 +B00000040000FFF202AC02A40011001F002B00350000133332161514062B01353E013F01 +363534271707061514333236353427262322051406232226353436333216073426220615 +14163236D3A04D60916994190F053C042C6B3C072555672F1C2D22016AC68F90C7C9938B +C52AACFAB2B2FAAC0205554B647416050C14F8110D100119F61D0A0E6D544920129D91C7 +C89092C8CB8F7FB1B27E7DB1B00000030000FFF202AC02A4002B00370041000001072336 +352E0123220F01333236373307233635342B01070615163332373637330721353E013F01 +363427350514062322263534363332160734262206151416323602081111010128412402 +1E24251A0E1025110421360E12012A482519171524FEE0190D053E042D01C2C68F90C7C9 +938BC52AACFAB2B2FAAC0205630D14150D097910228D1410153948070C150E226516040C +14FB0F1A0416BB91C7C89092C8CB8F7FB1B27E7DB1B000030000FFF202AC02A400290035 +003F000001072337342623220F01333236373307233635342E012B010706151417152335 +3E013F013635342627350514062322263534363332160734262206151416323602081112 +0224491F041E2524190F112711040A0D0F301C052B911810053C04101C01C1C68F90C7C9 +938BC52AACFAB2B2FAAC020563161E0F0C76111F8F14110C0A027614050C031616011114 +F6130D08060216BB91C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF202AC02A4 +00290035003F000001150E010F0106232226353436333217163332373307232627262322 +061514163332363F01363534273505140623222635343633321607342622061514163236 +02201E150519434A4C64885E2A26110815100C1B1303131C354C59403818270416043001 +2FC68F90C7C9938BC52AACFAB2B2FAAC014D16020C156B23564B5F860D06137D28161F81 +51373C120D490F0A0B04160391C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF2 +02AC02A4002E003A0044000001150E01070306151417152335363F012307061514171523 +353E013F013635342627353315060F013337363526273505140623222635343633321607 +3426220615141632360219130E0445051C8F30081F7D1F0519851516063F050F16902508 +197E19030221011AC68F90C7C9938BC52AACFAB2B2FAAC020516040F11FEFE1702090416 +160422797914050B021616021316EF130E0807021616031F675E09110F0216BB91C7C890 +92C8CB8F7FB1B27E7DB1B000000000030000FFF202AC02A400150021002B000001150E01 +0F0106151433152335363F01363534262735051406232226353436333216073426220615 +1416323601C01911073C071985270A3E071115017CC68F90C7C9938BC52AACFAB2B2FAAC +02051603121DEB190A0C16160427EB1A090A070216BB91C7C89092C8CB8F7FB1B27E7DB1 +B00000030000FFF202AC02A4001F002B0035000001150E020F010E012322263534363216 +151406151433323F01363534262735051406232226353436333216073426220615141632 +3601E01114050334112E37242D151C140315180A3F050E17015CC68F90C7C9938BC52AAC +FAB2B2FAAC020516020C0B0CCA413920181015100E0509030F29EF140C09070216BB91C7 +C89092C8CB8F7FB1B27E7DB1B00000030000FFF202AC02A4002E003A004400000115220F +01171E01171523353635342F010706151416171523353E01371336353427353315060F01 +373635342F01350514062322263534363332160734262206151416323602201318A2640D +131CA129114C1E061218951A10053E042FA33008164E4B1111010FC68F90C7C9938BC52A +ACFAB2B2FAAC02051612799F150B021616020B071A7B7A16070907021616030C13010214 +020F03161603216038360C06020216BB91C7C89092C8CB8F7FB1B27E7DB1B00000000003 +0000FFF202AC02A4001B00270031000025072135363F01363534273533150E010F010615 +141633323637363F0114062322263534363332160734262206151416323601EC22FEEC26 +09410626901513053C06151E2725101611CFC68F90C7C9938BC52AACFAB2B2FAACFD7016 +0422F4170910021616011312E918080B07080D11294D91C7C89092C8CB8F7FB1B27E7DB1 +B00000030000FFF202AC02A400270033003D000001150E0107030615141617152335363F +0103230307061514171523353E013F013635342735331B01171406232226353436333216 +07342622061514163236023B1710054305111996260C3DB90B133804276A15130A370527 +6010ABD6C68F90C7C9938BC52AACFAB2B2FAAC020516040E12FEFF16040705011616032C +E7FED40127E1140712031616021A2ADE13080A0316FEEF0111BB91C7C89092C8CB8F7FB1 +B27E7DB1B00000030000FFF202AC02A40022002E0038000001150E010703230307061514 +171523353E013F0136353426233533133337363534273505140623222635343633321607 +3426220615141632360208170F0A3C0B762F04256315110A2A0417114F6A012B04240107 +C68F90C7C9938BC52AACFAB2B2FAAC020516081730FEE6012DDE160611031616031C32C2 +18050A1016FEEECA100C120416BB91C7C89092C8CB8F7FB1B27E7DB1B00000040000FFF2 +02AC02A4000B00190025002F000001140623222635343633321607342623220615141633 +323E0217140623222635343633321607342622061514163236021F9C633F4F9A60444F46 +2D254A652C2728442A18D3C68F90C7C9938BC52AACFAB2B2FAAC017A62924B415D9D5025 +292C9E4A2F2F334C512C91C7C89092C8CB8F7FB1B27E7DB1B00000040000FFF202AC02A4 +00180023002F0039000013333215140623222707061516331523353E013F013635342717 +0716333236373626232205140623222635343633321607342622061514163236F98B8458 +4424111E040228961814073A092666200D0D333901012625180148C68F90C7C9938BC52A +ACFAB2B2FAAC02055C3B38067510060E161603151BDB210B1002197C011F2C23209D91C7 +C89092C8CB8F7FB1B27E7DB1B00000040000FFF202AC02A4001F002B0037004100003727 +36372E013534363332161514062307321716333236371706232227262322013426232206 +15141633323617140623222635343633321607342622061514163236A20D3C2931369861 +4151986512213A2317212B1B1144582C3225132401122C254A642C224D64D4C68F90C7C9 +938BC52AACFAB2B2FAAC741227210C47356099503E6098120E0813190D530F0A01532A2C +9A4F2B329E3D91C7C89092C8CB8F7FB1B27E7DB1B00000040000FFF202AC02A4001F0029 +0035003F000025232F0107061514171523353E013F01363534262735333216151407171E +013303071633323635342322051406232226353436333216073426220615141632360208 +5B52231B082A961814073D060F179137466C370A1616A91E0B0F3338481A0148C68F90C7 +C9938BC52AACFAB2B2FAAC8DB5026C1C0A0E01161603161BE4160A0B0603162D2A55127A +18120133720124223E9D91C7C89092C8CB8F7FB1B27E7DB1B00000030000FFF202AC02A4 +00350041004B00000107232E01232206151416171E011514062322272623220723373306 +15141633323635342E032F012E0135343633321716333237171406232226353436333216 +0734262206151416323601EC1612021B271C22132B2A1E4834202413081206101215032E +2520280B0C180C0D0E1D13422D231611091109D2C68F90C7C9938BC52AACFAB2B2FAAC02 +0B7A2F2B191612192626311F313E0D0714890911232C221C0916121A0C0D0C1A26192C32 +08050DC191C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF202AC02A400190025 +002F0000010723363534262B0103061514331523353E0137132206072337051406232226 +3534363332160734262206151416323602131C14021A1E24430A27A021170847452C1516 +1C01E2C68F90C7C9938BC52AACFAB2B2FAAC02046712091714FEFD260A0D161601151C01 +0E192D67BA91C7C89092C8CB8F7FB1B27E7DB1B0000000030000FFF202AC02A400260032 +003C000001150E010F010E0123222635343F01363534273533150E010F01061514163332 +363F013634273505140623222635343633321607342622061514163236021A180D0D280F +4936384C0929082B9B1A15072F05271F2C3314210A260100C68F90C7C9938BC52AACFAB2 +B2FAAC0205160614339E3A44332A17209C1B0D1001161601131AB91417191A2D4E80281E +0416BB91C7C89092C8CB8F7FB1B27E7DB1B000030000FFF202AC02A400180024002E0000 +011506070323032E01273533150E0115141F013736353427350514062322263534363332 +16073426220615141632360220150CD60D3D0C0F1A961B11022C7619270102C68F90C7C9 +938BC52AACFAB2B2FAAC0205160112FEB1011236170316160308090A0ADABD260D0F0316 +BB91C7C89092C8CB8F7FB1B27E7DB1B0000000030000FFF202AC02A4002A003600400000 +01150E0107032327072303262726273533150E0115141F0137272E01273533150615141F +01373635342735171406232226353436333216073426220615141632360239100F09970C +236E0C2A08090419821510021C5005020F1883220517590A24DAC68F90C7C9938BC52AAC +FAB2B2FAAC020515050E13FEC3F5F5011C36090403161602090A0C08C7B3211109021616 +040D0928AEBD170B100116BB91C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF2 +02AC02A4002E003A004400000115060F01171E01171523353235342F0107061514331523 +353E013F01272E01273533150615141F0137363534273505140623222635343633321607 +3426220615141632360208261C513909141C93280926470B247518263326390714188F25 +0A1F4808220116C68F90C7C9938BC52AACFAB2B2FAAC0205151123678B160E0316160D07 +14585E0F060D1616052543318C110F021616050A07174D5A0B080C0116BB91C7C89092C8 +CB8F7FB1B27E7DB1B00000030000FFF202AC02A40021002D003700000115060F02061514 +33152335323F01272E01273533150615141F013736353427350514062322263534363332 +160734262206151416323602182014801A0829A5360A1E3607111A9C2D0826511E2E0112 +C68F90C7C9938BC52AACFAB2B2FAAC0205150A1795651D070E1616287092130C03161604 +090815695B21090A0416BB91C7C89092C8CB8F7FB1B27E7DB1B000030000FFF202AC02A4 +0013001F0029000009013332373637330721350123220706072337211714062322263534 +36333216073426220615141632360208FEFC524C181515121CFED00105593B1614121219 +0122A4C68F90C7C9938BC52AACFAB2B2FAAC01F4FEBA1210276A1101460E0D2662BB91C7 +C89092C8CB8F7FB1B27E7DB1B00000040000FFF202AC02A4001F002B0037004100000117 +0E012322353437062322263534363332173F0217140E0115061516333227342322070615 +14333237360514062322263534363332160734262206151416323601E2102825141E073B +3E1E28754125100504280802033203030A20222E232E2726272D0114C68F90C7C9938BC5 +2AACFAB2B2FAAC01080C2B1B1F101B49262243771B1403040302080901B10E03A41C2F3B +3226333A0191C7C89092C8CB8F7FB1B27E7DB1B0000000040000FFF202AC02A400180025 +0031003B0000010736333216151406232226353437133635342735363717061734232207 +0615143332373E012514062322263534363332160734262206151416323601512A323E22 +2E7F491F3A014F052A35320608552C282A291B2A221B250101C68F90C7C9938BC52AACFA +B2B2FAAC01D591452823447415100302011B110408011505090720AF2B3C3D3A0F1F1844 +3091C7C89092C8CB8F7FB1B27E7DB1B0000000030000FFF202AC02A4001C002800320000 +011706232226353436333216151406232235343635342322061514333225140623222635 +34363332160734262206151416323601AC0F3D4330367346202A140E1D091637443C2B01 +34C68F90C7C9938BC52AACFAB2B2FAAC01060C44302E45601E150E121C0712010459333D +7391C7C89092C8CB8F7FB1B27E7DB1B0000000040000FFF202AC02A40025002F003B0045 +00000117060F010E02151433323717062322263534370623222635343633321737363534 +273536073423220615143332362514062322263534363332160734262206151416323601 +F008020F3B020A070A0C270F31330D12042D3B2327773F200B17072A34351C304B252A48 +0124C68F90C7C9938BC52AACFAB2B2FAAC020C070730D30824190205270B45120D0C123B +252344751C50170E07011503AE1B6D2E25683391C7C89092C8CB8F7FB1B27E7DB1B00004 +0000FFF202AC02A40015001D002900330000011706232226353436333216151406070615 +141633323734232206073E010514062322263534363332160734262206151416323601B1 +103B4F2935774A1B1E625504221B2825151B3814324A010AC68F90C7C9938BC52AACFAB2 +B2FAAC01050C432E26426D1814273B0A09101418B310352E062C2491C7C89092C8CB8F7F +B1B27E7DB1B000030000FFF202AC02A4002B003700410000013336373633321615140623 +22353436353423220733072307062322263534363332161514071633323F012305140623 +2226353436333216073426220615141632360116380E05273D17200F0D1B080A2E134106 +41232055161E0F0C0B0E06020725102A36019CC68F90C7C9938BC52AACFAB2B2FAAC01B0 +2C084C19110C1217060D0106691CA09516100C100D0A0708054ED04A91C7C89092C8CB8F +7FB1B27E7DB1B000000000050000FFF202AC02A400250030003D00490053000001231615 +140623222623220615141716151406232226353436372635343726353436333217330734 +2322061514333237360734272E0227061514333236251406232226353436333216073426 +2206151416323601F41E034D300C0E01070C2E674D4338421C2A0B242A533629262E5825 +1E2F2821151408210F202406355726320118C68F90C7C9938BC52AACFAB2B2FAAC01D307 +11293C030B060909163E29332523172116090D1E1115302D39152B263425291F1FDF150E +070A0902181E381C8E91C7C89092C8CB8F7FB1B27E7DB1B0000000030000FFF202AC02A4 +002C00380042000025170E01232235343F01363534232206070E010723133635342B0135 +363717073633321615140F01061514323637140623222635343633321607342622061514 +16323601E010212919200A21030F11351814160F38570519143E2A083F4B3C1618062205 +1012E5C68F90C7C9938BC52AACFAB2B2FAACD60B281E1E1221710B07062A201B323E0142 +12020915060807E26519140718751205040F8F91C7C89092C8CB8F7FB1B27E7DB1B00004 +0000FFF202AC02A4000B00240030003A000001140623222635343633321603170E012322 +2635343F0136353423353637170706151433322514062322263534363332160734262206 +15141632360196130E0F16150E0F14130F1B28180F140D1A05293E270A3902070E014CC6 +8F90C7C9938BC52AACFAB2B2FAAC01D90F14150E0F1415FEEF0B271F13120D315F110706 +15020D08C40605069E91C7C89092C8CB8F7FB1B27E7DB1B0000000040000FFF202AC02A4 +000900250031003B00000014062322263436333217070623222635343632161514071633 +32363F013635342B013536371714062322263534363332160734262206151416323601CE +140E0F15140E0F04362054171E12160F07020712160D26081B143B2FF7C68F90C7C9938B +C52AACFAB2B2FAAC021D1C14151C1578DB8116110C120F09040C0527359B20080B14030A +7891C7C89092C8CB8F7FB1B27E7DB1B0000000030000FFF202AC02A400260032003C0000 +01150E01071716333237170E0123222726270F0123133635342B01353637170737363534 +2B01350514062322263534363332160734262206151416323601E51932401623110D1911 +191F141F1C0A1C161B37510A1F0F254407412843150C0140C68F90C7C9938BC52AACFAB2 +B2FAAC018116011C322B43260A281D3011381063012827080615020D07F11D3007031637 +91C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF202AC02A400170023002D0000 +0103061514333237170E0123223534371337342B01353637051406232226353436333216 +07342622061514163236019F5306090F27111E2C1A230547021C0E2F340116C68F90C7C9 +938BC52AACFAB2B2FAAC0206FEC414080432092C21240914010D0F0515050AC291C7C890 +92C8CB8F7FB1B27E7DB1B000000000030000FFF202AC02A4003E004A0054000001170623 +2235343F013635342206070E0107233736353423220607060723373635342B0135363717 +073633321614073E0233321617140F0106151433323F0114062322263534363332160734 +2622061514163236022F1235331D0723031A2F151719123912280C1135181A22381C1F19 +135B12081C483A15170D191B331C121601042504050E198CC68F90C7C9938BC52AACFAB2 +B2FAAC01070948220D177A0A0907221B1D3A413B850E072A22246557601B06150E030360 +63162427221F2017120816830806041A5391C7C89092C8CB8F7FB1B27E7DB1B000000003 +0000FFF202AC02A4002A00360040000001170E0123222635343F0136342322070E010723 +373635342B01353637170736333215140F01061514333237140623222635343633321607 +34262206151416323601E2102425170F14081B0811243A11131538370318141F4F08214D +3B2D042007080CF1C68F90C7C9938BC52AACFAB2B2FAAC01070B2B1B1212101B571C1850 +182C41BD0A090815040D0363662A130D6F150A046D91C7C89092C8CB8F7FB1B27E7DB1B0 +000000040000FFF202AC02A4000B00140020002A00000114062322263534363332160734 +232206151432362514062322263534363332160734262206151416323601E677452F3676 +4A2A373C2E304B64450102C68F90C7C9938BC52AACFAB2B2FAAC016344692D2746692E1D +326E32316A1191C7C89092C8CB8F7FB1B27E7DB1B00000040000FFF202AC02A4001E002C +00380042000001170607363332161514062322270F01143315233532363F013635342B01 +35173426232206070E011514333236171406232226353436333216073426220615141632 +36015706020A2E39282C7A4A181612042C8F15110543091A13DE13161A300B0E181E3551 +E3C68F90C7C9938BC52AACFAB2B2FAAC020B080A23352B264A740941170A16160D13F61D +0A08164A1B1624192154100F693391C7C89092C8CB8F7FB1B27E7DB1B00000040000FFF2 +02AC02A4001A00270033003D000001030E0215143B01152335363F010E01232226353436 +3332173707342623220615143332373E0105140623222635343633321607342622061514 +16323601F060010302230EA534092B21382421297D45280D09151710324F201B1B1F3301 +0AC68F90C7C9938BC52AACFAB2B2FAAC0204FEC0040B06010B1616021C882A212823477B +1F18300C1170302813185E6891C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF2 +02AC02A4001C002800320000010736373633321615142322262322070E01072337363534 +2B01353637051406232226353436333216073426220615141632360157191A1D20171017 +21111204112117181639310A18183B2F015CC68F90C7C9938BC52AACFAB2B2FAAC01B55C +2D181B140E251632213946AB23050C1606086F91C7C89092C8CB8F7FB1B27E7DB1B00003 +0000FFF202AC02A400260032003C00000107232623221514171615140623222726232207 +233733163332353427263534333217163332371714062322263534363332160734262206 +151416323601C70D12072F22242F322A160F0D0E0F030F0B110C332C252E50131310050D +06F4C68F90C7C9938BC52AACFAB2B2FAAC01B8583F1712212E22262805050A6148201726 +2E1D400504096E91C7C89092C8CB8F7FB1B27E7DB1B000030000FFF202AC02A40020002C +0036000001072307061433323637170E01232227343F01232734373E013736333215140F +010514062322263534363332160734262206151416323601BA064334070A09171D142731 +1E29010B3135031C153C1207080B030E012FC68F90C7C9938BC52AACFAB2B2FAAC01BE26 +BE180A16220D362721062EBD0D1206042A19080C050C317491C7C89092C8CB8F7FB1B27E +7DB1B000000000030000FFF202AC02A400280034003E000001170E0123223534370E0123 +2235343F013635342B01353637170F011433323736373307061514333237140623222635 +34363332160734262206151416323601D6112028182311303A21260F1108181044210731 +040B1C3B241F373407060DFDC68F90C7C9938BC52AACFAB2B2FAAC01090A2C1C21193A44 +31241536401C0D0815070704BE10074E2F57B61706036C91C7C89092C8CB8F7FB1B27E7D +B1B000030000FFF202AC02A40022002E0038000013353E0133321716173E013534272635 +343633321615140706070623223534272E01230514062322263534363332160734262206 +1514163236CF183C05111305042B370B13100C1117262E3322070917070E1101C5C68F90 +C7C9938BC52AACFAB2B2FAAC019615020C7B1C3124501303080E0F0B0E161127303A2E1D +15425E1B104C91C7C89092C8CB8F7FB1B27E7DB1B00000030000FFF202AC02A40031003D +004700000117363534272635343633321615140706222F01070E042322353426272E012B +01353E023332161716173736333205140623222635343633321607342622061514163236 +01841B610F0F110B10188A180E04182F0314101410040C110A040B0D160B281D02070604 +100970070408012AC68F90C7C9938BC52AACFAB2B2FAAC01ACB86420010C0D0F0A0E1610 +3F87171E964A0421181D101F1A771F0B061502070507113C519C096F91C7C89092C8CB8F +7FB1B27E7DB1B000000000030000FFF202AC02A400370043004D000001173E0133321615 +142322262322071716323F01170E012322262F01070E0123222635343633321633323F01 +272E0123220727373633321605140623222635343633321607342622061514163236016B +06212A150D111805130A13281D06181011101F2314101308143316181011150E0B081707 +0C133113070C0B1015060A35180D120149C68F90C7C9938BC52AACFAB2B2FAAC0184172B +210F0B1B0A415A1213140B2A1D12183B3D190F110E0B0E0B183C3E150E06130410175891 +C7C89092C8CB8F7FB1B27E7DB1B000030000FFF202AC02A4002E003A0044000001173635 +342726353436333216151407060706232226273436333216333237363534262726232207 +353E023332160514062322263534363332160734262206151416323601760D4B1313100D +1117524B361B17111701110D0B18040F12333010102108121E3212030734013FC68F90C7 +C9938BC52AACFAB2B2FAAC0171366823040A0B120C0F1711397A702714120E0D11101233 +100E9120210216040803764C91C7C89092C8CB8F7FB1B27E7DB1B000000000030000FFF2 +02AC02A400240030003A000001071E01171E013332353426353436333216151406232227 +262322072737232206072737331714062322263534363332160734262206151416323601 +D9B8171D170E110F0D09100A0B0F241C19352A1A0D110EC54B1917091517C2D3C68F90C7 +C9938BC52AACFAB2B2FAAC01A9C00413180F0B07010D080A0E110D141C1A150B0ED20E15 +044D6F91C7C89092C8CB8F7FB1B27E7DB1B000040000FFF202AC02A4000B001A00260030 +0000011406232226353436333216073423220E0215141633323E02171406232226353436 +3332160734262206151416323601E86B50313770493139393220341C0F191622361C0EFD +C68F90C7C9938BC52AACFAB2B2FAAC01785999484065994F17493C57521E23263A57513B +91C7C89092C8CB8F7FB1B27E7DB1B00000000001FFF5010B02CF01540003000001213521 +02CFFD2602DA010B49000001013DFED10186038E000300000123113301864949FED104BD +00000001013DFED102D001540005000001211123112102D0FEB6490193010BFDC6028300 +00000001FFF5FED10186015400050000012311213521018649FEB80191FED1023A490001 +013D010B02D0038E0005000001211133112102D0FE6D49014A010B0283FDC60000000001 +FFF5010B0186038E000500000121352111330186FE6F014849010B49023A0001013DFED1 +02CF038E00070000012111231133112102CFFEB749490149010BFDC604BDFDC600000001 +FFF5FED10186038E000700000123112135211133018649FEB8014849FED1023A49023A00 +00000001FFF5FED102CF015400070000012111231121352102CFFEB749FEB802DA010BFD +C6023A4900000001FFF5010B02CF038E00070000012135211133112102CFFD2601484901 +49010B49023AFDC600000001FFF5FED102CF038E000B0000012111231121352111331121 +02CFFEB749FEB80148490149010BFDC6023A49023AFDC60000000002FFF500AE02CF01B1 +000300070000012135211121352102CFFD2602DAFD2602DA016849FEFD49000200E1FED1 +01E3038E000300070000012311330323113301E34949B94949FED104BDFB4304BD000001 +013DFED102D001B1000900002521112311211521152102D0FEB6490193FEB6014AAEFE23 +02E049710000000100E1FED102D00154000900000123112311231123112102D0ED497049 +01EF010BFDC60232FDCE02830000000200E1FED102CF01B10005000B0000012111231121 +11231123112102CFFE5B4901EEEC4901350168FD6902E0FEFDFE230226000001FFF5FED1 +018601B10009000001231121352135213521018649FEB80148FEB80191FED101DD497149 +00000001FFF5FED101E30154000900000123112311231123352101E3497049EC01EEFED1 +023AFDC6023A490000000002FFF5FED101E301B10005000B000001231121352103231123 +352101E349FE5B01EEB949EC0135FED1029749FD2001DD4900000001013D00AE02D0038E +000900002521113311211521152102D0FE6D49014AFEB6014AAE02E0FE23497100000001 +00E1010B02D0038E000900000121113311331133113302D0FE11497049ED010B0283FDC6 +023AFDC60000000200E100AE02CF038E0005000B000001211133113311211133112102CF +FECB49ECFE124901A501680226FE23FEFD02E0FD69000001FFF500AE0186038E00090000 +252135213521352111330186FE6F0148FEB8014849AE49714901DD0000000001FFF5010B +01E3038E000900000121353311331133113301E3FE12EC497049010B49023AFDC6023A00 +00000002FFF500AE01E3038E0005000B0000012135331133132135211133012AFECBEC49 +B9FE1201A54901684901DDFD2049029700000001013DFED102D0038E000B000025211123 +113311211521152102D0FEB64949014AFEB6014AAEFE2304BDFE23497100000200E1FED1 +02D0038E0007000B000001231123113311330123113302D0ED4949EDFE5A4949010BFDC6 +04BDFDC6FD7D04BD0000000300E1FED102D0038E00050009000F00000121113311330123 +113301231123112102D0FECA49EDFE5A494901A6ED49013601680226FE23FD2004BDFD20 +FE23022600000001FFF5FED10186038E000B0000012311213521352135211133018649FE +B80148FEB8014849FED101DD49714901DD000002FFF5FED101E3038E0003000B00000123 +1133032311233533113301E34949B949ECEC49FED104BDFB43023A49023A0003FFF5FED1 +01E3038E00050009000F000001213533113313231133032311233521012AFECBEC49B949 +49B949EC013501684901DDFB4304BDFB4301DD4900000002FFF5FED102CF01B10003000B +000001213521112111231121352102CFFD2602DAFEB749FEB802DA016849FEFDFE2301DD +49000001FFF5FED102CF0154000B000001231123112311231123352102CFEC497049EC02 +DA010BFDC6023AFDC6023A4900000003FFF5FED102CF01B100030009000F000001213521 +11231123112101231123352102CFFD2602DAEC490135FE5B49EC0135016849FEFDFE2302 +26FDDA01DD490002FFF500AE02CF038E0007000B000001213521113311211121352102CF +FD260148490149FD2602DA01684901DDFE23FEFD49000001FFF5010B02CF038E000B0000 +01213533113311331133113302CFFD26EC497049EC010B49023AFDC6023AFDC600000003 +FFF500AE02CF038E0005000B000F00000121113311330521353311330121352102CFFECB +49ECFE5BFECBEC4901A5FD2602DA01680226FE23494901DDFD20490000000001FFF5FED1 +02CF038E00130000252111231121352135213521113311211521152102CFFEB749FEB801 +48FEB80148490149FEB70149AEFE2301DD49714901DDFE2349710001FFF5FED102CF038E +00130000012311231123112311233533113311331133113302CFEC497049ECEC497049EC +010BFDC6023AFDC6023A49023AFDC6023AFDC60000000004FFF5FED102CF038E0005000B +00110017000001211133113305213533113301231123112101231123352102CFFECB49EC +FE5BFECBEC4901A5EC490135FE5B49EC013501680226FE23494901DDFD20FE230226FDDA +01DD490000000001FF6DFF3102A102A60059000013333637363332161514062322263534 +363534232206073307230302232226353436333216151406151433323713230302232226 +353436333216151406151433323713233733363736333216151406232226353436353423 +2206D3AC2721364D253219110E170A162A3A166B066C484088222D1610111509143E2358 +AA484088222D1610111509143E23585B075D2721364E253119110E170A162A3A01AC7E2F +4D231B111A16110A12030D6D7720FEC0FEE5231A1119151007110510A201A4FEC0FEE523 +1A1119151007110510A201A4207E2F4D231B111A16110A12030D6D0000000001FF73FF31 +01E102A90059000013333E0137363332161514062322263534373635342322070E010733 +3237323E01331715140F010E01070607021514333237170E0123223534133635342B0103 +060706232226353436333216151407061514333237363736372333581B2628384E2D3A18 +1213160904223620191C1696282904080402050501020C010106480B1538102B3C1E2E4A +0C109B482B4A272D222C160F101705041425181C1E2D165801AC5C4A2433271D131A1813 +0E090504102A20475606010103030712040A27040515FEF90C0A46093A30321D010B2A06 +0AFEC2BB4121211B121A1810070907040D2F34ACFD370002FF73FF34020602AA003D004F +000013333E02373633321716333237363332140706021514333237170E01232226343F01 +230302232226353436333216151407061514333237363736133723253734272635343736 +353426232207060F012E5C1514251D404A201D1C030418060405151E610B123C102D381F +161A2F21A645418C222A150F10180504141D120D0516451C5A015725050508041A113726 +251B0601AC40343B193607040803124F73FE650C0B45093D2C1B2EC28AFECAFEDE231B11 +19180F080907030D18110E3A014A842094050A0C050A0B0504090D33336A180000000001 +FF6DFF3102D502A90085000013333E013736333216151406232226353437363534232206 +070E0107333237323E01331715140F010E01070607021514333237170E01232235341336 +35342B010306070623222635343633321615140706151433323736371237230302232226 +353436333216151406151433323713233733363736333216151406232226353436353423 +2206D3AC1B2628384E2D3A181213160904221D2712191C1696282904080402050501020C +010106480B1538102B3C1E2E4A0C109B482B4A272D222C160F101705041425181C1E3111 +AB484088222D1610111509143E23585B075D2721364E253119110E170A162A3A01AC5C4A +2433271D131A18130E0905040D0F1820475606010103030712040A27040515FEF90C0A46 +093A30321D010B2A060AFEC2BB4121211B121A1810070907040D2F34AC011420FEC0FEE5 +231A1119151007110510A201A4207E2F4D231B111A16110A12030D6D00000002FF6DFF31 +02FB02AA0069007B000013333E0237363332171633323736333214070602151433323717 +0E01232226343F0123030E01232226353436333216151407061514333237363736133723 +030223222635343633321615140615143332371323373336373633321615140623222635 +34363534232206053734272635343736353426232207060F01D3AC1514251D404A201D1C +030418060405151E610B123C102D381F161A2F21A645206944222A150F10180504141718 +0D0516451CAB484088222D1610111509143E23585B075D2721364E253119110E170A162A +3A018B25050508041A113726251B0601AC40343B193607040803124F73FE650C0B45093D +2C1B2EC28AFECA9095251C1119180F08090703141F110E3A014A84FEC0FEE5231A111915 +1007110510A201A4207E2F4D231B111A16110A12030D6D7794050A0C050A0B0504090D33 +336A1800000000020023000002AD029B001B001F000029013533323635342F0123070615 +163B011523353637013313163B012F01230702ADFEE3102820020DC6540A02291DDA4329 +0164124E0A4010DB19068B101D20070E718C0B0F1D101008420241FDBB46E8E3E3000003 +0026000002AE028D001A0025003000001333321514070607171E01151406232137333236 +37133635342B0117071633323635342623220B01163332363534262322C6FCEC5B1C2E03 +344B9091FEBC0414342C077B024E13D53B1E17525646311758442027506E503E24028D91 +662D0E0C0409463B526F10211E01EF0A0B2A1AFC06584B342EFED4FEF509525A34380001 +0032FFF402C70293002300000107233635342623220E0215141633323736371706070623 +222635343E01333216323702C73510014652487C4F2C63607E5A0A07142336576A8A9678 +CB781B6934100293DF08223B55446C83415B7B620A0A0D35273DA07670B6631B1B000002 +0026000002FD028D00120023000013213216151406232137333237133635342B01170306 +151433323E0235342E022322C2011A8C95E7BEFECE04125A0F7A044D19D87A055F456C3E +2010254B3423028D9182A2D8103E01F110082518FE0813082B3D626E3830524D2D000001 +0026000002DE028D002D00000107233635262B010733323637330723343635342B010306 +15143B013236373307213733323713363534262B013702DE2A10020469A03C50333C0F10 +3A1106485A42023F43736C221042FDC50413590F7805202B1304028D9E1A0957F82639F2 +02300A33FEFE08021B4858C6103E01E8160E1310100000010026000002D3028D002A0000 +0107233635262B010733323E01373307233635342B0107140615143B0107213733323637 +133635342B013702D3281001036B933E53242B280A103C100546613E044F2004FEAA0412 +342E087C044C1304028DA0081C57F80B2B29F2172632FE011004291010201E01F1100827 +0F0000010032FFF402DE029C00350000011522060F010E0223222635343E013332171E01 +333236373307233635342623220E0315141E0233323F01363534262B013702DE3B2D093E +2556332B7FA56ECB7A3532062C0D0F1A02102D1002514F426D48311610264B3439283903 +202B13040149101F1FD81616037F7F6AC27E100213180DDF140C3E5C3758717338223A37 +2019C90C0D1213100000000100260000039B028D00360000010723220607030615143B01 +0721373332363F0121070615143B01072137333237133635342B013721072322060F0121 +373635342B0137039B0412342E087B044E1304FEB70412332F073DFEE83D044D1404FEB6 +04125B0F7B044E1404014A0412362C0734011635044C1404028D10211EFE10120A221010 +201EF2F20C0C2610103E01F00C0C271010201FD9D914042710000001002600000212028D +00190000010723220607030615143B0107213733323637133635342B013702120413352D +087B034E1104FEB80413342E077B044D1204028D10201EFE0F0C11211010211D01F11008 +26100001003CFFF4026C028D001F00000107232207030607062322263534363332161514 +0615143332371235342B0137026C04125C106719432C413549232319271B183119754B14 +04028D103FFE63642D1C352C1E291D16111D0D156001C71627100001002600000322028D +00390000010722060705131E013307213733323635342E022F0123070615143B01072137 +333237133635342B013721072322060F01332536352E012337032204233025FEF1BB1A39 +2C04FEE1040B1817030407029C043E034E1104FED004124410750A4C12040138040B3326 +083505011A1201141E04028D10181DD3FEEA262910100D14060A050B03EDF30C0A281010 +3E01D8270A2610101F1FD1DD0E0F0B0A1000000100260000029C028D001E000025072137 +333237133635342B0137210723220607030615143B013236373637029C3DFDC704115C0F +76074C120401470412342D087A05287A4F57210908BEBE103E01DD1C11251010201FFE1F +130C182F440E17000000000100260000041F028D00300000010723220607030615143B01 +07213733323637132301230323030615143B0107213733323637133635342B0137331333 +01041F0411372A087F024D1304FEB60411362C077006FEA7195E036D054C1404FEF50413 +362D0977054D1304D55904014D028D101E1FFE0E0715221010211D01B7FDFB0205FE5416 +12211010262301DB15112310FE1301ED00000001002600000385028D0024000001072322 +060703230123030615143B0107213733323713262B0137331333133635342B0137038504 +12352F079018FEEE066C034C1304FEF604125B0F7E16461304C8EC0561044C1404028D10 +201FFDC2020EFE400C0F2310103E01FD3210FE2F01821208251000020032FFF502C8029D +000F0020000001140E0223222635343E0233321607342623220E03151416323E0302C83A +669E5B708D3B679F5C768374454842683D270E4F7A643F2B1201A44B987D4F82674D9D83 +52934F56674465826C3046503C5B7369000000020026000002C0028D001E002900001321 +32161514062322272227070615143B0107213733323637133635342B0117031633323635 +34262322C9010F75738F8E2C1E040838034D2004FEAB0411352D087D044E13D644201C4E +683B4218028D604052750502DF0C0B271010201E01F110092518FEEE08604C323F000002 +0032FF6802DB029B00250035000017372E0135343E0233321615140E0123071736333216 +333237170E012322272E02232207013423220E0315141633323E0253A4586D4472A85C6E +8170CB76400415161EA92E4A3E0D307354444A0D44270E15250206823F6C463215463D4D +7D4726736B0D79604C9F8052837466C6822B050422420B3B3C10031008120252A344687E +702C484D5C8C9100000000020026000002D5028D00250030000025072303062322270706 +15143B0107213733323637133635342B013721321615140607171E010103163332363534 +26232202D502A99614090A1C3E034C1304FEBB0410342D087C054D1404010F7167615173 +122EFEEB40110F605F4439121010013B0204EF0C0B271010201E01F11405251052453E5D +10E5252E0252FEFC044C4249340000010032FFF602A8029C003800000107233635342623 +220615141E05171E011514062322262322072337331416151E0133323534272E01272635 +34363332163332363702A8351001514F295006100C1C0D2406373F83713174242017103C +1001034E5A9C2E10511149716E2E78110E1403029CDD0810445C31300E1B1A121B0C1E05 +2E593B4F702626EC051505466188322E104610493A456A25180D000100190000029E028D +001D00000107233635342E012B01030615143B010721373332363713232206072337029E +2E100523251B6285054C1504FEB70412362D07855B3E5F0E102E028DB32A1D1F2107FDE6 +1603251010201E021A513DB3000000010041FFF30307028D002F0000010723220607030E +0423222635343637133635342B013721072322060703061514163332371336352E012B01 +3703070410342F085709162B3758376F6F0C024C014B120401480411362C085A074C4495 +2E56040121291304028D10211EFE9C243A412B1D6E4D065108014A02051F1010201FFE98 +1A2A2D43BD015F0E0C13121000000001003CFFF002F8028D001800000107060701231134 +23372107232206151117013635342B013702F8044D37FE47186304012604132C1A040118 +26341404028D100548FDC0023B5210102620FE7901016E301A16100000000001003CFFF0 +044D028D002D0000010706070123112301231134262B0137210723220E02151133133534 +2B013721072322151133013635342B0137044D044E38FE4A1D0BFED71D2F2A0A04012004 +0713141A0B05E2520804011D04124706011628361104028D100449FDC001BAFE46023929 +2B1010030C1F19FE87014C3D37101048FE750172341419100000000100190000032A028D +003A0000010723220E010F01131E013B0107213733323635342F01070615143B01072337 +363F01272E032B0137210723220615141F01373635262B0137032A040421331C17BB600B +22220E04FEE7040B22210445B50C381604F9044E3ED055060917261A0904011B04131A1A +063CA40F022B0904028D1016191BD5FEF62024101010190D0DC2C90D0F2010100A43E6E7 +1212200F10100E150C10B3B812101810000000010023000002B7028D0027000001070607 +03070615143B010721373332363F01272E012337210723220615141F0133373635342337 +02B7044834D13B034C1404FEB80411352D073D590E373604012B04132317074B04AE104F +04028D100540FF00EA0D0D241010201EF6ED2527101013150318CFD4140E1C1000000001 +003C00000322028D00130000090133323E02373307210123220E020723370322FDCADA2A +383F2B0D102EFDBB023DCA2C37422C0E1130028DFD99081A3C2EB20268061634299E0002 +0028FFF601D801B9001E002C00000103061514333237170E012322353437230623222635 +343E01333216173337073423220607061514333236373601D85B010E18280C1C48212A0B +04525B2D2C4C84441E2202010919322F561D1C3A3160160F01ACFEA7020911360C2A4137 +0F23694334439A6F1C122157476447463642734B31000002002DFFF501C2029C00170025 +000013173633321615140E022322271336352E0123220727370307141633323E01353426 +232206D6044138333C284367384348900201120A130B039E6C372611395A292A1D2F3A01 +7E043F57402E685B3C3002330803090C041018FE60D7050E627B37302C5500010028FFF5 +019001B9001F0000251706232226353436333216151406232226353436353423220E0115 +14163332016B0C5D6944459F6828391B1B12141D223755243628496F0D6D5B406CBD2923 +1F27180E0C230B1563732D3C310000020028FFF4020F029C002500340000010306151433 +3237170E012322353437230623222635343E013332161733373635342322072703342623 +220E011514163332363736020F95020F19250B1C4620280A05545731273E7C4728280102 +350221061504081A1E2E572F2018325B1710029CFDAA060609380C2940291B25694C3338 +947A1E14D1080414030FFED024236D89322220704A3300020028FFF5019A01B900180022 +00002517062322263534373E0133321615140E0207151416333227333236353426232206 +016B0E5A6E44450614975B283E37585F30422F4CBD094F7719133256700D6E5E3E201C59 +93222A2D472917021C2F3CA35C4113187A0000010028FF450267029C003800001333323E +013736333216151406232235343635342322060F01330723030607062322263534363332 +16151406151433323E05371323F8251C1D1D0F3B5B252A181123090F243A1013500C4C4F +140A4768232517141312100F0D1914100F080B02574D01B52750165A201B141419081A03 +08473D4626FECE4E18B21E18141D140D0A110509112120321E2F0601560000030014FF45 +01EC01B9002400300040000001072316151406070E01141E021514062322263534372635 +343633352E01353436333217073426232206151433323E0113342E02270E010706151416 +33323601EC0D51114C412A2E4957496F685E5A751833222F2F624C3C1C1E171F273B3821 +2E110D2D374C100114053048433F40019D2D141E395308062028180D312A3C5B42314626 +101F1E39040B3C263E601C401F1F5734513A44FE7E1B230D0E06010B042325273A350002 +0032FFF50101026800090025000000140623222634363332070306151433323E04371706 +2322353413363534262322072701011C14151B1B1513015C010D0609060C0613060D5234 +284F020E110E0E030249261B1A281EAFFE9406080A03030A0814070A692E1F013304080D +0904100000000002FFF0FF45017402680009002700000014062322263436333217030E03 +23222635343633321615140615141633323713342322072701741C14151B1B1513086C09 +1D2C4226252417180F141006043C2B5C200F0C030249261B1A281EAFFE5724423F262518 +142015110713080307AC016B1605110000000001002DFFF5020F029C0031000001070E01 +0F01171E0133323E033717140E04222E022F010F01231336353423220727370337363534 +262B0137020F042F36206A400D16120910100B12040F110D191721201B1408073F382D4C +9403201308039E71A71511090C0401AC0C051A1B5C981F1C09110F1A060B011F16231611 +0F1F11109331A6024D090B16020E19FE38951311080B0C0000000001002DFFF60116029C +001B0000010306151433323637170E0423223534371336353423220727011694010C0F25 +0A10032311201F11270680031F031803029CFDB104070D26110C042B121D0C33101401FE +0D0B16030F000001001EFFF802A801B9003E000025170E01232235343F01363534232206 +0F0123133635342322060F01231334232207273707333633321615140F01333E01373633 +3215140F01061514333236029B0D1A4821270638061825810D224F4D051724800E234E5F +210B10029D2C066F511B1C090A05174724201D36073D020F0E236B0B2C3C310D18DD1513 +26BC3687012B171126BC368701761F020E18A9A92E23161E242952181647211CE706060C +1B000001001EFFF801D301B90039000025070E09232235343F01363534232207060F0123 +13342E0123220727370733323736333215140F0106151633323E033701D305010F061009 +100B110D100829073907182C583A0A214D5F030E0D0715029F330307315D43380B3B0102 +0D070E0F091001600601130712090F090B05042E0E1ADD1C0E268557237C01760A0D0903 +0F17BD49743E1A2CE702080E070F0914020000020028FFF501B601B9000B001700000114 +06232226343E01333216073426232206151433323E0101B6996E45423E77464053511F24 +3D6C3B31562A011F6EBC6282845C4D2E2439B7666B78880000000002FFE2FF4901DA01B9 +002300300000133736333215140E01232227070615143B010723373E033713342E012322 +0727370F021633323E01353426232206FD0E3D345E417845251818053D1003EA03161D11 +070675030E0E0C10029E12242E121C30542C1F14284601720D3A9B4189601364160D2B0C +0C010A1A131701D00C0B0A0410184586C81561823A2B2853000000020028FF4901CF01B9 +001F002D00000103061514163B010723373332363F012306232226353436373633321617 +333707342322070E0115143332373E0101CF83031A170E03FA03132E26092C064A592D2C +63463229222402030718311B22304B342218324901ACFDF60A13161A0C0C2A24B8674330 +59A92E221E16275A4A1B27A8423F11239B000001001E0000018901B9001F000013173637 +3633321615140623222E0223220E010F0123133635342322072737AF050A164B3E151724 +1D0D100507061B4D2A07154D5C04210912029D010B011424771B151F330C0E0C8A6E194C +016C0F0813030F17000000010032FFF5018601B9002A0000010723342623220615141E01 +151406232226232207233733141633323635342E023534363332163332370186180F3329 +151E4343493E1457030F0F1119103B2D2821293029432F17380D0E1001B99232461D1D16 +526129324B14159C325035241B3B2D4221373414140000010028FFF7011B0237001D0000 +01072303061514333637170E06232235343713233736373307011B0A464E021017250D02 +130B171217190C2705514D057B360D1F01B626FEC006050C02320B021B0D1A0E11083413 +1201401D1773810000000001001EFFF701BC01B900360000010306151433323E053F0117 +0E02232235343F01230E020706232235343F01363534232207273703061514333237363F +0101BC59010E040909070A050B02050D131B3C1A28041A010234411226223D083B062110 +0A039D54031A293B4D201501ACFEA404050E0306050B060D02060B1B232D3415105F0142 +4C0D1C4A1D1EE7170217030F1AFEBA0D0B224D66754B00010048FFF701DF01CA00270000 +130306151416333236373635342E02353433321514070E01232235343F0136353423353E +0237F759041F1D496C1D0F131713362F61216A3774093A05390E45331601C6FEBA0D131E +2479522D1D19220E160E2A5392862E3A5D201ED21405140F040E0E0A000000010048FFF7 +02EC01CC0039000001030615143332373635342E02272636333215140E0323223534370E +03232235343F013635342335363717030615143336373E01370213500C3B7B480F131613 +010120172F1830415A316F17112B3B4B284E132F0536544704510B1C3861252F1A01C2FE +DF2E183DCB2D1D19220E160E1812532C676653346429542746482C57154AB10F0E160F0E +1E03FECD30192F019638785D00000001001EFFF701FE01B9003900000137363332151406 +23222623220E010F011E02333237170E0323222F01070E0223222635343633321633323F +0127262322072737161701312D37383115120C18080E150B093709232214211C0E021A19 +2A142D1C283F14192E1A191B15110D160C1F2741212042170F0384311A0120455421101A +090B0D0E55146E43390A022A23204D71621F201D1C130E17143B655C590612211B470001 +001EFF4901F001B800320000011736373635342E01353436333216151402070E06232226 +343633321633323637032E0223220E012327371E01010E1C472F1C1A1A1A1519209A320A +291523191F1D0E1A1E1B0D191B050C4315260309201A050D0F030296110F0133EB615734 +1708131A101117211D33FEFF3F0D341B2A16180A2328151F4620013A1E26250103101918 +35000001002AFFF201D301C200260000011501173633321E023332353426353436333216 +15140623222726232207270123220607273701D3FEBE031C271F2E151B0F1B0C180E1216 +4327093B323143370B0148832E2A10122401C210FE9801151B201B1805150C0E18181225 +2B0E0E180A0172162704890000000004001FFFF1034E02A2003100390042004B00000117 +0603363717060F0123372627062322263534363332161514062322272314163332363726 +3534363332173E03333207230E0107161712033427060716173736272623220615141736 +03450967A23C2B092A521161125A485C51303B241A151D1714160702241D29422B7A7249 +663B2B41514D2510390138714C1F0281981D703C435431012C325D39596B3F02A00ECAFE +7D0C16101F0E2A2706225E342820281C14121E0C1121232A458A5362523E55562B241484 +733D53011BFEC9543DAC4320066D08BC4B543E76404500020025FFF1030D02AF003A0045 +000001072623220E0115141617362433321615140E012306151416333236353426232206 +0727343633321615140607062322263534372E013534363332031732373E013534232206 +01C10C3751356347564151010F682E3285DD73453E37507A291E366107167550373F362A +4E6055703047638F715B35017B79495B3248CB0285131F214A313E47077DB727243C7E54 +6463313D643A25256A4E025B863532294E1D365D50515A0C56484B71FEB4013E25602B28 +9E0000030024FFF1033202AF00370044004C000001170E01071615140623222706232226 +353436333217363736372E0123220615141633323637331615140E022322263534363332 +161736073427060706070607163332360526232215143332032A081B151739C57D46564B +563D443C2F4763527953602C9A5290A15C4860770113022E494F255976C39060A7362708 +25293E4B7921143A3572A4FE204B39435742024B140C101959658CC7211B221D191E3432 +C487513D4B80693C5581840F1C3F64381D65526E934E421CEB4C44377B9753160A14AF98 +28192100000000020053FFF102E402AF0044004E0000010726232206151416173E013332 +1615140E0123061514163332363717060706232226353436333216151406232227231E01 +3332363727062322263534372E01353436333205342623220607323E0101A109313E4C61 +4A4145D8592C3B73B961292220367F580F222052F84861211A141F181415080205492E57 +75320142483C4D1A4E597659460153201941A830539F60028D121644302C3C096D842923 +325F3A3C441D264750102257E0432B1E271C13151E0D18276060012C453A37330D49393D +556114167F55344F000000030009FF4F031802A20029003600420000251706070E012322 +2635343736373E01372E0135343E023217363332161514230607363717060F0136133526 +2322070E011514161736030E02070615141633323602280F475035B85C242A7D239B1443 +10374233525B5A52241E090F6842564D3610356D5744891A1152303146392751DE114D31 +145814103D67C1103F1D66A0241D4B3B103027AC220B4337314F2D1706140B09302FC112 +530B6715D01A01C701021112482F2634019FFE210617110A2B360F16620000010028FFF1 +03EC02AF0075000001170E050732173E01373E01333216140623222627230E02070E0107 +15161514061514333237170623222635343635342322270E032322263534363332161514 +06232227231E01333236373E043727062322272623220614163332371706232226353436 +333216171633323603030D1A2F2129142A081B0F20382C4C6325161D1614101C01010815 +1C0A316141304C203970137555253855281A082E5E5C53303F50221D141B181415080203 +3C2B3C5A3C0D3E19343624010F152C503F273046362E4D361236613C4F664B1D3A373C1A +2255029D101430273F224B0E0E0735416F69152819171607263D12595915023029257E11 +22810F902F2329861E1F0F6589461C402A1E2C1C16141D0D1926586A18712A4C381D0305 +2C223D503656096B47373B5C151C1F26000000010026FFF103BC02AF003C000025333637 +3637363332161406232226352306070E020723363736372706070E012322263534363332 +16151406232227231E0133323637123717060706023901235752382228171D1912101F03 +30672433562B141A060516013C535B7640222E2219161E171309070301130B356F4A9D5F +0D0E0C088336D1C43C25192A19171619FC59718A2468CEBC4D0151A7B6942D201D251F14 +121B040A0C9496013E270B30DBA500010052FFF1029702A8002E000001072623220E0115 +14163332373E01353426232206151417072635343633321615140E0223222635343E0133 +3216029714205E51B3733A3871642A4B3725334D1B1623614037553D648E4C535D77CC74 +404E02250262ABEA5A3A4D672C9456343A564935340A39494D6749494893764A75567EE4 +8A4C00010026FFF1037602AF005200000117060716151406232226353436371706151416 +3332363534270E03070E01232226353436333216151406232227231416333236373E0337 +26232206151416333237330E0123222635343633321736035807222059573E2325201A0A +241210293537182B1C311054C2794D602219161D171417070246325E7C542B2A423D234C +7495CA3C2B9E2F140876653F4FDEAB7D6024029D130E184362446B261C162B0E1215210D +10563F4B301744386A21A99448331F281B15141C0C1C3369964D48643F1A3096632937F9 +71A6473B6BA83B17000000010052FFDA029702A80043000001072623220E011514163332 +3726232207273633321736373E0135342623220615141707263534363332161514060716 +3332371706232226270623222635343E01333216029714205E51B3733A38473F1B212626 +0D3E2E35211C062A4B3725334D1B16236140375565521A2435310F4441212813494D535D +77CC743F4F02250262ABEA5A3A4D262B1D113A3819072C9456343A564935340A39494D67 +49495FBE3B2A300E52201D2675567EE48A4C00010043FFF102BD02A80037000025072E01 +232206141633323635342E0335343E0133321615140623353E0135342623220615141E03 +151406232226353436333216018D141542312848533C4C75202F2E20546F3A3854593530 +3D3F2D3F7021303021A477517465423D5D8F04472E4368465E521D372F30422443632A3A +3A354C1505382E2A2759401E393032422463784E4F47564F00000002002BFFF1038B02AF +001D003D000001170623222623220615141633323637330E012322263534363332163332 +07170E01070E0107062322263534363332161406232227231E01333236373E0103800B59 +5739F82C5C794431475B1C141470554057AF8838EF3134B10D263319175D3F5A613D5722 +1B141D191415070203392E537744305A02A31249395F4239405D59656F4F41607635510E +256E585189293C3D2D23271D281D0C1B24729367750000010024FFF1032502AF00400000 +09010615143332371706232226353437270E0123222635343F0136353426232206151416 +333237330E012322263534363332161514070306151433323E023F010325FED811213870 +15785325311103356D2B242D4691244C2D547A27226229140C563C33409D63496A26BA27 +1D276D674D18820292FDDA201723810D922B2321260148513028396EE3382D2430634F25 +2F77385D3F335A7B4D45423AFEE13D1A1C5F8C782DF300010023FFF1039A02AF00460000 +37173E03373E03333216151406232226352306070607062322273E01373E013727062322 +2623220614163332371706232226353436333216171633323F01170E010706D602366163 +533A343C48371A151D19121418013494926775570E21293733235846030F15349E1F3046 +372D4D3610365F3C4F6F481C313B3C1A413B1B0F4849285815020B3C6A7158505459251A +1512191A171EE7E4505C062666835880430305533D503656096B4737395E131E1F31160F +4D8165DB000000010023FFF1042E02AF0060000025173E01373E01333216151406232235 +230E01070223222736373E0137270603022322273E023736372706232226232206141633 +32363717062322263534363332171E0433323F011706070607173E0237123332170E0407 +0601C702447C4B7E7337151D19122B011B6A71AF90210D55382C6F520161A5B195131C1F +38211B3888020F1531981E2F3D302A26381B10365538495D46316107180A100D06413B1B +0F754F4F7502326C4429B77E12211524281333065B18021B9181DB911916121932089FC2 +FED30450997AB8570111FED9FEC1061A5C4E4896890205533C52352B2B096B47373B5C31 +040C06060331161073CECD69031270694901440B0F2A4D26770FCE00000000010024FFF1 +036902AF0050000001333633321615140623222635343722060706151433323637170E01 +232226353437230E01232226353436333216151406232227163332363736353423220615 +1416333237170E01232226353436333216023B02707221292016151D1135854443321B48 +39143E573830350902367349353D201A141F1B140F0A1846377E3B587C445D2D2A582613 +114F3439447756596C01E9C61F201A1E18161711B19A974D413A420F5049333120174D4F +41291E2A1C131520063683679B669458452A356A053D46443A506D7100000002005BFF1E +039402AF004800550000013302033637170E02070E01232226343E04373637270E012322 +26353436373E01353426232206151416333237330E012322263534363332161514060706 +15141633323637030E0515141633323603395BF2804B3E0F1F40232535B65C242D212C4E +355B10491E0333722B272E2F3C472B4F2F5B752D22672E14155F3B30429B654B683A5256 +0F0E339670DF32284C20290F17103C680292FEBCFEFF1C340F1C2A121066A2243A332123 +111B059034044753312C24564E5B4C1B21325A4D202B7D4E4D3D2E5771533F2D65676C2B +0C118E99FE67100E1D131E1F120F166400000003003BFFF1039002AF0053005C00650000 +01170E0123222635343633321E0117363332161514062322270607163336371706232227 +06071633323637170E012322262706232235343633321736372623220727363332173637 +2E02232206151416333236253423220716333236012623220615143332019712193F3233 +4D685D32754639484E39374232374E383C05063C210E373F0E0436686A334F6A13140E73 +5D244F524B577042322D613B3D0408352A113C420B064459393D6E32404733242A3501E9 +473E2C442C1B26FDB44C2A1B264741020708303240333B641A1A172E221A18221A40AC01 +0219113B018F4E24494201555C151C2B3C18221F389F0120104101A54C1614174F29282D +23781E211911FDE81C110B1E00000001001EFFF502F601B9003A00002517062322263534 +372706232226353437062322273716333237153E0133321707262322070E01151433323E +01373E01333215140706151433323602E1158D6A252704016E572C30162324271F0A1F1A +3E433895435B1B151C38494641602722513523093B2A1B6E0B371F71C211B93025130D01 +793B322D3214111A0D4F01465A470A334642A82B2D33352934651C2C6627183A51000002 +002FFFF4022F02AF0032003A0000133716173E01333216151406230E0115143332363726 +353436333216151407163332371706232227230E01232226353436372625342322060732 +367F142035428C3C1D209B713F7B243A7F3D0A381E090F37132C120C071016332101428C +3F29346343370173231F5543578301ED1626095C7F1F184B7D5EE0342C6D631D1B395011 +0C435B20051A0920686E332A40D0580EA91F5E6369000001001EFFF5024D01B9002C0000 +25170E012322263534370623222737163332373E013332151406232235343E0235342322 +0E0115141633323602371659A9462F38121E2E271F0A1F1A3D3B34984258241928131813 +22378B5C1E18349FE1116E6D3F362E2D18111A0D4549604F1A27210F12050C0A1582A12E +1A1D650000000002001EFFF5033B02AF002B003900000901061514333237170E01232226 +353437230E01232226353437062322273716333237153E01333216153313033423220607 +0615143316373E01033BFEBF0E1C478D174B7334252409023B61352B30181F28271F0A1F +1A3E43389442242A02ACCC302971325729404F405B02AFFDB218181BAC115F5A281F1718 +3D3C38312E3514111A0D4F01465A27270144FEBE2954497D4029014A3C9E0002001BFF2F +02A102AF001D002800001713263534373E06333216151405071633323717062322270301 +3423220E020F013E011BC20D751B1D2D1D2A242B172327FEF1732130453C1149503326A6 +02192A122521130D3E5789D101761921573C353551283217112B1B916BDD1236173D13FE +BC032F311930201978216C00000000020026FFF502B202AF0031003B0000251706232226 +35343F013635342322060F01231326273716173E0133321615140E022307173633321615 +140F010614333203173236353426232206029D15A06820262E6A111944CF521C4DED502E +142E4C455D391F20344B4C1C4E03866721282A67181758A001368113111E42C311BD2523 +3C449D1A0D1ACA9C3501B80E371536077B5F28192A492C1999029E211D3043A4272A01C5 +02593D0F145000020053FFF501E4028D000B002800000114062322263534363332160317 +0E0123222635343F0106232227371633323733030615141633323601E41E15131B21160F +1B43154E6B2D242C393C25342A1F0A1F1D454349A016120F235A0260141E1A1114201CFE +4B115F572B273F666D1E111A0D4FFED928260E124E0000030009FF2502CE028D000B002E +0039000001140623222635343633321607011633323717062322270E0123222635343736 +3332171306232227371633323E0137012726232206151416333202CE1E15131B21160F1B +7AFEF82A2B3B290C363F34302B633F2F33362A493A21D12C462A1A0A1A221F3F2718FEF3 +011D3739451F195D0260141E1A1114201CC9FE0D161E1A22163F332B1C30201A0D018A2B +101A0C22241CFE02010D2820121700020028FFF5029A02AF003C0046000025170E012322 +26343635342623373E0135342322060F01231326273716173E0133321615140E02230717 +363332161514060F01161514061514333236031732363534262322060283174B74332331 +2B1E1F0B4C852141DE4E1B4DED502E142E4C455D391F20344B4C1C580295771E26825201 +36211F216BD401368113111E42BB115D58203E4811121D1E04522A22CE983501B80E3715 +36077B5F28192B492B19A801AC241930590A0320321C3E0C1B51017402593D0F14500002 +0030FFF5023B02AF001E00280000133716333E0133321514060706151433323637170E01 +23222635343736372637173E013534232207064D0E243A55B33A40C1818F2421574F1555 +612D25324D0C1C319801759B23252D3A01851A197DAC4351A013E7432B4C5B1163512D2A +5486162C0222011A83481F2F3B0000010031FFF503D201B9003C00002517062322263534 +3F0136353423220607231336353423220607231306232227371633323733071736333216 +151407173633321615140F01061514333203BD1599701E272D68131B4ADE584BBE141C4A +DF554DB83449201A0A1919505744460386681F291102746722263160171757C310BE2622 +3F41981D0F1AEAB10151240B1BECAF01562E101A0C628B029E211D23270189211D314E98 +26151600000000010031FFF502BD01B9002C000025170623222635343F01363534262322 +060F0123130623222737163332373307173633321615140F010614333202A8159B6D1F27 +2E6A110F0D43CD521C4DB73449201A0A1A185057484903866721282A67181758C311BD25 +233C449D1A0D0A10CA9C3501562E101A0C628B029E211D3043A4272A000000010017FF2F +02B601B9002E0000011736333216151406071617072E0127062322263534363332173E01 +35342322060703230106232227371633323F01016A03866726366846462316283123312C +2424332724152E7C2443CB548D4D01282D50201A0A1919515648011D029E303347B22F1C +2F14231C031C120E121E041AD54328C99DFEFA02272E101A0C61010000000002001EFF2F +02E701B9002800350000251706070E02072313270E012322263534370623222737163332 +373E01333216173337330117362427342322070615141633323E0102D512439830532606 +4EA902366138262E171E28271F0A1F1A404139953C243004021C44FEE7031F010E97365F +6E5618113D9360CA12468B2B582C090136013A373A2F303314111A0D4F455A212030FDF3 +0222F7BA2A9F7D3E131681A30000000100300000023C01BC002000000117062322270507 +231306232227371633323733071737343633321615071633320239030E124215FEFC4749 +B73549201A0A1A185057487802D62B22131754112D0D01371B0730C085015A32101A0C62 +E401A0293012094A24000001003EFFF5019C0213002C0000372736372635343633321615 +1407161514070623222635343633321615140E02151416333236353427230E0181096F59 +0525100B112A3A4D4667293B2517121812161222144A7010023059F31B266F0F0C253010 +10302A5F5665474327241F2C131211120309090B1092712C3E383B0000000001001EFFF5 +0214029200250000010723030615141633323637170E0123222635343F01062322273716 +33323F0123373337330702140D92D10E120F245754154F6C32212B3932373E201A0A1921 +485627660D67474F48020E1EFE7D1B1F0E124D601162582A273B695C32101A0C75491E84 +84000001001EFFF502B501A8002D000025170E01232226353437270623222635343F0106 +2322273716333237330306151416333236373303061514333236029E174B713524220803 +755E222B2E443944201A0A191950574B9F16120F47C7554EA0171D2261BE115D5827201B +2001862D2B35567E2E101A0C62FED927270D13E4B1FED72A201F4F0000000001001EFFF5 +024601B9002E00002517062322270E0123222635343F0106232227371633323733030615 +14163332363726353436333215140607163332023F07151A2B2043804825363933354520 +1A0A191950574B9E141B15338037102D201D1D12192511D21B091D6C6A322F3C665C2C10 +1A0C62FED9242211176869242438553224601A1D00000001001EFFF5035401B900400000 +2517062322270E01232226353437230623222635343F0106232227371633323733030615 +141633323F013303061514163332363726353436333215140607163332034D07151A2B20 +438048253601025959253639333743201A0A191950574B9E141B156380624B9E141B1533 +8037102D201D1D12192510D21B091D6C6A322F0B0773322F3C665C2C101A0C62FED92422 +1117E5B0FED9242211176869242438553224601A1D0000010041FFF502A301B900410000 +251706232226352306232226353436333215140E02151433323E02353423220607273633 +32161533363332151406232235343E02353423220E011514163332028D16987826340257 +49272F25172A121612242B5D432C25297B4E169D7227310244445225182812151222256C +4C181259CB11C2292C5827241C2C241013050B091559766E1830525D11BC2D27574C1A2A +211012040B0A1688A52C141800000002001EFF25026901A80033003E0000010306071633 +32371706232227062322263534373633321737270623222635343F010623222737163332 +3733030615143332363701272623220615141633320269AB352C2C2E3B290C363F36304D +7E2F33362A493A216303755E222B2E443746201A0A191950574B9F162147C755FEF9011D +3739451F195D01A8FEB86743171E1A2216722B1C30201A0DB101862D2B35567E2E101A0C +62FED9272720E4B1FE02010D28201217000000010034FFF5026901DE0046000025170E01 +2322353436342623220706232235343F0127062322270E01232227371633323726353436 +333216151407163332373E01333215140F01173633321615140615141633320252174976 +3B3C14120B413E372A167FE7011219372C18441F231A0C19173A2C1A1B1413151219342A +222241161F78DF011F292426110B0659B21066472E10271610473F14313BBC020722191E +101A0C2C1F2017221D15172620112E2B173227BA011021170F230A0608000002001F0000 +027B02A20007000A00002123272307230133032707027B6021F9826001C91A132597C8C8 +02A2FE70E5E50003004A000002810296000C0013001C000013333215140607161514062B +0113333235342B01033332363534262B01EFCDC543405BA877F0B95AC87082807E606F55 +6C4F029675395F1B2159668E018A833FFDFE5A42342300010060FFF202F302A4001A0000 +0107232E02232206151416333237170623222635343E0133321602F31A1308424D2E85C1 +655E7B7B2688AA808877C9734B7B022A65374517D48D57626C3385836F77CF7A3B000002 +004A000002EF029600090014000013332015140E022B0137333236373E0135342123EFA6 +015A3E71B36CD769315E6A365061FEDC3C0296E94D977C4D4A0F1925A161B30000000001 +004A000002A60296000B000001072107210721072107211302A613FEA032013113FECF3B +017B23FE3FA502964ACA4BED4A02960000000001004A000002A702960009000001072107 +21072103231302A70AFE9632012813FED84D57A502964ACA4BFEC902960000010061FFF2 +02F302A4002000000107232623220E011514163332363F01233533030E0223222635343E +02333202F31913209D5E9B5368645357062859C34B2D306D4481914979A456A50235678A +659A53577124199E4CFED61A1619877157A3784800000001004A000002ED0296000B0000 +01032313210323133303211302EDA5574BFEB04B57A557480150480296FD6A012DFED302 +96FEE1011F000001003B000002000296000B000001072303330721373313233702000A6F +806F1BFEE0046F806F2102964AFDFE4A4A02024A000000010016FFF201D6029600110000 +01030E0123222737331E0133323E02371301D6711B645B4C2926110E2F171926190C0872 +0296FE3E6B77384C17211933242001C800000001004A000002D90296000C000001070901 +23030703231333030102D905FE86011C77F12A4357A55748017C029611FEE0FE9B012E21 +FEF30296FEE1011F00000001004A00000234029600050000250721133303023424FE3AA5 +57934A4A0296FDB400000001004B000003A50296000C00000103231301230B0123133313 +0103A5A55587FE7E15758655A57B6801570296FD6A021FFDE1021DFDE30296FE1E01E200 +00000001004AFFF202FE0296000900000103270B012313331B0102FEA941E78C57A579C6 +790296FD5C0E0234FDCC0296FE1C01E4000000020063FFF2030B02A4000D001D00000114 +0E0123222635343E0133321607342623220607061514163332363736030B7FC368748A7F +C269748A5A5F586EA41F0C5F586EA41F0C01AE7BD07185717CD070857A555E917C312955 +5E917C3200000002004A0000027E02960009001100001333321514062B01032313333236 +35342B01EFB8D7A27C784757B05869689D53029685668EFEE3016751445000020063FF51 +030B02A4001E002E00000507222E06272E0135343E0133321615140E02071E0333133426 +23220607061514163332363736028B04272845273823271C0A5A677FC269748A416A8847 +0B27382C23675F586EA41F0C5F586EA41F0C9D120103090F1923301F1180607CCF708571 +559D714B0A1F261105020A555E917C3129555E917C320002004A0000027F0296000C0014 +0000133332151406071323032303231333323534262B01EFB9D78263AB67AD464A57B359 +CF4C5353029682587E0FFED1012BFED501758D2723000001003EFFF2025502A400240000 +0107232E012322061514171E0115140623222737331E01333236353426272E0135343633 +3202551E1309503A3351704F45A56E912F15120E61394A5C2F34524B95617C0254773C3F +462A3B33234A326881548749464735213117254D335E7E000000000100A1000002EC0296 +00070000010723032313233702EC09F5935792F51D02964AFDB4024C4A0000010075FFF2 +02F502960016000001030E012322353437133303061514163332363736371302F56920A5 +81D10C6957660E4A42375E1D1918620296FE5D7E83A8283101A3FE6A371C343B2B241F61 +0189000100C4FFF50314029600060000090123033313010314FE3D1A735F4D01440296FD +5F02A1FE0E01F2000000000100C2FFF504210296000D0000090123030123033313350113 +35010421FE741C43FEE41C3C5F22012D4101110296FD5F01BEFE4202A1FE240201DAFE24 +0201DA0000000001001F000003260296000B00000901132303012301033317370326FEC3 +B46991FEE5690160A26A7DF70296FEC7FEA3011BFEE5015D0139F8F80000000100BA0000 +03060296000800000901032313033313010306FEA84857479C5F7C01120296FE8AFEE001 +200176FED4012C0000000001001C000002FB0296000900000107012107213701213702FB +06FDC701B11AFDC9060236FE781B029615FDC94A1902334A000000020037FFF601D301CF +001D0024000013273E0133321615140F0106152337062322263534253736353426232207 +17370615143332A9291E6F413F46053F085505485A2C320140080227204C369617DA3044 +012E1F443E35300E16FA1D2F2C362725A83C210A05171B5AAA5A235B23000002004AFFF6 +021702AC000D001900000103363332161514062322271337030716333236353426232206 +01374243494650BE786730994C56341F3448843331244902A7FEF731514583C039026518 +FE82D0217E6530382F0000010043FFF601F701CF00170000010723262322061514163332 +37170623222635343633321601F739100E514079392E514A38608B4A58B86A374F017336 +4B7A65333969228E544C82B72E0000020048FFF5025802AC0012001E00000103060F0127 +37062322263534363332173F0103372623220615141633323602588A10064E080A455141 +4DB868372B3A4CC92B1B3D417E352C234C02A7FDD942311805323651497DC225EA18FDF2 +AB3F835D35363500000000020045FFF601E701CF00180020000025170E0123222635343E +0133321615140F0121061514163332273336353423220601933938694849555B80414046 +070EFECB07392E4F90D70450284EA7234C42534B609447453C1D1C371C1A3338E8101142 +3200000100650000020E02AB00180000010F012E012322060F0133072303231323373337 +3637363332020E2611181B131F330D0E7211725F4F5F5111510B1C3832364402793B0116 +1132353847FE82017E472965302800><0003FFF9FF28023F01CF00290034003F00000107 +23161514062322270615141716171615140E022322263534363726353436372635343633 +321633073423220615141633323603230E0115143332363534023F104E0484511F1C3628 +902B3D3B5E6734434F362E1727321C89591D5A1F674731482A253140CF142E3759517501 +B443140A5376081C1B1A01050E1438314B29142C2C294B16121B1C342B212D507F1B6B3F +492C1E2449FED70D2C1A2F2D302500000001003F000001FE02AC0017000001033E013332 +1615140703231336353423220607032313370137452C492E313806504F4B05411D59194B +4FA44C02A7FEEE2317322F1518FEBF0130160C363228FED2029418000002004500000145 +02A70003000800000107233717032313370145175C172172536D5002A75D5DDCFE3501B8 +180000000002FF8AFF28016202A70003001200000107233717030E012322273733163332 +3713370162175D17217918644332322B1024163F19784F02A75D5DDCFE1B5D6124401C66 +01E119000001003F0000022C02AC000C000001070517232707231337170337022C03FEDC +BF679C334FA44C085EEB01C50EB8FFCFCF02941805FE88960001003D0000013902AC0004 +000001032313370139A953A45002A7FD59029418000100410000030701D0002400001307 +3633321736333216151407032313363534232206070323133635342322060703231337FD +0E405A581B4A54313C064E4D49063D1B5A174B4D47073B1B5B184B4D6D4701CB393D4141 +34321519FEC501251812393327FED20122200D393228FED201B818000001003F000001FE +01D00017000013073E013332161514070323133635342322060703231337FB0C2D4C2D2F +3A06504F4B05411D59194B4F6D4701CB3924193328181BFEBF0130160D353228FED201B8 +180000000002004CFFF6021801CF000D001B000001140E02232226353436333216073426 +23220615141633323E020218294871414F5AA973525E513B354B6F3932304E2B16012C31 +6C5D3C5E4C73BC565F343A8651353F344A4600000002000EFF28021A01D00010001C0000 +0107363332161514062322270F012713370F011633323635342623220601000B4552414D +B967382A364C08A2491E2B17414C73322F234C01CB323653497BC224DA1805028B18A9AC +3E8758303C35000000020048FF28022501D0000F001C0000010307273706232226353436 +3332173703372E012322061514163332360225A70A403A43514250B96742274A902B0C33 +1D417E322F235101CAFD630518EA34534A7BC12324FECEAB1E21835D303B36000001003F +000001B701D0001100000107232E012322060F01231337170736333201B712140318082B +58233A4F6D4C080E40502501BF460708544AEA01B8180540440000000001003DFFF601B0 +01CF002300000107232E0122061416171E0115140623222627373316333236353426272E +01353436333201B0160F11324E2722213C2F70572D361A190F125B223A20263731684D57 +019A592C1C2B281D101D2D2144641A1D675723200F1B131B35223E6200010060FFF60178 +0244001B00000107230706151433323733070623222635343713233F01333F0133070178 +11683D062624220C1E223B292B054650043A23104D122001C547F5180F251A4B1629240F +1601161136403F7F00010059FFF5021801C5001900000103060F0127370E012322263534 +37133303061514333236371302185110064D080E255C272F3A06504F4B05411C5D194801 +C5FEBB42311805412025332819190142FED01310353B2901240000000001008FFFF2022B +01C50006000009012303331B01022BFEC9164F5530C201C5FE2D01D3FED5012B0001008C +FFF2031301C5000F0000090123270723033313372E0127331B010313FEC81625B7154854 +2F81030F015430BD01C5FE2DF4F401D3FED0B0196106FEDA012600000001001E00000220 +01C5000B00000107172327072337273317370220C86C5E4F9B5EDC605E448601C5D9ECAF +AFEFD697970000000001FFEDFF28023501C50016000009010E0123222737331633323637 +3E0137260227331B010235FED3366140301412101A1C204A22020E030E3E06563BBD01C5 +FE0F59531848184D3D04170651013821FEBD0143000100190000020501C5000900000107 +0121072137012337020505FEAA01081BFE82040156E31D01C514FE964714016A47000000 +0001002FFFF500EB01B90019000037170E01232235343F01363534262735363717030615 +14333236DD0E293C253218300916292C73056009100C24730C3F3339145AAF220B0F0801 +10031603FEAA220B0F2300000001FF84FF3100F601B9001E000013030E01232226353436 +333215140615143332363713363534262B01353637F6681F5B41232C1811270C121E291A +4810151B1A2D7C01B6FE667972221B121A250C0F070C4E680124410F110E100316000000 +00020023000002AD029B001B001F000029013533323635342F0123070615163B01152335 +3637013313163B012F01230702ADFEE3102820020DC6540A02291DDA43290164124E0A40 +10DB19068B101D20070E718C0B0F1D101008420241FDBB46E8E3E30000030026000002AE +028D001A0025003000001333321514070607171E0115140623213733323637133635342B +0117071633323635342623220B01163332363534262322C6FCEC5B1C2E03344B9091FEBC +0414342C077B024E13D53B1E17525646311758442027506E503E24028D91662D0E0C0409 +463B526F10211E01EF0A0B2A1AFC06584B342EFED4FEF509525A343800010026000002D1 +028D001B00000107233635262B01030615143B0107213733323637133635342B013702D1 +27110501719285044F2004FEAA0412352D077B044C1304028DAF2D0558FDE61206261010 +201E01F11008270F0002001E0000022C029B000300070000290101330B012303022CFDF2 +019C13202D06F1029BFDB70189FE770000010026000002DE028D002D0000010723363526 +2B010733323637330723343635342B01030615143B013236373307213733323713363534 +262B013702DE2A10020469A03C50333C0F103A1106485A42023F43736C221042FDC50413 +590F7805202B1304028D9E1A0957F82639F202300A33FEFE08021B4858C6103E01E8160E +131010000001003C00000322028D00130000090133323E02373307210123220E02072337 +0322FDCADA2A383F2B0D102EFDBB023DCA2C37422C0E1130028DFD99081A3C2EB2026806 +1634299E000100260000039B028D00360000010723220607030615143B01072137333236 +3F0121070615143B01072137333237133635342B013721072322060F0121373635342B01 +37039B0412342E087B044E1304FEB70412332F073DFEE83D044D1404FEB604125B0F7B04 +4E1404014A0412362C0734011635044C1404028D10211EFE10120A221010201EF2F20C0C +2610103E01F00C0C271010201FD9D9140427100000030032FFF502C8029D001600280039 +00000133061D01232E012B0122060723363D01331E013B013237140E0123222635343E03 +33321E0207342623220E0315141633323E020208101E1001181A751C1E10101E1006161C +7630D465C174708C244861854A4263391C74454942693C270D4E3D4B743F2001A94D561B +1F1A18214B650E22171B60B97D82673D7E745A362E4F5F0555674565826C30484D568588 +0001002600000212028D00190000010723220607030615143B0107213733323637133635 +342B013702120413352D087B034E1104FEB80413342E077B044D1204028D10201EFE0F0C +11211010211D01F1100826100001002600000322028D00390000010722060705131E0133 +07213733323635342E022F0123070615143B01072137333237133635342B013721072322 +060F01332536352E012337032204233025FEF1BB1A392C04FEE1040B1817030407029C04 +3E034E1104FED004124410750A4C12040138040B3326083505011A1201141E04028D1018 +1DD3FEEA262910100D14060A050B03EDF30C0A2810103E01D8270A2610101F1FD1DD0E0F +0B0A100000010023000002AE029B001B000029013733323534270323030615143B011523 +3536370133131E013B0102AEFEE1020F46012C05F50A2B1DDA43290165114E0624201110 +3D0E07017AFE6B0F0B1D101008420241FDC72E24000100260000041F028D003000000107 +23220607030615143B0107213733323637132301230323030615143B0107213733323637 +133635342B013733133301041F0411372A087F024D1304FEB60411362C077006FEA7195E +036D054C1404FEF50413362D0977054D1304D55904014D028D101E1FFE0E071522101021 +1D01B7FDFB0205FE541612211010262301DB15112310FE1301ED00000001002600000385 +028D0024000001072322060703230123030615143B0107213733323713262B0137331333 +133635342B013703850412352F079018FEEE066C034C1304FEF604125B0F7E16461304C8 +EC0561044C1404028D10201FFDC2020EFE400C0F2310103E01FD3210FE2F018212082510 +00030034000002E5028D000F0026003500000107233635342E0223212206072337050723 +3734262B01220607233733061514163B0132363713072137331D0114163321323E013702 +E523100309171214FED5282D0B10250168340F021611B91B2B0910341003201F9132220B +2428FDE7280D21200153221D1D0C028D980D1410120702232998CBD11A1217281BD10F0A +18111C26FEDC9E9E0E1518110621250000020032FFF502C8029D000F0020000001140E02 +23222635343E0233321607342623220E03151416323E0302C83A669E5B708D3B679F5C76 +8374454842683D270E4F7A643F2B1201A44B987D4F82674D9D8352934F56674465826C30 +46503C5B73690000000100260000039B028D002A000001072322060703061514163B0107 +213733323637132103061514163B0107213733323713363534262B0137039B0413352C08 +7B03212C1404FEB50412362B0984FEEA850323281304FEB704125B0F7A03222A1404028D +101F1EFE0E0B10121110101B23021AFDE60F0B160E10103E01F20C0C1312100000020026 +000002C0028D001E00290000132132161514062322272227070615143B01072137333236 +37133635342B011703163332363534262322C9010F75738F8E2C1E040838034D2004FEAB +0411352D087D044E13D644201C4E683B4218028D604052750502DF0C0B271010201E01F1 +10092518FEEE08604C323F0000030032FFF502C8029D0011001C0025000001140E012322 +2635343E0333321E0205213635342623220E0205210615141633323602C865C174708C24 +4861854A4263391CFDFB018B06454936593D29016EFE75094E3D6785018B60B97D82673D +7E745A362E4F5F51282455672C4F5881393B484D980000000001003A000002F8028D0016 +00000107233635342E032B01170121323637330721010302F82810031015271D18CDA1FE +C3013B394B1B1053FDCA017AD2028D9F07161A25120A02FFFEE93439BF01470146000000 +000100190000029E028D001D00000107233635342E012B01030615143B01072137333236 +3713232206072337029E2E100523251B6285054C1504FEB70412362D07855B3E5F0E102E +028DB32A1D1F2107FDE61603251010201E021A513DB300000001001C000002AF029C0027 +00000115220F020615143B010721373332363F01272E0123373633321E031F0133373E02 +333202AF3A44D439044D1504FEB80411352C073D4E1738370435201F301B1809094005B0 +1B1E341C1A02951051FBEB0E0C241010201EF7D03D3510050F11271719B9CF211E1D0000 +00030019000002EB028D002A0031003900000107321615140E032B01070615143B010721 +3732363723222635343E02333635342B013721072322070332363534260113220E011514 +1601FC096692304A5D5927130702491A04FEB60342320D0D717A426C7B400A4B13040141 +041052235F5C8D45FEEC5D3F6E3B4602421F5858385E3E2C141F0A052110101E315D414C +7543221D162710107FFE8780713A4EFE8701795276383445000100190000032A028D003A +0000010723220E010F01131E013B0107213733323635342F01070615143B01072337363F +01272E032B0137210723220615141F01373635262B0137032A040421331C17BB600B2222 +0E04FEE7040B22210445B50C381604F9044E3ED055060917261A0904011B04131A1A063C +A40F022B0904028D1016191BD5FEF62024101010190D0DC2C90D0F2010100A43E6E71212 +200F10100E150C10B3B81210181000000001001C000002E7029B00480000010706070607 +06070E0123070615143332370721373237363F01222E0135343635342737333216151406 +151433373635342E042223372107220E0107033236373E0237363302E704261105061213 +2B8C692B084D100504FEB80442121E0C2A32574222260412313F297C3611070F0E160D14 +03040141041D272A06444E4D1A030D0B082F67029B10082B0D1C5626594CB525061F0110 +10080E33B51844331F7A1F32041042311F721F45DA4706080C070502011010051A19FEEE +4B510A30230F60000001002000000309029A003100002537263534373637363332171615 +1407060F0133363736371707213736373635342322070615141716170721371706151433 +011805932924376BA67E3C30544C7A078038171F121238FEE8285E3D338C6C5461171726 +18FEF12A11043E5E25309B45463B2D59493B6273574F1825060D132F05AEA6155E4F5CB5 +576573412C2B0CA6B30511132C0000000002002AFFF10258028D00030006000009012303 +0521130258FE701A8401C9FEAA57028DFD64029C5EFE500000020028FFF6021101B9001D +002E00000107151416333237170E012322262723062322263534373E0133321615370734 +2E0223220615141633323E01373602118B1111141A0F0E31201A2602023B5E3D4407167D +4E373B4080030C1A152E5A14221D3E240C0501ACF125283B4A073A463423575E43251D5C +844F397B9B1F282D16AD713435484A241900000000020019FF490206029C001B003C0000 +17133E0137363332161514060715161514060706232227070607233613031633323E0135 +3423220623222635343633321633323635342623220E032F7419303C403B2A39372D3825 +1A4B75261A1C080F4F08E966161B365525150A17090D1317180321081D1F1F1E16231614 +0B6901DF675434374032396614010F57306B2160157A2521110271FE5D17657C353F080D +090A11055C20283C121A2E2600010023FF4501CA01B9001B000009010607062322263534 +3E013736353423220723363332151406071301CAFEF60808183B11131D360C0D3A1D1B10 +273F420D01B301ACFE8D5729741913243F4D185C5A8A4A8A802586080126000000020028 +FFF501C3029C002500310000011406222E02220615141E031514070E0123222635343637 +2E0335343E0133321603342623220615141633323601C3182C14021D3E3B2E42422E121D +7951464C8A650C2E19153A49242B42641B1B3D76271F3F640260121C141914281B1A3631 +354827422E4660523F5DA20A0C271825132D421B1EFE8F273DB1482B3BAC000000010019 +FFF501BC01B9002E000001072E0123220615141633323633321514232226232206151416 +33323637170E01232226353436372635343633321601BC0E12382E33582C220A270D262B +032E0C2D5848352A55180D23624D565040343A7A672E500174081A154031181B09141F06 +37302A2427220A32363A2C35470D142E3F542A0000010028FF4901E0029C003A00000117 +0E011514173E013332151406070E0115143B013236333216151406232226353436333216 +3332363534232206232226353436373522263534360122042426311F7024207F4548725C +0606170C3F3D763F181D19170B2109243D52053E0937486D5B1B2E40029C100D2914230A +24461822490632BB5C5B012E353E69221412181E271D31094E406EB45101201B203A0000 +0001001EFF4901C701B9003000003733373E013332151407030615141723263534361336 +3534262322060F0123133635342322072736373E01333215140E01AD02392D4D293C0F4B +17054C0B2142080F0D1F93161F4E4D0D0D16260D1E10133110290B13FB493A3B360D41FE +C7612F190A0D211E8E01081B150D10C44B69013036050D320A2A10131C29133545000000 +00030028FFF501DA029C000E00160021000001140E0123222635343E0233321605333635 +342322061723061514333236373E0101DA528E4947422B4C78433947FEC5CE1933326CAF +CF193A2D551C040B01D862E1A0585B44A89D6B64E0753181C2894E577D8B5B0D2B000000 +00010032FFF500E301B9001B0000130306151433323E0437170623223534133635342623 +220727E35C010D0609070A0811070D5234284F020E110E0E0301B9FE9406080B03030B08 +14070A692E1B013304080D0904100000000100320000022501B9002500003F013E013332 +15140623222623220F01171E01170723373235342F0107231335342322072737C1B72C34 +1E2F15150F1C111A1A8F7011243001EB013A0D5A394E5E200C1002A1FB8821152910161B +1571BB1D1205101011101199DB01730914030F1B00010032FFF001FF029C001E00002533 +0623222635343E0137032301363534272623220723363332151114333201EF1026421620 +050803DA65013F02010731191C1025433726197C8C36291B3C5C27FED701AF100F230B60 +4A8A88FE816500000001001EFF49022501AC002700000103061514333237170E01232235 +34370E0123222627060706072336371333030615143332363F0102255608111E1B0F0C55 +2229214757240D1804270C06094A1A137D4F4704362590101001ACFEAE20070E30062248 +2925866B540F0A9B180D0A254D01F1FED612092DE645470000010032FFF701CE01BE000E +000001170E01072303262B01353637133601BC120E8B74153E07251039473C8801BE0586 +ED4F01712A100215FEA2670000010019FF4901BB029C004C000001170E02151416333633 +3215140623220614173633321615140E012322270E011514163332363332161514062322 +263534363332163332363534232206232635343637263736372E01353436012504202507 +101058382E633925311F4646181F323217251F4258303D0C20053F3D773E181D19170B21 +09243D520A3709935E4C1E010336151D3C029C10092114080C1343221F283238182E100D +181E080919653C2C22012F353E69221412181E271D3109027452842A1A2B3326051E1022 +3200000000020028FFF501B601B9000B00170000011406232226343E0133321607342623 +2206151433323E0101B6996E45423E77464053511F243D6C3B31562A011F6EBC6282845C +4D2E2439B7666B788800000000010023FFF3023801AC002F00000107230E031514163332 +37330E012322263534373E0137230E030706232226353437363F01220E01072336330238 +1770031A141216102C1D10114E311C1D0909370E5E0C291C331D0D19141F1F3119592423 +2F1510458F01AC49084A3A4B17121E423B532B20221C1B972F2081535B160B1A1228080E +38CE0418197E00000002001EFF4901D801B9000E001B00001713363736333215140E0123 +22270713071633323E013534262322061E5432743A285E41784525182C632E121C30542C +1F142944B70150CA391D9B41896012BD01A5C81561823A2B284E000000010023FF4901D0 +01EA002E0000011423222623220E01151416333236333215140623222635343633321633 +3236353407220623222635343E0133321601D03014181337775623380D1E037E6F46181D +19170B2109233E520A3A122F4669A04C203801B62B253E74433235015C4A662414121820 +2A1D310109504550A4681A0000020028FFF5022301AC000F001D00000107231E02151406 +2322263534363307230E0115141E01333236353426022314AD092B20966A47479D670E02 +396A091F1945672B01AC49192A2C205689613F7A9D49058362142A288C432C430001001E +FFFB01D801AC0020000001072306070615141633323637330E01232235343E023F012322 +0607233E013301D8169B1B21071014132D0D101253304005060B01423327481910197442 +01AC49427B19151A17281B3C53430C1C121F04C831273A670001001EFFF501BD01B70025 +000001371615140623222635343F0136353423220607273E013332161514070615143332 +3E0135340125029693673E4C1329090F0D191E0D234A1F1816440D3F375A2A01A90E1890 +65B53A2E1B438D1D0A0E13250933360F1513DD29214761793578000000020032FF490277 +01B9001A002400003F011233321615140E012307233722263534363B01070E0115141637 +073236353426232206F7224BAA2F3A49925C28532B536BA2751905568142B43549931814 +2448138201214D4E488559ACAC594D70AE10099F623B51DBDBAE6037265300000001001E +FF36028501B9002000000901141633323733062322353437072301353426232207233E01 +333215140F01370285FEDE12171D191022443707B9650128181C211810102E28380104BF +01ACFEA9835C438370225BDA015D4E463F423B478A150A48E40000000001001EFF4902C7 +01B90032000001150E05070E012307233723222635343635342737363332161514061514 +16171333033E01373E073302C717261C1312080716995D2A512B0A4257203D051324292F +1F2C276451643A5E120806100D181A242F1C01B7100314262138211E607DACAC4F561B79 +2350061002412E1C801D3742050199FE670B624E22173B172B13170900010014FFF50295 +01B900310000013732151406232226270623222E013534363B0117220615141633323637 +2635343E01333216151407141633323E0135342601D106BEA0552C39034951304119A783 +10035D8E2420294B0B03112F210E134E212529502C3701A910A178AB3227593645267BA8 +10BB7E25384425161D2950411C1352712B39667E2D45390000020028FFF501D7029C0019 +00260000132736333211140E032322263534363332173635342623221334262322061514 +3332363736C30A263CBC102A406A434543976A451B073D46218325184970433657170F02 +711417FEFC3168745B3B5C466DAF30271F4D68FEC12C2AB56B6276543700000000010028 +FFF501AE01B900210000010723262322060733072306151416333237170E012322263534 +3E0133321633323701AE130F104D385514B505B907322A4A460C2E5B394349447F4A1B2D +0B0E0901B98469674024142E2F36440E3439564248895B141400000000020014FFF601FB +02A6002B00360000372736333215140615143332372E0135343633321615140716170726 +2706232235343E03373635342322253423220E011514161736210D483B2C25396B3D5878 +61504E4D2F280505141D5798860307050B02080C1501564F1F290D473F1ECD096A2D1E77 +254ED4268F585169735281800D01130107D1680C171C0F2206180F0EDBA1363E1F4B8126 +7B0000000001000CFFF3024B01B9002B0000010706070615143332151407062322272635 +3437270723373637363534232227263534373633321514071725024B9C16141531331110 +1A2518163001FC6D941013141A1B0B11150E12442D01010C01AC912432361E2926140E0D +1A17263C5E01E5881A4042221C060B19160D0A4D486D01F600030028FF49026C029C0015 +001C002300000107321615140706230723372226353437363B0136371303323635342603 +13220615141601E037566D50558D30512E556A54537A1020151461568038EF635D7A4202 +9CE4495276565CACAC58536E56547F65FEFFFE78AC593F45FE7701889E5F335800020028 +FF4501E901B90021002F000017273635342E0227263534373E0132161514062322263523 +0615141E03151413342623220615141633323E02DD0E041E27320F255229758849937935 +4202061D29291DAF1F21407D23232C4A2A17BB0707090D17111F112B4F6C7D3E57544673 +B63D2D1616222F1A1622181701E22236B7662745486662000002001EFFF5036201AC001A +003400000107231615140E022322262706232226353437232207233E0133052106151416 +333236372635343332161514071433323E01353403621650122F4956272C390347534644 +5A324C3B1016744501C5FE8D602420294C0A04451216384629502C01AC4922313D6D472A +302656613D7B55573F61495D99253541251E15951D1B445161627A2D2A0000000001FF73 +FF3101F802A90066000013333E0137363332171E01151406232227263534373635342322 +070E0107333237323E01331715140F010E010706070E02150E0123222635343633321514 +06151433323637133635342B010306070623222635343633321615140706151433323736 +3736372333581B2628384E371E11181D14280A070904223620191C169628290408040205 +0501020C0101061223131F5B41232C1811270C121E291A530B109B482B4A272D222C160F +101705041425181C1E2D165801AC5C4A24331C041F13151E250A100E090504102A204756 +06010103030712040A27040515438D52047972221B121A250C0F070C4E6801552E020AFE +C2BB4121211B121A1810070907040D2F34ACFD37000100000001000035A55AAF5F0F3CF5 +000B03E800000000C836D18900000000C836D189FC36FECF059503FF0002000800020001 +0000000000010000041FFE39000005C7FC36FE1605950064001D00000000000000000000 +0000042800FA000000000000014D000000FA0000014D002701A4009001F5000201F40020 +02F30050030A004C00D60084014D002A014D001001F4008002A3005600FAFFFB014D0031 +00FA001B0116FFBF01F4002001F4003201F4000C01F4001001F4000101F4000F01F4001E +01F4004B01F4001E01F40017014D0032014D001A02A3005402A3005602A3005401F40084 +039800760263FFCD0263FFF8029B004202D2FFF80263FFFF0263000802D2003402D2FFF8 +014DFFF801BCFFFA029B0007022CFFF80341FFEE029BFFEC02D2003C0263000002D2003B +0263FFF301F40011022C003B02D200660263004C034100470263FFE3022C004E022CFFFA +018500150116FFD70185000C01A6000001F40000014D007801F5001101F4001701BC001E +01F4000F01BC001F0116FF6D01F4000801F40013011600310116FF8401BC000E01160029 +02D2000C01F4000E01F4001B01F8FFB501F400190185002D018500100116002601F4002A +01BC0014029B000F01BCFFE501BCFFE80185FFFE01900033011300690190FFF9021D0028 +00FA00000185003B01F4004D01F4000A01F4FFEA01F4001C0113006901F40035014D006B +02F800290114002A01F4003502A30056014D003102F80029014D00630190006502A30056 +012C0021012C002B014D00B401F4FFE2022F003C00FA0046014DFFE2012C002B01360043 +01F4003702EE002102EE002202EE001701F4001C0263FFCD0263FFCD0263FFCD0263FFCD +0263FFCD0263FFCD0379FFE5029B00420263FFFF0263FFFF0263FFFF0263FFFF014DFFF8 +014DFFF8014DFFF8014DFFF802D2FFF8029BFFEC02D2003C02D2003C02D2003C02D2003C +02D2003C02A3005D02D2003C02D2006602D2006602D2006602D20066022C004E02630000 +01F4FF5801F5001101F5001101F5001101F5001101F5001101F50011029B001701BC001A +01BC001F01BC001F01BC001F01BC001F0116002F0116002F0116002F0116002F01F4001B +01F4000E01F4001B01F4001B01F4001B01F4001B01F4001B02A3005601F4001C01F4002A +01F4002A01F4002A01F4002A01BCFFE801F4FFB501BCFFE80263FFCD01F500110263FFCD +01F500110263FFCD01F50011029B004201BC001E029B004201BC001E029B004201BC001E +029B004201BC001E02D2FFF80261000F02D2FFF801F4000F0263FFFF01BC001F0263FFFF +01BC001F0263FFFF01BC001F0263FFFF01BC001F0263FFFF01BC001F02D2003401F40008 +02D2003401F4000802D2003401F4000802D2003401F4000802D2FFF801F4001302D2FFF8 +01F40013014DFFF80116001E014DFFF80116001D014DFFF80116002E014DFFF801160031 +014DFFF80116002F02EEFFF801F4003101BCFFFA0116FF84029B000701BC000E021E0005 +022CFFF801160029022CFFF801160007022CFFF801160029022CFFF801430029022CFFF8 +01160025029BFFEC01F4000E029BFFEC01F4000E029BFFEC01F4000E0241003A02D2FFF8 +01F4000E02D2003C01F4001B02D2003C01F4001B02D2003C01F4001B03B00031029B0014 +0263FFF30185002D0263FFF30185FFFE0263FFF30185002D01F400110185001001F40011 +0185001001F400110185001001F4001101850010022C003B0116FFDA022C003B01160026 +022C003B0116001C02D2006601F4002A02D2006601F4002A02D2006601F4002A02D20066 +01F4002A02D2006601F4002A02D2006601F4002A03410047029B000F022C004E01BCFFE8 +022C004E022CFFFA0185FFFE022CFFFA0185FFFE022CFFFA0185FFFE017F000D01F40017 +01F4001E029B004201D8FFC202A0001301F4000E0116002901EA001E01F4000E02D2003C +0216001B01F8FFB50154001F0116FFCA0136002602F20066023D002A01F4000801F4000C +01F4002F00AA000F0122000F0154000F014D00270116FF840263FFCD01F500110379FFE5 +029B001702D2003C01F4001C0116FF8401BC001301FF001101FF001101F4001701BC001E +01BCFFFD01F4000F01F4000F01BC001F01BC001F027F001F01BC001F01E0001F029A001F +01EA001E0165FF9C02CA0008025300080232003401BC000F01E0000401F4001301F40013 +01F4FFFA01160010014D003300F7FFF8011600040177000C00FC0008023F002902D2000C +02D2000C02B2000C025EFF9201F2000E021BFFEC01F4001B02CE0031029C001E0294001E +0192FFD3017FFFD30161FFD3014DFFEC018600180191002D0152004201DB001901DB0019 +01850009019FFF9201C5FF920153004F01B7FFC2014A00260116000601F4000902190031 +01F4003401BC0014029B000F01BC000A0279003E0195001101890011019D001501C90007 +01F4003701F400370189FFE701C2001802D3001601CC001301DF0014023A001D023C0019 +0193FFB001CF001A01D6001901E0001901F4003701F4003702E7000F02E7000F02F2000F +01F400260205FFE002780026017A0007017A0007012C002C014000020140000001400000 +0186000601F4000F014A0010014D004F0146001E01460017014D005B014D0079014D0075 +014D00CF014D009B014DFFEC014D0064014D005D013B001700DC0029012C0010017C0004 +013E00080140000F0195000A012100A0014D00460263FFCD014D00960276000702E40004 +015E000302D2003A0244000802FAFFFA011600310263FFCD0263FFF8026300080263FFE0 +0263FFFF022CFFFA02D2FFF802D2003C014DFFF8029B00070263FFCD0341FFEE029BFFEC +028BFFFA02D2003C02D2FFF802630000026CFFFA022C003B022C004E02E500320263FFE3 +02A3004D02FAFFFA014DFFF8022C004E0228001B01BC001E01DA000E0116003101DE0013 +0228001B01FAFFD8019A001301CC001801BC001E01C6001E01DA000E01E0001B01160031 +01BC000E01CAFFF4020EFFDF01D6001401C6001E01F4001B01F8001301F8FFD801C6001E +01F2001B019A000C01DE0013026E001B01C9FF940248000F02AE001B0116003101DE0013 +01F4001B01DE001302AE001B01C8002D022C00130254004E0273001B0318001102D2003C +01F4001B02A1003701BC001E022D000801E700200285001301C9001F02C400070210005D +0215FFF00204001B02D2003C01BC001E01BC00180263000102D300460239FFDC02910043 +01F40007014DFFF9014DFFE101BCFFDE03C1FFDD03C6FFE403120046026DFFE40290006E +02D2FFE70263FFCF024EFFE40255FFE90239FFDC028FFF990263000103BCFFC902340009 +02C4FFE702C4FFE7026DFFE402BBFFDD032EFFDF02C4FFE602C8003C02C0FFE30238FFE8 +029B0043022C00460290006E03040049023FFFBD02C2FFE7026E003603A8FFF203A8FFF2 +02B7003F0354FFE40255FFE40292000F036DFFE0027BFFCF0202001701F2002401BA001F +0186000101E9001E01B80022031F00000178FFEE020F001D020F001D01EB001201DAFFD4 +0279FFD301F8001401E9001D01FF001301E3FFB301B9001B02E5001101A5FFC302BE001D +01BCFFDD020F001D01E2002A0311001F0311001F0237000C02B1003201D7003201980007 +02A2001501E1FFE701B8002201DF00140186000101AC001A0185FFF70116002B0116002B +0116FF5402A7FFD402B9001501FF001401EB001201A5FFC3020F001D02A90013021E000D +03B9FFC902E5000002C8003C01E9001D0286004C01D00022020CFFE20151002A03410047 +029B000F03410047029B000F03410047029B000F022C004E01BCFFE8014D0031014D0031 +01F4FFF801F4FFFA0379FFFA014D00AB014D0097014D002C014D00A9022C00A6022C0097 +022C0039022C00A901F4006501F40016020B004603790039045D005005C70050014D0033 +014D003401F4000000A7FF570263000801F4000A047D000001F400100000FE3B0000FE56 +0000FED40000FE3B0000FE3B0000FE3B0000FDBA0000FD2D0000FE3B0000FC360000FE52 +0000FEA10000FDAD0000FE320000FE220000FCE70000FE3B0000FE3B0000FE3B0000FE3B +02BE00230357002F02E2001E03E5003502DC00110201002D0381001A03B2002102430030 +02D700190416001302AF001102D3002303B0002202AF001103D4001E02F2000703B60022 +0273001E02EE00640397002B0430002602B9001E027B002802EE001E02C9001102450028 +020300280125001B0155FF9801D7002802A3005601F4002802AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC000002AC0000 +02AC000002AC000002AC000002AC000002AC000002C4FFF502C4013D02C4013D02C4FFF5 +02C4013D02C4FFF502C4013D02C4FFF502C4FFF502C4FFF502C4FFF502C4FFF502C400E1 +02C4013D02C400E102C400E102C4FFF502C4FFF502C4FFF502C4013D02C400E102C400E1 +02C4FFF502C4FFF502C4FFF502C4013D02C400E102C400E102C4FFF502C4FFF502C4FFF5 +02C4FFF502C4FFF502C4FFF502C4FFF502C4FFF502C4FFF502C4FFF502C4FFF502C4FFF5 +020FFF6D01F4FF7301F4FF7302E8FF6D02E9FF6D02CD002302B80026029F003203160026 +02CA0026026A002602DE00320369002601E00026021C003C02FA002602C4002603ED0026 +0353002602DC003202520026030D003202E40026028A00320226001902C10041023F003C +0394003C03160019021700230304003C01F6002801D6002D019F00280214002801BD0028 +022B002801EC0014013700320185FFF0021E002D013E002D02C6001E01F1001E01CA0028 +01E9FFE201CA00280198001E01B800320139002801DA001E01FA0048030700480226001E +01F0001E01F3002A0357001F031D002503750024030500530322000903F1002803CA0026 +02B40052038E002602B4005202E700430390002B034A002403A4002304360023037B0024 +039E005B03A4003B0333001E0244002F0296001E034D001E02AD001B02F1002601F00053 +02DA000902D6002802430030040E003102F9003103050017030C001E024400300203003E +0227001E02F1001E026A001E0378001E02F000410292001E02B30034029A001F025C004A +029F006002B4004A0247004A0217004A02B700610292004A0191003B018E0016027A004A +022F004A034B004B02A3004A02CA0063020D004A02CC0063024D004A021D003E026000A1 +02950075028E00C4039900C202BC001F027600BA027D001C01C0003701F0004A01C80043 +01EE004801BC00450150006501F0FFF901E7003F00DC004500FEFF8A01C5003F00CD003D +02F4004101E7003F01F3004C01F2000E01F200480150003F0185003D0123006001EB0059 +01DA008F02BE008C01E2001E01E4FFED01BF00190116002F0116FF8402CD002302B80026 +026800260254001E02CA00260304003C0369002602E1003201E0002602FA002602CE0023 +03ED00260353002602C2003402DC0032036900260252002602E1003202DF003A02260019 +0237001C0304001903160019029E001C032000200273002A020C002801ED001901AC0023 +01CF002801E4001901B3002801CC001E01E40028010B003202160032021D00320243001E +01C4003201B1001901CA0028022E002301F6001E01B700230219002801BA001E01CC001E +029A00320253001E0295001E02A9001401D7002801AE0028022A00140231000C02850028 +01FD00280358001E01F4FF73000000000000000000000000000000000000000000000050 +0000009C00000100000001AC0000027C000003440000036C000003A8000003EC000004CC +000004F400000528000005440000057000000590000005EC00000648000006A800000728 +0000076C000007C80000083C000008740000090800000988000009D000000A2400000A4C +00000A7400000A9C00000B1C00000BC400000C2800000CC000000D2800000D9C00000E30 +00000EC400000F5800000FF80000104C000010A80000114C000011B000001238000012A8 +000013100000138800001430000014BC00001558000015B40000163C0000169400001728 +000017C00000183C00001890000018C8000018E40000191C00001944000019600000198C +00001A1800001A9400001AFC00001B9C00001C0000001C8400001D4400001DCC00001E3C +00001EBC00001F4000001F9400002050000020E400002148000021D800002258000022B8 +000023300000239C0000241C000024A000002544000025FC000026900000270400002778 +00002790000027FC0000284C0000284C0000289C0000293C000029E400002A6000002B08 +00002B3000002C1000002C5C00002CF400002D7C00002E0400002E2400002E4000002EF0 +00002F0C00002F5400002F8C00002FE80000305C00003084000031080000319C000031C8 +0000321400003264000032B40000333C000033D40000348C00003544000035C80000364C +000036CC00003748000037E40000388800003928000039F000003A9400003B4400003BF0 +00003C9800003D6C00003DDC00003E4800003EB000003F4000003FC80000406C000040F0 +00004170000041EC000042880000432C00004360000043F40000449800004538000045D4 +0000469800004730000047B40000488000004928000049CC00004A6C00004B2C00004BF8 +00004CBC00004D7C00004E1800004E9800004F1400004F8C00005030000050A400005114 +00005180000052140000529C00005364000053E00000545C000054D40000556C0000560C +00005664000056F80000579400005830000058C40000598400005A3400005AD000005BA4 +00005C1C00005CB800005D4400005DF400005E8000005F3000005FB000006030000060AC +00006128000061AC00006234000062B00000632C000063B80000648000006508000065B8 +0000665C000066D0000067840000680C000068C000006944000069FC00006A8800006B30 +00006BAC00006C5800006D3000006DE800006ECC00006F80000070600000711C00007204 +000072B80000735400007410000074A800007530000075BC0000761C00007680000076F4 +0000776C000077E000007874000078E400007938000079D800007AB800007B2800007B9C +00007C6800007D1400007DEC00007E6C00007ED800007F6400007FE00000806C000080E8 +00008168000081DC00008254000082BC00008348000083F80000849400008550000085D8 +0000868400008740000087DC00008874000088EC0000895C000089E400008A6800008B08 +00008BA400008C6C00008D3400008DDC00008E5800008F0C00008F9400009038000090B0 +00009164000091F4000092A40000933000009404000094B400009564000095F000009684 +0000972C0000979C0000982C0000989C00009918000099D400009A8800009B2000009BB0 +00009C5800009CFC00009DBC00009E7400009F3400009FEC0000A09C0000A1400000A1EC +0000A2A40000A3340000A3E00000A49C0000A5080000A5980000A6080000A69C0000A704 +0000A7900000A7FC0000A8900000A9200000A9C00000AA540000AB080000AB940000ABFC +0000AC5C0000ACCC0000AD640000ADFC0000AE9C0000AF200000AFBC0000B03C0000B0EC +0000B1940000B2280000B29C0000B3080000B3280000B3540000B39C0000B3EC0000B460 +0000B5200000B6000000B6E40000B7BC0000B8640000B90C0000B96C0000BA100000BA8C +0000BB040000BB8C0000BBF40000BC940000BD400000BDE40000BE500000BEB40000BF5C +0000BFE00000C0640000C1200000C1A00000C2140000C2B80000C3480000C3D80000C488 +0000C5400000C5CC0000C6640000C6FC0000C7800000C7D80000C82C0000C8B80000C95C +0000C9B40000CA6C0000CB2C0000CBD80000CC980000CD580000CDEC0000CE600000CED0 +0000CF800000D0140000D0C00000D1240000D1A00000D21C0000D2800000D3080000D344 +0000D3880000D4040000D4800000D5200000D57C0000D5EC0000D6540000D6EC0000D754 +0000D7C40000D8640000D8D80000D9540000D9D80000DA7C0000DB100000DB840000DBF4 +0000DC680000DCE40000DD800000DDF00000DE680000DEE40000DF600000DFD80000E05C +0000E0D80000E1740000E2100000E2B40000E3340000E3940000E43C0000E4C00000E54C +0000E6140000E7100000E8080000E8D80000E98C0000EA6C0000EAF80000EBA00000EC1C +0000EC800000ECE40000ED600000EDDC0000EE740000EF040000EF500000EFA80000F004 +0000F0280000F04C0000F07C0000F0AC0000F0F40000F12C0000F1700000F1B80000F254 +0000F2A80000F3200000F3D00000F43C0000F4600000F4880000F4B40000F51C0000F5A0 +0000F5CC0000F67C0000F7380000F7A80000F82C0000F8D00000F97C0000FA280000FA8C +0000FB240000FB880000FBB40000FC480000FC9C0000FD3C0000FDD80000FE2C0000FED0 +0000FF280000FFB000010020000100C000010128000101A40001021C00010270000102CC +00010354000103F80001049000010544000105D00001066000010728000107C000010868 +000109140001098400010A4C00010AC800010B7000010BD800010C6C00010CF800010D98 +00010E2800010E9800010EE800010F6000010FC80001104C0001109C00011164000111C8 +00011248000112B80001133C00011398000113E800011458000114E000011560000115E4 +0001169C0001172C000117D800011858000118E4000119B800011A5C00011B0000011B94 +00011C2400011CD000011D5800011DC400011E4C00011ED000011F3C00011F6800011FE8 +0001201800012090000120EC00012168000121F400012268000122D00001233800012404 +000124B000012520000125AC000126480001269C0001272C0001278800012838000128F4 +0001299800012A8400012B4800012BEC00012C5000012CD000012D6000012DB800012E48 +00012ED800012FF40001309C0001313C00013220000132F00001338C00013414000134B4 +0001351C000135A80001362000013688000136E000013760000138000001389800013934 +000139CC00013A8C00013B5C00013BD000013C7C00013CE800013D6800013E1400013EA4 +00013F3400013FD400014054000140BC000141280001418C00014260000142F40001437C +0001443C000144E00001458C0001461800014688000146EC0001477C000148040001486C +0001492C000149BC00014AA000014B5000014C0400014C7C00014D3C00014E2800014EB0 +00014F5800014FC800015040000150CC0001517C00015220000152CC0001534C000153BC +00015434000154A40001553C000155B80001567C00015718000157B40001587400015940 +00015A0400015AA000015B4000015BE800015C8C00015D2800015DB800015E2000015E90 +00015EEC00015F4C00015FFC000160BC0001616800016224000162F4000163D40001646C +0001651C0001653800016554000165700001658C000165A8000165E00001661400016648 +00016684000166E0000167400001679C00016804000168A4000169C0000169EC00016A54 +00016B6C00016CC800016D1800016D6400016D8000016DA000016E2800016EE400017038 +000170C0000170E8000171100001712800017168000171A8000172040001727C000172CC +0001732C00017358000173740001739C000173BC000174180001743C0001749C000174C4 +000174EC0001752C00017568000175DC0001769C0001776C00017884000178D800017968 +00017A2400017B0C00017B8800017BD800017CBC00017D2800017DE400017EEC00017F78 +00018024000180680001818C00018208000182E4000183DC000184D400018560000185F8 +00018640000186A40001872C000187B0000187F400018858000188CC000188E80001890C +0001899C00018A3000018ADC00018B5000018BE400018C8400018CF000018DB400018E50 +00018EEC00018FA400019034000190D00001918C00019244000192FC000193C400019448 +000194E4000195AC00019640000196F8000197A00001982C000198D40001999000019A48 +00019B1C00019BAC00019C5C00019CEC00019DAC00019E7000019F1000019F940001A050 +0001A1000001A1900001A2540001A2EC0001A3A40001A48C0001A5480001A5F40001A6A0 +0001A7540001A7E00001A8CC0001A9840001AA040001AAC00001AB740001AC080001ACB0 +0001AD540001AE040001AEA80001AF740001B0500001B1140001B1BC0001B24C0001B268 +0001B2840001B2A80001B2C80001B2EC0001B30C0001B3340001B35C0001B3840001B3AC +0001B3E00001B4080001B4300001B45C0001B4880001B4BC0001B4E80001B5140001B548 +0001B5740001B5A00001B5D40001B6000001B62C0001B6600001B6900001B6C40001B708 +0001B7380001B7680001B7A80001B7DC0001B80C0001B84C0001B8800001B8B00001B8F4 +0001B9380001B97C0001B9D80001BAC80001BBBC0001BCA00001BE040001BF540001BFB8 +0001C0480001C0B40001C1200001C1A40001C2200001C2B80001C3580001C3AC0001C40C +0001C4B80001C51C0001C5B40001C6280001C68C0001C7080001C7A40001C8380001C8D4 +0001C9340001C9C40001CA1C0001CAA80001CB540001CBD00001CC180001CC9C0001CD10 +0001CD6C0001CE040001CE6C0001CF080001CFC00001D0340001D0AC0001D1440001D19C +0001D24C0001D2EC0001D33C0001D3CC0001D4540001D4B80001D5300001D5900001D62C +0001D6A00001D7440001D7E80001D87C0001D8F40001D9D40001DA980001DB740001DC50 +0001DD180001DE500001DF000001DF840001E0640001E1200001E1B80001E2680001E31C +0001E3E40001E4F80001E5D00001E6C00001E7DC0001E8800001E9280001E9A80001EA50 +0001EAD00001EB7C0001EBF80001ECA40001ED6C0001EDE80001EE940001EF180001EFA8 +0001F04C0001F0B40001F1340001F1A80001F2300001F2B80001F36C0001F4180001F4D0 +0001F5900001F5C00001F6180001F66C0001F6B40001F6EC0001F71C0001F7800001F7B8 +0001F7EC0001F8300001F8700001F8940001F8D40001F9080001F9680001F9A40001FA2C +0001FA740001FAE40001FB0C0001FB5C0001FB880001FBCC0001FC080001FC3C0001FC70 +0001FCE00001FD380001FD840001FDEC0001FE500001FEA40001FF580001FFAC0001FFDC +000200240002005C0002007C000200F0000201440002019C000201FC0002025C000202A0 +0002030C00020364000203C0000203E80002042C00020460000204B4000204E80002053C +0002059C0002060000020690000206E80002071400020798000207E00002088000020920 +0002097400020A2000020A7800020B1000020B8400020C2400020C8800020D0C00020D88 +00020E0000020E5400020EB400020F2C00020FD40002108000021150000211E800021214 +000212A00002134C000213A800021438000214BC0002155C000215EC00021658000216B0 +0002172000021780000217FC000218380002190800021958000219E400021A4000021AC0 +00021B1C00021B8000021BF000021C6000021CC800021D5800021DE400021E5800021EC0 +00021F5C00021FDC00022050000220D80002217000022284000100000428008600080000 +000000020000000100010000004000000000000000>]def +/CharStrings 2 dict dup begin +/.notdef 0 def +/m 80 def +end readonly def + +systemdict/resourcestatus known + {42 /FontType resourcestatus + {pop pop false}{true}ifelse} + {true}ifelse +{/TrueDict where{pop}{(%%[ Error: no TrueType rasterizer ]%%)= flush}ifelse +/FontType 3 def + /TrueState 271 string def + TrueDict begin sfnts save + 72 0 matrix defaultmatrix dtransform dup + mul exch dup mul add sqrt cvi 0 72 matrix + defaultmatrix dtransform dup mul exch dup + mul add sqrt cvi 3 -1 roll restore + TrueState initer end + /BuildGlyph{exch begin + CharStrings dup 2 index known + {exch}{exch pop /.notdef}ifelse + get dup xcheck + {currentdict systemdict begin begin exec end end} + {TrueDict begin /bander load cvlit exch TrueState render end} + ifelse + end}bind def + /BuildChar{ + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec + }bind def +}if + +FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +75.6 223.2 translate +460.8 345.6 0 0 clipbox +gsave +0 0 m +460.8 0 l +460.8 345.6 l +0 345.6 l +cl +1.000 setgray +fill +grestore +0.000 setgray +gsave +230.400000 172.800000 translate +0.000000 rotate +/DejaVuSans 10.0 selectfont +0.000000 0.703125 moveto +/M glyphshow +8.627930 0.703125 moveto +/a glyphshow +14.755859 0.703125 moveto +/s glyphshow +19.965820 0.703125 moveto +/s glyphshow +25.175781 0.703125 moveto +/space glyphshow +/STIXGeneral-Italic 10.0 selectfont +28.354492 0.703125 moveto +/m glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/useafm.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/useafm.eps new file mode 100644 index 000000000000..0fbbce73c42d --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/useafm.eps @@ -0,0 +1,66 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: useafm.eps +%%Creator: Matplotlib v3.3.2.post1066.dev0+g61d2a5649, https://matplotlib.org/ +%%CreationDate: Fri Sep 18 22:34:48 2020 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 7 dict def +mpldict begin +/m { moveto } bind def +/l { lineto } bind def +/r { rlineto } bind def +/c { curveto } bind def +/cl { closepath } bind def +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } bind def +/clipbox { + box + clip + newpath + } bind def +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +1.000 setlinewidth +1 setlinejoin +2 setlinecap +[] 0 setdash +0.000 0.000 1.000 setrgbcolor +gsave +446.4 345.6 72 43.2 clipbox +72 216 m +518.4 216 l +stroke +grestore +0.000 setgray +gsave +/Helvetica findfont +12.0 scalefont +setfont +295.200000 216.000000 translate +0.000000 rotate +0.000000 0 m /q glyphshow +6.672000 0 m /k glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_svg/bold_font_output_with_none_fonttype.svg b/lib/matplotlib/tests/baseline_images/test_backend_svg/bold_font_output_with_none_fonttype.svg index 20526ec9476e..9fe5ce39b941 100644 --- a/lib/matplotlib/tests/baseline_images/test_backend_svg/bold_font_output_with_none_fonttype.svg +++ b/lib/matplotlib/tests/baseline_images/test_backend_svg/bold_font_output_with_none_fonttype.svg @@ -1,402 +1,413 @@ - - + + + + + + 2024-03-27T18:41:46.786798 + image/svg+xml + + + Matplotlib v3.9.0.dev1430+g883dca40e7, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - +" style="fill: #ffffff"/> - + - + - + - + - + - + - + - + - + - 0 + 0 - + - + - 1 + 1 - + - + - 2 + 2 - + - + - 3 + 3 - + - + - 4 + 4 - + - + - 5 + 5 - + - + - 6 + 6 - + - + - 7 + 7 - + - + - 8 + 8 - + - + - 9 + 9 - nonbold-xlabel + nonbold-xlabel - + - + - + - + - 0 + 0 - + - + - 1 + 1 - + - + - 2 + 2 - + - + - 3 + 3 - + - + - 4 + 4 - + - + - 5 + 5 - + - + - 6 + 6 - + - + - 7 + 7 - + - + - 8 + 8 - + - + - 9 + 9 - bold-ylabel + bold-ylabel - bold-title + bold-title - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_aspath.svg b/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_aspath.svg new file mode 100644 index 000000000000..b1e4fecfd2f4 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_aspath.svg @@ -0,0 +1,15523 @@ + + + + + + + + 2024-09-05T21:08:26.637648 + image/svg+xml + + + Matplotlib v3.10.0.dev632+g9c5136f7df.d20240906, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_astext.svg b/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_astext.svg new file mode 100644 index 000000000000..c9902ca1b806 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_svg/multi_font_astext.svg @@ -0,0 +1,52 @@ + + + + + + + + 2024-09-05T21:08:27.107851 + image/svg+xml + + + Matplotlib v3.10.0.dev632+g9c5136f7df.d20240906, https://matplotlib.org/ + + + + + + + + + + + + + + There are basic characters + ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz + 0123456789 !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ + and accented characters + ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞß + àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ + Ä€ÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğ + ĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿ + Å€ÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞş + ŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ + Æ€ÆÆ‚ÆƒÆ„Æ…Æ†Æ‡ÆˆÆ‰ÆŠÆ‹ÆŒÆÆŽÆÆÆ‘Æ’Æ“Æ”Æ•Æ–Æ—Æ˜Æ™ÆšÆ›ÆœÆÆžÆŸ + ƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿ + Ç€ÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟ + ǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿ + È€ÈȂȃȄȅȆȇȈȉȊȋȌÈÈŽÈÈȑȒȓȔȕȖȗȘșȚțȜÈȞȟ + ȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿ + É€ÉɂɃɄɅɆɇɈɉɊɋɌÉÉŽÉ + in between! + + + diff --git a/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.pdf b/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.pdf index 15c936058d75..85afbeb34bb2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.svg b/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.svg index ee013d2332d3..fed1dbbf83a2 100644 --- a/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.svg +++ b/lib/matplotlib/tests/baseline_images/test_backend_svg/noscale.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T19:48:49.288464 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,100 +35,100 @@ L 468 388.8 L 468 43.2 L 122.4 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -126,70 +137,70 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -197,8 +208,8 @@ L -4 0 - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_fixed_aspect.png b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_fixed_aspect.png new file mode 100644 index 000000000000..0fd7a35e3303 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_fixed_aspect.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_inset_rasterized.pdf b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_inset_rasterized.pdf new file mode 100644 index 000000000000..17a80ef876d1 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_inset_rasterized.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.pdf b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.pdf index 9214e27e528c..b7a90a37dcf1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.pdf and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.png b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.png index f2827b5b78ea..aea824adc864 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.png and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.svg b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.svg index 1e6229adef8a..d08fff8810b2 100644 --- a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.svg +++ b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight.svg @@ -1,531 +1,1062 @@ - - + + + + + + 2025-04-15T18:37:34.962367 + image/svg+xml + + + Matplotlib v3.11.0.dev671+ga5ce26bb9e.d20250415, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - +" style="fill: #ffffff"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - +"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - +"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - +"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - +"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - +"/> - +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> - - - + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + +" clip-path="url(#p95703875b0)" style="fill: #1f77b4"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #1f77b4"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #1f77b4"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #1f77b4"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #1f77b4"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #ff7f0e"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #ff7f0e"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #ff7f0e"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #ff7f0e"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #ff7f0e"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #2ca02c"/> - - + + - - + +" clip-path="url(#p95703875b0)" style="fill: #2ca02c"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #2ca02c"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #2ca02c"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #d62728"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #d62728"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #d62728"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #d62728"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #d62728"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #9467bd"/> - - + +" clip-path="url(#p95703875b0)" style="fill: #9467bd"/> - - - - - + + - - + + - - + + - - - - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + - - + +" style="fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter"/> - - + +" style="fill: #1f77b4"/> - - + +"/> - - + +" style="fill: #ff7f0e"/> - - + +"/> - - + + + + + + + + + + +"/> + + + + + + - - + + + + + + + + + + + + + + + + +" style="fill: #2ca02c"/> - - + +"/> - - + +" style="fill: #d62728"/> - - + +"/> - - + +" style="fill: #9467bd"/> - - + +"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_layout.png b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_layout.png new file mode 100644 index 000000000000..657eaed42267 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_layout.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_legend.svg b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_legend.svg index c99489ca7dfb..5cf932d60cb7 100644 --- a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_legend.svg +++ b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_legend.svg @@ -1,21 +1,32 @@ - - + + + + + + 2022-07-07T13:08:55.409721 + image/svg+xml + + + Matplotlib v3.5.0.dev5238+g5d127c48a6.d20220707, https://matplotlib.org/ + + + + + - + +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 542.014375 387.36 L 542.014375 41.76 L 95.614375 41.76 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#p602791bb46)" style="fill: none; stroke: #0000ff; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - - - + + + + @@ -112,32 +125,33 @@ Q 19.53125 74.21875 31.78125 74.21875 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -145,42 +159,43 @@ z - + - + - - - - + + + + @@ -188,50 +203,51 @@ Q 31.109375 20.453125 19.1875 8.296875 - + - + - - - - + + + + @@ -239,36 +255,38 @@ Q 46.96875 40.921875 40.578125 39.3125 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -276,43 +294,44 @@ z - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -320,47 +339,49 @@ z - + - + - - - - + + + + @@ -368,28 +389,29 @@ Q 48.484375 72.75 52.59375 71.296875 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -397,55 +419,58 @@ z - + - + - - - - + + + + @@ -453,163 +478,172 @@ Q 18.3125 60.0625 18.3125 54.390625 - + - + - - - - + + + + - - + + - - + + - - + + - - - +M 603 4863 +L 1178 4863 +L 1178 4134 +L 603 4134 +L 603 4863 +z +" transform="scale(0.015625)"/> + + - - - - - + + + + + @@ -617,446 +651,462 @@ Q 40.578125 54.546875 44.28125 53.078125 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - + + - - +" transform="scale(0.015625)"/> + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - - + + - - + - - + - - - + - - +" transform="scale(0.015625)"/> + + + + + - - - - - - - - - - - + + + + + + + + + + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - - + + - + - + - - +" transform="scale(0.015625)"/> + - - - - - - - - - + + + + + + + + + @@ -1066,105 +1116,107 @@ L 656.183875 74.4165 L 656.183875 48.96 L 504.574375 48.96 z -" style="fill:#ffffff;stroke:#000000;stroke-linejoin:miter;"/> +" style="fill: #ffffff; stroke: #000000; stroke-linejoin: miter"/> +" style="fill: none; stroke: #0000ff; stroke-linecap: square"/> - - - + + - - +M 3481 434 +Q 3481 -459 3084 -895 +Q 2688 -1331 1869 -1331 +Q 1566 -1331 1297 -1286 +Q 1028 -1241 775 -1147 +L 775 -588 +Q 1028 -725 1275 -790 +Q 1522 -856 1778 -856 +Q 2344 -856 2625 -561 +Q 2906 -266 2906 331 +L 2906 616 +Q 2728 306 2450 153 +Q 2172 0 1784 0 +Q 1141 0 747 490 +Q 353 981 353 1791 +Q 353 2603 747 3093 +Q 1141 3584 1784 3584 +Q 2172 3584 2450 3431 +Q 2728 3278 2906 2969 +L 2906 3500 +L 3481 3500 +L 3481 434 +z +" transform="scale(0.015625)"/> + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - +" transform="scale(0.015625)"/> + - - - - - - - - - - - + + + + + + + + + + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_non_default.png b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_non_default.png new file mode 100644 index 000000000000..453ea566804d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_suptile_non_default.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.pdf deleted file mode 100644 index c4019065ae7a..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.svg deleted file mode 100644 index f2b8d40dced5..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__add_positions.svg +++ /dev/null @@ -1,811 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.pdf deleted file mode 100644 index 3da31c23f37b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.svg deleted file mode 100644 index dea1649a020e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.svg +++ /dev/null @@ -1,812 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.pdf deleted file mode 100644 index 24edb67d2989..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.svg deleted file mode 100644 index ed927a817852..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.svg +++ /dev/null @@ -1,692 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.pdf deleted file mode 100644 index 350240c3a1ef..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.svg deleted file mode 100644 index 02e2f614bd11..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.svg +++ /dev/null @@ -1,798 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.pdf deleted file mode 100644 index 897d90653212..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.svg deleted file mode 100644 index 1be6be46f002..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.svg +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.pdf deleted file mode 100644 index 1de0c4066bc0..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.svg deleted file mode 100644 index c6864997c697..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.svg +++ /dev/null @@ -1,762 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.pdf deleted file mode 100644 index 17ac2ec9a703..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.svg deleted file mode 100644 index ebb92a50afb4..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.svg +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.pdf deleted file mode 100644 index 4a4c75b5a6af..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.svg deleted file mode 100644 index 2da07c3d9810..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linestyle.svg +++ /dev/null @@ -1,655 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.pdf deleted file mode 100644 index 240cf285f2cf..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.svg deleted file mode 100644 index a4e63b10de3b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.svg +++ /dev/null @@ -1,696 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.pdf deleted file mode 100644 index e8eb805afcfa..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.svg deleted file mode 100644 index 6d8187c688ff..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_orientation.svg +++ /dev/null @@ -1,686 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.pdf deleted file mode 100644 index 5e7c3569af07..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.svg deleted file mode 100644 index c6fb8e815924..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.svg +++ /dev/null @@ -1,760 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.pdf deleted file mode 100644 index 78ffdf083661..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.svg deleted file mode 100644 index 8d804f5b85fe..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.svg +++ /dev/null @@ -1,722 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.pdf deleted file mode 100644 index aa7db682592b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.svg deleted file mode 100644 index 856034c6ffbb..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.svg +++ /dev/null @@ -1,741 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_collections/polycollection_close.png b/lib/matplotlib/tests/baseline_images/test_collections/polycollection_close.png index bda2857d6321..9c089517638b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_collections/polycollection_close.png and b/lib/matplotlib/tests/baseline_images/test_collections/polycollection_close.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/test_check_masked_offsets.png b/lib/matplotlib/tests/baseline_images/test_collections/test_check_masked_offsets.png new file mode 100644 index 000000000000..ebac9df75ddb Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_collections/test_check_masked_offsets.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png new file mode 100644 index 000000000000..2dfb3b0366da Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf deleted file mode 100644 index 81271ec29c81..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png index bb41d922189f..b4abeda53746 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg deleted file mode 100644 index 5e205feadc5e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg +++ /dev/null @@ -1,650 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extend_alpha.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extend_alpha.png new file mode 100644 index 000000000000..68cbf3d341f3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extend_alpha.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png index 9f2067a45a40..230f8d7332ba 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png index d68d71bb2d0e..7dbdbbd9b767 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png index c4e72454cd4f..eb1e8b82bf02 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png index 4a10633a6253..86e4094208d6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png new file mode 100644 index 000000000000..410b9f5b0878 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png index 63a58245f7e0..18d9cf02add0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png new file mode 100644 index 000000000000..b92103ae1347 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/contour_colorbar.png b/lib/matplotlib/tests/baseline_images/test_colorbar/contour_colorbar.png new file mode 100644 index 000000000000..bf084c30ddad Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/contour_colorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/contourf_extend_patches.png b/lib/matplotlib/tests/baseline_images/test_colorbar/contourf_extend_patches.png new file mode 100644 index 000000000000..0e5ef52cf549 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/contourf_extend_patches.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png b/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png index 2ddb219eda3a..b139a4664c17 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/extend_drawedges.png b/lib/matplotlib/tests/baseline_images/test_colorbar/extend_drawedges.png new file mode 100644 index 000000000000..864eeefbae10 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/extend_drawedges.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/nonorm_colorbars.svg b/lib/matplotlib/tests/baseline_images/test_colorbar/nonorm_colorbars.svg new file mode 100644 index 000000000000..85f92c3c8d64 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_colorbar/nonorm_colorbars.svg @@ -0,0 +1,151 @@ + + + + + + + + 2021-12-06T14:23:33.155376 + image/svg+xml + + + Matplotlib v3.6.0.dev954+ged9e9c2ef2.d20211206, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + 1 + + + + + + + + + + 2 + + + + + + + + + + 3 + + + + + + + + + + 4 + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/proportional_colorbars.png b/lib/matplotlib/tests/baseline_images/test_colorbar/proportional_colorbars.png new file mode 100644 index 000000000000..a1f9745230ba Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/proportional_colorbars.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/test_boundaries.png b/lib/matplotlib/tests/baseline_images/test_colorbar/test_boundaries.png new file mode 100644 index 000000000000..e2e05a375742 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/test_boundaries.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png b/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png index bb759674e557..bb0e9a2538da 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png and b/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout1.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout1.png index 25eade2b6297..bfbbfd6f7eeb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout1.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout1.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png index 1d23c1db38de..2f0998c5e66c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png index 514bf02ce13c..e850318d8d8f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png index b674803ba2e8..4d150d3cf06f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png index 07e35e9d800a..6f5625ae2605 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png index 5889b0583432..9fa680160c3d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png index e030c3c9f6c1..3a908eb8bf58 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout15.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout15.png index 6790ac835838..02f7f29fe7de 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout15.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout15.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout16.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout16.png index 841cf77b7e08..f2a8172ffb7d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout16.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout16.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png index 8af582f00926..7ac78631798e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout2.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout2.png index 575cd9a45b7e..1d8e03c3cd54 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout2.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png index c679609be54e..77e90bc09062 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png index 2a6e55c08f64..42b87f03f1d0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png index af691c44867d..297391b17837 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png index 6ba96e41a34d..a50f2f1dabcc 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png index 757230e25363..99ba9f294a7a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png index 59fd2c76c5bc..a1e9da294e64 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bbox.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bbox.png new file mode 100644 index 000000000000..f21e247f21fa Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bbox.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bboxtight.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bboxtight.png new file mode 100644 index 000000000000..6e5414ee0a25 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_bboxtight.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapH.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapH.png index d91bcdf22b7a..b1081ba44d0a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapH.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapH.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapV.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapV.png index 1768fc2fdc35..b4a1133e5eda 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapV.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapV.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png index 2115054d13b2..07a442841e95 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_all_algorithms.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_all_algorithms.png new file mode 100644 index 000000000000..1ef0c15a678f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_all_algorithms.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_closed_line_loop.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_closed_line_loop.png new file mode 100644 index 000000000000..2e8e73a8f1a1 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_closed_line_loop.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png new file mode 100644 index 000000000000..ceb700e09de2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_line_start_on_corner_edge.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_line_start_on_corner_edge.png new file mode 100644 index 000000000000..d8af58f80eaf Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_line_start_on_corner_edge.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png index c1feda1e9cba..316b6370edca 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_locator.svg b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_locator.svg new file mode 100644 index 000000000000..a4a397104ef7 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_locator.svg @@ -0,0 +1,3375 @@ + + + + + + + + 2022-12-11T14:01:58.700972 + image/svg+xml + + + Matplotlib v3.6.0.dev2642+g907c10911d.d20221211, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual.png new file mode 100644 index 000000000000..c98879360c45 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png index 632d1f7e7594..b01bcb239535 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf index 8e4a28a55362..f46eb90b46a2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png index d6acf227567a..2b257bc152f1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg index 3fbf8b835e46..8a3952e2f846 100644 --- a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg +++ b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg @@ -1,12 +1,23 @@ - + + + + + 2020-11-06T19:00:48.952407 + image/svg+xml + + + Matplotlib v3.3.2.post1573+gcdb08ceb8, https://matplotlib.org/ + + + + + - + @@ -32,73 +43,73 @@ z +" id="mbc9e29bc37" style="stroke:#000000;stroke-width:0.8;"/> - + - + - + - + - + - + - + - + - + - + @@ -109,60 +120,61 @@ L 0 3.5 +" id="m8b888be400" style="stroke:#000000;stroke-width:0.8;"/> - + - + - + - + - + - - - - + - + - - + - - - - - - + + + + - + - + - - - - - - - +" id="DejaVuSans-30" transform="scale(0.015625)"/> + + + + - - - - + + + + - + - - - - - - - +" id="DejaVuSans-35" transform="scale(0.015625)"/> + + + + - - - - + + + + - - - - - - - +" id="DejaVuSans-36" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_rasterization.pdf b/lib/matplotlib/tests/baseline_images/test_contour/contour_rasterization.pdf new file mode 100644 index 000000000000..2c3048ad707b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_rasterization.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png index 723e501ed287..08a321297e9d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png b/lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png new file mode 100644 index 000000000000..18d949773ded Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_empty.png b/lib/matplotlib/tests/baseline_images/test_dates/date_empty.png deleted file mode 100644 index 4bb9efcba428..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_dates/date_empty.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png new file mode 100644 index 000000000000..78dffc18e20c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png new file mode 100644 index 000000000000..f719ae6931f0 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.pdf b/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.pdf deleted file mode 100644 index 4c9d292ad180..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.svg b/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.svg deleted file mode 100644 index d3be94a5e07c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_figure/figure_legend.svg +++ /dev/null @@ -1,882 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.pdf b/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.pdf deleted file mode 100644 index a240dc52d2ed..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.svg b/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.svg deleted file mode 100644 index 04c69d7df02a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_figure/figure_suptitle.svg +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_today.pdf b/lib/matplotlib/tests/baseline_images/test_figure/figure_today.pdf deleted file mode 100644 index d1909c6c80ee..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_figure/figure_today.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_today.svg b/lib/matplotlib/tests/baseline_images/test_figure/figure_today.svg deleted file mode 100644 index e94c8e3c72c2..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_figure/figure_today.svg +++ /dev/null @@ -1,715 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png new file mode 100644 index 000000000000..bcfc4494b944 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_double.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_double.png new file mode 100644 index 000000000000..594ce7d4e72f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_double.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_scatter_size.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_scatter_size.png new file mode 100644 index 000000000000..c193b5d0ec22 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_scatter_size.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png new file mode 100644 index 000000000000..d06a5db7a5dd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/tightbbox_box_aspect.svg b/lib/matplotlib/tests/baseline_images/test_figure/tightbbox_box_aspect.svg index 7ac69c1a1daa..208b2f72baa1 100644 --- a/lib/matplotlib/tests/baseline_images/test_figure/tightbbox_box_aspect.svg +++ b/lib/matplotlib/tests/baseline_images/test_figure/tightbbox_box_aspect.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-03-16T23:01:41.733119 + image/svg+xml + + + Matplotlib v3.6.0.dev5975+g6cbe21b7c8.d20230317, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 395.385982 176.727273 L 395.385982 -0 L 0 -0 z -" style="fill:#008080;"/> +" style="fill: #008080"/> @@ -24,53 +35,53 @@ L 173.027273 128.945455 L 173.027273 47.781818 L 10.7 47.781818 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + @@ -79,40 +90,40 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + @@ -120,22 +131,22 @@ L -3.5 0 +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> @@ -144,7 +155,7 @@ L 367.82 169.527273 L 367.82 7.2 L 205.492727 7.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -152,7 +163,7 @@ z L 249.995363 84.057477 L 248.546814 27.319429 L 211.509533 53.841184 -" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/> +" style="fill: #f2f2f2; opacity: 0.5; stroke: #f2f2f2; stroke-linejoin: miter"/> @@ -161,7 +172,7 @@ L 211.509533 53.841184 L 363.914025 117.547317 L 366.905689 58.227341 L 248.546814 27.319429 -" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/> +" style="fill: #e6e6e6; opacity: 0.5; stroke: #e6e6e6; stroke-linejoin: miter"/> @@ -170,81 +181,139 @@ L 248.546814 27.319429 L 332.811323 150.362296 L 363.914025 117.547317 L 249.995363 84.057477 -" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/> +" style="fill: #ececec; opacity: 0.5; stroke: #ececec; stroke-linejoin: miter"/> - - - - + +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> +" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> + + + + + + + + + + + + + + + + + + + + + + + + + +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> @@ -252,74 +321,48 @@ L 329.750639 150.115413 - - - - - - - - +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> @@ -327,74 +370,48 @@ L 365.274441 118.739933 - - - - - - - - +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linecap: square"/> diff --git a/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.pdf b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.pdf new file mode 100644 index 000000000000..186cc985c454 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.png b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.png new file mode 100644 index 000000000000..be3938324f6f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.svg b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.svg new file mode 100644 index 000000000000..a8b40568db4a --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_ft2font/last_resort.svg @@ -0,0 +1,806 @@ + + + + + + + + 2025-04-03T00:20:20.243856 + image/svg+xml + + + Matplotlib v3.11.0.dev619+g0125923f7b.d20250403, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.pdf b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.pdf index df07fd91a9c6..e0baa115a6b3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.pdf and b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/downsampling.png b/lib/matplotlib/tests/baseline_images/test_image/downsampling.png new file mode 100644 index 000000000000..4e68e52d787b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/downsampling.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/downsampling_speckle.png b/lib/matplotlib/tests/baseline_images/test_image/downsampling_speckle.png new file mode 100644 index 000000000000..7c18bc89a9af Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/downsampling_speckle.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage.pdf b/lib/matplotlib/tests/baseline_images/test_image/figimage.pdf index 53b98a11d5cb..83ed7cef5668 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/figimage.pdf and b/lib/matplotlib/tests/baseline_images/test_image/figimage.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.pdf index 6411ec5fe76c..f5923c481fa7 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.pdf and b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg index 7f279edff96e..dd6884710e69 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T19:11:24.492650 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,112 +35,112 @@ L 203.294118 281.647059 L 203.294118 150.352941 L 72 150.352941 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + @@ -138,82 +149,82 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + @@ -226,102 +237,102 @@ L 360.847059 281.647059 L 360.847059 150.352941 L 229.552941 150.352941 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + @@ -330,72 +341,72 @@ L 360.847059 150.352941 - + - + - + - + - + - + - + - + - + - + - + - + @@ -408,102 +419,102 @@ L 518.4 281.647059 L 518.4 150.352941 L 387.105882 150.352941 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + @@ -512,72 +523,72 @@ L 518.4 150.352941 - + - + - + - + - + - + - + - + - + - + - + - + @@ -585,14 +596,14 @@ L 518.4 150.352941 - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_interps.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_interps.pdf deleted file mode 100644 index d4ee5a70e014..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_interps.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_interps.png b/lib/matplotlib/tests/baseline_images/test_image/image_interps.png deleted file mode 100644 index 125d5e7b21b3..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_interps.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_interps.svg b/lib/matplotlib/tests/baseline_images/test_image/image_interps.svg deleted file mode 100644 index cee45754af4f..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_image/image_interps.svg +++ /dev/null @@ -1,1132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_placement.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_placement.pdf new file mode 100644 index 000000000000..a86faf3b0f47 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/image_placement.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_placement.svg b/lib/matplotlib/tests/baseline_images/test_image/image_placement.svg new file mode 100644 index 000000000000..42b246a73b40 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_image/image_placement.svg @@ -0,0 +1,177 @@ + + + + + + + + 2023-04-16T21:33:22.285642 + image/svg+xml + + + Matplotlib v3.8.0.dev856+gc37d9d6dd4.d20230417, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_shift.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_shift.pdf index 03aad738e573..037a145712f9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_shift.pdf and b/lib/matplotlib/tests/baseline_images/test_image/image_shift.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_shift.svg b/lib/matplotlib/tests/baseline_images/test_image/image_shift.svg index 401971d3ca34..1dbf494c9007 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_shift.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_shift.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T19:11:25.513009 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,100 +35,100 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -126,70 +137,70 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -197,8 +208,8 @@ L -4 0 - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow.pdf b/lib/matplotlib/tests/baseline_images/test_image/imshow.pdf deleted file mode 100644 index 4f1fb7db06cf..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow.png b/lib/matplotlib/tests/baseline_images/test_image/imshow.png deleted file mode 100644 index 34355b932da2..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow.svg b/lib/matplotlib/tests/baseline_images/test_image/imshow.svg deleted file mode 100644 index 2a67f11627a1..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_image/imshow.svg +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.pdf b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.pdf index 7bc22fc67197..0342a2baa4b2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.pdf and b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.png b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.png index 72918a27fbc1..9e68784cff4f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.png and b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.svg b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.svg index 6bfde7b4fdcd..c0385c18467c 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.svg @@ -1,26 +1,23 @@ - - + - + - 2020-06-15T14:24:58.421515 + 2024-04-23T11:45:45.434641 image/svg+xml - Matplotlib v3.2.1.post2859.dev0+gc3bfeb9c3c, https://matplotlib.org/ + Matplotlib v3.9.0.dev1543+gdd88cca65b.d20240423, https://matplotlib.org/ - + @@ -29,171 +26,171 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.svg b/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.svg index 26a26ff23a4d..6854b0ed81cf 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T18:07:28.590874 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,100 +35,100 @@ L 274.909091 317.454545 L 274.909091 114.545455 L 72 114.545455 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -126,70 +137,70 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -202,90 +213,90 @@ L 518.4 317.454545 L 518.4 114.545455 L 315.490909 114.545455 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + @@ -294,60 +305,60 @@ L 518.4 114.545455 - + - + - + - + - + - + - + - + - + - + @@ -355,11 +366,11 @@ L 518.4 114.545455 - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf index d1d3ca14dcf7..c26419850251 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf and b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png index 9d93c8fb00bf..1df80c1b2045 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png and b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg index 6c958cc79592..259eb2c9c7f3 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg @@ -1,12 +1,23 @@ - - + + + + + + 2025-05-14T18:02:41.587512 + image/svg+xml + + + Matplotlib v3.11.0.dev832+gc5ea66e278, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,100 +35,100 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -126,248 +137,248 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -375,8 +386,8 @@ L -2 0 - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png index 904e0c3d44a0..5ff776ad3de5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png and b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.pdf b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.pdf index 1c44402b6426..9b0edaba007b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.pdf and b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.svg b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.svg index ae99b1200db7..0c6f485835f7 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T19:31:03.637820 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,100 +35,100 @@ L 518.4 130.673455 L 518.4 112.817455 L 72 112.817455 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -126,70 +137,70 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + @@ -202,90 +213,90 @@ L 518.4 319.182545 L 518.4 301.326545 L 72 301.326545 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + @@ -294,60 +305,60 @@ L 518.4 301.326545 - + - + - + - + - + - + - + - + - + - + @@ -355,11 +366,11 @@ L 518.4 301.326545 - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/nonuniform_and_pcolor.png b/lib/matplotlib/tests/baseline_images/test_image/nonuniform_and_pcolor.png new file mode 100644 index 000000000000..0d6060411751 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/nonuniform_and_pcolor.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/nonuniform_logscale.png b/lib/matplotlib/tests/baseline_images/test_image/nonuniform_logscale.png new file mode 100644 index 000000000000..da2c0467864e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/nonuniform_logscale.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png b/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png new file mode 100644 index 000000000000..62cae15ccc91 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.pdf b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.pdf index 127333c64c2a..e1da2d0cb2d5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.pdf and b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png index f0edf0225890..31e62e4ed4cb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png and b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.svg b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.svg index 8052f1eaf3f5..6dacd512e1a3 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-04-16T19:34:04.839428 + image/svg+xml + + + Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,130 +35,130 @@ L 424.8 388.8 L 424.8 43.2 L 165.6 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + - +" clip-path="url(#pd7bb2fcca8)" style="fill: none; stroke-dasharray: 6,6; stroke-dashoffset: 0; stroke: #ff0000; stroke-width: 3"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -156,118 +167,118 @@ L 0 4 - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -275,8 +286,8 @@ L -4 0 - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/upsampling.png b/lib/matplotlib/tests/baseline_images/test_image/upsampling.png new file mode 100644 index 000000000000..281dae56a30e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/upsampling.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_connector_styles.png b/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_connector_styles.png new file mode 100644 index 000000000000..df8b768725d7 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_connector_styles.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_transform.png b/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_transform.png new file mode 100644 index 000000000000..4990efa5cfc9 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_inset/zoom_inset_transform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.pdf b/lib/matplotlib/tests/baseline_images/test_legend/fancy.pdf deleted file mode 100644 index 7930f6d68249..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/fancy.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.png b/lib/matplotlib/tests/baseline_images/test_legend/fancy.png index aba46e19e727..fbb827bbefa5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/fancy.png and b/lib/matplotlib/tests/baseline_images/test_legend/fancy.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg deleted file mode 100644 index 427ab827f4a1..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg +++ /dev/null @@ -1,760 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/hatching.pdf b/lib/matplotlib/tests/baseline_images/test_legend/hatching.pdf index e345dbff7ce5..5d752d52634d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_legend/hatching.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.pdf b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.pdf deleted file mode 100644 index fef8197061cf..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.svg b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.svg deleted file mode 100644 index 17a7270dc97b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto1.svg +++ /dev/null @@ -1,561 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.pdf b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.pdf deleted file mode 100644 index 95642d306bdc..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.svg b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.svg deleted file mode 100644 index 4dbb53e56e0b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto2.svg +++ /dev/null @@ -1,1990 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.pdf b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.pdf deleted file mode 100644 index 69c1ffba7a60..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.svg b/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.svg deleted file mode 100644 index dcc9d3f83f9a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/legend_auto3.svg +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.pdf b/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.pdf deleted file mode 100644 index 7db4ea697579..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.svg b/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.svg deleted file mode 100644 index 142da9bd7fe6..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/legend_expand.svg +++ /dev/null @@ -1,1135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_stackplot.png b/lib/matplotlib/tests/baseline_images/test_legend/legend_stackplot.png index 850dcc83cd40..646753355838 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_stackplot.png and b/lib/matplotlib/tests/baseline_images/test_legend/legend_stackplot.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.pdf b/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.pdf deleted file mode 100644 index a16341c99039..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.svg b/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.svg deleted file mode 100644 index d0ee0f63fdf8..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/legend_various_labels.svg +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.pdf b/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.pdf deleted file mode 100644 index c7fae8bd6dde..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.svg b/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.svg deleted file mode 100644 index ea371456253f..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc1.svg +++ /dev/null @@ -1,470 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.pdf b/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.pdf deleted file mode 100644 index 321d75f1ae41..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.svg b/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.svg deleted file mode 100644 index cd5aa2fcef1e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/scatter_rc3.svg +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png b/lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png new file mode 100644 index 000000000000..c38699467d55 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf index 18b2e46e0f11..44b45f925bd3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf and b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png index aa1d865353ec..cd696194ec67 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png and b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg index c12572fa9384..111f20a7588f 100644 --- a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg +++ b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-05-08T08:25:28.247789 + image/svg+xml + + + Matplotlib v3.8.0.dev1016+gecc2e28867.d20230508, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,53 +35,53 @@ L 414.72 307.584 L 414.72 41.472 L 57.6 41.472 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + @@ -79,332 +90,311 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + - + - + - - - + + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - +z +" clip-path="url(#pbb83ba8d32)" style="fill: none; stroke-dasharray: 4.5,4.5; stroke-dashoffset: 0; stroke: #addc30; stroke-width: 1.5"/> + - +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png new file mode 100644 index 000000000000..758ef251d5ee Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_60.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_60.png new file mode 100644 index 000000000000..92c6be388eb3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_60.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_61.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_61.png new file mode 100644 index 000000000000..eb3ccc2ba586 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_61.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_62.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_62.png new file mode 100644 index 000000000000..a646f604a3e3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_62.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_63.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_63.png new file mode 100644 index 000000000000..d9e3e47b176e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_63.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_64.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_64.png new file mode 100644 index 000000000000..5985624ec817 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_cm_64.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_60.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_60.png new file mode 100644 index 000000000000..58c3cf96845b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_60.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_61.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_61.png new file mode 100644 index 000000000000..5c9e07694012 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_61.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_62.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_62.png new file mode 100644 index 000000000000..0a06cd6a2598 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_62.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_63.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_63.png new file mode 100644 index 000000000000..cca544da1bc2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_63.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_64.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_64.png new file mode 100644 index 000000000000..a5cd8629efea Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavusans_64.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_60.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_60.png new file mode 100644 index 000000000000..46ff984ed8c4 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_60.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_61.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_61.png new file mode 100644 index 000000000000..59dfb57d9de2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_61.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_62.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_62.png new file mode 100644 index 000000000000..f899ba21bb2d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_62.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_63.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_63.png new file mode 100644 index 000000000000..4d670f64af3e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_63.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_64.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_64.png new file mode 100644 index 000000000000..621dfad14623 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_dejavuserif_64.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_60.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_60.png new file mode 100644 index 000000000000..92c6be388eb3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_60.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_61.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_61.png new file mode 100644 index 000000000000..eb3ccc2ba586 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_61.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_62.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_62.png new file mode 100644 index 000000000000..a646f604a3e3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_62.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_63.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_63.png new file mode 100644 index 000000000000..d9e3e47b176e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_63.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_64.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_64.png new file mode 100644 index 000000000000..5985624ec817 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stix_64.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_60.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_60.png new file mode 100644 index 000000000000..92c6be388eb3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_60.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_61.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_61.png new file mode 100644 index 000000000000..ee51257365e6 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_61.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_62.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_62.png new file mode 100644 index 000000000000..1fcf6a6dcb53 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_62.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_63.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_63.png new file mode 100644 index 000000000000..851cc51fc1ad Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_63.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_64.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_64.png new file mode 100644 index 000000000000..39c976685823 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathfont_stixsans_64.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_cm_00.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_cm_00.svg new file mode 100644 index 000000000000..619549bbbe68 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_cm_00.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + ¡ + - + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_dejavusans_00.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_dejavusans_00.svg new file mode 100644 index 000000000000..72ba7d0a32cc --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext0_dejavusans_00.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + −- + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_00.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_00.png new file mode 100644 index 000000000000..4cc6978860e3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_00.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_01.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_01.png new file mode 100644 index 000000000000..f303fe49e281 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_01.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_02.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_02.png new file mode 100644 index 000000000000..b37505cc62b0 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_02.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_03.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_03.png new file mode 100644 index 000000000000..f28fb31c542f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_03.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_04.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_04.png new file mode 100644 index 000000000000..a6861a8f1f08 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_04.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_05.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_05.png new file mode 100644 index 000000000000..f7ac08149bcd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_05.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_06.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_06.png new file mode 100644 index 000000000000..85d0adf15112 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_06.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_07.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_07.png new file mode 100644 index 000000000000..a84cd1d28274 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_07.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png new file mode 100644 index 000000000000..565464062e39 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.pdf index 5753c8eb1c86..e12f32e865fb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.png index bb72bc66a321..fc50fe7349ec 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.svg index 1e6b1af28d07..3f4657da04b5 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_18.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T17:59:47.072291 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -18,240 +29,1063 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.pdf deleted file mode 100644 index 3e03c8d6ab32..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.png deleted file mode 100644 index 4a029269a72b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.svg deleted file mode 100644 index 9726fe044aab..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_22.svg +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.pdf deleted file mode 100644 index 503cd69d37ec..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.png deleted file mode 100644 index c160c75b2d2c..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.svg deleted file mode 100644 index c0d0e3b91162..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_30.svg +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.pdf deleted file mode 100644 index 5e69ff9a8b25..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.png deleted file mode 100644 index a89807c75c60..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.svg deleted file mode 100644 index 51be8184671a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_34.svg +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.pdf index 9ce5d2ad3c31..3187700c2e2e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.png index 6b32a18b6ff2..99895da0be9e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.svg index d0b787e8cfc3..b5644d244d5d 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_52.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:36.929181 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,474 +30,493 @@ z - - - + + - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="Cmmi10-69" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.pdf deleted file mode 100644 index efb88619b2ab..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.png deleted file mode 100644 index bb0f7f544f50..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.svg deleted file mode 100644 index 85b254513ebb..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_67.svg +++ /dev/null @@ -1,451 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.pdf index 94cdddaf6dcf..c463850d8d0f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.png index ae23ecedc0ce..2cf6829952b2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.svg index 1d225fbb273a..ea21bf1f35e2 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_68.svg @@ -1,160 +1,216 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2022-10-24T12:21:21.236679 + image/svg+xml + + + Matplotlib v3.6.0.dev4028+gdd27a7f3c3.d20221024, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf deleted file mode 100644 index 8b8bc68dfd5b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf new file mode 100644 index 000000000000..31ec241a04fc Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png new file mode 100644 index 000000000000..a4ce71fb244b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg new file mode 100644 index 000000000000..01650aa1cfc5 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg @@ -0,0 +1,199 @@ + + + + + + + + 2024-05-08T19:52:27.776189 + image/svg+xml + + + Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.pdf index e97795982139..0d4bc9fa69a3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.png index d4f0d5d20f56..ebdbbbed07ce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.svg index c7717e0d9bc7..ab482549db6c 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_18.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T17:59:55.050120 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -18,141 +29,634 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.pdf deleted file mode 100644 index 137a4698dfaa..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.png deleted file mode 100644 index 3a451305f3ae..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.svg deleted file mode 100644 index 0543f2f6ba1a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_22.svg +++ /dev/null @@ -1,422 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_29.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_29.svg index 5a60dc1cdf70..671945a5274a 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_29.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_29.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:43.012123 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,198 +30,212 @@ z - - + + - - + - - - - - + - + + - - - - - - - - - - - +M 1991 3584 +L 1991 3584 +z +" id="DejaVuSans-75" transform="scale(0.015625)"/> + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.pdf deleted file mode 100644 index 1783edfa14e6..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.png deleted file mode 100644 index 05b1e65e1419..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.svg deleted file mode 100644 index 13ba043bf787..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_30.svg +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.pdf deleted file mode 100644 index ab067acbe277..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.png deleted file mode 100644 index d2f422df3029..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.svg deleted file mode 100644 index 8017ca8769cc..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_34.svg +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.pdf index c6832bf30ecf..654a40855e1b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.png index c4b6c35ecac9..0f639fc84c3c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.svg index 653e54c4af27..aa4045b3263a 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_52.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:43.790396 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,277 +30,296 @@ z - - + + - - + + - - + - - + + - - + - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="DejaVuSans-Oblique-6b" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.pdf deleted file mode 100644 index 9c1d4de19fc5..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.png deleted file mode 100644 index 8e2528770bc9..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.svg deleted file mode 100644 index 60e60f585109..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_67.svg +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.pdf index 3a95452967ac..a33f6b7d8385 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.png index d655592d81c5..5cc8c6ef7b31 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.svg index bdb560153175..94bd4b633188 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_68.svg @@ -1,128 +1,184 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2022-10-24T13:47:25.928529 + image/svg+xml + + + Matplotlib v3.6.0.dev4028+g98b55b4eed.d20221024, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.pdf index 9f4e5515c4c1..4e348e9ba03e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.png index 3b0aa1e7d159..53c8295911ae 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.svg index 4134153405c0..33dc31b427a0 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_71.svg @@ -1,12 +1,23 @@ - - + + + + + + 2021-08-10T16:39:40.187285 + image/svg+xml + + + Matplotlib v3.4.2.post1645+ge771650c3a.d20210810, https://matplotlib.org/ + + + + + - + @@ -15,117 +26,125 @@ L 378 54 L 378 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - - + + - + + - + - + - + - - - - - - - - - - - - +" transform="scale(0.015625)"/> + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf deleted file mode 100644 index 94ca3d3c6d94..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png deleted file mode 100644 index 82e0821cd3ea..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg deleted file mode 100644 index 98902d769804..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf new file mode 100644 index 000000000000..e09af853ea1f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png new file mode 100644 index 000000000000..8a03c6e92bc6 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg new file mode 100644 index 000000000000..b0a6fe95cfa3 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg @@ -0,0 +1,159 @@ + + + + + + + + 2024-05-08T19:52:35.349617 + image/svg+xml + + + Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.pdf index e7e234c912f7..5cee7d7fd3ea 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.png index 497132c6903b..0d5711683aeb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.svg index 0f68718b4d54..6693e3eec2f9 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_18.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T17:59:57.353384 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -18,174 +29,715 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.pdf deleted file mode 100644 index f609974e6a36..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.png deleted file mode 100644 index c3684cd87fe7..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.svg deleted file mode 100644 index ed728a022512..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_22.svg +++ /dev/null @@ -1,479 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_29.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_29.pdf index 57e6c4e926e6..ec5a585b2aee 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_29.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_29.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.pdf deleted file mode 100644 index 35697c58a00e..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.png deleted file mode 100644 index 718302dfb8ca..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.svg deleted file mode 100644 index eb8e7376d155..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_30.svg +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.pdf deleted file mode 100644 index 58369bb0185c..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.png deleted file mode 100644 index 9d26f036987c..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.svg deleted file mode 100644 index 528ba563ed6d..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_34.svg +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.pdf index 34fd24000bad..22e0f6e6d5e5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.png index 36f441e49eae..ec2add682e09 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.svg index 849d257dd986..a9ad2cabdfe0 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_52.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:46.112261 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,304 +30,323 @@ z - - + + - + - + - + - - - + - + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="DejaVuSerif-Italic-72" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.pdf deleted file mode 100644 index fbdd07c1f359..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.png deleted file mode 100644 index bc105950d01b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.svg deleted file mode 100644 index e90ba97f7d2c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_67.svg +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.pdf index dc4c1802c743..64e9b2fe1a97 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.png index 35caf2d2b6ad..9336942936a6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.svg index 0284835785a3..f8773bd214fc 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_68.svg @@ -1,117 +1,173 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2022-10-24T13:47:45.388857 + image/svg+xml + + + Matplotlib v3.6.0.dev4028+g98b55b4eed.d20221024, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf deleted file mode 100644 index d6fc89ad9f25..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png deleted file mode 100644 index b3892b25c4ee..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg deleted file mode 100644 index 3c9961669b69..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf new file mode 100644 index 000000000000..db06e90e5490 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png new file mode 100644 index 000000000000..d5c323fa9bd2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg new file mode 100644 index 000000000000..1c3ac31ac5eb --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg @@ -0,0 +1,148 @@ + + + + + + + + 2024-05-08T19:52:37.707152 + image/svg+xml + + + Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.pdf index d55ed3d0fcb6..363ddaae0c8a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.png index a9273624c140..a75c41e02bb7 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.svg index e9eb3275632f..f211b9f942e5 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_18.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:38.196787 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -18,193 +29,827 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.pdf deleted file mode 100644 index 6fde8125d923..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.png deleted file mode 100644 index eb3c7a21a3e8..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.svg deleted file mode 100644 index 6ab7570f476f..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_22.svg +++ /dev/null @@ -1,562 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.pdf deleted file mode 100644 index 49ad0e80b04a..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.png deleted file mode 100644 index 7001c4f0e3dd..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.svg deleted file mode 100644 index 70746902f681..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_30.svg +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.pdf deleted file mode 100644 index 6712f411793a..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.png deleted file mode 100644 index 7f24990e7c0e..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.svg deleted file mode 100644 index b5c042c3966e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_34.svg +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.pdf index 578adf84aed2..e9d5111e04a3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.png index 6925ae9cd791..dfc27853f20f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.svg index 79101740fc00..69716e0c4a45 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_52.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:39.161171 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,350 +30,369 @@ z - - + + - - + - + - - + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="STIXGeneral-Italic-71" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.pdf deleted file mode 100644 index bb5f89e414dc..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.png deleted file mode 100644 index 1c7481589126..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.svg deleted file mode 100644 index e060ff31fca0..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_67.svg +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.pdf index 2aca1bfd4a52..c6cf56ddba12 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.png index f51fc6032d83..0aa0ac62b063 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.svg index be7cdc9f2da8..078cb0fdb8c4 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_68.svg @@ -1,127 +1,183 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2022-10-24T13:46:49.965486 + image/svg+xml + + + Matplotlib v3.6.0.dev4028+g98b55b4eed.d20221024, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf deleted file mode 100644 index 3966ee6ddd59..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf new file mode 100644 index 000000000000..6679c1e8af13 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png new file mode 100644 index 000000000000..d6f17be104fa Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg new file mode 100644 index 000000000000..3268d5d3d26d --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg @@ -0,0 +1,159 @@ + + + + + + + + 2024-05-08T19:52:30.625389 + image/svg+xml + + + Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.pdf index e3c88d940987..b671324c9749 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.png index 077c97942777..6fd67255629f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.svg index fb8bd9b9b757..8850d02e15bf 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_18.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:40.464406 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -18,167 +29,667 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.pdf deleted file mode 100644 index 25a833a4ef92..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.png deleted file mode 100644 index 8b294adedf3f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.svg deleted file mode 100644 index cb6a89db581e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_22.svg +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.pdf deleted file mode 100644 index ab1a2ee082ae..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.png deleted file mode 100644 index 58bb828044e7..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.svg deleted file mode 100644 index 7bc674a36430..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_30.svg +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.pdf deleted file mode 100644 index 1b6dffc6a9bd..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.png deleted file mode 100644 index f0999dc346d1..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.svg deleted file mode 100644 index 7b49ea90f799..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_34.svg +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.pdf index 4397f810b4b9..8cdb7a680241 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.png index a60e349d4cbb..6a4afbc07290 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.svg index 1da98b129ac6..b6418ec608fa 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_52.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:14:41.432583 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -19,247 +30,266 @@ z - - - + + - - + - - + - + - + - + - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="STIXGeneral-Italic-1d633" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.pdf deleted file mode 100644 index e802f9a7b744..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.png deleted file mode 100644 index a261a4225691..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.svg deleted file mode 100644 index a6b94371b8f7..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_67.svg +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.pdf index 323afa6acf0d..e277346b7f1e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.pdf and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.png index a8ea567f675e..1ea8e26f8d17 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.png and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.svg index 7fdb080a70a9..0a7baa3550a4 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_68.svg @@ -1,111 +1,167 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2022-10-24T13:47:05.108205 + image/svg+xml + + + Matplotlib v3.6.0.dev4028+g98b55b4eed.d20221024, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf deleted file mode 100644 index 98512f54330d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf new file mode 100644 index 000000000000..22e75bb9b0a3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png new file mode 100644 index 000000000000..c23070cdf8b9 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg new file mode 100644 index 000000000000..97c40174b3ef --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg @@ -0,0 +1,136 @@ + + + + + + + + 2024-05-08T19:52:33.020611 + image/svg+xml + + + Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png new file mode 100644 index 000000000000..f22b446fc84b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png new file mode 100644 index 000000000000..415dfda291dc Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_offsetbox/paddedbox.png b/lib/matplotlib/tests/baseline_images/test_offsetbox/paddedbox.png new file mode 100644 index 000000000000..dfb68e2c35a1 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_offsetbox/paddedbox.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/annulus.png b/lib/matplotlib/tests/baseline_images/test_patches/annulus.png new file mode 100644 index 000000000000..549443f523cf Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/annulus.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.png b/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.png new file mode 100644 index 000000000000..3a6d3324e9d8 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.svg b/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.svg new file mode 100644 index 000000000000..dca6c1f226b3 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_patches/autoscale_arc.svg @@ -0,0 +1,289 @@ + + + + + + + + 2022-07-01T10:48:08.014263 + image/svg+xml + + + Matplotlib v3.6.0.dev2827+g83cb036.d20220701, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf deleted file mode 100644 index ecff2574e0a5..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg deleted file mode 100644 index c5f152be9748..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg +++ /dev/null @@ -1,498 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_patches/multi_color_hatch.pdf b/lib/matplotlib/tests/baseline_images/test_patches/multi_color_hatch.pdf index e956cbdf248d..a9db7c30998e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patches/multi_color_hatch.pdf and b/lib/matplotlib/tests/baseline_images/test_patches/multi_color_hatch.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png b/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png index 4e871e6cd02c..989b84092300 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png and b/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_path/nan_path.eps b/lib/matplotlib/tests/baseline_images/test_path/nan_path.eps index 48d473aa3f09..2950e612010f 100644 --- a/lib/matplotlib/tests/baseline_images/test_path/nan_path.eps +++ b/lib/matplotlib/tests/baseline_images/test_path/nan_path.eps @@ -1,9 +1,10 @@ %!PS-Adobe-3.0 EPSF-3.0 -%%Title: /home/tcaswell/source/p/matplotlib/result_images/test_path/nan_path.eps -%%Creator: matplotlib version 2.0.0.post99.dev0+gcdc493d33, http://matplotlib.org/ -%%CreationDate: Tue Mar 14 16:50:24 2017 +%%Title: nan_path.eps +%%Creator: Matplotlib v3.3.2.post1066.dev0+g61d2a5649, https://matplotlib.org/ +%%CreationDate: Fri Sep 18 22:34:54 2020 %%Orientation: portrait -%%BoundingBox: 75 223 536 568 +%%BoundingBox: 75 223 537 569 +%%HiResBoundingBox: 75.600000 223.200000 536.400000 568.800000 %%EndComments %%BeginProlog /mpldict 7 dict def diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf index d74333643910..f7364954ee90 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf and b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png index 07410efba929..eea0410fd02a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png and b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg index 988cc34ebe56..2585026e247b 100644 --- a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg +++ b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-05-08T08:29:00.655917 + image/svg+xml + + + Matplotlib v3.8.0.dev1016+gecc2e28867.d20230508, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,281 +35,284 @@ L 414.72 307.584 L 414.72 41.472 L 57.6 41.472 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - - - - + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - - + + + + + + + - + - - - - + + + + - + - - - - + + + + @@ -307,82 +321,84 @@ z - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - - - - + + + + + - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + - - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf index 8adac0a0d262..45fd3b5b2b88 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf and b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.pdf index a8b20ed3ce98..ba027d57a34b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.pdf and b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png index bc21b72c4f23..af91778e7d80 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png and b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg index be0bbc4cbaad..08e4a9a6f08c 100644 --- a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg +++ b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg @@ -1,12 +1,23 @@ - - + + + + + + 2023-05-08T08:28:59.785389 + image/svg+xml + + + Matplotlib v3.8.0.dev1016+gecc2e28867.d20230508, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,50 +35,50 @@ L 369.216 307.584 L 369.216 41.472 L 103.104 41.472 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + @@ -76,181 +87,177 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - - + + + - + - - - - + - - - + - - - + - - + - - - + - - + - - - + - - + - - - + - - + - - + - - - + - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - - - - - + + + + + + + - + - - - - - - - - - - - +" clip-path="url(#p282092cd29)"/> + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect3.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect3.pdf index ad98ff7223cc..402ec545847d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect3.pdf and b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect3.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/spaces_and_newlines.png b/lib/matplotlib/tests/baseline_images/test_patheffects/spaces_and_newlines.png new file mode 100644 index 000000000000..9dd4c1e8d7ff Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patheffects/spaces_and_newlines.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/tickedstroke.png b/lib/matplotlib/tests/baseline_images/test_patheffects/tickedstroke.png index 60af86eaadcb..e6a860f09bb4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/tickedstroke.png and b/lib/matplotlib/tests/baseline_images/test_patheffects/tickedstroke.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png b/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png deleted file mode 100644 index 3a6f41fa2948..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.pdf deleted file mode 100644 index f56b04c4d3a6..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.svg deleted file mode 100644 index cf8b876685f7..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_axes.svg +++ /dev/null @@ -1,1250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.pdf deleted file mode 100644 index c9fa69334e88..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.svg deleted file mode 100644 index a2b3a95d136b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_coords.svg +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_errorbar.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_errorbar.png new file mode 100644 index 000000000000..6b587a78ae10 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_polar/polar_errorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png new file mode 100644 index 000000000000..ab8e20b482a5 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.pdf deleted file mode 100644 index aa98d5bb8cf1..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.svg deleted file mode 100644 index 47dd9d1a6792..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_negative_rmin.svg +++ /dev/null @@ -1,973 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.pdf deleted file mode 100644 index 8e41309e1736..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.svg deleted file mode 100644 index 07c6695f7790..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_rlabel_position.svg +++ /dev/null @@ -1,687 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.pdf deleted file mode 100644 index 8bcbd4b17bcc..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.svg deleted file mode 100644 index 347ae67589e6..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_rmin.svg +++ /dev/null @@ -1,1012 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.pdf deleted file mode 100644 index 962f95a40d4f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.svg deleted file mode 100644 index 2048cb1e7f73..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_rorigin.svg +++ /dev/null @@ -1,1074 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.pdf deleted file mode 100644 index f132d8f33262..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.svg deleted file mode 100644 index 8dfbb7a36482..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_position.svg +++ /dev/null @@ -1,960 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.pdf b/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.pdf deleted file mode 100644 index 0a7c328ac737..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.svg b/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.svg deleted file mode 100644 index d45bf67a02a9..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_polar/polar_theta_wedge.svg +++ /dev/null @@ -1,6558 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_title_position.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_title_position.png new file mode 100644 index 000000000000..4a8786b1f892 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_polar/polar_title_position.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_quiver/quiver_key_xy.png b/lib/matplotlib/tests/baseline_images/test_quiver/quiver_key_xy.png index 8475b0270930..f4b2b83d08e2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_quiver/quiver_key_xy.png and b/lib/matplotlib/tests/baseline_images/test_quiver/quiver_key_xy.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_simplification/clipping_with_nans.svg b/lib/matplotlib/tests/baseline_images/test_simplification/clipping_with_nans.svg index fbe862667424..9970c51bf07a 100644 --- a/lib/matplotlib/tests/baseline_images/test_simplification/clipping_with_nans.svg +++ b/lib/matplotlib/tests/baseline_images/test_simplification/clipping_with_nans.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-07-07T03:43:38.220504 + image/svg+xml + + + Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#pe2e2378c9e)" style="fill: none; stroke: #0000ff; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - - - + + + + @@ -121,32 +134,33 @@ Q 19.53125 74.21875 31.78125 74.21875 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -154,42 +168,43 @@ z - + - + - - - - + + + + @@ -197,50 +212,51 @@ Q 31.109375 20.453125 19.1875 8.296875 - + - + - - - - + + + + @@ -248,36 +264,38 @@ Q 46.96875 40.921875 40.578125 39.3125 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -285,43 +303,44 @@ z - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -329,47 +348,49 @@ z - + - + - - - - + + + + @@ -377,28 +398,29 @@ Q 48.484375 72.75 52.59375 71.296875 - + - + - - + + - - +" transform="scale(0.015625)"/> + @@ -408,126 +430,128 @@ z - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - - + + - + - - +" transform="scale(0.015625)"/> + - - - + + + - + - + - + - - - + + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + @@ -535,8 +559,8 @@ z - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.pdf b/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.pdf deleted file mode 100644 index affd68412e62..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.svg b/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.svg deleted file mode 100644 index d5410d62d7b2..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_skew/skew_axes.svg +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf deleted file mode 100644 index c16fc9c2d916..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg deleted file mode 100644 index ff18e2b4df0b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg +++ /dev/null @@ -1,5368 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_spines/black_axes.png b/lib/matplotlib/tests/baseline_images/test_spines/black_axes.png new file mode 100644 index 000000000000..4fbcbe9b6756 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_spines/black_axes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.pdf b/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.pdf deleted file mode 100644 index 309a299edb40..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.svg b/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.svg deleted file mode 100644 index 3893e172bf78..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.svg +++ /dev/null @@ -1,819 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.pdf b/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.pdf deleted file mode 100644 index f596d08ce38d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.svg b/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.svg deleted file mode 100644 index c241158c7fd9..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_spines/spines_capstyle.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.pdf b/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.pdf deleted file mode 100644 index 0819ac3993c4..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.svg b/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.svg deleted file mode 100644 index 86a79b5fe89d..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_spines/spines_data_positions.svg +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf deleted file mode 100644 index 7f29b4a9eabf..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png index 99d7e1d44cd8..21f202e5edec 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg deleted file mode 100644 index 869361f76dd0..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg +++ /dev/null @@ -1,2559 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png index 835e159665c9..5381bad998c6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_integration.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_integration.png new file mode 100644 index 000000000000..73730ea5a653 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_integration.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf deleted file mode 100644 index f1644d5b613c..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png index 51e4e5d859c6..13dafd199523 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg deleted file mode 100644 index ba0baa1da49e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg +++ /dev/null @@ -1,2547 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf deleted file mode 100644 index f8f94f0b2a3f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png index 02800cbde2c4..c3ce699983c4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg deleted file mode 100644 index 8b40bc4c66f9..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg +++ /dev/null @@ -1,3709 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png index 4cc023134222..f07ffb89aa20 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength_no_broken.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength_no_broken.png new file mode 100644 index 000000000000..7f32ada3f6d5 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength_no_broken.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf deleted file mode 100644 index f9bf53975faf..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png index c2c3e28d3eab..7823b33770b5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg deleted file mode 100644 index fca2e620fa7c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg +++ /dev/null @@ -1,1494 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.pdf b/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.pdf deleted file mode 100644 index 5fdc39730c4f..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.svg b/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.svg deleted file mode 100644 index 0c231c4d5c25..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.svg +++ /dev/null @@ -1,1774 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_table/table_cell_manipulation.png b/lib/matplotlib/tests/baseline_images/test_table/table_cell_manipulation.png index 778e57802982..ee2558b3ebc6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_table/table_cell_manipulation.png and b/lib/matplotlib/tests/baseline_images/test_table/table_cell_manipulation.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/antialiased.png b/lib/matplotlib/tests/baseline_images/test_text/antialiased.png index 11ba1b082df9..06c007591d96 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/antialiased.png and b/lib/matplotlib/tests/baseline_images/test_text/antialiased.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/basictext_wrap.png b/lib/matplotlib/tests/baseline_images/test_text/basictext_wrap.png index 267e2af4c469..b041afb8b7b8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/basictext_wrap.png and b/lib/matplotlib/tests/baseline_images/test_text/basictext_wrap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/font_styles.svg b/lib/matplotlib/tests/baseline_images/test_text/font_styles.svg index e62797eb4c23..343b5c0bd3d7 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/font_styles.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/font_styles.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-07-09T01:10:40.151601 + image/svg+xml + + + Matplotlib v0.1.0.dev50524+g1791319.d20240709, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,802 +35,853 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_text/fonttext_wrap.png b/lib/matplotlib/tests/baseline_images/test_text/fonttext_wrap.png index 7ca96d571331..64c24344f334 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/fonttext_wrap.png and b/lib/matplotlib/tests/baseline_images/test_text/fonttext_wrap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/large_subscript_title.png b/lib/matplotlib/tests/baseline_images/test_text/large_subscript_title.png index 20eeea59d121..460feef8cb79 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/large_subscript_title.png and b/lib/matplotlib/tests/baseline_images/test_text/large_subscript_title.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/multiline.svg b/lib/matplotlib/tests/baseline_images/test_text/multiline.svg index 15bfc30bebdc..598c1d92d1c1 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/multiline.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/multiline.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-07-09T01:10:40.770401 + image/svg+xml + + + Matplotlib v0.1.0.dev50524+g1791319.d20240709, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,481 +35,502 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - - + + - - - +" transform="scale(0.015625)"/> + + - - - - - + + + + + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - - - - + + + + + - + - - - - - + + + + + - - - - - + + + + + - + - - - - - + + + + + - + - - - - - + + + + + - - - - - - - - + + + + + + + + - + - - - - - + + + + + - - - + + + - - + - + + - - - - +" transform="scale(0.015625)"/> + + + - - - - - - - - + + + + + + + + - - + + + + - - + - - - +" transform="scale(0.015625)"/> + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_text/multiline2.pdf b/lib/matplotlib/tests/baseline_images/test_text/multiline2.pdf index 70cd50471844..a365b2b29fdf 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/multiline2.pdf and b/lib/matplotlib/tests/baseline_images/test_text/multiline2.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/multiline2.png b/lib/matplotlib/tests/baseline_images/test_text/multiline2.png index 691608e36065..2f33b4d75499 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/multiline2.png and b/lib/matplotlib/tests/baseline_images/test_text/multiline2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/multiline2.svg b/lib/matplotlib/tests/baseline_images/test_text/multiline2.svg index f0e372271158..a768fd0536cb 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/multiline2.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/multiline2.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:30:15.434491 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -61,16 +72,16 @@ z @@ -107,16 +118,16 @@ z " style="fill:#1f77b4;"/> - - - @@ -155,18 +166,18 @@ z " style="fill:#1f77b4;"/> - - @@ -204,17 +215,17 @@ z @@ -224,285 +235,289 @@ z +" id="mae436018bc" style="stroke:#000000;stroke-width:0.8;"/> - + - - - - - - - + + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - - - - + + + + + + - + - - - + + + - + - - - + + + @@ -513,205 +528,207 @@ z +" id="m5017e2efe8" style="stroke:#000000;stroke-width:0.8;"/> - + - - - - + + + + - + - - - - - - - + + + + + + + - + - - - - + + + + - + - - - - - - - + + + + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - - - - @@ -737,822 +754,840 @@ L 414.72 41.472 - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.pdf b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.pdf index ffba4f7754c3..1c3b6a27b78d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.pdf and b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.png b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.png index 7da3bff035ed..b93a3fabe6a1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.png and b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.svg b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.svg index f860e3c793fa..08f4fadf3fb5 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/text_alignment.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/text_alignment.svg @@ -1,12 +1,23 @@ - + + + + + 2021-02-07T18:30:17.338435 + image/svg+xml + + + Matplotlib v3.3.2.post2013.dev0+g37d022c62, https://matplotlib.org/ + + + + + - + @@ -29,12 +40,12 @@ z - - @@ -73,198 +84,209 @@ z " style="fill:#f5deb3;opacity:0.5;stroke:#000000;stroke-linejoin:miter;"/> - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -282,83 +304,84 @@ z " style="fill:#f5deb3;opacity:0.5;stroke:#000000;stroke-linejoin:miter;"/> - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + @@ -376,153 +399,158 @@ z " style="fill:#f5deb3;opacity:0.5;stroke:#000000;stroke-linejoin:miter;"/> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - + + + + + @@ -540,65 +568,66 @@ z " style="fill:#f5deb3;opacity:0.5;stroke:#000000;stroke-linejoin:miter;"/> - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + @@ -617,22 +646,22 @@ z - - - - - - + + + + + + - - - - - - + + + + + + @@ -651,25 +680,25 @@ z - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + @@ -688,27 +717,27 @@ z - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + @@ -727,31 +756,31 @@ z - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - + diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg index 69d287e3536c..cbc1fbb85717 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg @@ -1,31 +1,42 @@ - - + + + + + + 2024-03-27T18:41:24.756455 + image/svg+xml + + + Matplotlib v3.9.0.dev1430+g883dca40e7, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - 50% using `color` + 50% using `color` - 50% using `alpha` + 50% using `alpha` - 50% using `alpha` and 100% `color` + 50% using `alpha` and 100% `color` diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_pdf_chars_beyond_bmp.pdf b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_chars_beyond_bmp.pdf new file mode 100644 index 000000000000..8890790d2ea2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_chars_beyond_bmp.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf new file mode 100644 index 000000000000..a8ce9fca346c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_pdf_kerning.pdf b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_kerning.pdf new file mode 100644 index 000000000000..7db9a1b44fad Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_kerning.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/xtick_rotation_mode.png b/lib/matplotlib/tests/baseline_images/test_text/xtick_rotation_mode.png new file mode 100644 index 000000000000..2485b4ac09b6 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/xtick_rotation_mode.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_text/ytick_rotation_mode.png b/lib/matplotlib/tests/baseline_images/test_text/ytick_rotation_mode.png new file mode 100644 index 000000000000..876ab5d8f9d8 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/ytick_rotation_mode.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.pdf index c414e59fe1b8..e1901b3b3a7a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.png index 00c8afb79604..461e51941979 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.svg index 4bc99a39910f..3664df99d1a4 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout1.svg @@ -1,505 +1,200 @@ - - + + + + + + 2025-04-09T03:27:51.168685 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - + - + - + - - - - - - - - - - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - + - - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - + - - - - - - +"/> - - - - + - - - - - - - - - - +"/> - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - +"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.pdf index e26705d0038a..a70180a9192c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.png index 5197602445ae..b690212612b8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.svg index c68cafc5e9c7..2617cd5c2495 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout2.svg @@ -1,1087 +1,668 @@ - - + + + + + + 2025-04-09T03:27:52.212752 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - - - - + - + - + - - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - + - - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - - - - + - - - - - + - - - - - - - - - +"/> - - - - - - + - - - - - - - - +"/> - - - - + + - - + + - - + + - - + + - - + + - - + + + + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.pdf index 5d386cc05fe3..c5307c13d72f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.png index d6428c63e39f..fe5d3a483670 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.svg index 88e6a404ac25..a32d43a6f703 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout3.svg @@ -1,891 +1,512 @@ - - + + + + + + 2025-04-09T03:27:51.909780 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - + - + - + - - - - - - - - - - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - - - + - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - - - - + - - - - - + - - - - - - - - - +"/> - - - - - - + - - - - - - - - +"/> - - - - + + - - + + - - + + + + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.pdf index 1eaa92bd153e..74a45957ea72 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.png index afcf843968d9..6a0088d6e18b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.svg index de5e5efd5a1a..28b001a01475 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout4.svg @@ -1,1093 +1,668 @@ - - + + + + + + 2025-04-09T03:27:51.858300 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - - - - + - + - + - - - - + - - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - - + - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - + - - - - - - +"/> - - - - + - - - - - - - - - - +"/> - - - - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - + - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.pdf index d278fe84adc9..e034e898d257 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.png index b28d5098b835..5c176e83934c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.svg index 88b4235bb26b..2b3aba91681b 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout5.svg @@ -1,405 +1,235 @@ - - + + + + + + 2025-04-09T03:27:51.453043 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - + + - - - - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - - - - + - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + + + + + + + + + + + + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.pdf index 32c853cad2da..67fc2d901fc9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.png index cc203bf27864..faf641ba1ef3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.svg index 51e048e2d92b..4b4aa7071372 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout6.svg @@ -1,1229 +1,784 @@ - - + + + + + + 2025-04-09T03:27:52.608841 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - + - + - + - - - - - - - - - - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - - - + - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - - - - + - - - - - + - - - - - - - - - +"/> - - - - - - + - - - - - - - - +"/> - - - - + + - - + + - - + + - - + + - - + + - - + + + + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - + - + - - - - - - - - - - - - + + - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.pdf index b352ed0ebadd..03efb3454886 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.png index fa02aad51ce1..613e695f05a4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.svg index c0aa6d0755a3..da698dd4ffb9 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout7.svg @@ -1,636 +1,208 @@ - - + + + + + + 2025-04-09T03:27:52.779409 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - + - + - + - - - - - - - - - - - - + - - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - - - + - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - + - - - - - - +"/> - - - - + - - - - - - - - - - +"/> - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + + + + + + + + + + + + - + + + - - - - - - - - - - - - - - +"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.pdf index 73a34ff7e3ac..01d5d00781b8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.png index 13b7d894a265..e8aaa577dfcd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.svg index 538d4441b2b8..f5acea9d851b 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout8.svg @@ -1,505 +1,200 @@ - - + + + + + + 2025-04-09T03:27:52.671035 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - - - - +" style="fill: #ffffff"/> - + - + - + - - - - - - - - - - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - + - + - - - - + - - - - - - +"/> - - - - - - - + - - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - + - - - - - - +"/> - - - - + - - - - - - - - - - +"/> - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - +"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.pdf index c694c8431a21..d3a9b1286581 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.pdf and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.png index 2d63e6987a38..cd61aba5d9da 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.png and b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.svg index ea6a6ea62151..0524e0b4a589 100644 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.svg +++ b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout9.svg @@ -1,1017 +1,684 @@ - - + + + + + + 2025-04-09T03:27:52.897268 + image/svg+xml + + + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ + + + + + - + - +" style="fill: #ffffff"/> - - - - - - - - - - - - - +" style="fill: #ffffff"/> - - - - - - - - - + - + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - - - - + + - - - - - - + - + - - - - - - - - - - + + - - - - - - + - + - - - - - - - - - - + + - - - - - - + - + - - - - - - - - - - + + - - - - - - + - + - - - - - - - - - - + + - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.pdf deleted file mode 100644 index 7300cf8ed51d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.png deleted file mode 100644 index a4d5e8f6f22a..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.svg deleted file mode 100644 index e0587bbb902e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes1.svg +++ /dev/null @@ -1,1613 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.pdf b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.pdf deleted file mode 100644 index 85f669de45bc..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.png b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.png deleted file mode 100644 index effa2c5d9f64..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.svg b/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.svg deleted file mode 100644 index 88f7e0637e4e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_tightlayout/tight_layout_offsetboxes2.svg +++ /dev/null @@ -1,1469 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf index 640df51ac227..43daec43d648 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png index 22b08c23bd49..e889450de817 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg index 8d52765e3ae0..091a693b0e53 100644 --- a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg +++ b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-08T03:00:34.113585 + image/svg+xml + + + Matplotlib v3.3.0rc1.post628+gb07dd36f3, https://matplotlib.org/ + + + + + - + @@ -27,37 +38,37 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -487,1956 +498,1956 @@ C -2.683901 -1.55874 -3 -0.795609 -3 0 C -3 0.795609 -2.683901 1.55874 -2.12132 2.12132 C -1.55874 2.683901 -0.795609 3 0 3 z -" id="mefe0d42b30" style="stroke:#1f77b4;"/> +" id="mfb1b36b408" style="stroke:#1f77b4;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2446,45 +2457,45 @@ z +" id="m03b3b1dbf4" style="stroke:#000000;stroke-width:0.8;"/> - + - + - + - + - + - + @@ -2495,2971 +2506,3790 @@ L 0 3.5 +" id="m01a773088f" style="stroke:#000000;stroke-width:0.8;"/> - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -5467,7 +6297,7 @@ L 414.72 41.472 - + diff --git a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png index 80ea47079014..fee5b15e5182 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png and b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png b/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png new file mode 100644 index 000000000000..249f15d238dd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/rotation.eps b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.eps new file mode 100644 index 000000000000..ba19f4bc5c50 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.eps @@ -0,0 +1,9427 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%BoundingBox: 0 0 461 346 +%%HiResBoundingBox: 0.000000 0.000000 460.800000 345.600000 +%%Invocation: gs -dBATCH -dNOPAUSE -r6000 -sDEVICE=ps2write -dEPSCrop -sOutputFile=? ? +%%Creator: GPL Ghostscript 9561 (ps2write) +%%LanguageLevel: 2 +%%CreationDate: D:20230427203839-04'00' +%%EndComments +%%BeginProlog +save +countdictstack +mark +newpath +/showpage {} def +/setpagedevice {pop} def +%%EndProlog +%%Page 1 1 +%%BeginProlog +10 dict dup begin +/DSC_OPDFREAD true def +/SetPageSize true def +/EPS2Write false def +end +count 0 ne{ +dup type/dicttype eq{ +dup/EPS2Write known{ +dup/EPS2Write get not +} +{ +true +}ifelse +} +{ +true +}ifelse +} +{ +true +}ifelse +10 dict begin +/this currentdict def +/y 720 def +/ebuf 200 string def +/prnt{ +36//this/y get moveto//ebuf cvs show +//this/y 2 copy get 12 sub put +}bind def +/newline{ +36//this/y get moveto +//this/y 2 copy get 12 sub put +}bind def +{ +errordict/handleerror +{systemdict begin +$error begin +newerror +{(%%[ Error handled by opdfread.ps : )print errorname//ebuf cvs print(; OffendingCommand: ) +print/command load//ebuf cvs print( ]%%)= flush +/newerror false store vmstatus pop pop 0 ne +{grestoreall +}if +errorname(VMerror)ne +{showpage +}if +initgraphics +0 720 moveto +errorname(VMerror)eq +{//this/ehsave known +{clear//this/ehsave get restore 2 vmreclaim +}if +vmstatus exch pop exch pop +} +/Courier 12 selectfont +{ +(ERROR: )//prnt exec errorname//prnt exec +(OFFENDING COMMAND: )//prnt exec +/command load//prnt exec +$error/ostack known{ +(%%[STACK:)= +(STACK:)//prnt exec +$error/ostack get aload length{ +//newline exec +dup mark eq{ +(-mark-)dup = show +}{ +dup type/nametype eq{ +dup xcheck not{ +(/)show +(/)print +}if +}if +dup =//ebuf cvs show +}ifelse +}repeat +}if +}ifelse +(%%]%)= +//systemdict/showpage get exec +quit +}if +end +end +}bind readonly put +}if +end +50 dict begin +count 0 ne{ +dup type/dicttype eq{ +{def}forall +false +} +{ +true +}ifelse +} +{ +true +}ifelse +{ +( *** Warning: global definitions dictionary not found, file may be corrupted.\n)print flush +}if +/DefaultSwitch +{ +dup where{ +pop pop +}{ +false def +}ifelse +}bind def +/=string 256 string def +/=only{ +//=string cvs print +}bind def +/HexDigits(0123456789ABCDEF)readonly def +/PrintHex +{8{ +dup -28 bitshift 15 and//HexDigits exch 1 getinterval//=only exec +4 bitshift +}repeat +pop +}bind def +/PDFR_DEBUG DefaultSwitch +/PDFR_DUMP DefaultSwitch +/PDFR_STREAM DefaultSwitch +/TTFDEBUG DefaultSwitch +/RotatePages DefaultSwitch +/FitPages DefaultSwitch +/CenterPages DefaultSwitch +/SetPageSize DefaultSwitch +/error +{ +counttomark 1 sub -1 0{ +index dup type/arraytype eq{==}{=only}ifelse +}for +()= +cleartomark +....Undefined +}bind def +//SetPageSize{ +//RotatePages//FitPages or//CenterPages or{ +mark(/RotatePages, /FitPages and CenterPages are not allowed with /SetPageSize)//error exec +}if +} +{ +//FitPages//CenterPages and{ +mark(CenterPages is not allowed with /FitPages)//error exec +}if +} +ifelse +/knownget +{ +2 copy known{ +get true +}{ +pop pop false +}ifelse +}bind def +/IsUpper +{dup(A)0 get ge exch(Z)0 get le and +}bind def +/cpa2g{ +dup length array +0 1 2 index length 1 sub{ +dup 3 index exch get cp2g +3 copy put pop pop +}for +exch pop +}bind def +/cpd2g{ +dup length dict exch{ +cp2g 2 index 3 1 roll put +}forall +}bind def +/cps2g{ +dup length string copy +}bind def +/cp2gprocs +<> +def +/cp2g{ +dup gcheck not{ +dup//cp2gprocs 1 index type +2 copy known{ +get currentglobal 3 1 roll true setglobal exec exch setglobal +1 index wcheck not{readonly}if +1 index xcheck{cvx}if +exch pop +}{ +pop pop +}ifelse +}if +}bind def +/BlockBuffer 65535 string def +/PDFReader currentdict def +/ObjectRegistryMaxLength 50000 def +/ObjectRegistry 10 dict def +ObjectRegistry +begin +0 ObjectRegistryMaxLength dict def +end +/CurrentObject null def +/DoneDocumentStructure false def +/GraphicState 20 dict begin +/InitialTextMatrix matrix def +/InitialMatrix matrix currentmatrix def +currentdict end def +/TempMatrix matrix def +/GraphicStateStack 20 array def +/GraphicStateStackPointer 0 def +/InitialTextMatrixStack 20 array def +/InitialTextMatrixStackPointer 0 def +/PDFColorSpaces 50 dict def +/InstalledFonts 50 dict def +/MacRomanEncodingInverse null def +currentglobal false setglobal +userdict/PDFR_InitialGS gstate put +userdict/PDFR_Patterns 50 dict put +userdict/FuncDataReader 10 dict put +setglobal +/InitialExtGState 20 dict begin +/BG2 currentblackgeneration cp2g def +/UCR2 currentundercolorremoval cp2g def +/TR2 currentglobal false setglobal[currentcolortransfer]exch setglobal cp2g def +/HT currenthalftone cp2g def +currentdict end readonly def +/InitialGraphicState 20 dict begin +/FontSize 0 def +/CharacterSpacing 0 def +/TextLeading 0 def +/TextRenderingMode 0 def +/WordSpacing 0 def +currentdict end readonly def +/SimpleColorSpaceNames 15 dict begin +/DeviceGray true def +/DeviceRGB true def +/DeviceCMYK true def +currentdict end readonly def +/1_24_bitshift_1_sub 1 24 bitshift 1 sub def +/ReadFontProcs 10 dict def +/GetObject +{ +dup ObjectRegistryMaxLength idiv +//PDFReader/ObjectRegistry get exch knownget{ +exch knownget +}{ +pop false +}ifelse +}bind def +/PutObject +{ +1 index ObjectRegistryMaxLength idiv +//PDFReader/ObjectRegistry get 1 index knownget{ +exch pop +3 1 roll put +}{ +//PDFReader/ObjectRegistry get dup +begin +1 index ObjectRegistryMaxLength dict def +end +exch get +3 1 roll put +}ifelse +}bind def +/Register +{ +1 index GetObject{ +dup xcheck{ +4 3 roll pop +//PDFR_DEBUG{ +(Have a daemon for )print 2 index == +}if +exec +}{ +dup null ne{ +mark(The object )4 index(is already defined : )4 index//error exec +}{ +pop +}ifelse +3 2 roll +exec +}ifelse +}{ +3 2 roll +exec +}ifelse +PutObject +}bind def +/IsRegistered +{ +GetObject{ +null ne +}{ +false +}ifelse +}bind def +/GetRegistered +{ +dup GetObject not{ +exch mark exch(Object )exch( isn't defined before needed (1).)//error exec +}if +dup xcheck{ +exch mark exch(Object )exch( isn't defined before needed (2).)//error exec +}{ +dup null eq{ +exch mark exch(Object )exch( isn't defined before needed (3).)//error exec +}if +exch pop +}ifelse +}bind def +/StandardFontNames<< +/Times-Roman true +/Helvetica true +/Courier true +/Symbol true +/Times-Bold true +/Helvetica-Bold true +/Courier-Bold true +/ZapfDingbats true +/Times-Italic true +/Helvetica-Oblique true +/Courier-Oblique true +/Times-BoldItalic true +/Helvetica-BoldOblique true +/Courier-BoldOblique true +>>def +/CleanAllResources +{//PDFR_DEBUG{ +(CleanAllResources beg)= +}if +//PDFReader/ObjectRegistry get{ +dup length 0 exch 1 exch 1 sub{ +2 copy get dup xcheck{ +pop pop +}{ +dup null eq{ +pop pop +}{ +dup type/dicttype eq{/.Global known}{pop false}ifelse{ +pop +}{ +//PDFR_DEBUG{ +(Dropping )print dup = +}if +1 index exch/DroppedObject put +}ifelse +}ifelse +}ifelse +}for +pop +}forall +FontDirectory length dict begin +FontDirectory{ +pop +dup//StandardFontNames exch known not{ +dup null def +}if +pop +}forall +currentdict +end{ +pop +//PDFR_DEBUG{ +(Undefining font )print dup = +}if +undefinefont +}forall +//PDFR_DEBUG{ +(CleanAllResources end)= +}if +}bind def +/PrintReference +{ +//PDFR_DEBUG{ +({ )print +dup{ +=only( )print +}forall +( })= +}if +}bind def +/R +{ +0 ne{ +exch mark exch(A referred object generation )exch( isn't 0.)//error exec +}if +[ +exch//GetRegistered/exec load +]cvx +//PrintReference exec +}bind def +/IsObjRef +{ +dup type/arraytype eq{ +dup length 3 eq{ +dup xcheck exch +dup 0 get type/integertype eq 3 2 roll and exch +dup 1 get//GetRegistered eq 3 2 roll and exch +2 get/exec load eq and +}{ +pop false +}ifelse +}{ +pop false +}ifelse +}bind def +/DoNothing +{ +}def +/RunTypeDaemon +{ +dup type/dicttype eq{ +dup/Type//knownget exec{ +//PDFReader/TypeDaemons get exch +//knownget exec{ +exec +}if +}if +}if +}bind def +/obj +{ +//PDFR_DEBUG{ +(Defining )print 1 index =only( )print dup =only( obj)= +}if +0 ne{ +exch mark exch(An object generation )exch( isn't 0.)//error exec +}if +}bind def +/endobj +{ +//PDFR_DEBUG{ +(endobj )= +}if +count 1 eq{ +pop +}{ +dup type/dicttype eq{ +dup/.endobj_daemon//knownget exec{ +//PDFR_DEBUG{(.endobj_daemon for )print 2 index =}if +exec +}if +}if +dup type/dicttype eq{dup/ImmediateExec known}{false}ifelse{ +pop pop +}{ +//PDFR_DEBUG{ +(Storing )print 1 index = +}if +//RunTypeDaemon exec +//DoNothing 3 1 roll//Register exec +}ifelse +}ifelse +}bind def +/StoreBlock +{ +//PDFR_DEBUG{ +(StoreBlock )print//PDFReader/BlockCount get =only(, Length = )print dup length = +}if +dup length string copy +//PDFReader/BlockCount get exch +//PDFReader/CurrentObject get 3 1 roll +put +//PDFReader/BlockCount get 1 add +//PDFReader exch/BlockCount exch put +}bind def +/CheckLength +{dup type/integertype ne{ +mark(Object length isn't an integer.)//error exec +}if +}bind def +/ResolveD +{ +3 copy pop get +dup//IsObjRef exec{ +//PDFR_DEBUG{ +(Resolving )print//PrintReference exec +}if +exec +exch exec +}{ +exch pop +}ifelse +dup 4 1 roll +put +}bind def +/ResolveA +{2 index 2 index get +dup//IsObjRef exec{ +exec +exch exec +3 copy put +}{ +exch pop +}ifelse +exch pop exch pop +}bind def +/StoreStream +{ +dup//PDFReader exch/CurrentObject exch put +//PDFReader/BlockCount 0 put +dup/Length//CheckLength//ResolveD exec +//PDFR_DEBUG{ +(StoreStream Length = )print dup = +}if +currentfile exch()/SubFileDecode filter +{dup//BlockBuffer readstring{ +//StoreBlock exec +}{ +//StoreBlock exec +exit +}ifelse +}loop +pop +//PDFReader/CurrentObject null put +//PDFR_DEBUG{ +(StoreStream end.)= +}if +}bind def +/MakeStreamDumper +{ +//PDFR_DEBUG{ +(MakeStreamDumper beg.)= +}if +currentglobal exch dup gcheck setglobal +[exch +1 dict dup/c 0 put exch +1024 string +{readstring pop +(StreamDumper )print 1 index/c get =string cvs print( )print +dup length =string cvs print( <)print dup print(>\n)print +dup length +3 2 roll +dup/c get +3 2 roll +add/c exch put +}/exec load +] +cvx 0()/SubFileDecode filter +exch setglobal +//PDFR_DEBUG{ +(MakeStreamDumper end.)= +}if +}bind def +/ShortFilterNames 15 dict begin +/AHx/ASCIIHexDecode def +/A85/ASCII85Decode def +/LZW/LZWDecode def +/Fl/FlateDecode def +/RL/RunLengthDecode def +/CCF/CCITTFaxDecode def +/DCT/DCTDecode def +currentdict end readonly def +/AppendFilters +{ +//PDFR_DEBUG{ +(AppendFilters beg.)= +}if +dup 3 1 roll +/Filter//knownget exec{ +dup type/nametype eq{ +dup//ShortFilterNames exch//knownget exec{ +exch pop +}if +2 index/DecodeParms//knownget exec{ +exch +}if +filter +}{ +dup 0 exch 1 exch length 1 sub{ +2 copy get +dup//ShortFilterNames exch//knownget exec{ +exch pop +}if +3 1 roll +4 index/DecodeParms//knownget exec{ +exch get +}{ +pop null +}ifelse +dup null eq{ +pop 3 1 roll filter exch +}{ +3 1 roll +4 1 roll filter exch +}ifelse +}for +pop +}ifelse +//PDFR_DEBUG//PDFR_DUMP and{ +//MakeStreamDumper exec +}if +}if +exch pop +//PDFR_DEBUG{ +(AppendFilters end.)= +}if +}bind def +/ExecuteStream +{ +dup//PDFReader exch/CurrentObject exch put +dup/Length//CheckLength//ResolveD exec +//PDFR_DEBUG{ +(ExecuteStream id = )print 2 index =only( Length = )print dup = +}if +//PDFReader/InitialGraphicState get +//PDFReader/GraphicState get copy pop +//PDFReader/Operators get begin +currentfile exch()/SubFileDecode filter +1 index//AppendFilters exec +cvx mark exch +exec +counttomark 0 ne{ +mark(Data left on ostack after an immediate stream execution.)//error exec +}if +cleartomark +end +//PDFR_DEBUG{ +(ExecuteStream end.)= +}if +//PDFReader/CurrentObject null put +dup/IsPage known{ +dup/Context get/NumCopies//knownget exec{ +1 sub{ +copypage +}repeat +}if +EPS2Write not{showpage}if +pagesave restore +}if +}bind def +/stream +{ +//PDFR_DEBUG{ +1 index =only( stream)= +}if +1 index GetObject{ +dup xcheck{ +exec +1 index null PutObject +}{ +pop +}ifelse +}if +dup/ImmediateExec known{ +dup/GlobalExec//knownget exec{ +currentglobal 4 1 roll +setglobal +//ExecuteStream exec +3 2 roll setglobal +}{ +//ExecuteStream exec +}ifelse +}{ +//StoreStream exec +}ifelse +dup/.CleanResources//knownget exec{ +/All eq{ +//CleanAllResources exec +}if +}if +}bind def +/HookFont +{ +//PDFR_DEBUG{ +(Loaded the font )print dup/FontName get = +}if +{ +dup/FontFileType get dup/Type1 eq exch/MMType1 eq or{ +dup/FontName get +//PDFReader/RemoveFontNamePrefix get exec +findfont +exit +}if +dup/FontFileType get/TrueType eq{ +//PDFReader/MakeType42 get exec +//PDFR_DEBUG{ +(Font dict <<)= +dup{ +1 index/sfnts eq{ +exch pop +(/sfnts [)print +{ +(-string\()print length//=only exec(\)- )= +}forall +(])= +}{ +exch//=only exec( )print == +}ifelse +}forall +(>>)= +}if +dup/FontName get exch definefont +exit +}if +mark(FontHook has no proc for )2 index/FontFileType get//error exec +}loop +/Font exch put +}bind def +/endstream +{ +}bind def +/xref +{ +//PDFR_DEBUG{ +(xref)= +//PDFR_DUMP{ +//PDFReader/ObjectRegistry get == +}if +}if +end +count 0 ne{ +mark(Excessive data on estack at the end of the interpretation.)//error exec +}if +currentfile 1(%%EOF)/SubFileDecode filter +flushfile +cleardictstack +}bind def +/ResolveDict +{dup{ +pop 1 index exch +//DoNothing//ResolveD exec +pop +}forall +pop +}bind def +/SetupPageView +{ +//PDFR_DEBUG{ +(SetupPageView beg)= +}if +//DSC_OPDFREAD not{ +//GraphicState/InitialMatrix get setmatrix +}if +/MediaBox get aload pop +3 index neg 3 index neg translate +3 -1 roll sub 3 1 roll exch sub exch +userdict/.HWMargins//knownget exec{ +aload pop +}{ +currentpagedevice/.HWMargins//knownget exec{ +aload pop +}{ +0 0 0 0 +}ifelse +}ifelse +currentpagedevice/PageSize get aload pop +3 -1 roll sub 3 1 roll exch sub exch +exch 3 index sub exch 3 index sub +//SetPageSize{ +//PDFR_DEBUG{ +(Setting page size to )print 1 index//=only exec( )print dup = +}if +pop pop 3 index 3 index 2 copy +currentglobal false setglobal 3 1 roll +currentpagedevice dup/PageSize known{ +/PageSize get aload pop +}{ +0 0 +}ifelse +round cvi 2 index round cvi eq +exch round cvi 3 index round cvi eq and +{ +//PDFR_DEBUG{(PageSize matches request)== flush}if +pop pop +}{ +/MediaRequested where{ +//PDFR_DEBUG{(MediaRequested is true, check against new request)== flush}if +/MediaRequested get aload pop +round cvi 2 index round cvi eq +exch round cvi 3 index round cvi eq and +{ +//PDFR_DEBUG{(MediaRequested same as current request, ignore)== flush}if +pop pop false +}{ +//PDFR_DEBUG{(MediaRequested different to current request)== flush}if +true +}ifelse +}{ +//PDFR_DEBUG{(No MediaRequested yet)== flush}if +true +}ifelse +{ +//PDFR_DEBUG{(Setting pagesize)== flush}if +2 array astore +dup/MediaRequested exch def +<< exch/PageSize exch >>setpagedevice +}if +}ifelse +userdict/PDFR_InitialGS gstate put +setglobal +}if +//RotatePages{ +2 copy gt 6 index 6 index gt ne{ +1 index 5 index le 1 index 5 index le and not +}{ +false +}ifelse +}{ +false +}ifelse +{//CenterPages{ +//PDFR_DEBUG{ +(Rotating page, and then centering it)== +}if +90 rotate +0 5 index neg translate +5 index 1 index exch sub 2 div +2 index 6 index sub 2 div neg +translate +}{ +//FitPages{ +1 index 5 index div 1 index 7 index div +2 copy gt{ +exch +}if +pop dup scale +}if +90 rotate +0 5 index neg translate +}ifelse +}{ +//CenterPages{ +//PDFR_DEBUG{ +(Ccentering page)== +}if +1 index 6 index sub 2 div +1 index 6 index sub 2 div +translate +}{ +//FitPages{ +1 index 6 index div 1 index 6 index div +2 copy gt{ +exch +}if +pop dup scale +}if +}ifelse +}ifelse +pop pop +translate +pop pop +//PDFR_DEBUG{ +(SetupPageView end)= +}if +}bind def +/PageContentsDaemon +{ +//PDFR_DEBUG{ +(Executing PageContentsDaemon for )print 2 index = +}if +1 index exch/Context exch put +dup/ImmediateExec true put +/pagesave save def +dup/IsPage true put +SetPageSize{dup/Context get//SetupPageView exec}if +}bind def +/FontFileDaemon +{ +//PDFR_DEBUG{ +(Executing FontFileDaemon for )print 2 index = +}if +dup/FontFileType get +2 index exch +dup//ReadFontProcs exch//knownget exec{ +exch pop exec +}{ +mark(FontFile reader for )2 index( isn't implemented yet.)//error exec +}ifelse +//PDFR_DEBUG{ +(FontFileDaemon end)= +}if +pop +}bind def +/FontDescriptorDaemon +{ +//PDFR_DEBUG{ +(Executing FontDescriptorDaemon for )print 2 index = +}if +2 copy/FontResource exch put +/Subtype get 1 index exch/FontFileType exch put +}bind def +/UnPDFEscape{ +dup dup length string cvs +dup(#)search{ +{ +pop +(16#--)2 index 0 2 getinterval +1 index 3 2 getinterval copy pop +cvi +0 exch put +0 +1 index 2 1 index length 2 sub getinterval +3 copy putinterval +length +3 copy exch put +getinterval +(#)search not{ +pop exit +}if +}loop +(\0)search pop exch pop exch pop +cvn +exch pop +}{ +pop pop +}ifelse +}bind def +/TypeDaemons<< +/Page +{//PDFR_DEBUG{ +(Recognized a page.)= +}if +dup/Contents//knownget exec{ +0 get//DoNothing exch +[ +3 index//PageContentsDaemon/exec load +]cvx +//Register exec +}{ +(fixme: page with no Contents won't be printed.)= +}ifelse +}bind +/FontDescriptor +{//PDFR_DEBUG{ +(Recognized a font descriptor.)= +}if +dup/FontName//knownget exec{ +1 index/FontName 3 -1 roll//UnPDFEscape exec put +}if +dup dup/FontFile known{/FontFile}{/FontFile2}ifelse +//knownget exec{ +0 get//DoNothing exch +[ +3 index//FontFileDaemon/exec load +]cvx +//Register exec +}{ +(Font descriptor )print 1 index =only( has no FontFile.)= +}ifelse +}bind +/Font +{//PDFR_DEBUG{ +(Recognized a font resource.)= +}if +dup/BaseFont//knownget exec{ +//UnPDFEscape exec 2 copy/BaseFont exch put +//PDFReader/RemoveFontNamePrefix get exec +currentglobal exch +dup/Font resourcestatus{ +pop pop +//PDFReader/GetInstalledFont get exec pop +}{ +pop +}ifelse +setglobal +}if +dup/FontDescriptor//knownget exec{ +0 get +dup//IsRegistered exec{ +//PDFR_DEBUG{ +(already registered )print dup = +}if +pop +}{ +//DoNothing exch +[ +3 index//FontDescriptorDaemon/exec load +]cvx +//Register exec +}ifelse +}if +}bind +>>def +/MakeStreamReader +{dup +[ +exch +//PDFR_DEBUG{ +(Stream proc ) +/print load +//PDFR_STREAM{ +(<) +/print load +}if +}if +1 dict dup/i -1 put +/dup load +/i +/get load +1 +/add load +/dup load +3 +1 +/roll load +/i +/exch load +/put load +//knownget +/exec load +/not load +{()} +/if load +//PDFR_DEBUG{ +//PDFR_STREAM{ +/dup load +/print load +(>) +/print load +}if +( end of stream proc.\n) +/print load +}if +]cvx +//PDFR_DEBUG{ +(Stream reader )print dup == +}if +0()/SubFileDecode filter +exch//AppendFilters exec +}bind def +/RunDelayedStream +{ +//GraphicState/InitialTextMatrix get +//InitialTextMatrixStack//PDFReader/InitialTextMatrixStackPointer get +2 copy get null eq{ +2 copy currentglobal true setglobal matrix exch setglobal put +}if +get copy pop +//PDFReader/InitialTextMatrixStackPointer 2 copy get 1 add put +//MakeStreamReader exec +mark exch +cvx exec +counttomark 0 ne{ +mark(Data left on ostack after a delayed stream execution.)//error exec +}if +cleartomark +//PDFReader/InitialTextMatrixStackPointer 2 copy get 1 sub put +//InitialTextMatrixStack//PDFReader/InitialTextMatrixStackPointer get get +//GraphicState/InitialTextMatrix get +copy pop +}bind def +//ReadFontProcs begin +/Type1 +{//PDFR_DEBUG{ +(ReadFontProcs.Type1)= +}if +dup/.endobj_daemon[4 index//HookFont/exec load]cvx put +dup/ImmediateExec true put +/GlobalExec true put +}bind def +/MMType1//Type1 def +/TrueType +{//PDFR_DEBUG{ +(ReadFontProcs.TrueType)= +}if +dup/.endobj_daemon[4 index//HookFont/exec load]cvx put +pop +}bind def +end +/.opdloadttfontdict 50 dict def +.opdloadttfontdict begin +/maxstring 65400 def +end +/.InsertionSort +{ +/CompareProc exch def +/Array exch def +1 1 Array length 1 sub +{ +/Ix exch def +/Value1 Array Ix get def +/Jx Ix 1 sub def +{ +Jx 0 lt{ +exit +}if +/Value2 Array Jx get def +Value1 Value2 CompareProc{ +exit +}if +Array Jx 1 add Value2 put +/Jx Jx 1 sub def +}loop +Array Jx 1 add Value1 put +}for +Array +}bind def +/putu16{ +3 copy -8 bitshift put +exch 1 add exch 16#ff and put +}bind def +/putu32{ +3 copy -16 bitshift putu16 +exch 2 add exch 16#ffff and putu16 +}bind def +/.readtable{ +dup dup 1 and add string +dup 0 4 -1 roll getinterval +3 -1 roll exch +dup()ne{readstring}if pop pop +}bind def +/.readbigtable{ +dup maxstring lt{ +.readtable +}{ +currentuserparams/VMReclaim get -2 vmreclaim +[4 2 roll{ +dup maxstring le{exit}if +1 index maxstring string readstring pop 3 1 roll maxstring sub +}loop .readtable] +exch vmreclaim +}ifelse +}bind def +/ReadTTF +{ +.opdloadttfontdict begin +/TTFontFile exch def +/TableDir TTFontFile 12 string readstring pop def +/tables TTFontFile TableDir 4 getu16 16 mul string readstring pop def +/tabarray tables length 16 idiv array def +TableDir 0 4 getinterval(ttcf)eq{ +QUIET not{(Can't handle TrueType font Collections.)=}if +/.loadttfonttables cvx/invalidfont signalerror +}{ +0 16 tables length 1 sub{ +dup +tables exch 16 getinterval +exch 16 div cvi exch +tabarray 3 1 roll put +}for +}ifelse +tabarray{exch 8 getu32 exch 8 getu32 gt}.InsertionSort pop +/Read TableDir length tables length add def +/tabs[ +tabarray{ +dup 8 getu32 +Read sub +dup 0 gt{ +dup string TTFontFile exch readstring pop pop +Read add/Read exch def +}{ +pop +}ifelse +12 getu32 +dup Read add +/Read exch def +TTFontFile exch .readbigtable +}forall +]def +end +}bind def +/GetLocaType +{ +0 1 tabarray length 1 sub{ +dup tabarray exch get +0 4 getinterval(head)eq{ +tabs exch get +50 gets16 +/LocaType exch def +exit +}{ +pop +}ifelse +}for +}bind def +/GetNumGlyphs +{ +0 1 tabarray length 1 sub{ +dup tabarray exch get +0 4 getinterval(maxp)eq{ +tabs exch get +4 getu16 +/NumGlyphs exch def +exit +}{ +pop +}ifelse +}for +}bind def +/StringToLoca +{ +/LocaIndex exch def +/StringOffset 0 def +{ +dup length StringOffset gt{ +dup +LocaType 1 eq{ +StringOffset getu32 +LocaArray LocaIndex 3 -1 roll put +/LocaIndex LocaIndex 1 add def +/StringOffset StringOffset 4 add +def +}{ +StringOffset getu16 2 mul +LocaArray length LocaIndex gt{ +LocaArray LocaIndex 3 -1 roll put +}{ +pop +}ifelse +/LocaIndex LocaIndex 1 add def +/StringOffset StringOffset 2 add +def +}ifelse +}{ +pop +LocaIndex +exit +}ifelse +}loop +}bind def +/GetSortedLoca +{ +NumGlyphs 1 add array/LocaArray exch def +0 1 tabarray length 1 sub{ +dup tabarray exch get +0 4 getinterval(loca)eq{ +tabs exch get +exit +}{ +pop +}ifelse +}for +dup type/stringtype eq{ +0 StringToLoca pop +}{ +0 exch +{ +exch StringToLoca +}forall +pop +}ifelse +LocaArray{gt}.InsertionSort pop +}bind def +/GetWorkingString +{ +WorkString 0 +GlyfArray GlyfStringIndex get +putinterval +/WorkBytes GlyfArray GlyfStringIndex get length def +/GlyfStringIndex GlyfStringIndex 1 add def +}bind def +/GetWorkingBytes +{ +/BytesToRead exch def +WorkString 0 BytesToRead getinterval +dup length string copy +WorkString BytesToRead WorkBytes BytesToRead sub getinterval +dup length string copy +WorkString 0 3 -1 roll putinterval +/WorkBytes WorkBytes BytesToRead sub def +}bind def +/GetGlyfBytes +{ +/ToRead exch def +WorkBytes 0 eq{ +GetWorkingString +}if +WorkBytes ToRead ge{ +ToRead string dup 0 +ToRead GetWorkingBytes putinterval +}{ +ToRead string +dup +0 +WorkString 0 WorkBytes getinterval +putinterval +dup +WorkBytes +ToRead WorkBytes sub +GetWorkingString +GetWorkingBytes +putinterval +}ifelse +}bind def +/SplitGlyf +{ +/GlyfArray exch def +/DestArray GlyfArray length 2 mul array def +/DestArrayIndex 0 def +/LastLoca 0 def +/NextLocaIndex 0 def +/LastLocaIndex 0 def +/GlyfStringIndex 0 def +/WorkString maxstring string def +/WorkBytes 0 def +{ +LocaArray NextLocaIndex get +LastLoca sub maxstring gt +{ +LocaArray LastLocaIndex get LastLoca sub +GetGlyfBytes +DestArray DestArrayIndex 3 -1 roll put +/DestArrayIndex DestArrayIndex 1 add def +LocaArray LastLocaIndex get/LastLoca exch def +}{ +/LastLocaIndex NextLocaIndex def +/NextLocaIndex NextLocaIndex 1 add def +NextLocaIndex NumGlyphs gt +{ +WorkBytes +GlyfStringIndex GlyfArray length lt{ +GlyfArray GlyfStringIndex get length +add string dup +0 +WorkString 0 WorkBytes getinterval +putinterval +dup +WorkBytes +GetWorkingString +WorkString 0 WorkBytes getinterval +putinterval +}{ +pop +WorkString 0 WorkBytes getinterval +}ifelse +dup length string copy +DestArray DestArrayIndex 3 -1 roll put +exit +}if +}ifelse +}loop +DestArray +}bind def +/ProcessTTData +{ +.opdloadttfontdict begin +0 1 tabarray length 1 sub{ +/ix exch def +tabarray ix get +12 getu32 dup maxstring le{ +dup 4 mod 0 ne{ +4 div cvi 1 add 4 mul string/newstring exch def +/oldstring tabs ix get def +newstring 0 oldstring putinterval +0 1 newstring length oldstring length sub 1 sub{ +newstring exch oldstring length add 0 put +}for +tabs ix newstring put +}{ +pop +}ifelse +}{ +dup 4 mod 0 ne{ +dup maxstring idiv maxstring mul sub +4 idiv 1 add 4 mul string/newstring exch def +tabs ix get +dup length 1 sub dup/iy exch def get/oldstring exch def +newstring 0 oldstring putinterval +0 1 newstring length oldstring length sub 1 sub{ +newstring exch oldstring length add 0 put +}for +tabs ix get iy newstring put +}{ +pop +}ifelse +}ifelse +}for +0 1 tabarray length 1 sub{ +dup tabarray exch get +dup 12 getu32 maxstring gt{ +0 4 getinterval dup(glyf)eq{ +pop +GetLocaType +GetNumGlyphs +GetSortedLoca +dup tabs exch get +SplitGlyf +tabs 3 1 roll put +}{ +(Warning, table )print print( > 64Kb\n)print +pop +}ifelse +}{ +pop +pop +}ifelse +}for +end +}bind def +/Makesfnts +{ +.opdloadttfontdict begin +0 +tabs{ +dup type/stringtype eq{ +pop +1 add +}{ +{ +type/stringtype eq{ +1 add +}if +}forall +}ifelse +}forall +1 add +/TTOffset +TableDir length +tabarray length 16 mul add +def +0 +tabarray{ +exch dup 1 add +3 1 roll +dup +tabs exch get +dup type/stringtype eq{ +length +2 index exch +TTOffset +dup 3 1 roll add +/TTOffset exch def +8 exch putu32 +exch tabarray 3 1 roll +put +}{ +0 exch +{ +dup type/stringtype eq{ +length add +}{ +pop +}ifelse +}forall +2 index exch +TTOffset +dup 3 1 roll add +/TTOffset exch def +8 exch putu32 +exch tabarray 3 1 roll +put +}ifelse +}forall +pop +array +dup 0 +TableDir length +tables length add +string +dup 0 TableDir putinterval +dup 12 tables putinterval +put +dup +/ix 1 def +tabs{ +dup type/stringtype eq{ +ix exch +put dup +/ix ix 1 add def +}{ +{ +dup type/stringtype eq{ +ix exch put dup +/ix ix 1 add def +}{ +pop +}ifelse +}forall +}ifelse +}forall +pop +end +}bind def +/MakeType42 +{ +//PDFR_DEBUG{ +(MakeType42 beg)= +}if +10 dict begin +/FontName 1 index/FontName get def +/FontType 42 def +/FontMatrix[1 0 0 1 0 0]def +/FontBBox 1 index/FontBBox get def +dup/FontResource get +dup/Encoding known{ +//PDFReader/ObtainEncoding get exec +/Encoding get +}{ +pop null +}ifelse +/PDFEncoding exch def +/CharStrings 2 index//PDFReader/MakeTTCharStrings get exec def +/sfnts 2 index//MakeStreamReader exec +ReadTTF +ProcessTTData +Makesfnts +def +/Encoding StandardEncoding def +/PaintType 0 def +currentdict end +//PDFR_DEBUG{ +(MakeType42 end)= +}if +}bind def +/GetInstalledFont +{ +dup//InstalledFonts exch knownget{ +exch pop +}{ +dup findfont dup 3 1 roll +//InstalledFonts 3 1 roll put +}ifelse +}bind def +/RemoveFontNamePrefix +{//=string cvs true +0 1 5{ +2 index exch get//IsUpper exec not{ +pop false exit +}if +}for +{(+)search{ +pop pop +}if +}if +cvn +}bind def +/CheckFont +{dup/Type get/Font ne{ +mark(Resource )3 index( must have /Type/Font .)//error exec +}if +}bind def +/CheckEncoding +{dup type/nametype ne{ +dup/Type get/Encoding ne{ +mark(Resource )3 index( must have /Type/Encoding .)//error exec +}if +}if +}bind def +/ObtainEncoding +{dup/Encoding known{ +dup dup/Encoding//CheckEncoding//ResolveD exec +dup type dup/arraytype eq exch/packedarraytype eq or{ +pop pop +}{ +dup type/nametype eq{ +/Encoding findresource +}{ +dup/BaseEncoding//knownget exec not{ +/StandardEncoding +}if +/Encoding findresource +exch +/Differences//knownget exec{ +exch dup length array copy exch +0 exch +{ +dup type/integertype eq{ +exch pop +}{ +3 copy put pop +1 add +}ifelse +}forall +pop +}if +}ifelse +/Encoding exch put +}ifelse +}{ +dup/Encoding/StandardEncoding/Encoding findresource put +}ifelse +}bind def +/ObtainMetrics +{dup/Widths//knownget exec{ +1 index/Encoding get +256 dict +3 index/Subtype get/TrueType eq{ +1000 +}{ +1 +}ifelse +4 index/MissingWidth//knownget exec not{ +0 +}if +5 index/FirstChar//knownget exec not{ +0 +}if +6 5 roll +dup 0 exch 1 exch length 1 sub{ +2 copy get +exch 3 index add +7 index exch get +dup dup null ne exch/.notdef ne and{ +6 index 3 1 roll exch +6 index div +3 copy pop//knownget exec{ +0 eq +}{ +true +}ifelse +{put +}{ +pop pop pop +}ifelse +}{ +pop pop +}ifelse +}for +pop pop pop pop exch pop +1 index exch/Metrics exch put +}{ +dup/MissingWidth//knownget exec{ +256 dict +2 index/Encoding get{ +dup null ne{ +3 copy 3 2 roll put +}if +pop +}forall +exch pop +1 index exch/Metrics exch put +}if +}ifelse +}bind def +/NotDef +{ +FontMatrix aload pop pop pop exch pop exch pop +1 exch div exch +1 exch div exch +1 index 0 setcharwidth +0 setlinewidth +0 0 moveto +2 copy rlineto +1 index 0 rlineto +neg exch neg exch rlineto +closepath stroke +}bind def +/SaveResourcesToStack +{ +[ +//PDFReader/OldResources known{ +//PDFReader/OldResources get +}{ +null +}ifelse +//PDFReader/CurrentObject get/Context get/Resources get +] +//PDFReader/OldResources 3 -1 roll put +}bind def +/RestoreResourcesFromStack +{ +//PDFReader/OldResources get dup +0 get//PDFReader/OldResources 3 -1 roll put +1 get//PDFReader/CurrentObject get/Context get/Resources 3 -1 roll put +}bind def +/BuildChar +{//PDFR_DEBUG{ +(BuildChar )print dup//=only exec( )print +}if +exch begin +Encoding exch get +//PDFR_DEBUG{ +dup = +}if +dup null eq{ +pop//NotDef exec +} +{ +CharProcs exch//knownget exec +{ +currentfont/Font get/Resources//knownget exec{ +exec +SaveResourcesToStack +//PDFReader/CurrentObject get/Context get +/Resources 3 -1 roll put +//RunDelayedStream exec +RestoreResourcesFromStack +}{ +//RunDelayedStream exec +}ifelse +} +{ +//NotDef exec +}ifelse +}ifelse +end +}bind def +/printdict +{(<<)= +{exch = ==}forall +(>>)= +}bind def +/printfont +{ +dup{ +exch dup = +dup/Encoding eq{ +pop = +}{ +dup/FontInfo eq exch/Private eq or{ +//printdict exec +}{ +== +}ifelse +}ifelse +}forall +}bind def +/ScaleMetrics +{1 index{ +2 index div +3 index +3 1 roll put +}forall +pop +}bind def +/ResolveAndSetFontAux +{exch dup +//PDFReader/CurrentObject get/Context get/Resources get +/Font//DoNothing//ResolveD exec +exch//CheckFont//ResolveD exec +dup/Font//knownget exec{ +exch pop exch pop +}{ +{ +dup/Subtype get dup dup/Type1 eq exch/TrueType eq or exch/MMType1 eq or{ +exch pop +dup/BaseFont get +//RemoveFontNamePrefix exec +//PDFR_DEBUG{ +(Font )print dup = +}if +1 index/FontDescriptor known{ +//PDFR_DEBUG{ +(Font from a font descriptor.)= +}if +1 index +/FontDescriptor//DoNothing//ResolveD exec +/Font//knownget exec{ +exch pop +}{ +//PDFR_DEBUG{ +(Font descriptor has no Font resolved.)= +}if +//GetInstalledFont exec +}ifelse +}{ +//GetInstalledFont exec +}ifelse +exch +dup/Encoding known not{ +1 index/Encoding get 1 index exch/Encoding exch put +}if +//ObtainEncoding exec +//ObtainMetrics exec +exch +dup length dict copy +dup 2 index/Encoding get +/Encoding exch put +1 index/Metrics//knownget exec{ +2 index/Subtype get/TrueType ne{ +1 index/FontMatrix get 0 get +dup 0 eq{ +pop +1 index/FontMatrix get 1 get +dup 0 eq{pop 1}if +}if +0.001 div +//ScaleMetrics exec +}{ +1 index/sfnts known not{ +1 index/FontMatrix get 0 get +dup 0 eq{ +pop +1 index/FontMatrix get 1 get +dup 0 eq{pop 1}if +}if +//ScaleMetrics exec +}if +}ifelse +1 index exch/Metrics exch put +}if +1 index/BaseFont get +exch +dup/FID undef +dup/UniqueID undef +definefont +dup 3 1 roll +/Font exch put +exit +}if +dup/Subtype get/Type3 eq{ +//ObtainEncoding exec +2 copy exch/FontName exch put +dup/CharProcs get//ResolveDict exec +dup/FontType 3 put +dup/BuildChar//BuildChar put +dup dup/Font exch put +dup 3 1 roll +definefont +2 copy ne{ +2 copy/Font exch put +}if +exch pop +exit +}if +dup/Subtype get/Type0 eq{ +}if +dup/Subtype get/CIDFontType0 eq{ +}if +dup/Subtype get/CIDFontType2 eq{ +}if +mark(Unknown font type )2 index/Subtype get//error exec +}loop +}ifelse +exch scalefont setfont +}bind def +/ResolveAndSetFont +{ +//ResolveAndSetFontAux exec +}bind def +/.knownget +{2 copy known{ +get true +}{ +pop pop false +}ifelse +}bind def +/.min +{2 copy lt{ +exch +}if +pop +}bind def +/.max +{2 copy gt{ +exch +}if +pop +}bind def +/.dicttomark +{>> +}bind def +/getu16{ +2 copy get 8 bitshift 3 1 roll 1 add get add +}bind def +/gets16{ +getu16 16#8000 xor 16#8000 sub +}bind def +/getu32{ +2 copy getu16 16 bitshift 3 1 roll 2 add getu16 add +}bind def +/gets32{ +2 copy gets16 16 bitshift 3 1 roll 2 add getu16 add +}bind def +/cmapformats mark +0{ +6 256 getinterval{}forall 256 packedarray +}bind +2{ +/sHK_sz 2 def +/sH_sz 8 def +dup 2 getu16/cmapf2_tblen exch def +dup 4 getu16/cmapf2_lang exch def +dup 6 256 sHK_sz mul getinterval/sHKs exch def +0 +0 1 255{ +sHKs exch +2 mul getu16 +1 index +1 index +lt{exch}if pop +}for +/sH_len exch def +dup 6 256 sHK_sz mul add +cmapf2_tblen 1 index sub getinterval +/sH_gIA exch def +/cmapf2_glyph_array 65535 array def +/.cmapf2_putGID{ +/cmapf2_ch cmapf2_ch_hi 8 bitshift cmapf2_ch_lo add def +firstCode cmapf2_ch_lo le +cmapf2_ch_lo firstCode entryCount add lt +and{ +sH_offset idRangeOffset add +cmapf2_ch_lo firstCode sub 2 mul +add 6 add +sH_gIA exch getu16 +dup 0 gt{ +idDelta add +cmapf2_glyph_array exch cmapf2_ch exch put +}{ +pop +}ifelse +}{ +}ifelse +}def +16#00 1 16#ff{ +/cmapf2_ch_hi exch def +sHKs cmapf2_ch_hi sHK_sz mul getu16 +/sH_offset exch def +sH_gIA sH_offset sH_sz getinterval +dup 0 getu16/firstCode exch def +dup 2 getu16/entryCount exch def +dup 4 gets16/idDelta exch def +dup 6 getu16/idRangeOffset exch def +pop +sH_offset 0 eq{ +/cmapf2_ch_lo cmapf2_ch_hi def +/cmapf2_ch_hi 0 def +.cmapf2_putGID +}{ +16#00 1 16#ff{ +/cmapf2_ch_lo exch def +.cmapf2_putGID +}for +}ifelse +}for +pop +0 1 cmapf2_glyph_array length 1 sub{ +dup cmapf2_glyph_array exch get +null eq{cmapf2_glyph_array exch 0 put}{pop}ifelse +}for +cmapf2_glyph_array +}bind +4{ +/etab exch def +/nseg2 etab 6 getu16 def +14/endc etab 2 index nseg2 getinterval def +2 add +nseg2 add/startc etab 2 index nseg2 getinterval def +nseg2 add/iddelta etab 2 index nseg2 getinterval def +nseg2 add/idroff etab 2 index nseg2 getinterval def +pop +/firstcode startc 0 getu16 16#ff00 and dup 16#f000 ne{pop 0}if def +/lastcode firstcode def +/striptopbyte false def +/putglyph{ +glyphs code 3 -1 roll put/code code 1 add def +}bind def +/numcodes 0 def/glyphs 0 0 2 nseg2 3 sub{ +/i2 exch def +/scode startc i2 getu16 def +/ecode endc i2 getu16 def +ecode lastcode gt{ +/lastcode ecode def +}if +}for pop +firstcode 16#f000 ge lastcode firstcode sub 255 le and{ +lastcode 255 and +/striptopbyte true def +}{ +lastcode +}ifelse +1 add +array def +glyphs length 1024 ge{ +.array1024z 0 1024 glyphs length 1023 sub{glyphs exch 2 index putinterval}for +glyphs dup length 1024 sub 3 -1 roll +putinterval +}{ +0 1 glyphs length 1 sub{glyphs exch 0 put}for +}ifelse +/numcodes 0 def/code 0 def +0 2 nseg2 3 sub{ +/i2 exch def +/scode startc i2 getu16 def +/ecode endc i2 getu16 def +numcodes scode firstcode sub +exch sub 0 .max dup/code exch code exch add def +ecode scode sub 1 add add numcodes add/numcodes exch def +/delta iddelta i2 gets16 def +TTFDEBUG{ +(scode=)print scode =only +( ecode=)print ecode =only +( delta=)print delta =only +( droff=)print idroff i2 getu16 = +}if +idroff i2 getu16 dup 0 eq{ +pop scode delta add 65535 and 1 ecode delta add 65535 and +striptopbyte{ +/code scode 255 and def +}{ +/code scode def +}ifelse +{putglyph}for +}{ +/gloff exch 14 nseg2 3 mul add 2 add i2 add add def +striptopbyte{ +/code scode 255 and def +}{ +/code scode def +}ifelse +0 1 ecode scode sub{ +2 mul gloff add etab exch getu16 +dup 0 ne{delta add 65535 and}if putglyph +}for +}ifelse +}for glyphs/glyphs null def +}bind +6{ +dup 6 getu16/firstcode exch def dup 8 getu16/ng exch def +firstcode ng add array +0 1 firstcode 1 sub{2 copy 0 put pop}for +dup firstcode ng getinterval +0 1 ng 1 sub{ +dup 2 mul 10 add 4 index exch getu16 3 copy put pop pop +}for pop exch pop +}bind +.dicttomark readonly def +/cmaparray{ +dup 0 getu16 cmapformats exch .knownget{ +TTFDEBUG{ +(cmap: format )print 1 index 0 getu16 = flush +}if exec +}{ +(Can't handle format )print 0 getu16 = flush +0 1 255{}for 256 packedarray +}ifelse +TTFDEBUG{ +(cmap: length=)print dup length = dup == +}if +}bind def +/postremap mark +/Cdot/Cdotaccent +/Edot/Edotaccent +/Eoverdot/Edotaccent +/Gdot/Gdotaccent +/Ldot/Ldotaccent +/Zdot/Zdotaccent +/cdot/cdotaccent +/edot/edotaccent +/eoverdot/edotaccent +/gdot/gdotaccent +/ldot/ldotaccent +/zdot/zdotaccent +.dicttomark readonly def +/get_from_stringarray +{1 index type/stringtype eq{ +get +}{ +exch{ +2 copy length ge{ +length sub +}{ +exch get exit +}ifelse +}forall +}ifelse +}bind def +/getinterval_from_stringarray +{ +2 index type/stringtype eq{ +getinterval +}{ +string exch 0 +4 3 roll{ +dup length +dup 4 index lt{ +3 index exch sub +exch pop 3 1 roll exch pop +}{ +dup 3 1 roll +4 index sub +5 index length 4 index sub +2 copy gt{exch}if pop +dup 3 1 roll +5 index exch getinterval +5 index 4 index 3 index +getinterval +copy pop +exch pop add exch pop 0 exch +dup 3 index length ge{exit}if +}ifelse +}forall +pop pop +}ifelse +}bind def +/string_array_size +{dup type/stringtype eq{ +length +}{ +0 exch{length add}forall +}ifelse +}bind def +/postformats mark +16#00010000{ +pop MacGlyphEncoding +} +16#00020000{ +dup dup type/arraytype eq{0 get}if length 36 lt{ +TTFDEBUG{(post format 2.0 invalid.)= flush}if +pop[] +}{ +/postglyphs exch def +/post_first postglyphs dup type/arraytype eq{0 get}if def +post_first 32 getu16/numglyphs exch def +/glyphnames numglyphs 2 mul 34 add def +/postpos glyphnames def +/total_length postglyphs//string_array_size exec def +numglyphs array 0 1 numglyphs 1 sub{ +postpos total_length ge{ +1 numglyphs 1 sub{1 index exch/.notdef put}for +exit +}if +postglyphs postpos//get_from_stringarray exec +postglyphs postpos 1 add 2 index//getinterval_from_stringarray exec cvn +exch postpos add 1 add/postpos exch def +2 index 3 1 roll +put +}for +/postnames exch def +numglyphs array 0 1 numglyphs 1 sub{ +dup 2 mul 34 add postglyphs exch 2//getinterval_from_stringarray exec +dup 0 get 8 bitshift exch 1 get add dup 258 lt{ +MacGlyphEncoding exch get +}{ +dup 32768 ge{ +pop/.notdef +}{ +258 sub dup postnames length ge{ +TTFDEBUG{( *** warning: glyph index past end of 'post' table)= flush}if +pop +exit +}if +postnames exch get +postremap 1 index .knownget{exch pop}if +}ifelse +}ifelse +2 index 3 1 roll put +}for +} +ifelse +}bind +16#00030000{ +pop[] +}bind +.dicttomark readonly def +/first_post_string +{ +post dup type/arraytype eq{0 get}if +}bind def +/.getpost{ +/glyphencoding post null eq{ +TTFDEBUG{(post missing)= flush}if[] +}{ +postformats first_post_string 0 getu32 .knownget{ +TTFDEBUG{ +(post: format )print +first_post_string +dup 0 getu16 =only(,)print 2 getu16 = flush +}if +post exch exec +}{ +TTFDEBUG{(post: unknown format )print post 0 getu32 = flush}if[] +}ifelse +}ifelse def +}bind def +/MacRomanEncoding[ +StandardEncoding 0 39 getinterval aload pop +/quotesingle +StandardEncoding 40 56 getinterval aload pop +/grave +StandardEncoding 97 31 getinterval aload pop +/Adieresis/Aring/Ccedilla/Eacute/Ntilde/Odieresis/Udieresis/aacute +/agrave/acircumflex/adieresis/atilde/aring/ccedilla/eacute/egrave +/ecircumflex/edieresis/iacute/igrave +/icircumflex/idieresis/ntilde/oacute +/ograve/ocircumflex/odieresis/otilde +/uacute/ugrave/ucircumflex/udieresis +/dagger/degree/cent/sterling/section/bullet/paragraph/germandbls +/registered/copyright/trademark/acute/dieresis/.notdef/AE/Oslash +/.notdef/plusminus/.notdef/.notdef/yen/mu/.notdef/.notdef +/.notdef/.notdef/.notdef/ordfeminine/ordmasculine/.notdef/ae/oslash +/questiondown/exclamdown/logicalnot/.notdef +/florin/.notdef/.notdef/guillemotleft +/guillemotright/ellipsis/space/Agrave/Atilde/Otilde/OE/oe +/endash/emdash/quotedblleft/quotedblright +/quoteleft/quoteright/divide/.notdef +/ydieresis/Ydieresis/fraction/currency +/guilsinglleft/guilsinglright/fi/fl +/daggerdbl/periodcentered/quotesinglbase/quotedblbase +/perthousand/Acircumflex/Ecircumflex/Aacute +/Edieresis/Egrave/Iacute/Icircumflex +/Idieresis/Igrave/Oacute/Ocircumflex +/.notdef/Ograve/Uacute/Ucircumflex +/Ugrave/dotlessi/circumflex/tilde +/macron/breve/dotaccent/ring/cedilla/hungarumlaut/ogonek/caron +]/Encoding defineresource pop +/TTParser<< +/Pos 0 +/post null +>>def +/readu8 +{read not{ +mark(Insufficient data in the stream.)//error exec +}if +}bind def +/readu16 +{dup//readu8 exec 8 bitshift exch//readu8 exec or +}bind def +/reads16 +{//readu16 exec 16#8000 xor 16#8000 sub +}bind def +/readu32 +{dup//readu16 exec 16 bitshift exch//readu16 exec or +}bind def +/reads32 +{dup//reads16 exec 16 bitshift exch//readu16 exec or +}bind def +/SkipToPosition +{dup//TTParser/Pos get +exch//TTParser exch/Pos exch put +sub +//PDFR_DEBUG{ +(Skipping )print dup//=only exec( bytes.)= +}if +dup 0 eq{ +pop pop +}{ +dup 3 1 roll +()/SubFileDecode filter +exch +{1 index//BlockBuffer readstring pop length +dup 0 eq{pop exch pop exit}if +sub +}loop +0 ne{ +mark(Insufficient data in the stream for SkipToPosition.)//error exec +}if +}ifelse +}bind def +/TagBuffer 4 string def +/ParseTTTableDirectory +{//PDFR_DEBUG{ +(ParseTTTableDirectory beg)= +}if +15 dict begin +dup//readu32 exec 16#00010000 ne{ +mark(Unknown True Type version.)//error exec +}if +dup//readu16 exec/NumTables exch def +dup//readu16 exec/SearchRange exch def +dup//readu16 exec/EntrySelector exch def +dup//readu16 exec/RangeShift exch def +//PDFR_DEBUG{ +(NumTables = )print NumTables = +}if +NumTables{ +dup//TagBuffer readstring not{ +mark(Could not read TT tag.)//error exec +}if +cvn +[2 index//readu32 exec pop +2 index//readu32 exec +3 index//readu32 exec +] +//PDFR_DEBUG{ +2 copy exch//=only exec( )print == +}if +def +}repeat +pop +//TTParser/Pos 12 NumTables 16 mul add put +currentdict end +//PDFR_DEBUG{ +(ParseTTTableDirectory end)= +}if +}bind def +/ParseTTcmap +{//PDFR_DEBUG{ +(ParseTTcmap beg)= +}if +/cmap get aload pop +3 1 roll +7 dict begin +//PDFR_DEBUG{ +(Current position = )print//TTParser/Pos get = +(cmap position = )print dup = +}if +1 index exch//SkipToPosition exec +//TTParser/Pos get/TablePos exch def +dup//readu16 exec pop +dup//readu16 exec/NumEncodings exch def +//PDFR_DEBUG{ +(NumEncodings = )print NumEncodings = +}if +null +NumEncodings{ +1 index//readu32 exec +2 index//readu32 exec +3 array dup 3 2 roll 0 exch put +2 index null ne{ +dup 0 get 3 index 0 get sub +3 index exch 1 exch put +}if +dup 4 3 roll pop 3 1 roll +def +}repeat +dup 0 get +4 3 roll exch sub +1 exch put +//PDFR_DEBUG{ +currentdict{ +exch dup type/integertype eq{ +//PrintHex exec( )print == +}{ +pop pop +}ifelse +}forall +}if +4 NumEncodings 8 mul add/HeaderLength exch def +//TTParser/Pos//TTParser/Pos get HeaderLength add put +0 +NumEncodings{ +16#7FFFFFF null +currentdict{ +1 index type/integertype eq{ +exch pop dup 0 get +dup 5 index gt{ +dup 4 index lt{ +4 1 roll +exch pop exch pop +}{ +pop pop +}ifelse +}{ +pop pop +}ifelse +}{ +pop pop +}ifelse +}forall +//PDFR_DEBUG{ +(Obtaining subtable for )print dup == +}if +3 2 roll pop +3 copy pop +TablePos add//SkipToPosition exec +3 copy exch pop 1 get +//TTParser/Pos//TTParser/Pos get 3 index add put +string +readstring not{ +mark(Can't read a cmap subtable.)//error exec +}if +2 exch put +}repeat +pop pop +currentdict end +//PDFR_DEBUG{ +(ParseTTcmap end)= +}if +}bind def +/GetTTEncoding +{//PDFR_DEBUG{ +(GetTTEncoding beg)= +}if +get +exch pop +2 get +10 dict begin +/TTFDEBUG//PDFR_DEBUG def +//cmaparray exec +end +//PDFR_DEBUG{ +(GetTTEncoding end)= +dup == +}if +}bind def +/InverseEncoding +{ +256 dict begin +dup length 1 sub -1 0{ +2 copy get +exch +1 index currentdict exch//knownget exec{ +dup type/arraytype eq{ +aload length 1 add array astore +}{ +2 array astore +}ifelse +}if +def +}for +pop +currentdict end +}bind def +/GetMacRomanEncodingInverse +{//PDFReader/MacRomanEncodingInverse get +dup null eq{ +pop +MacRomanEncoding//InverseEncoding exec +dup//PDFReader exch/MacRomanEncodingInverse exch put +}if +}bind def +/PutCharStringSingle +{ +dup 3 index length lt{ +2 index exch get +dup 0 ne{ +def +}{ +pop pop +}ifelse +}{ +pop pop +}ifelse +}bind def +/PutCharString +{1 index type/nametype ne{ +mark(Bad charstring name)//error exec +}if +dup type/arraytype eq{ +{ +3 copy//PutCharStringSingle exec +pop pop +}forall +pop +}{ +//PutCharStringSingle exec +}ifelse +}bind def +/ComposeCharStrings +{ +//PDFR_DEBUG{ +(ComposeCharStrings beg)= +}if +1 index length 1 add dict begin +/.notdef 0 def +exch +//TTParser/post get +dup null ne{ +exch +1 index length 1 sub -1 0{ +dup 3 index exch get exch +dup 0 eq 2 index/.notdef eq or{ +pop pop +}{ +def +}ifelse +}for +}if +exch pop exch +{ +//PutCharString exec +}forall +pop +currentdict end +//PDFR_DEBUG{ +(ComposeCharStrings end)= +}if +}bind def +/ParseTTpost +{ +//PDFR_DEBUG{ +(ParseTTpost beg)= +}if +/post get aload pop +3 1 roll +//PDFR_DEBUG{ +(Current position = )print//TTParser/Pos get = +(post position = )print dup = +}if +1 index exch//SkipToPosition exec +//TTParser/Pos//TTParser/Pos get 4 index add put +exch dup 65535 le{ +string +readstring not{ +mark(Insufficient data in the stream for ParseTTpost.)//error exec +}if +}{ +[3 1 roll +dup 16384 div floor cvi +exch 1 index 16384 mul +sub exch +1 sub 0 1 3 -1 roll +{ +1 add index +16384 string readstring not{ +mark(Insufficient data in the stream for ParseTTpost.)//error exec +}if +}for +counttomark -2 roll +string readstring not{ +mark(Insufficient data in the stream for ParseTTpost.)//error exec +}if +] +}ifelse +1 dict begin +/post exch def +//.getpost exec +//TTParser/post glyphencoding put +//PDFR_DEBUG{ +(ParseTTpost end)= +glyphencoding == +}if +end +}bind def +/MakeTTCharStrings +{//MakeStreamReader exec +dup dup//ParseTTTableDirectory exec +//TTParser/post null put +dup/post//knownget exec{ +0 get +1 index/cmap get 0 get +lt{ +2 copy//ParseTTpost exec +//ParseTTcmap exec +}{ +2 copy//ParseTTcmap exec +3 1 roll +//ParseTTpost exec +}ifelse +}{ +//ParseTTcmap exec +}ifelse +{ +dup 16#00030001 known{ +//PDFR_DEBUG{ +(Using the TT cmap encoding for Windows Unicode.)= +}if +16#00030001//GetTTEncoding exec +AdobeGlyphList//ComposeCharStrings exec +exit +}if +dup 16#00010000 known{ +//PDFR_DEBUG{ +(Using the TT cmap encoding for Macintosh Roman.)= +}if +16#00010000//GetTTEncoding exec +PDFEncoding dup null eq{ +pop//GetMacRomanEncodingInverse exec +}{ +//InverseEncoding exec +}ifelse +//ComposeCharStrings exec +exit +}if +dup 16#00030000 known{ +//PDFR_DEBUG{ +(Using the TT cmap encoding 3.0 - not sure why Ghostscript writes it since old versions.)= +}if +16#00030000//GetTTEncoding exec +PDFEncoding dup null eq{ +pop//GetMacRomanEncodingInverse exec +}{ +//InverseEncoding exec +}ifelse +//ComposeCharStrings exec +exit +}if +mark(True Type cmap has no useful encodings.)//error exec +}loop +//PDFR_DEBUG{ +(CharStrings <<)= +dup{ +exch +dup type/nametype eq{ +//=only exec +}{ +== +}ifelse +( )print == +}forall +(>>)= +}if +}bind def +/ScaleVal +{ +aload pop +1 index sub +3 2 roll mul add +}bind def +/ScaleArg +{ +aload pop +1 index sub +3 1 roll +sub exch div +}bind def +/ScaleArgN +{ +dup length 2 sub -2 0{ +2 +2 index 3 1 roll getinterval +3 2 roll +exch//ScaleArg exec +1 index length 2 idiv 1 add 1 roll +}for +pop +}bind def +/ComputeFunction_10 +{ +//PDFR_DEBUG{ +(ComputeFunction_10 beg )print 1 index//=only exec( stack=)print count = +}if +exch +dup 1 eq{ +pop dup length 1 sub get +}{ +1 index length 1 sub mul +dup dup floor sub +dup 0 eq{ +pop cvi get +}{ +3 1 roll floor cvi +2 getinterval +aload pop +2 index mul 3 2 roll 1 exch sub 3 2 roll mul add +}ifelse +}ifelse +//PDFR_DEBUG{ +(ComputeFunction_10 end )print dup//=only exec( stack=)print count = +}if +}bind def +/ComputeFunction_n0 +{ +//PDFR_DEBUG{ +(ComputeFunction_n0 beg N=)print dup//=only exec( stack=)print count = +}if +dup 0 eq{ +pop +}{ +dup 2 add -1 roll +dup 3 index length 1 sub ge{ +pop 1 sub +exch dup length 1 sub get exch +//PDFReader/ComputeFunction_n0 get exec +}{ +dup floor cvi dup +4 index exch get +3 index dup +5 add copy +6 2 roll +pop pop pop pop +1 sub +//PDFReader/ComputeFunction_n0 get exec +3 2 roll pop +exch +4 3 roll exch +4 add 2 roll 1 add +3 2 roll exch get +exch 1 sub +//PDFReader/ComputeFunction_n0 get exec +1 index mul +3 1 roll +1 exch sub mul add +}ifelse +}ifelse +//PDFR_DEBUG{ +(ComputeFunction_n0 end )print dup//=only exec( stack=)print count = +}if +}bind def +/FunctionToProc_x01 +{ +dup/Domain get exch +dup/Data get 0 get exch +/Size get length +[4 1 roll +//PDFR_DEBUG{ +{(function beg, stack =)print count//=only exec(\n)print}/exec load +5 2 roll +}if +dup 1 gt{ +{mark exch +3 add 2 roll +//ScaleArgN exec +counttomark dup +3 add -2 roll +pop exch +//ComputeFunction_n0 exec +}/exec load +}{ +pop +3 1/roll load//ScaleArg/exec load +/exch load +//ComputeFunction_10/exec load +}ifelse +//PDFR_DEBUG{ +(function end, stack =)/print load/count load//=only/exec load(\n)/print load +}if +]cvx +//PDFR_DEBUG{ +(Made a procedure for the 1-result function :)= +dup == +}if +}bind def +/FunctionProcDebugBeg +{(FunctionProcDebugBeg )print count = +}bind def +/FunctionProcDebugEnd +{(FunctionProcDebugEnd )print count = +}bind def +/FunctionToProc_x0n +{ +PDFR_DEBUG{ +(FunctionToProc_x0n beg m=)print dup = +}if +1 index/Size get length exch +dup 7 mul 2 add array +PDFR_DEBUG{ +dup 0//FunctionProcDebugBeg put +}{ +dup 0//DoNothing put +}ifelse +dup 1/exec load put +dup 2 5 index/Domain get put +2 index 1 eq{ +dup 3//ScaleArg put +}{ +dup 3//ScaleArgN put +}ifelse +dup 4/exec load put +1 index 1 sub 0 exch 1 exch{ +dup 7 mul 5 add +1 index 4 index 1 sub ne{ +dup 3 index exch 6 index put 1 add +dup 3 index exch/copy load put 1 add +}if +[ +6 index/Data get 3 index get +6 index 1 eq{ +//ComputeFunction_10/exec load +}{ +6 index +//ComputeFunction_n0/exec load +}ifelse +]cvx +3 index exch 2 index exch put 1 add +2 index 1 index/exec load put 1 add +1 index 4 index 1 sub ne{ +2 index 1 index 6 index 1 add put 1 add +2 index 1 index 1 put 1 add +2 index 1 index/roll load put +}if +pop pop +}for +PDFR_DEBUG{ +dup dup length 2 sub//FunctionProcDebugEnd put +}{ +dup dup length 2 sub//DoNothing put +}ifelse +dup dup length 1 sub/exec load put +cvx exch pop exch pop exch pop +//PDFR_DEBUG{ +(Made a procedure for the n-argument function :)= +dup == +}if +PDFR_DEBUG{ +(FunctionToProc_x0n end)= +}if +}bind def +/MakeTableRec +{ +0 +exec +}bind def +/MakeTable +{//PDFR_DEBUG{ +(MakeTable beg )print count = +}if +1 index/Size get exch +1 sub dup +3 1 roll +get +array +1 index 0 eq{ +exch pop exch pop +}{ +dup length 1 sub -1 0{ +3 index 3 index//MakeTableRec exec +2 index 3 1 roll put +}for +exch pop exch pop +}ifelse +//PDFR_DEBUG{ +(MakeTable end )print count = +}if +}bind def +//MakeTableRec 0//MakeTable put +/StoreSample +{ +1 sub +dup 0 eq{ +pop +}{ +-1 1{ +I exch get get +}for +}ifelse +I 0 get 3 2 roll put +}bind def +/ReadSample32 +{ +4{ +File read not{ +mark(Insufficient data for function.)//error exec +}if +}repeat +pop +3 1 roll exch +256 mul add 256 mul add +//1_24_bitshift_1_sub div +}bind def +/ReadSample +{ +Buffer BitsLeft BitsPerSample +{2 copy ge{ +exit +}if +3 1 roll +8 add 3 1 roll +256 mul File read not{ +mark(Insufficient data for function.)//error exec +}if +add +3 1 roll +}loop +sub dup +2 index exch +neg bitshift +2 copy exch bitshift +4 3 roll exch sub +/Buffer exch def +exch/BitsLeft exch def +Div div +}bind def +/ReadSamplesRec +{0 +exec +}bind def +/ReadSamples +{ +//PDFR_DEBUG{ +(ReadSamples beg )print count = +}if +dup 1 eq{ +pop +0 1 Size 0 get 1 sub{ +I exch 0 exch put +0 1 M 1 sub{ +dup Range exch 2 mul 2 getinterval +//PDFR_DEBUG{ +(Will read a sample ... )print +}if +BitsPerSample 32 eq{//ReadSample32}{//ReadSample}ifelse +exec exch//ScaleVal exec +//PDFR_DEBUG{ +(value=)print dup = +}if +exch Table exch get +Size length//StoreSample exec +}for +}for +}{ +1 sub +dup Size exch get 0 exch 1 exch 1 sub{ +I exch 2 index exch put +dup//ReadSamplesRec exec +}for +pop +}ifelse +//PDFR_DEBUG{ +(ReadSamples end )print count = +}if +}bind def +//ReadSamplesRec 0//ReadSamples put +/StreamToArray +{//PDFR_DEBUG{ +(StreamToArray beg )print count = +}if +userdict/FuncDataReader get begin +dup/BitsPerSample get/BitsPerSample exch def +dup/Size get length/N exch def +dup/Range get length 2 idiv/M exch def +1 BitsPerSample bitshift 1 sub/Div exch def +/BitsLeft 0 def +/Buffer 0 def +dup/Size get/Size exch def +dup/Range get/Range exch def +/File 1 index//MakeStreamReader exec def +/I[N{0}repeat]def +M array +dup length 1 sub -1 0{ +2 index N//MakeTable exec +2 index 3 1 roll put +}for +/Table exch def +N//ReadSamples exec +PDFR_DEBUG{ +(Table = )print Table == +}if +/Data Table put +end +//PDFR_DEBUG{ +(StreamToArray end )print count = +}if +}bind def +/FunctionToProc10 +{ +PDFR_DEBUG{ +(FunctionToProc10 beg, Range = )print dup/Range get == +}if +dup/Order//knownget exec{ +1 ne{ +(Underimplemented function Type 0 Order 3.)= +}if +}if +dup//StreamToArray exec +dup/Range get length dup 2 eq{ +pop//FunctionToProc_x01 exec +}{ +2 idiv//FunctionToProc_x0n exec +}ifelse +PDFR_DEBUG{ +(FunctionToProc10 end)= +}if +}bind def +/FunctionToProc12 +{begin +currentdict/C0//knownget exec{length 1 eq}{true}ifelse{ +N +currentdict/C0//knownget exec{ +0 get +}{ +0 +}ifelse +currentdict/C1//knownget exec{ +0 get +}{ +1 +}ifelse +1 index sub +[4 1 roll +{ +4 2 roll +exp mul add +}aload pop +]cvx +}{ +[ +0 1 C0 length 1 sub{ +N +C0 2 index get +C1 3 index get +4 3 roll pop +1 index sub +[/dup load +5 2 roll +{ +4 2 roll +exp mul add +exch +}aload pop +]cvx +/exec load +}for +/pop load +]cvx +}ifelse +end +//PDFR_DEBUG{ +(FunctionType2Proc : )print dup == +}if +}bind def +/FunctionToProc14 +{//MakeStreamReader exec cvx exec +//PDFR_DEBUG{ +(FunctionType4Proc : )print dup == +}if +}bind def +/FunctionToProc1 +{ +dup/FunctionType get +{dup 0 eq{ +pop//FunctionToProc10 exec exit +}if +dup 2 eq{ +pop//FunctionToProc12 exec exit +}if +dup 4 eq{ +pop//FunctionToProc14 exec exit +}if +mark exch(Function type )exch( isn't implemented yet.)//error exec +}loop +}bind def +/FunctionToProc20 +{ +PDFR_DEBUG{ +(FunctionToProc20, Range = )print dup/Range get == +}if +dup/Order//knownget exec{ +1 ne{ +(Underimplemented function Type 0 Order 3.)= +}if +}if +dup//StreamToArray exec +dup/Range get length dup 2 eq{ +pop//FunctionToProc_x01 exec +}{ +2 idiv//FunctionToProc_x0n exec +}ifelse +}bind def +/FunctionToProc +{//PDFR_DEBUG{ +(FunctionToProc beg )print count = +}if +dup type/dicttype eq{ +dup/Domain get length 2 idiv +{ +dup 1 eq{ +pop//FunctionToProc1 exec exit +}if +dup 2 eq{ +pop//FunctionToProc20 exec exit +}if +mark(Functions with many arguments aren't implemented yet.)//error exec +}loop +}{ +//PDFR_DEBUG{(Not a function dict, assume already a procedure.)print}if +}ifelse +//PDFR_DEBUG{ +(FunctionToProc end )print count = +}if +}bind def +/spotfunctions mark +/Round{ +abs exch abs 2 copy add 1 le{ +dup mul exch dup mul add 1 exch sub +}{ +1 sub dup mul exch 1 sub dup mul add 1 sub +}ifelse +} +/Diamond{ +abs exch abs 2 copy add .75 le{ +dup mul exch dup mul add 1 exch sub +}{ +2 copy add 1.23 le{ +.85 mul add 1 exch sub +}{ +1 sub dup mul exch 1 sub dup mul add 1 sub +}ifelse +}ifelse +} +/Ellipse{ +abs exch abs 2 copy 3 mul exch 4 mul add 3 sub dup 0 lt{ +pop dup mul exch .75 div dup mul add 4 div 1 exch sub +}{ +dup 1 gt{ +pop 1 exch sub dup mul exch 1 exch sub +.75 div dup mul add 4 div 1 sub +}{ +.5 exch sub exch pop exch pop +}ifelse +}ifelse +} +/EllipseA{dup mul .9 mul exch dup mul add 1 exch sub} +/InvertedEllipseA{dup mul .9 mul exch dup mul add 1 sub} +/EllipseB{dup 5 mul 8 div mul exch dup mul exch add sqrt 1 exch sub} +/EllipseC{dup mul .9 mul exch dup mul add 1 exch sub} +/InvertedEllipseC{dup mul .9 mul exch dup mul add 1 sub} +/Line{exch pop abs neg} +/LineX{pop} +/LineY{exch pop} +/Square{abs exch abs 2 copy lt{exch}if pop neg} +/Cross{abs exch abs 2 copy gt{exch}if pop neg} +/Rhomboid{abs exch abs 0.9 mul add 2 div} +/DoubleDot{2{360 mul sin 2 div exch}repeat add} +/InvertedDoubleDot{2{360 mul sin 2 div exch}repeat add neg} +/SimpleDot{dup mul exch dup mul add 1 exch sub} +/InvertedSimpleDot{dup mul exch dup mul add 1 sub} +/CosineDot{180 mul cos exch 180 mul cos add 2 div} +/Double{exch 2 div exch 2{360 mul sin 2 div exch}repeat add} +/InvertedDouble{ +exch 2 div exch 2{360 mul sin 2 div exch}repeat add neg +} +.dicttomark readonly def +/CheckColorSpace +{ +dup type/arraytype ne{ +mark(Resource )3 index( must be an array.)//error exec +}if +}bind def +/SubstitutePDFColorSpaceRec +{0 +exec +}bind def +/SubstitutePDFColorSpace +{ +{ +dup 0 get/Pattern eq{ +dup length 1 gt{ +dup dup 1//CheckColorSpace//ResolveA exec +dup type/nametype ne{ +//SubstitutePDFColorSpaceRec exec +}if +1 exch put +}if +exit +}if +dup 0 get/Indexed eq{ +exit +}if +dup 0 get/Separation eq{ +dup dup 2//CheckColorSpace//ResolveA exec +dup type/nametype ne{ +//SubstitutePDFColorSpaceRec exec +}if +2 exch put +exit +}if +dup 0 get/CalGray eq{ +1 get +dup/Gamma//knownget exec{ +[exch[exch/exp load]cvx dup dup] +1 index exch/DecodeLMN exch put +}if +[exch/CIEBasedA exch] +exit +}if +dup 0 get/CalRGB eq{ +1 get +dup/Matrix//knownget exec{ +1 index exch/MatrixLMN exch put +}if +dup/Gamma//knownget exec{ +aload pop +[exch/exp load]cvx +3 1 roll +[exch/exp load]cvx +3 1 roll +[exch/exp load]cvx +3 1 roll +3 array astore +1 index exch/DecodeLMN exch put +}if +[exch/CIEBasedABC exch] +exit +}if +dup 0 get/Lab eq{ +1 get +begin +currentdict/Range//knownget exec{aload pop}{-100 100 -100 100}ifelse +0 100 6 2 roll 6 array astore +/RangeABC exch def +/DecodeABC[{16 add 116 div}bind{500 div}bind{200 div}bind]def +/MatrixABC[1 1 1 1 0 0 0 0 -1]def +{dup 6 29 div ge{dup dup mul mul}{4 29 div sub 108 841 div mul}ifelse} +/DecodeLMN[ +[3 index aload pop WhitePoint 0 get/mul load]cvx +[4 index aload pop WhitePoint 1 get/mul load]cvx +[5 index aload pop WhitePoint 2 get/mul load]cvx +]def pop +//PDFR_DEBUG{ +(Constructed from Lab <<)= +currentdict{exch = ==}forall +(>>)= +}if +[/CIEBasedABC currentdict] +end +exit +pop +}if +dup 0 get/CIEBasedA eq{exit}if +dup 0 get/CIEBasedABC eq{exit}if +mark exch(Unimplemented color space )exch//error exec +}loop +}bind def +//SubstitutePDFColorSpaceRec 0//SubstitutePDFColorSpace put +/ResolveArrayElement +{2 copy get +dup type dup/arraytype eq exch +/packedarraytype eq or{ +dup length 1 ge exch xcheck and{ +2 copy get +dup 0 get type/integertype eq +1 index 1 get type dup/arraytype +eq exch +/packedarraytype eq or +and{ +exec +2 index 4 1 roll put +}{ +pop pop +}ifelse +}{ +pop +}ifelse +}{ +pop pop +}ifelse +}bind def +/ResolveColorSpaceArrayRec +{0 +exec +}bind def +/SetColorSpaceSafe +{ +PDFR_DEBUG{ +(SetColorSpaceSafe beg)= +}if +currentcolorspace dup type/arraytype eq{ +1 index type/arraytype eq{ +dup length 2 index length eq{ +false exch +dup length 0 exch 1 exch 1 sub{ +dup +4 index exch get exch +2 index exch get +ne{ +exch pop true exch exit +}if +}for +pop +{ +setcolorspace +}{ +pop +}ifelse +}{ +pop setcolorspace +}ifelse +}{ +pop setcolorspace +}ifelse +}{ +pop setcolorspace +}ifelse +PDFR_DEBUG{ +(SetColorSpaceSafe end)= +}if +}bind def +/ResolveColorSpaceArray +{ +//PDFR_DEBUG{ +(ResolveColorSpaceArray beg )print dup == +}if +dup 0 get/Indexed eq{ +1//ResolveArrayElement exec +dup dup 1 get +dup type/arraytype eq{ +//SubstitutePDFColorSpace exec +//ResolveColorSpaceArrayRec exec +1 exch put +}{ +pop pop +}ifelse +}if +dup 0 get/Separation eq{ +dup dup 1 get UnPDFEscape 1 exch put +3//ResolveArrayElement exec +dup 3 get//FunctionToProc exec +2 copy 3 exch put +pop +}if +dup 0 get/Pattern eq{ +dup length 1 gt{ +dup 1 get dup type/arraytype eq{ +ResolveColorSpaceArray +1 index 1 3 -1 roll put +}{ +pop +}ifelse +}if +}if +PDFR_DEBUG{ +(Construcrted color space :)= +dup == +}if +//PDFR_DEBUG{ +(ResolveColorSpaceArray end )print dup == +}if +}bind def +//ResolveColorSpaceArrayRec 0//ResolveColorSpaceArray put +/ResolveColorSpace +{ +//PDFR_DEBUG{ +(ResolveColorSpace beg )print dup = +}if +dup//SimpleColorSpaceNames exch known not{ +dup//PDFColorSpaces exch//knownget exec{ +exch pop +//PDFR_DEBUG{ +(ResolveColorSpace known )= +}if +}{ +dup +//PDFReader/CurrentObject get/Context get/Resources get +/ColorSpace//DoNothing//ResolveD exec +exch//CheckColorSpace//ResolveD exec +dup type/arraytype eq{ +//SubstitutePDFColorSpace exec +//ResolveColorSpaceArray exec +dup//PDFColorSpaces 4 2 roll put +}if +}ifelse +}if +//PDFR_DEBUG{ +(ResolveColorSpace end )print dup == +}if +}bind def +/CheckPattern +{ +dup/PatternType//knownget exec{ +dup 1 ne{ +mark(Resource )4 index( is a shading, which can't be handled at level 2. )//error exec +}if +pop +}if +dup/Type knownget{ +/Pattern ne{ +mark(Resource )4 index( must have /Type/Pattern .)//error exec +}if +}if +}bind def +/PaintProc +{/Context get +//RunDelayedStream exec +}bind def +/ResolvePattern +{ +dup +userdict/PDFR_Patterns get +exch//knownget exec{ +exch pop +}{ +dup +//PDFReader/CurrentObject get/Context get/Resources get +/Pattern//DoNothing//ResolveD exec +exch//CheckPattern//ResolveD exec +dup dup/Context exch put +dup/Resources//DoNothing//ResolveD exec pop +dup/PaintProc//PaintProc put +gsave userdict/PDFR_InitialGS get setgstate +currentglobal exch false setglobal +dup/Matrix get +makepattern +exch setglobal +grestore +dup userdict/PDFR_Patterns get +4 2 roll +put +}ifelse +}bind def +/SetColor +{//PDFR_DEBUG{ +(SetColor beg)= +}if +currentcolorspace dup type/nametype eq{ +pop setcolor +}{ +0 get/Pattern eq{ +//ResolvePattern exec setpattern +}{ +setcolor +}ifelse +}ifelse +//PDFR_DEBUG{ +(SetColor end)= +}if +}bind def +/ImageKeys 15 dict begin +/BPC/BitsPerComponent def +/CS/ColorSpace def +/D/Decode def +/DP/DecodeParms def +/F/Filter def +/H/Height def +/IM/ImageMask def +/I/Interpolate def +/W/Width def +currentdict end readonly def +/ImageValues 15 dict begin +/G/DeviceGray def +/RGB/DeviceRGB def +/CMYK/DeviceCMYK def +/I/Indexed def +/AHx/ASCIIHexDecode def +/A85/ASCII85Decode def +/LZW/LZWDecode def +/Fl/FlateDecode def +/RL/RunLengthDecode def +/CCF/CCITTFaxDecode def +/DCT/DCTDecode def +currentdict end readonly def +/GetColorSpaceRange +{2 index/ColorSpace get +dup type/arraytype eq{ +1 get +}if +exch//knownget exec{ +exch pop +}if +}bind def +/DecodeArrays 15 dict begin +/DeviceGray{[0 1]}def +/DeviceRGB{[0 1 0 1 0 1]}def +/DeviceCMYK{[0 1 0 1 0 1 0 1]}def +/Indexed{ +dup/BitsPerComponent get 1 exch bitshift 1 sub[exch 0 exch] +}def +/Separation{[0 1]}def +/CIEBasedA{[0 1]/RangeA//GetColorSpaceRange exec}def +/CIEBasedABC{[0 1 0 1 0 1]/RangeABC//GetColorSpaceRange exec}def +currentdict end readonly def +/Substitute +{1 index//knownget exec{ +exch pop +}if +}bind def +/DebugImagePrinting +{ +//PDFR_DEBUG{ +(Image :)= +dup{exch//=only exec( )print == +}forall +}if +}bind def +/CompleteImage +{ +dup/ColorSpace known{ +dup/ColorSpace//CheckColorSpace//ResolveD exec pop +}if +dup/Decode known not{ +dup/ColorSpace//knownget exec{ +dup type/arraytype eq{ +0 get +}if +//DecodeArrays exch get exec +}{ +[0 1] +}ifelse +1 index exch/Decode exch put +}if +dup/ImageMatrix[2 index/Width get 0 0 5 index/Height get neg +0 7 index/Height get]put +//DebugImagePrinting exec +}bind def +/CompleteInlineImage +{ +//PDFR_DEBUG{ +(CompleteInlineImage beg)= +}if +dup/ImageType known not{ +dup/ImageType 1 put +}if +dup length dict exch{ +exch//ImageKeys//Substitute exec +dup/Filter eq{ +exch//ImageValues//Substitute exec exch +}if +dup/ColorSpace eq{ +exch +dup//ImageValues exch//knownget exec{ +exch pop +}{ +//ResolveColorSpace exec +}ifelse +exch +}if +exch +2 index 3 1 roll put +}forall +//CompleteImage exec +dup/DataSource 2 copy get +2 index//AppendFilters exec put +//PDFR_DEBUG{ +(CompleteInlineImage end)= +}if +}bind def +/CompleteOutlineImage +{ +currentglobal exch dup gcheck setglobal +//PDFR_DEBUG{ +(CompleteOutlineImage beg)= +}if +dup dup//MakeStreamReader exec/DataSource exch put +dup/ImageType known not{ +//CompleteImage exec +dup/ImageType 1 put +dup/ColorSpace known{ +dup/ColorSpace//CheckColorSpace//ResolveD exec +dup type/arraytype eq{ +//ResolveColorSpaceArray exec +//SubstitutePDFColorSpace exec +1 index exch/ColorSpace exch put +}{ +pop +}ifelse +}if +}if +//PDFR_DEBUG{ +(CompleteOutlineImage end)= +}if +exch setglobal +}bind def +/DoImage +{ +//PDFR_DEBUG{ +(DoImage beg)= +}if +gsave +dup/ColorSpace//knownget exec{setcolorspace}if +dup/ImageMask//knownget exec not{false}if +{imagemask}{image}ifelse +grestore +//PDFR_DEBUG{ +(DoImage end)= +}if +}bind def +/GSave +{ +gsave +//PDFReader/GraphicStateStackPointer get +dup//GraphicStateStack exch get null eq{ +dup//GraphicStateStack exch//InitialGraphicState length dict put +}if +dup//GraphicStateStack exch get +//GraphicState exch copy pop +1 add//PDFReader exch/GraphicStateStackPointer exch put +}bind def +/GRestore +{ +grestore +//PDFReader/GraphicStateStackPointer get +1 sub dup +//PDFReader exch/GraphicStateStackPointer exch put +//GraphicStateStack exch get +//GraphicState copy pop +}bind def +/SetFont +{dup//GraphicState exch/FontSize exch put +//ResolveAndSetFont exec +//GraphicState/FontMatrixNonHV currentfont/FontMatrix get 1 get 0 ne put +}bind def +/ShowText +{ +//GraphicState/TextRenderingMode get dup 0 eq +exch 3 eq not currentfont/FontType get 3 eq and or +{ +//GraphicState/WordSpacing get 0 +32 +//GraphicState/CharacterSpacing get 0 +6 5 roll +//GraphicState/FontMatrixNonHV get{ +[ +7 -2 roll pop +5 -2 roll pop +5 -1 roll +{ +exch +pop +3 index add +exch 2 index eq{3 index add}if +4 1 roll +} +currentfont/FontMatrix get 0 get 0 ne{ +1 1 index length 1 sub getinterval cvx +}if +5 index +cshow +pop pop pop] +xshow +}{ +awidthshow +}ifelse +}{ +//GraphicState/CharacterSpacing get 0 eq +//GraphicState/FontMatrixNonHV get not and +//GraphicState/WordSpacing get 0 eq and{ +true charpath +}{ +{ +exch +pop 0 +currentpoint 5 4 roll +( )dup 0 3 index put true charpath +5 1 roll +moveto rmoveto +//GraphicState/CharacterSpacing get 0 rmoveto +32 eq{ +//GraphicState/WordSpacing get 0 rmoveto +}if +} +//GraphicState/FontMatrixNonHV get dup not exch{ +pop currentfont/FontMatrix get 0 get 0 ne +}if{ +1 1 index length 1 sub getinterval cvx +}if +exch cshow +}ifelse +}ifelse +}bind def +/ShowTextBeg +{ +//GraphicState/TextRenderingMode get dup 0 ne +{ +3 ne +currentfont/FontType get 3 eq not and{ +currentpoint newpath moveto +}if +} +{ +pop +}ifelse +}bind def +/ShowTextEnd +{ +//GraphicState/TextRenderingMode get +currentfont/FontType get 3 eq{ +dup 3 ne{ +pop 0 +}if +}if +{dup 1 eq{ +stroke exit +}if +dup 2 eq{ +gsave fill grestore stroke exit +}if +dup 3 eq{ +currentpoint newpath moveto +}if +dup 4 eq{ +gsave fill grestore clip exit +}if +dup 5 eq{ +gsave stroke grestore clip exit +}if +dup 6 eq{ +gsave fill grestore gsave stroke grestore fill exit +}if +dup 7 eq{ +clip exit +}if +exit +}loop +pop +}bind def +/ShowTextWithGlyphPositioning +{//ShowTextBeg exec +{dup type/stringtype eq{ +//ShowText exec +}{ +neg 1000 div//GraphicState/FontSize get mul 0 rmoveto +}ifelse +}forall +//ShowTextEnd exec +}bind def +/CheckFont +{dup/Type get/ExtGState ne{ +mark(Resource )3 index( must have /Type/ExtGState.)//error exec +}if +}bind def +/SetTransfer +{ +//PDFR_DEBUG{(SetTransfer beg )print count =}if +dup type/arraytype eq 1 index xcheck not and{ +0 4 getinterval aload pop +setcolortransfer +}{ +settransfer +}ifelse +//PDFR_DEBUG{(SetTransfer end )print count =}if +}bind def +/CheckExtGState +{dup/Type get/ExtGState ne{ +mark(Resource )3 index( must have /Type/ExtGState.)//error exec +}if +}bind def +/CheckHalftone +{dup/HalftoneType known not{ +mark(Resource )3 index( must have /HalftoneType.)//error exec +}if +}bind def +/ResolveFunction +{ +//PDFR_DEBUG{(ResolveFunction beg )print dup = count =}if +2 copy get//IsObjRef exec{ +2 copy//DoNothing//ResolveD exec +3 copy put pop +}if +2 copy get dup type/arraytype eq exch xcheck and not{ +2 copy get +dup type/arraytype eq 1 index xcheck not and{ +dup length 1 sub -1 0{ +2 copy//DoNothing ResolveA +dup/Identity eq{ +pop 2 copy{}put +}{ +//FunctionToProc exec +3 copy put pop +}ifelse +pop +}for +}{ +dup/Default eq{ +}{ +dup/Identity eq{ +pop{} +}{dup type/nametype eq{ +//spotfunctions exch get +}{ +//FunctionToProc exec +}ifelse +}ifelse +}ifelse +}ifelse +3 copy put +exch pop +}{ +1 index exch get +}ifelse +//PDFR_DEBUG{(ResolveFunction end )print dup == count =}if +}bind def +/ResolveFunctionSafe +{2 copy known{ +//ResolveFunction exec +}if +pop +}bind def +/CreateHalftoneThresholds +{ +dup/Thresholds known not{ +dup/HalftoneType get 10 eq{ +dup dup//MakeStreamReader exec +/Thresholds exch put +}if +dup/HalftoneType get dup 3 eq exch 6 eq or{ +dup dup//MakeStreamReader exec +//BlockBuffer readstring pop +dup length +dup 0 eq{ +mark(Could not read Thresholds)//error exec +}if +string copy/Thresholds exch put +dup/HalftoneType 3 put +}if +}if +}bind def +/SetExtGState +{ +//PDFReader/CurrentObject get/Context get/Resources get +/ExtGState//DoNothing//ResolveD exec +exch//CheckExtGState//ResolveD exec +dup/LW//knownget exec{ +setlinewidth +}if +dup/LC//knownget exec{ +setlinecap +}if +dup/LJ//knownget exec{ +setlinejoin +}if +dup/ML//knownget exec{ +setmeterlimit +}if +dup/D//knownget exec{ +setdash +}if +dup/RI//knownget exec{ +mark(Unimplemented ExtGState.RI)//error exec +}if +dup/OP//knownget exec{ +setoverprint +}if +dup/op//knownget exec{ +setoverprint +}if +dup/OPM//knownget exec{ +mark(Unimplemented ExtGState.OPM)//error exec +}if +dup/Font//knownget exec{ +mark(Unimplemented ExtGState.Font)//error exec +}if +dup/BG known{ +/BG//ResolveFunction exec +setblackgeneration +}if +dup/BG2 known{ +/BG2//ResolveFunction exec +dup/Default eq{ +//InitialExtGState/BG2 get +}if +setblackgeneration +}if +dup/UCR known{ +/UCR//ResolveFunction exec +setundercolorremoval +}if +dup/UCR2 known{ +/UCR2//ResolveFunction exec +dup/Default eq{ +//InitialExtGState/UCR2 get +}if +setundercolorremoval +}if +dup/TR known{ +/TR//ResolveFunction exec +//SetTransfer exec +}if +dup/TR2 known{ +/TR2//ResolveFunction exec +dup/Default eq{ +pop//InitialExtGState/TR2 get +aload pop setcolortransfer +}{ +//SetTransfer exec +}ifelse +}if +dup/HT//knownget exec{ +dup/Default eq{ +pop//InitialExtGState/HT get +sethalftone +}{ +//PDFR_DEBUG{(Ht beg)=}if +pop dup/HT//CheckHalftone//ResolveD exec +/SpotFunction//ResolveFunctionSafe exec +/TransferFunction//ResolveFunctionSafe exec +null exch +dup/HalftoneType get dup 5 eq exch dup 4 eq exch 2 eq or or{ +dup{ +dup//IsObjRef exec{ +pop +1 index exch//CheckHalftone ResolveD +}if +dup type/dicttype eq{ +dup/SpotFunction//ResolveFunctionSafe exec +/TransferFunction//ResolveFunctionSafe exec +//CreateHalftoneThresholds exec +dup/HalftoneType get 5 gt{ +4 3 roll pop +dup 4 1 roll +}if +}if +pop pop +}forall +}if +//CreateHalftoneThresholds exec +//PDFR_DEBUG{ +(HT:)= +dup{ +1 index/Default eq{ +(Default <<)= +exch pop +{exch = ==}forall +(>>)= +}{ +exch = == +}ifelse +}forall +(HT end)= flush +}if +exch dup null ne{ +(Warning: Ignoring a halftone with a Level 3 component halftone Type )print dup/HalftoneType get = +pop pop +}{ +pop +dup/HalftoneType get 5 gt{ +(Warning: Ignoring a Level 3 halftone Type )print dup/HalftoneType get = +pop +}{ +sethalftone +}ifelse +}ifelse +//PDFR_DEBUG{(HT set)= flush}if +}ifelse +}if +dup/FL//knownget exec{ +setflattness +}if +dup/SM//knownget exec{ +setsmoothness +}if +dup/SA//knownget exec{ +setstrokeadjust +}if +dup/BM//knownget exec{ +mark(Unimplemented ExtGState.BM)//error exec +}if +dup/SMask//knownget exec{ +mark(Unimplemented ExtGState.SMask)//error exec +}if +dup/CA//knownget exec{ +mark(Unimplemented ExtGState.CA)//error exec +}if +dup/ca//knownget exec{ +mark(Unimplemented ExtGState.ca)//error exec +}if +dup/AIS//knownget exec{ +mark(Unimplemented ExtGState.AIS)//error exec +}if +dup/TK//knownget exec{ +mark(Unimplemented ExtGState.TK)//error exec +}if +pop +}bind def +/CheckXObject +{dup/Subtype get dup/Image ne exch dup/Form ne exch/PS ne and and{ +mark(Resource )3 index( must have /Subtype /Image or /Form or /PS.)//error exec +}if +}bind def +/DoXObject +{ +//PDFReader/CurrentObject get/Context get/Resources get +/XObject//DoNothing//ResolveD exec +exch//CheckXObject//ResolveD exec +dup/Subtype get +dup/Image eq{ +pop +//CompleteOutlineImage exec +//DoImage exec +}{ +dup/PS eq{ +PDFR_DEBUG{ +(Executing a PS Xobject)= +}if +pop +//RunDelayedStream exec +}{ +dup/Form eq{ +pop +PDFR_DEBUG{ +(Executing a Form XObject)= +}if +//PDFReader/CurrentObject get exch +dup//PDFReader exch<< exch/Context exch >>/CurrentObject exch put +dup/Matrix get concat +dup/BBox get aload pop exch 3 index sub exch 2 index sub rectclip +//RunDelayedStream exec +//PDFReader exch/CurrentObject exch put +}{ +mark exch(unimplemented XObject type )exch//error exec +}ifelse +}ifelse +}ifelse +}bind def +/Operators 50 dict begin +/q{//GSave exec}bind def +/Q{//GRestore exec}bind def +/cm{//TempMatrix astore concat}bind def +/i{1 .min setflat}bind def +/J/setlinecap load def +/d/setdash load def +/j/setlinejoin load def +/w/setlinewidth load def +/M/setmiterlimit load def +/gs{SetExtGState}bind def +/g/setgray load def +/rg/setrgbcolor load def +/k/setcmykcolor load def +/cs{//ResolveColorSpace exec//SetColorSpaceSafe exec +}bind def +/sc/setcolor load def +/scn{//SetColor exec}bind def +/G/setgray load def +/RG/setrgbcolor load def +/K/setcmykcolor load def +/CS//cs def +/ri{SetColorRenderingIntent}bind def +/SC/setcolor load def +/SCN{//SetColor exec}bind def +/m/moveto load def +/l/lineto load def +/c/curveto load def +/v{currentpoint 6 2 roll curveto}bind def +/y{2 copy curveto}bind def +/re{ +4 2 roll moveto exch dup 0 rlineto 0 3 -1 roll rlineto neg 0 rlineto +closepath +}def +/h/closepath load def +/n/newpath load def +/S/stroke load def +/s{closepath stroke}bind def +/f/fill load def +/f*/eofill load def +/B{gsave fill grestore stroke}bind def +/b{closepath gsave fill grestore stroke}bind def +/B*{gsave eofill grestore stroke}bind def +/b*{closepath gsave eofill grestore stroke}bind def +/W/clip load def +/W*/eoclip load def +/sh{ +ResolveShading +dup/Background known{ +gsave +dup/ColorSpace get setcolorspace +dup/Background get aload pop setcolor +pathbbox +2 index sub exch 3 index sub exch +rectfill +grestore +}if +shfill +}bind def +/Do{//DoXObject exec}bind def +/BI{currentglobal false setglobal<<}bind def +/ID{>> +dup/DataSource currentfile +2 index/F//knownget exec{ +/A85 eq{ +0(~>)/SubFileDecode filter +}if +}if +put +//CompleteInlineImage exec +exch setglobal +//DoImage exec +}bind def +/EI{}bind def +/BT{gsave//GraphicState/InitialTextMatrix get currentmatrix pop}bind def +/ET{grestore}bind def +/Tc{//GraphicState exch/CharacterSpacing exch put}bind def +/TL{//GraphicState exch/TextLeading exch put}bind def +/Tr{//GraphicState exch/TextRenderingMode exch put}bind def +/Ts{ +mark(Unimplemented SetTextRise)//error exec +}bind def +/Tw{//GraphicState exch/WordSpacing exch put}bind def +/Tz{ +mark(Unimplemented SetHorizontalTextScaling)//error exec +}bind def +/Td{translate 0 0 moveto}bind def +/TD{dup neg//TL exec//Td exec}bind def +/Tm{//GraphicState/InitialTextMatrix get setmatrix +//TempMatrix astore concat +0 0 moveto}bind def +/T*{0//GraphicState/TextLeading get neg//Td exec}bind def +/Tj{//ShowTextBeg exec//ShowText exec//ShowTextEnd exec}bind def +/'{//T* exec//ShowText exec//ShowTextEnd exec}bind def +/"{3 2 roll//Tw exec exch//Tc exec//' exec}bind def +/TJ//ShowTextWithGlyphPositioning def +/Tf//SetFont def +/d0/setcharwidth load def +/d1/setcachedevice load def +/BDC{pop pop}bind def +/BMC{pop}bind def +/EMC{}bind def +/BX{BeginCompatibilitySection}bind def +/EX{EndCompatibilitySection}bind def +/DP{DefineMarkedContentPointWithPropertyList}bind def +/MP{DefineMarkedContentPoint}bind def +/PS{cvx exec}bind def +currentdict end def +//PDFR_STREAM{ +//Operators length dict begin +//Operators{ +exch dup +[exch//=only/exec load +( )/print load +8 7 roll +dup type/arraytype eq{ +/exec load +}if +( )/print load +]cvx +def +}forall +currentdict end/Operators exch def +}if +/.registerencoding +{pop pop +}bind def +/.defineencoding +{def +}bind def +/.findencoding +{load +}bind def +/currentglobal where +{pop currentglobal{setglobal}true setglobal} +{{}} +ifelse +/MacRomanEncoding +StandardEncoding 0 39 getinterval aload pop +/quotesingle +StandardEncoding 40 56 getinterval aload pop +/grave +StandardEncoding 97 31 getinterval aload pop +/Adieresis/Aring/Ccedilla/Eacute/Ntilde/Odieresis/Udieresis/aacute +/agrave/acircumflex/adieresis/atilde/aring/ccedilla/eacute/egrave +/ecircumflex/edieresis/iacute/igrave +/icircumflex/idieresis/ntilde/oacute +/ograve/ocircumflex/odieresis/otilde +/uacute/ugrave/ucircumflex/udieresis +/dagger/degree/cent/sterling/section/bullet/paragraph/germandbls +/registered/copyright/trademark/acute/dieresis/.notdef/AE/Oslash +/.notdef/plusminus/.notdef/.notdef/yen/mu/.notdef/.notdef +/.notdef/.notdef/.notdef/ordfeminine/ordmasculine/.notdef/ae/oslash +/questiondown/exclamdown/logicalnot/.notdef +/florin/.notdef/.notdef/guillemotleft +/guillemotright/ellipsis/space/Agrave/Atilde/Otilde/OE/oe +/endash/emdash/quotedblleft/quotedblright +/quoteleft/quoteright/divide/.notdef +/ydieresis/Ydieresis/fraction/currency +/guilsinglleft/guilsinglright/fi/fl +/daggerdbl/periodcentered/quotesinglbase/quotedblbase +/perthousand/Acircumflex/Ecircumflex/Aacute +/Edieresis/Egrave/Iacute/Icircumflex +/Idieresis/Igrave/Oacute/Ocircumflex +/.notdef/Ograve/Uacute/Ucircumflex +/Ugrave/dotlessi/circumflex/tilde +/macron/breve/dotaccent/ring/cedilla/hungarumlaut/ogonek/caron +256 packedarray +5 1 index .registerencoding +.defineencoding +exec +/AdobeGlyphList mark +/A 16#0041 +/AE 16#00c6 +/AEacute 16#01fc +/AEmacron 16#01e2 +/AEsmall 16#f7e6 +/Aacute 16#00c1 +/Aacutesmall 16#f7e1 +/Abreve 16#0102 +/Abreveacute 16#1eae +/Abrevecyrillic 16#04d0 +/Abrevedotbelow 16#1eb6 +/Abrevegrave 16#1eb0 +/Abrevehookabove 16#1eb2 +/Abrevetilde 16#1eb4 +/Acaron 16#01cd +/Acircle 16#24b6 +/Acircumflex 16#00c2 +/Acircumflexacute 16#1ea4 +/Acircumflexdotbelow 16#1eac +/Acircumflexgrave 16#1ea6 +/Acircumflexhookabove 16#1ea8 +/Acircumflexsmall 16#f7e2 +/Acircumflextilde 16#1eaa +/Acute 16#f6c9 +/Acutesmall 16#f7b4 +/Acyrillic 16#0410 +/Adblgrave 16#0200 +/Adieresis 16#00c4 +/Adieresiscyrillic 16#04d2 +/Adieresismacron 16#01de +/Adieresissmall 16#f7e4 +/Adotbelow 16#1ea0 +/Adotmacron 16#01e0 +/Agrave 16#00c0 +/Agravesmall 16#f7e0 +/Ahookabove 16#1ea2 +/Aiecyrillic 16#04d4 +/Ainvertedbreve 16#0202 +/Alpha 16#0391 +/Alphatonos 16#0386 +/Amacron 16#0100 +/Amonospace 16#ff21 +/Aogonek 16#0104 +/Aring 16#00c5 +/Aringacute 16#01fa +/Aringbelow 16#1e00 +/Aringsmall 16#f7e5 +/Asmall 16#f761 +/Atilde 16#00c3 +/Atildesmall 16#f7e3 +/Aybarmenian 16#0531 +/B 16#0042 +/Bcircle 16#24b7 +/Bdotaccent 16#1e02 +/Bdotbelow 16#1e04 +/Becyrillic 16#0411 +/Benarmenian 16#0532 +/Beta 16#0392 +/Bhook 16#0181 +/Blinebelow 16#1e06 +/Bmonospace 16#ff22 +/Brevesmall 16#f6f4 +/Bsmall 16#f762 +/Btopbar 16#0182 +/C 16#0043 +/Caarmenian 16#053e +/Cacute 16#0106 +/Caron 16#f6ca +/Caronsmall 16#f6f5 +/Ccaron 16#010c +/Ccedilla 16#00c7 +/Ccedillaacute 16#1e08 +/Ccedillasmall 16#f7e7 +/Ccircle 16#24b8 +/Ccircumflex 16#0108 +/Cdot 16#010a +/Cdotaccent 16#010a +/Cedillasmall 16#f7b8 +/Chaarmenian 16#0549 +/Cheabkhasiancyrillic 16#04bc +/Checyrillic 16#0427 +/Chedescenderabkhasiancyrillic 16#04be +/Chedescendercyrillic 16#04b6 +/Chedieresiscyrillic 16#04f4 +/Cheharmenian 16#0543 +/Chekhakassiancyrillic 16#04cb +/Cheverticalstrokecyrillic 16#04b8 +/Chi 16#03a7 +/Chook 16#0187 +/Circumflexsmall 16#f6f6 +/Cmonospace 16#ff23 +/Coarmenian 16#0551 +/Csmall 16#f763 +/D 16#0044 +/DZ 16#01f1 +/DZcaron 16#01c4 +/Daarmenian 16#0534 +/Dafrican 16#0189 +/Dcaron 16#010e +/Dcedilla 16#1e10 +/Dcircle 16#24b9 +/Dcircumflexbelow 16#1e12 +/Dcroat 16#0110 +/Ddotaccent 16#1e0a +/Ddotbelow 16#1e0c +/Decyrillic 16#0414 +/Deicoptic 16#03ee +/Delta 16#2206 +/Deltagreek 16#0394 +/Dhook 16#018a +/Dieresis 16#f6cb +/DieresisAcute 16#f6cc +/DieresisGrave 16#f6cd +/Dieresissmall 16#f7a8 +/Digammagreek 16#03dc +/Djecyrillic 16#0402 +/Dlinebelow 16#1e0e +/Dmonospace 16#ff24 +/Dotaccentsmall 16#f6f7 +/Dslash 16#0110 +/Dsmall 16#f764 +/Dtopbar 16#018b +/Dz 16#01f2 +/Dzcaron 16#01c5 +/Dzeabkhasiancyrillic 16#04e0 +/Dzecyrillic 16#0405 +/Dzhecyrillic 16#040f +/E 16#0045 +/Eacute 16#00c9 +/Eacutesmall 16#f7e9 +/Ebreve 16#0114 +/Ecaron 16#011a +/Ecedillabreve 16#1e1c +/Echarmenian 16#0535 +/Ecircle 16#24ba +/Ecircumflex 16#00ca +/Ecircumflexacute 16#1ebe +/Ecircumflexbelow 16#1e18 +/Ecircumflexdotbelow 16#1ec6 +/Ecircumflexgrave 16#1ec0 +/Ecircumflexhookabove 16#1ec2 +/Ecircumflexsmall 16#f7ea +/Ecircumflextilde 16#1ec4 +/Ecyrillic 16#0404 +/Edblgrave 16#0204 +/Edieresis 16#00cb +/Edieresissmall 16#f7eb +/Edot 16#0116 +/Edotaccent 16#0116 +/Edotbelow 16#1eb8 +/Efcyrillic 16#0424 +/Egrave 16#00c8 +/Egravesmall 16#f7e8 +/Eharmenian 16#0537 +/Ehookabove 16#1eba +/Eightroman 16#2167 +/Einvertedbreve 16#0206 +/Eiotifiedcyrillic 16#0464 +/Elcyrillic 16#041b +/Elevenroman 16#216a +/Emacron 16#0112 +/Emacronacute 16#1e16 +/Emacrongrave 16#1e14 +/Emcyrillic 16#041c +/Emonospace 16#ff25 +/Encyrillic 16#041d +/Endescendercyrillic 16#04a2 +/Eng 16#014a +/Enghecyrillic 16#04a4 +/Enhookcyrillic 16#04c7 +/Eogonek 16#0118 +/Eopen 16#0190 +/Epsilon 16#0395 +/Epsilontonos 16#0388 +/Ercyrillic 16#0420 +/Ereversed 16#018e +/Ereversedcyrillic 16#042d +/Escyrillic 16#0421 +/Esdescendercyrillic 16#04aa +/Esh 16#01a9 +/Esmall 16#f765 +/Eta 16#0397 +/Etarmenian 16#0538 +/Etatonos 16#0389 +/Eth 16#00d0 +/Ethsmall 16#f7f0 +/Etilde 16#1ebc +/Etildebelow 16#1e1a +/Euro 16#20ac +/Ezh 16#01b7 +/Ezhcaron 16#01ee +/Ezhreversed 16#01b8 +/F 16#0046 +/Fcircle 16#24bb +/Fdotaccent 16#1e1e +/Feharmenian 16#0556 +/Feicoptic 16#03e4 +/Fhook 16#0191 +/Fitacyrillic 16#0472 +/Fiveroman 16#2164 +/Fmonospace 16#ff26 +/Fourroman 16#2163 +/Fsmall 16#f766 +/G 16#0047 +/GBsquare 16#3387 +/Gacute 16#01f4 +/Gamma 16#0393 +/Gammaafrican 16#0194 +/Gangiacoptic 16#03ea +/Gbreve 16#011e +/Gcaron 16#01e6 +/Gcedilla 16#0122 +/Gcircle 16#24bc +/Gcircumflex 16#011c +/Gcommaaccent 16#0122 +/Gdot 16#0120 +/Gdotaccent 16#0120 +/Gecyrillic 16#0413 +/Ghadarmenian 16#0542 +/Ghemiddlehookcyrillic 16#0494 +/Ghestrokecyrillic 16#0492 +/Gheupturncyrillic 16#0490 +/Ghook 16#0193 +/Gimarmenian 16#0533 +/Gjecyrillic 16#0403 +/Gmacron 16#1e20 +/Gmonospace 16#ff27 +/Grave 16#f6ce +/Gravesmall 16#f760 +/Gsmall 16#f767 +/Gsmallhook 16#029b +/Gstroke 16#01e4 +/H 16#0048 +/H18533 16#25cf +/H18543 16#25aa +/H18551 16#25ab +/H22073 16#25a1 +/HPsquare 16#33cb +/Haabkhasiancyrillic 16#04a8 +/Hadescendercyrillic 16#04b2 +/Hardsigncyrillic 16#042a +/Hbar 16#0126 +/Hbrevebelow 16#1e2a +/Hcedilla 16#1e28 +/Hcircle 16#24bd +/Hcircumflex 16#0124 +/Hdieresis 16#1e26 +/Hdotaccent 16#1e22 +/Hdotbelow 16#1e24 +/Hmonospace 16#ff28 +/Hoarmenian 16#0540 +/Horicoptic 16#03e8 +/Hsmall 16#f768 +/Hungarumlaut 16#f6cf +/Hungarumlautsmall 16#f6f8 +/Hzsquare 16#3390 +/I 16#0049 +/IAcyrillic 16#042f +/IJ 16#0132 +/IUcyrillic 16#042e +/Iacute 16#00cd +/Iacutesmall 16#f7ed +/Ibreve 16#012c +/Icaron 16#01cf +/Icircle 16#24be +/Icircumflex 16#00ce +/Icircumflexsmall 16#f7ee +/Icyrillic 16#0406 +/Idblgrave 16#0208 +/Idieresis 16#00cf +/Idieresisacute 16#1e2e +/Idieresiscyrillic 16#04e4 +/Idieresissmall 16#f7ef +/Idot 16#0130 +/Idotaccent 16#0130 +/Idotbelow 16#1eca +/Iebrevecyrillic 16#04d6 +/Iecyrillic 16#0415 +/Ifraktur 16#2111 +/Igrave 16#00cc +/Igravesmall 16#f7ec +/Ihookabove 16#1ec8 +/Iicyrillic 16#0418 +/Iinvertedbreve 16#020a +/Iishortcyrillic 16#0419 +/Imacron 16#012a +/Imacroncyrillic 16#04e2 +/Imonospace 16#ff29 +/Iniarmenian 16#053b +/Iocyrillic 16#0401 +/Iogonek 16#012e +/Iota 16#0399 +/Iotaafrican 16#0196 +/Iotadieresis 16#03aa +/Iotatonos 16#038a +/Ismall 16#f769 +/Istroke 16#0197 +/Itilde 16#0128 +/Itildebelow 16#1e2c +/Izhitsacyrillic 16#0474 +/Izhitsadblgravecyrillic 16#0476 +/J 16#004a +/Jaarmenian 16#0541 +/Jcircle 16#24bf +/Jcircumflex 16#0134 +/Jecyrillic 16#0408 +/Jheharmenian 16#054b +/Jmonospace 16#ff2a +/Jsmall 16#f76a +/K 16#004b +/KBsquare 16#3385 +/KKsquare 16#33cd +/Kabashkircyrillic 16#04a0 +/Kacute 16#1e30 +/Kacyrillic 16#041a +/Kadescendercyrillic 16#049a +/Kahookcyrillic 16#04c3 +/Kappa 16#039a +/Kastrokecyrillic 16#049e +/Kaverticalstrokecyrillic 16#049c +/Kcaron 16#01e8 +/Kcedilla 16#0136 +/Kcircle 16#24c0 +/Kcommaaccent 16#0136 +/Kdotbelow 16#1e32 +/Keharmenian 16#0554 +/Kenarmenian 16#053f +/Khacyrillic 16#0425 +/Kheicoptic 16#03e6 +/Khook 16#0198 +/Kjecyrillic 16#040c +/Klinebelow 16#1e34 +/Kmonospace 16#ff2b +/Koppacyrillic 16#0480 +/Koppagreek 16#03de +/Ksicyrillic 16#046e +/Ksmall 16#f76b +/L 16#004c +/LJ 16#01c7 +/LL 16#f6bf +/Lacute 16#0139 +/Lambda 16#039b +/Lcaron 16#013d +/Lcedilla 16#013b +/Lcircle 16#24c1 +/Lcircumflexbelow 16#1e3c +/Lcommaaccent 16#013b +/Ldot 16#013f +/Ldotaccent 16#013f +/Ldotbelow 16#1e36 +/Ldotbelowmacron 16#1e38 +/Liwnarmenian 16#053c +/Lj 16#01c8 +/Ljecyrillic 16#0409 +/Llinebelow 16#1e3a +/Lmonospace 16#ff2c +/Lslash 16#0141 +/Lslashsmall 16#f6f9 +/Lsmall 16#f76c +/M 16#004d +/MBsquare 16#3386 +/Macron 16#f6d0 +/Macronsmall 16#f7af +/Macute 16#1e3e +/Mcircle 16#24c2 +/Mdotaccent 16#1e40 +/Mdotbelow 16#1e42 +/Menarmenian 16#0544 +/Mmonospace 16#ff2d +/Msmall 16#f76d +/Mturned 16#019c +/Mu 16#039c +/N 16#004e +/NJ 16#01ca +/Nacute 16#0143 +/Ncaron 16#0147 +/Ncedilla 16#0145 +/Ncircle 16#24c3 +/Ncircumflexbelow 16#1e4a +/Ncommaaccent 16#0145 +/Ndotaccent 16#1e44 +/Ndotbelow 16#1e46 +/Nhookleft 16#019d +/Nineroman 16#2168 +/Nj 16#01cb +/Njecyrillic 16#040a +/Nlinebelow 16#1e48 +/Nmonospace 16#ff2e +/Nowarmenian 16#0546 +/Nsmall 16#f76e +/Ntilde 16#00d1 +/Ntildesmall 16#f7f1 +/Nu 16#039d +/O 16#004f +/OE 16#0152 +/OEsmall 16#f6fa +/Oacute 16#00d3 +/Oacutesmall 16#f7f3 +/Obarredcyrillic 16#04e8 +/Obarreddieresiscyrillic 16#04ea +/Obreve 16#014e +/Ocaron 16#01d1 +/Ocenteredtilde 16#019f +/Ocircle 16#24c4 +/Ocircumflex 16#00d4 +/Ocircumflexacute 16#1ed0 +/Ocircumflexdotbelow 16#1ed8 +/Ocircumflexgrave 16#1ed2 +/Ocircumflexhookabove 16#1ed4 +/Ocircumflexsmall 16#f7f4 +/Ocircumflextilde 16#1ed6 +/Ocyrillic 16#041e +/Odblacute 16#0150 +/Odblgrave 16#020c +/Odieresis 16#00d6 +/Odieresiscyrillic 16#04e6 +/Odieresissmall 16#f7f6 +/Odotbelow 16#1ecc +/Ogoneksmall 16#f6fb +/Ograve 16#00d2 +/Ogravesmall 16#f7f2 +/Oharmenian 16#0555 +/Ohm 16#2126 +/Ohookabove 16#1ece +/Ohorn 16#01a0 +/Ohornacute 16#1eda +/Ohorndotbelow 16#1ee2 +/Ohorngrave 16#1edc +/Ohornhookabove 16#1ede +/Ohorntilde 16#1ee0 +/Ohungarumlaut 16#0150 +/Oi 16#01a2 +/Oinvertedbreve 16#020e +/Omacron 16#014c +/Omacronacute 16#1e52 +/Omacrongrave 16#1e50 +/Omega 16#2126 +/Omegacyrillic 16#0460 +/Omegagreek 16#03a9 +/Omegaroundcyrillic 16#047a +/Omegatitlocyrillic 16#047c +/Omegatonos 16#038f +/Omicron 16#039f +/Omicrontonos 16#038c +/Omonospace 16#ff2f +/Oneroman 16#2160 +/Oogonek 16#01ea +/Oogonekmacron 16#01ec +/Oopen 16#0186 +/Oslash 16#00d8 +/Oslashacute 16#01fe +/Oslashsmall 16#f7f8 +/Osmall 16#f76f +/Ostrokeacute 16#01fe +/Otcyrillic 16#047e +/Otilde 16#00d5 +/Otildeacute 16#1e4c +/Otildedieresis 16#1e4e +/Otildesmall 16#f7f5 +/P 16#0050 +/Pacute 16#1e54 +/Pcircle 16#24c5 +/Pdotaccent 16#1e56 +/Pecyrillic 16#041f +/Peharmenian 16#054a +/Pemiddlehookcyrillic 16#04a6 +/Phi 16#03a6 +/Phook 16#01a4 +/Pi 16#03a0 +/Piwrarmenian 16#0553 +/Pmonospace 16#ff30 +/Psi 16#03a8 +/Psicyrillic 16#0470 +/Psmall 16#f770 +/Q 16#0051 +/Qcircle 16#24c6 +/Qmonospace 16#ff31 +/Qsmall 16#f771 +/R 16#0052 +/Raarmenian 16#054c +/Racute 16#0154 +/Rcaron 16#0158 +/Rcedilla 16#0156 +/Rcircle 16#24c7 +/Rcommaaccent 16#0156 +/Rdblgrave 16#0210 +/Rdotaccent 16#1e58 +/Rdotbelow 16#1e5a +/Rdotbelowmacron 16#1e5c +/Reharmenian 16#0550 +/Rfraktur 16#211c +/Rho 16#03a1 +/Ringsmall 16#f6fc +/Rinvertedbreve 16#0212 +/Rlinebelow 16#1e5e +/Rmonospace 16#ff32 +/Rsmall 16#f772 +/Rsmallinverted 16#0281 +/Rsmallinvertedsuperior 16#02b6 +/S 16#0053 +/SF010000 16#250c +/SF020000 16#2514 +/SF030000 16#2510 +/SF040000 16#2518 +/SF050000 16#253c +/SF060000 16#252c +/SF070000 16#2534 +/SF080000 16#251c +/SF090000 16#2524 +/SF100000 16#2500 +/SF110000 16#2502 +/SF190000 16#2561 +/SF200000 16#2562 +/SF210000 16#2556 +/SF220000 16#2555 +/SF230000 16#2563 +/SF240000 16#2551 +/SF250000 16#2557 +/SF260000 16#255d +/SF270000 16#255c +/SF280000 16#255b +/SF360000 16#255e +/SF370000 16#255f +/SF380000 16#255a +/SF390000 16#2554 +/SF400000 16#2569 +/SF410000 16#2566 +/SF420000 16#2560 +/SF430000 16#2550 +/SF440000 16#256c +/SF450000 16#2567 +/SF460000 16#2568 +/SF470000 16#2564 +/SF480000 16#2565 +/SF490000 16#2559 +/SF500000 16#2558 +/SF510000 16#2552 +/SF520000 16#2553 +/SF530000 16#256b +/SF540000 16#256a +/Sacute 16#015a +/Sacutedotaccent 16#1e64 +/Sampigreek 16#03e0 +/Scaron 16#0160 +/Scarondotaccent 16#1e66 +/Scaronsmall 16#f6fd +/Scedilla 16#015e +/Schwa 16#018f +/Schwacyrillic 16#04d8 +/Schwadieresiscyrillic 16#04da +/Scircle 16#24c8 +/Scircumflex 16#015c +/Scommaaccent 16#0218 +/Sdotaccent 16#1e60 +/Sdotbelow 16#1e62 +/Sdotbelowdotaccent 16#1e68 +/Seharmenian 16#054d +/Sevenroman 16#2166 +/Shaarmenian 16#0547 +/Shacyrillic 16#0428 +/Shchacyrillic 16#0429 +/Sheicoptic 16#03e2 +/Shhacyrillic 16#04ba +/Shimacoptic 16#03ec +/Sigma 16#03a3 +/Sixroman 16#2165 +/Smonospace 16#ff33 +/Softsigncyrillic 16#042c +/Ssmall 16#f773 +/Stigmagreek 16#03da +/T 16#0054 +/Tau 16#03a4 +/Tbar 16#0166 +/Tcaron 16#0164 +/Tcedilla 16#0162 +/Tcircle 16#24c9 +/Tcircumflexbelow 16#1e70 +/Tcommaaccent 16#0162 +/Tdotaccent 16#1e6a +/Tdotbelow 16#1e6c +/Tecyrillic 16#0422 +/Tedescendercyrillic 16#04ac +/Tenroman 16#2169 +/Tetsecyrillic 16#04b4 +/Theta 16#0398 +/Thook 16#01ac +/Thorn 16#00de +/Thornsmall 16#f7fe +/Threeroman 16#2162 +/Tildesmall 16#f6fe +/Tiwnarmenian 16#054f +/Tlinebelow 16#1e6e +/Tmonospace 16#ff34 +/Toarmenian 16#0539 +/Tonefive 16#01bc +/Tonesix 16#0184 +/Tonetwo 16#01a7 +/Tretroflexhook 16#01ae +/Tsecyrillic 16#0426 +/Tshecyrillic 16#040b +/Tsmall 16#f774 +/Twelveroman 16#216b +/Tworoman 16#2161 +/U 16#0055 +/Uacute 16#00da +/Uacutesmall 16#f7fa +/Ubreve 16#016c +/Ucaron 16#01d3 +/Ucircle 16#24ca +/Ucircumflex 16#00db +/Ucircumflexbelow 16#1e76 +/Ucircumflexsmall 16#f7fb +/Ucyrillic 16#0423 +/Udblacute 16#0170 +/Udblgrave 16#0214 +/Udieresis 16#00dc +/Udieresisacute 16#01d7 +/Udieresisbelow 16#1e72 +/Udieresiscaron 16#01d9 +/Udieresiscyrillic 16#04f0 +/Udieresisgrave 16#01db +/Udieresismacron 16#01d5 +/Udieresissmall 16#f7fc +/Udotbelow 16#1ee4 +/Ugrave 16#00d9 +/Ugravesmall 16#f7f9 +/Uhookabove 16#1ee6 +/Uhorn 16#01af +/Uhornacute 16#1ee8 +/Uhorndotbelow 16#1ef0 +/Uhorngrave 16#1eea +/Uhornhookabove 16#1eec +/Uhorntilde 16#1eee +/Uhungarumlaut 16#0170 +/Uhungarumlautcyrillic 16#04f2 +/Uinvertedbreve 16#0216 +/Ukcyrillic 16#0478 +/Umacron 16#016a +/Umacroncyrillic 16#04ee +/Umacrondieresis 16#1e7a +/Umonospace 16#ff35 +/Uogonek 16#0172 +/Upsilon 16#03a5 +/Upsilon1 16#03d2 +/Upsilonacutehooksymbolgreek 16#03d3 +/Upsilonafrican 16#01b1 +/Upsilondieresis 16#03ab +/Upsilondieresishooksymbolgreek 16#03d4 +/Upsilonhooksymbol 16#03d2 +/Upsilontonos 16#038e +/Uring 16#016e +/Ushortcyrillic 16#040e +/Usmall 16#f775 +/Ustraightcyrillic 16#04ae +/Ustraightstrokecyrillic 16#04b0 +/Utilde 16#0168 +/Utildeacute 16#1e78 +/Utildebelow 16#1e74 +/V 16#0056 +/Vcircle 16#24cb +/Vdotbelow 16#1e7e +/Vecyrillic 16#0412 +/Vewarmenian 16#054e +/Vhook 16#01b2 +/Vmonospace 16#ff36 +/Voarmenian 16#0548 +/Vsmall 16#f776 +/Vtilde 16#1e7c +/W 16#0057 +/Wacute 16#1e82 +/Wcircle 16#24cc +/Wcircumflex 16#0174 +/Wdieresis 16#1e84 +/Wdotaccent 16#1e86 +/Wdotbelow 16#1e88 +/Wgrave 16#1e80 +/Wmonospace 16#ff37 +/Wsmall 16#f777 +/X 16#0058 +/Xcircle 16#24cd +/Xdieresis 16#1e8c +/Xdotaccent 16#1e8a +/Xeharmenian 16#053d +/Xi 16#039e +/Xmonospace 16#ff38 +/Xsmall 16#f778 +/Y 16#0059 +/Yacute 16#00dd +/Yacutesmall 16#f7fd +/Yatcyrillic 16#0462 +/Ycircle 16#24ce +/Ycircumflex 16#0176 +/Ydieresis 16#0178 +/Ydieresissmall 16#f7ff +/Ydotaccent 16#1e8e +/Ydotbelow 16#1ef4 +/Yericyrillic 16#042b +/Yerudieresiscyrillic 16#04f8 +/Ygrave 16#1ef2 +/Yhook 16#01b3 +/Yhookabove 16#1ef6 +/Yiarmenian 16#0545 +/Yicyrillic 16#0407 +/Yiwnarmenian 16#0552 +/Ymonospace 16#ff39 +/Ysmall 16#f779 +/Ytilde 16#1ef8 +/Yusbigcyrillic 16#046a +/Yusbigiotifiedcyrillic 16#046c +/Yuslittlecyrillic 16#0466 +/Yuslittleiotifiedcyrillic 16#0468 +/Z 16#005a +/Zaarmenian 16#0536 +/Zacute 16#0179 +/Zcaron 16#017d +/Zcaronsmall 16#f6ff +/Zcircle 16#24cf +/Zcircumflex 16#1e90 +/Zdot 16#017b +/Zdotaccent 16#017b +/Zdotbelow 16#1e92 +/Zecyrillic 16#0417 +/Zedescendercyrillic 16#0498 +/Zedieresiscyrillic 16#04de +/Zeta 16#0396 +/Zhearmenian 16#053a +/Zhebrevecyrillic 16#04c1 +/Zhecyrillic 16#0416 +/Zhedescendercyrillic 16#0496 +/Zhedieresiscyrillic 16#04dc +/Zlinebelow 16#1e94 +/Zmonospace 16#ff3a +/Zsmall 16#f77a +/Zstroke 16#01b5 +/a 16#0061 +/aabengali 16#0986 +/aacute 16#00e1 +/aadeva 16#0906 +/aagujarati 16#0a86 +/aagurmukhi 16#0a06 +/aamatragurmukhi 16#0a3e +/aarusquare 16#3303 +/aavowelsignbengali 16#09be +/aavowelsigndeva 16#093e +/aavowelsigngujarati 16#0abe +/abbreviationmarkarmenian 16#055f +/abbreviationsigndeva 16#0970 +/abengali 16#0985 +/abopomofo 16#311a +/abreve 16#0103 +/abreveacute 16#1eaf +/abrevecyrillic 16#04d1 +/abrevedotbelow 16#1eb7 +/abrevegrave 16#1eb1 +/abrevehookabove 16#1eb3 +/abrevetilde 16#1eb5 +/acaron 16#01ce +/acircle 16#24d0 +/acircumflex 16#00e2 +/acircumflexacute 16#1ea5 +/acircumflexdotbelow 16#1ead +/acircumflexgrave 16#1ea7 +/acircumflexhookabove 16#1ea9 +/acircumflextilde 16#1eab +/acute 16#00b4 +/acutebelowcmb 16#0317 +/acutecmb 16#0301 +/acutecomb 16#0301 +/acutedeva 16#0954 +/acutelowmod 16#02cf +/acutetonecmb 16#0341 +/acyrillic 16#0430 +/adblgrave 16#0201 +/addakgurmukhi 16#0a71 +/adeva 16#0905 +/adieresis 16#00e4 +/adieresiscyrillic 16#04d3 +/adieresismacron 16#01df +/adotbelow 16#1ea1 +/adotmacron 16#01e1 +/ae 16#00e6 +/aeacute 16#01fd +/aekorean 16#3150 +/aemacron 16#01e3 +/afii00208 16#2015 +/afii08941 16#20a4 +/afii10017 16#0410 +/afii10018 16#0411 +/afii10019 16#0412 +/afii10020 16#0413 +/afii10021 16#0414 +/afii10022 16#0415 +/afii10023 16#0401 +/afii10024 16#0416 +/afii10025 16#0417 +/afii10026 16#0418 +/afii10027 16#0419 +/afii10028 16#041a +/afii10029 16#041b +/afii10030 16#041c +/afii10031 16#041d +/afii10032 16#041e +/afii10033 16#041f +/afii10034 16#0420 +/afii10035 16#0421 +/afii10036 16#0422 +/afii10037 16#0423 +/afii10038 16#0424 +/afii10039 16#0425 +/afii10040 16#0426 +/afii10041 16#0427 +/afii10042 16#0428 +/afii10043 16#0429 +/afii10044 16#042a +/afii10045 16#042b +/afii10046 16#042c +/afii10047 16#042d +/afii10048 16#042e +/afii10049 16#042f +/afii10050 16#0490 +/afii10051 16#0402 +/afii10052 16#0403 +/afii10053 16#0404 +/afii10054 16#0405 +/afii10055 16#0406 +/afii10056 16#0407 +/afii10057 16#0408 +/afii10058 16#0409 +/afii10059 16#040a +/afii10060 16#040b +/afii10061 16#040c +/afii10062 16#040e +/afii10063 16#f6c4 +/afii10064 16#f6c5 +/afii10065 16#0430 +/afii10066 16#0431 +/afii10067 16#0432 +/afii10068 16#0433 +/afii10069 16#0434 +/afii10070 16#0435 +/afii10071 16#0451 +/afii10072 16#0436 +/afii10073 16#0437 +/afii10074 16#0438 +/afii10075 16#0439 +/afii10076 16#043a +/afii10077 16#043b +/afii10078 16#043c +/afii10079 16#043d +/afii10080 16#043e +/afii10081 16#043f +/afii10082 16#0440 +/afii10083 16#0441 +/afii10084 16#0442 +/afii10085 16#0443 +/afii10086 16#0444 +/afii10087 16#0445 +/afii10088 16#0446 +/afii10089 16#0447 +/afii10090 16#0448 +/afii10091 16#0449 +/afii10092 16#044a +/afii10093 16#044b +/afii10094 16#044c +/afii10095 16#044d +/afii10096 16#044e +/afii10097 16#044f +/afii10098 16#0491 +/afii10099 16#0452 +/afii10100 16#0453 +/afii10101 16#0454 +/afii10102 16#0455 +/afii10103 16#0456 +/afii10104 16#0457 +/afii10105 16#0458 +/afii10106 16#0459 +/afii10107 16#045a +/afii10108 16#045b +/afii10109 16#045c +/afii10110 16#045e +/afii10145 16#040f +/afii10146 16#0462 +/afii10147 16#0472 +/afii10148 16#0474 +/afii10192 16#f6c6 +/afii10193 16#045f +/afii10194 16#0463 +/afii10195 16#0473 +/afii10196 16#0475 +/afii10831 16#f6c7 +/afii10832 16#f6c8 +/afii10846 16#04d9 +/afii299 16#200e +/afii300 16#200f +/afii301 16#200d +/afii57381 16#066a +/afii57388 16#060c +/afii57392 16#0660 +/afii57393 16#0661 +/afii57394 16#0662 +/afii57395 16#0663 +/afii57396 16#0664 +/afii57397 16#0665 +/afii57398 16#0666 +/afii57399 16#0667 +/afii57400 16#0668 +/afii57401 16#0669 +/afii57403 16#061b +/afii57407 16#061f +/afii57409 16#0621 +/afii57410 16#0622 +/afii57411 16#0623 +/afii57412 16#0624 +/afii57413 16#0625 +/afii57414 16#0626 +/afii57415 16#0627 +/afii57416 16#0628 +/afii57417 16#0629 +/afii57418 16#062a +/afii57419 16#062b +/afii57420 16#062c +/afii57421 16#062d +/afii57422 16#062e +/afii57423 16#062f +/afii57424 16#0630 +/afii57425 16#0631 +/afii57426 16#0632 +/afii57427 16#0633 +/afii57428 16#0634 +/afii57429 16#0635 +/afii57430 16#0636 +/afii57431 16#0637 +/afii57432 16#0638 +/afii57433 16#0639 +/afii57434 16#063a +/afii57440 16#0640 +/afii57441 16#0641 +/afii57442 16#0642 +/afii57443 16#0643 +/afii57444 16#0644 +/afii57445 16#0645 +/afii57446 16#0646 +/afii57448 16#0648 +/afii57449 16#0649 +/afii57450 16#064a +/afii57451 16#064b +/afii57452 16#064c +/afii57453 16#064d +/afii57454 16#064e +/afii57455 16#064f +/afii57456 16#0650 +/afii57457 16#0651 +/afii57458 16#0652 +/afii57470 16#0647 +/afii57505 16#06a4 +/afii57506 16#067e +/afii57507 16#0686 +/afii57508 16#0698 +/afii57509 16#06af +/afii57511 16#0679 +/afii57512 16#0688 +/afii57513 16#0691 +/afii57514 16#06ba +/afii57519 16#06d2 +/afii57534 16#06d5 +/afii57636 16#20aa +/afii57645 16#05be +/afii57658 16#05c3 +/afii57664 16#05d0 +/afii57665 16#05d1 +/afii57666 16#05d2 +/afii57667 16#05d3 +/afii57668 16#05d4 +/afii57669 16#05d5 +/afii57670 16#05d6 +/afii57671 16#05d7 +/afii57672 16#05d8 +/afii57673 16#05d9 +/afii57674 16#05da +/afii57675 16#05db +/afii57676 16#05dc +/afii57677 16#05dd +/afii57678 16#05de +/afii57679 16#05df +/afii57680 16#05e0 +/afii57681 16#05e1 +/afii57682 16#05e2 +/afii57683 16#05e3 +/afii57684 16#05e4 +/afii57685 16#05e5 +/afii57686 16#05e6 +/afii57687 16#05e7 +/afii57688 16#05e8 +/afii57689 16#05e9 +/afii57690 16#05ea +/afii57694 16#fb2a +/afii57695 16#fb2b +/afii57700 16#fb4b +/afii57705 16#fb1f +/afii57716 16#05f0 +/afii57717 16#05f1 +/afii57718 16#05f2 +/afii57723 16#fb35 +/afii57793 16#05b4 +/afii57794 16#05b5 +/afii57795 16#05b6 +/afii57796 16#05bb +/afii57797 16#05b8 +/afii57798 16#05b7 +/afii57799 16#05b0 +/afii57800 16#05b2 +/afii57801 16#05b1 +/afii57802 16#05b3 +/afii57803 16#05c2 +/afii57804 16#05c1 +/afii57806 16#05b9 +/afii57807 16#05bc +/afii57839 16#05bd +/afii57841 16#05bf +/afii57842 16#05c0 +/afii57929 16#02bc +/afii61248 16#2105 +/afii61289 16#2113 +/afii61352 16#2116 +/afii61573 16#202c +/afii61574 16#202d +/afii61575 16#202e +/afii61664 16#200c +/afii63167 16#066d +/afii64937 16#02bd +/agrave 16#00e0 +/agujarati 16#0a85 +/agurmukhi 16#0a05 +/ahiragana 16#3042 +/ahookabove 16#1ea3 +/aibengali 16#0990 +/aibopomofo 16#311e +/aideva 16#0910 +/aiecyrillic 16#04d5 +/aigujarati 16#0a90 +/aigurmukhi 16#0a10 +/aimatragurmukhi 16#0a48 +/ainarabic 16#0639 +/ainfinalarabic 16#feca +/aininitialarabic 16#fecb +/ainmedialarabic 16#fecc +/ainvertedbreve 16#0203 +/aivowelsignbengali 16#09c8 +/aivowelsigndeva 16#0948 +/aivowelsigngujarati 16#0ac8 +/akatakana 16#30a2 +/akatakanahalfwidth 16#ff71 +/akorean 16#314f +/alef 16#05d0 +/alefarabic 16#0627 +/alefdageshhebrew 16#fb30 +/aleffinalarabic 16#fe8e +/alefhamzaabovearabic 16#0623 +/alefhamzaabovefinalarabic 16#fe84 +/alefhamzabelowarabic 16#0625 +/alefhamzabelowfinalarabic 16#fe88 +/alefhebrew 16#05d0 +/aleflamedhebrew 16#fb4f +/alefmaddaabovearabic 16#0622 +/alefmaddaabovefinalarabic 16#fe82 +/alefmaksuraarabic 16#0649 +/alefmaksurafinalarabic 16#fef0 +/alefmaksurainitialarabic 16#fef3 +/alefmaksuramedialarabic 16#fef4 +/alefpatahhebrew 16#fb2e +/alefqamatshebrew 16#fb2f +/aleph 16#2135 +/allequal 16#224c +/alpha 16#03b1 +/alphatonos 16#03ac +/amacron 16#0101 +/amonospace 16#ff41 +/ampersand 16#0026 +/ampersandmonospace 16#ff06 +/ampersandsmall 16#f726 +/amsquare 16#33c2 +/anbopomofo 16#3122 +/angbopomofo 16#3124 +/angkhankhuthai 16#0e5a +/angle 16#2220 +/anglebracketleft 16#3008 +/anglebracketleftvertical 16#fe3f +/anglebracketright 16#3009 +/anglebracketrightvertical 16#fe40 +/angleleft 16#2329 +/angleright 16#232a +/angstrom 16#212b +/anoteleia 16#0387 +/anudattadeva 16#0952 +/anusvarabengali 16#0982 +/anusvaradeva 16#0902 +/anusvaragujarati 16#0a82 +/aogonek 16#0105 +/apaatosquare 16#3300 +/aparen 16#249c +/apostrophearmenian 16#055a +/apostrophemod 16#02bc +/apple 16#f8ff +/approaches 16#2250 +/approxequal 16#2248 +/approxequalorimage 16#2252 +/approximatelyequal 16#2245 +/araeaekorean 16#318e +/araeakorean 16#318d +/arc 16#2312 +/arighthalfring 16#1e9a +/aring 16#00e5 +/aringacute 16#01fb +/aringbelow 16#1e01 +/arrowboth 16#2194 +/arrowdashdown 16#21e3 +/arrowdashleft 16#21e0 +/arrowdashright 16#21e2 +/arrowdashup 16#21e1 +/arrowdblboth 16#21d4 +/arrowdbldown 16#21d3 +/arrowdblleft 16#21d0 +/arrowdblright 16#21d2 +/arrowdblup 16#21d1 +/arrowdown 16#2193 +/arrowdownleft 16#2199 +/arrowdownright 16#2198 +/arrowdownwhite 16#21e9 +/arrowheaddownmod 16#02c5 +/arrowheadleftmod 16#02c2 +/arrowheadrightmod 16#02c3 +/arrowheadupmod 16#02c4 +/arrowhorizex 16#f8e7 +/arrowleft 16#2190 +/arrowleftdbl 16#21d0 +/arrowleftdblstroke 16#21cd +/arrowleftoverright 16#21c6 +/arrowleftwhite 16#21e6 +/arrowright 16#2192 +/arrowrightdblstroke 16#21cf +/arrowrightheavy 16#279e +/arrowrightoverleft 16#21c4 +/arrowrightwhite 16#21e8 +/arrowtableft 16#21e4 +/arrowtabright 16#21e5 +/arrowup 16#2191 +/arrowupdn 16#2195 +/arrowupdnbse 16#21a8 +/arrowupdownbase 16#21a8 +/arrowupleft 16#2196 +/arrowupleftofdown 16#21c5 +/arrowupright 16#2197 +/arrowupwhite 16#21e7 +/arrowvertex 16#f8e6 +/asciicircum 16#005e +/asciicircummonospace 16#ff3e +/asciitilde 16#007e +/asciitildemonospace 16#ff5e +/ascript 16#0251 +/ascriptturned 16#0252 +/asmallhiragana 16#3041 +/asmallkatakana 16#30a1 +/asmallkatakanahalfwidth 16#ff67 +/asterisk 16#002a +/asteriskaltonearabic 16#066d +/asteriskarabic 16#066d +/asteriskmath 16#2217 +/asteriskmonospace 16#ff0a +/asterisksmall 16#fe61 +/asterism 16#2042 +/asuperior 16#f6e9 +/asymptoticallyequal 16#2243 +/at 16#0040 +/atilde 16#00e3 +/atmonospace 16#ff20 +/atsmall 16#fe6b +/aturned 16#0250 +/aubengali 16#0994 +/aubopomofo 16#3120 +/audeva 16#0914 +/augujarati 16#0a94 +/augurmukhi 16#0a14 +/aulengthmarkbengali 16#09d7 +/aumatragurmukhi 16#0a4c +/auvowelsignbengali 16#09cc +/auvowelsigndeva 16#094c +/auvowelsigngujarati 16#0acc +/avagrahadeva 16#093d +/aybarmenian 16#0561 +/ayin 16#05e2 +/ayinaltonehebrew 16#fb20 +/ayinhebrew 16#05e2 +/b 16#0062 +/babengali 16#09ac +/backslash 16#005c +/backslashmonospace 16#ff3c +/badeva 16#092c +/bagujarati 16#0aac +/bagurmukhi 16#0a2c +/bahiragana 16#3070 +/bahtthai 16#0e3f +/bakatakana 16#30d0 +/bar 16#007c +/barmonospace 16#ff5c +/bbopomofo 16#3105 +/bcircle 16#24d1 +/bdotaccent 16#1e03 +/bdotbelow 16#1e05 +/beamedsixteenthnotes 16#266c +/because 16#2235 +/becyrillic 16#0431 +/beharabic 16#0628 +/behfinalarabic 16#fe90 +/behinitialarabic 16#fe91 +/behiragana 16#3079 +/behmedialarabic 16#fe92 +/behmeeminitialarabic 16#fc9f +/behmeemisolatedarabic 16#fc08 +/behnoonfinalarabic 16#fc6d +/bekatakana 16#30d9 +/benarmenian 16#0562 +/bet 16#05d1 +/beta 16#03b2 +/betasymbolgreek 16#03d0 +/betdagesh 16#fb31 +/betdageshhebrew 16#fb31 +/bethebrew 16#05d1 +/betrafehebrew 16#fb4c +/bhabengali 16#09ad +/bhadeva 16#092d +/bhagujarati 16#0aad +/bhagurmukhi 16#0a2d +/bhook 16#0253 +/bihiragana 16#3073 +/bikatakana 16#30d3 +/bilabialclick 16#0298 +/bindigurmukhi 16#0a02 +/birusquare 16#3331 +/blackcircle 16#25cf +/blackdiamond 16#25c6 +/blackdownpointingtriangle 16#25bc +/blackleftpointingpointer 16#25c4 +/blackleftpointingtriangle 16#25c0 +/blacklenticularbracketleft 16#3010 +/blacklenticularbracketleftvertical 16#fe3b +/blacklenticularbracketright 16#3011 +/blacklenticularbracketrightvertical 16#fe3c +/blacklowerlefttriangle 16#25e3 +/blacklowerrighttriangle 16#25e2 +/blackrectangle 16#25ac +/blackrightpointingpointer 16#25ba +/blackrightpointingtriangle 16#25b6 +/blacksmallsquare 16#25aa +/blacksmilingface 16#263b +/blacksquare 16#25a0 +/blackstar 16#2605 +/blackupperlefttriangle 16#25e4 +/blackupperrighttriangle 16#25e5 +/blackuppointingsmalltriangle 16#25b4 +/blackuppointingtriangle 16#25b2 +/blank 16#2423 +/blinebelow 16#1e07 +/block 16#2588 +/bmonospace 16#ff42 +/bobaimaithai 16#0e1a +/bohiragana 16#307c +/bokatakana 16#30dc +/bparen 16#249d +/bqsquare 16#33c3 +/braceex 16#f8f4 +/braceleft 16#007b +/braceleftbt 16#f8f3 +/braceleftmid 16#f8f2 +/braceleftmonospace 16#ff5b +/braceleftsmall 16#fe5b +/bracelefttp 16#f8f1 +/braceleftvertical 16#fe37 +/braceright 16#007d +/bracerightbt 16#f8fe +/bracerightmid 16#f8fd +/bracerightmonospace 16#ff5d +/bracerightsmall 16#fe5c +/bracerighttp 16#f8fc +/bracerightvertical 16#fe38 +/bracketleft 16#005b +/bracketleftbt 16#f8f0 +/bracketleftex 16#f8ef +/bracketleftmonospace 16#ff3b +/bracketlefttp 16#f8ee +/bracketright 16#005d +/bracketrightbt 16#f8fb +/bracketrightex 16#f8fa +/bracketrightmonospace 16#ff3d +/bracketrighttp 16#f8f9 +/breve 16#02d8 +/brevebelowcmb 16#032e +/brevecmb 16#0306 +/breveinvertedbelowcmb 16#032f +/breveinvertedcmb 16#0311 +/breveinverteddoublecmb 16#0361 +/bridgebelowcmb 16#032a +/bridgeinvertedbelowcmb 16#033a +/brokenbar 16#00a6 +/bstroke 16#0180 +/bsuperior 16#f6ea +/btopbar 16#0183 +/buhiragana 16#3076 +/bukatakana 16#30d6 +/bullet 16#2022 +/bulletinverse 16#25d8 +/bulletoperator 16#2219 +/bullseye 16#25ce +/c 16#0063 +/caarmenian 16#056e +/cabengali 16#099a +/cacute 16#0107 +/cadeva 16#091a +/cagujarati 16#0a9a +/cagurmukhi 16#0a1a +/calsquare 16#3388 +/candrabindubengali 16#0981 +/candrabinducmb 16#0310 +/candrabindudeva 16#0901 +/candrabindugujarati 16#0a81 +/capslock 16#21ea +/careof 16#2105 +/caron 16#02c7 +/caronbelowcmb 16#032c +/caroncmb 16#030c +/carriagereturn 16#21b5 +/cbopomofo 16#3118 +/ccaron 16#010d +/ccedilla 16#00e7 +/ccedillaacute 16#1e09 +/ccircle 16#24d2 +/ccircumflex 16#0109 +/ccurl 16#0255 +/cdot 16#010b +/cdotaccent 16#010b +/cdsquare 16#33c5 +/cedilla 16#00b8 +/cedillacmb 16#0327 +/cent 16#00a2 +/centigrade 16#2103 +/centinferior 16#f6df +/centmonospace 16#ffe0 +/centoldstyle 16#f7a2 +/centsuperior 16#f6e0 +/chaarmenian 16#0579 +/chabengali 16#099b +/chadeva 16#091b +/chagujarati 16#0a9b +/chagurmukhi 16#0a1b +/chbopomofo 16#3114 +/cheabkhasiancyrillic 16#04bd +/checkmark 16#2713 +/checyrillic 16#0447 +/chedescenderabkhasiancyrillic 16#04bf +/chedescendercyrillic 16#04b7 +/chedieresiscyrillic 16#04f5 +/cheharmenian 16#0573 +/chekhakassiancyrillic 16#04cc +/cheverticalstrokecyrillic 16#04b9 +/chi 16#03c7 +/chieuchacirclekorean 16#3277 +/chieuchaparenkorean 16#3217 +/chieuchcirclekorean 16#3269 +/chieuchkorean 16#314a +/chieuchparenkorean 16#3209 +/chochangthai 16#0e0a +/chochanthai 16#0e08 +/chochingthai 16#0e09 +/chochoethai 16#0e0c +/chook 16#0188 +/cieucacirclekorean 16#3276 +/cieucaparenkorean 16#3216 +/cieuccirclekorean 16#3268 +/cieuckorean 16#3148 +/cieucparenkorean 16#3208 +/cieucuparenkorean 16#321c +/circle 16#25cb +/circlemultiply 16#2297 +/circleot 16#2299 +/circleplus 16#2295 +/circlepostalmark 16#3036 +/circlewithlefthalfblack 16#25d0 +/circlewithrighthalfblack 16#25d1 +/circumflex 16#02c6 +/circumflexbelowcmb 16#032d +/circumflexcmb 16#0302 +/clear 16#2327 +/clickalveolar 16#01c2 +/clickdental 16#01c0 +/clicklateral 16#01c1 +/clickretroflex 16#01c3 +/club 16#2663 +/clubsuitblack 16#2663 +/clubsuitwhite 16#2667 +/cmcubedsquare 16#33a4 +/cmonospace 16#ff43 +/cmsquaredsquare 16#33a0 +/coarmenian 16#0581 +/colon 16#003a +/colonmonetary 16#20a1 +/colonmonospace 16#ff1a +/colonsign 16#20a1 +/colonsmall 16#fe55 +/colontriangularhalfmod 16#02d1 +/colontriangularmod 16#02d0 +/comma 16#002c +/commaabovecmb 16#0313 +/commaaboverightcmb 16#0315 +/commaaccent 16#f6c3 +/commaarabic 16#060c +/commaarmenian 16#055d +/commainferior 16#f6e1 +/commamonospace 16#ff0c +/commareversedabovecmb 16#0314 +/commareversedmod 16#02bd +/commasmall 16#fe50 +/commasuperior 16#f6e2 +/commaturnedabovecmb 16#0312 +/commaturnedmod 16#02bb +/compass 16#263c +/congruent 16#2245 +/contourintegral 16#222e +/control 16#2303 +/controlACK 16#0006 +/controlBEL 16#0007 +/controlBS 16#0008 +/controlCAN 16#0018 +/controlCR 16#000d +/controlDC1 16#0011 +/controlDC2 16#0012 +/controlDC3 16#0013 +/controlDC4 16#0014 +/controlDEL 16#007f +/controlDLE 16#0010 +/controlEM 16#0019 +/controlENQ 16#0005 +/controlEOT 16#0004 +/controlESC 16#001b +/controlETB 16#0017 +/controlETX 16#0003 +/controlFF 16#000c +/controlFS 16#001c +/controlGS 16#001d +/controlHT 16#0009 +/controlLF 16#000a +/controlNAK 16#0015 +/controlRS 16#001e +/controlSI 16#000f +/controlSO 16#000e +/controlSOT 16#0002 +/controlSTX 16#0001 +/controlSUB 16#001a +/controlSYN 16#0016 +/controlUS 16#001f +/controlVT 16#000b +/copyright 16#00a9 +/copyrightsans 16#f8e9 +/copyrightserif 16#f6d9 +/cornerbracketleft 16#300c +/cornerbracketlefthalfwidth 16#ff62 +/cornerbracketleftvertical 16#fe41 +/cornerbracketright 16#300d +/cornerbracketrighthalfwidth 16#ff63 +/cornerbracketrightvertical 16#fe42 +/corporationsquare 16#337f +/cosquare 16#33c7 +/coverkgsquare 16#33c6 +/cparen 16#249e +/cruzeiro 16#20a2 +/cstretched 16#0297 +/curlyand 16#22cf +/curlyor 16#22ce +/currency 16#00a4 +/cyrBreve 16#f6d1 +/cyrFlex 16#f6d2 +/cyrbreve 16#f6d4 +/cyrflex 16#f6d5 +/d 16#0064 +/daarmenian 16#0564 +/dabengali 16#09a6 +/dadarabic 16#0636 +/dadeva 16#0926 +/dadfinalarabic 16#febe +/dadinitialarabic 16#febf +/dadmedialarabic 16#fec0 +/dagesh 16#05bc +/dageshhebrew 16#05bc +/dagger 16#2020 +/daggerdbl 16#2021 +/dagujarati 16#0aa6 +/dagurmukhi 16#0a26 +/dahiragana 16#3060 +/dakatakana 16#30c0 +/dalarabic 16#062f +/dalet 16#05d3 +/daletdagesh 16#fb33 +/daletdageshhebrew 16#fb33 +/dalethebrew 16#05d3 +/dalfinalarabic 16#feaa +/dammaarabic 16#064f +/dammalowarabic 16#064f +/dammatanaltonearabic 16#064c +/dammatanarabic 16#064c +/danda 16#0964 +/dargahebrew 16#05a7 +/dargalefthebrew 16#05a7 +/dasiapneumatacyrilliccmb 16#0485 +/dblGrave 16#f6d3 +/dblanglebracketleft 16#300a +/dblanglebracketleftvertical 16#fe3d +/dblanglebracketright 16#300b +/dblanglebracketrightvertical 16#fe3e +/dblarchinvertedbelowcmb 16#032b +/dblarrowleft 16#21d4 +/dblarrowright 16#21d2 +/dbldanda 16#0965 +/dblgrave 16#f6d6 +/dblgravecmb 16#030f +/dblintegral 16#222c +/dbllowline 16#2017 +/dbllowlinecmb 16#0333 +/dbloverlinecmb 16#033f +/dblprimemod 16#02ba +/dblverticalbar 16#2016 +/dblverticallineabovecmb 16#030e +/dbopomofo 16#3109 +/dbsquare 16#33c8 +/dcaron 16#010f +/dcedilla 16#1e11 +/dcircle 16#24d3 +/dcircumflexbelow 16#1e13 +/dcroat 16#0111 +/ddabengali 16#09a1 +/ddadeva 16#0921 +/ddagujarati 16#0aa1 +/ddagurmukhi 16#0a21 +/ddalarabic 16#0688 +/ddalfinalarabic 16#fb89 +/dddhadeva 16#095c +/ddhabengali 16#09a2 +/ddhadeva 16#0922 +/ddhagujarati 16#0aa2 +/ddhagurmukhi 16#0a22 +/ddotaccent 16#1e0b +/ddotbelow 16#1e0d +/decimalseparatorarabic 16#066b +/decimalseparatorpersian 16#066b +/decyrillic 16#0434 +/degree 16#00b0 +/dehihebrew 16#05ad +/dehiragana 16#3067 +/deicoptic 16#03ef +/dekatakana 16#30c7 +/deleteleft 16#232b +/deleteright 16#2326 +/delta 16#03b4 +/deltaturned 16#018d +/denominatorminusonenumeratorbengali 16#09f8 +/dezh 16#02a4 +/dhabengali 16#09a7 +/dhadeva 16#0927 +/dhagujarati 16#0aa7 +/dhagurmukhi 16#0a27 +/dhook 16#0257 +/dialytikatonos 16#0385 +/dialytikatonoscmb 16#0344 +/diamond 16#2666 +/diamondsuitwhite 16#2662 +/dieresis 16#00a8 +/dieresisacute 16#f6d7 +/dieresisbelowcmb 16#0324 +/dieresiscmb 16#0308 +/dieresisgrave 16#f6d8 +/dieresistonos 16#0385 +/dihiragana 16#3062 +/dikatakana 16#30c2 +/dittomark 16#3003 +/divide 16#00f7 +/divides 16#2223 +/divisionslash 16#2215 +/djecyrillic 16#0452 +/dkshade 16#2593 +/dlinebelow 16#1e0f +/dlsquare 16#3397 +/dmacron 16#0111 +/dmonospace 16#ff44 +/dnblock 16#2584 +/dochadathai 16#0e0e +/dodekthai 16#0e14 +/dohiragana 16#3069 +/dokatakana 16#30c9 +/dollar 16#0024 +/dollarinferior 16#f6e3 +/dollarmonospace 16#ff04 +/dollaroldstyle 16#f724 +/dollarsmall 16#fe69 +/dollarsuperior 16#f6e4 +/dong 16#20ab +/dorusquare 16#3326 +/dotaccent 16#02d9 +/dotaccentcmb 16#0307 +/dotbelowcmb 16#0323 +/dotbelowcomb 16#0323 +/dotkatakana 16#30fb +/dotlessi 16#0131 +/dotlessj 16#f6be +/dotlessjstrokehook 16#0284 +/dotmath 16#22c5 +/dottedcircle 16#25cc +/doubleyodpatah 16#fb1f +/doubleyodpatahhebrew 16#fb1f +/downtackbelowcmb 16#031e +/downtackmod 16#02d5 +/dparen 16#249f +/dsuperior 16#f6eb +/dtail 16#0256 +/dtopbar 16#018c +/duhiragana 16#3065 +/dukatakana 16#30c5 +/dz 16#01f3 +/dzaltone 16#02a3 +/dzcaron 16#01c6 +/dzcurl 16#02a5 +/dzeabkhasiancyrillic 16#04e1 +/dzecyrillic 16#0455 +/dzhecyrillic 16#045f +/e 16#0065 +/eacute 16#00e9 +/earth 16#2641 +/ebengali 16#098f +/ebopomofo 16#311c +/ebreve 16#0115 +/ecandradeva 16#090d +/ecandragujarati 16#0a8d +/ecandravowelsigndeva 16#0945 +/ecandravowelsigngujarati 16#0ac5 +/ecaron 16#011b +/ecedillabreve 16#1e1d +/echarmenian 16#0565 +/echyiwnarmenian 16#0587 +/ecircle 16#24d4 +/ecircumflex 16#00ea +/ecircumflexacute 16#1ebf +/ecircumflexbelow 16#1e19 +/ecircumflexdotbelow 16#1ec7 +/ecircumflexgrave 16#1ec1 +/ecircumflexhookabove 16#1ec3 +/ecircumflextilde 16#1ec5 +/ecyrillic 16#0454 +/edblgrave 16#0205 +/edeva 16#090f +/edieresis 16#00eb +/edot 16#0117 +/edotaccent 16#0117 +/edotbelow 16#1eb9 +/eegurmukhi 16#0a0f +/eematragurmukhi 16#0a47 +/efcyrillic 16#0444 +/egrave 16#00e8 +/egujarati 16#0a8f +/eharmenian 16#0567 +/ehbopomofo 16#311d +/ehiragana 16#3048 +/ehookabove 16#1ebb +/eibopomofo 16#311f +/eight 16#0038 +/eightarabic 16#0668 +/eightbengali 16#09ee +/eightcircle 16#2467 +/eightcircleinversesansserif 16#2791 +/eightdeva 16#096e +/eighteencircle 16#2471 +/eighteenparen 16#2485 +/eighteenperiod 16#2499 +/eightgujarati 16#0aee +/eightgurmukhi 16#0a6e +/eighthackarabic 16#0668 +/eighthangzhou 16#3028 +/eighthnotebeamed 16#266b +/eightideographicparen 16#3227 +/eightinferior 16#2088 +/eightmonospace 16#ff18 +/eightoldstyle 16#f738 +/eightparen 16#247b +/eightperiod 16#248f +/eightpersian 16#06f8 +/eightroman 16#2177 +/eightsuperior 16#2078 +/eightthai 16#0e58 +/einvertedbreve 16#0207 +/eiotifiedcyrillic 16#0465 +/ekatakana 16#30a8 +/ekatakanahalfwidth 16#ff74 +/ekonkargurmukhi 16#0a74 +/ekorean 16#3154 +/elcyrillic 16#043b +/element 16#2208 +/elevencircle 16#246a +/elevenparen 16#247e +/elevenperiod 16#2492 +/elevenroman 16#217a +/ellipsis 16#2026 +/ellipsisvertical 16#22ee +/emacron 16#0113 +/emacronacute 16#1e17 +/emacrongrave 16#1e15 +/emcyrillic 16#043c +/emdash 16#2014 +/emdashvertical 16#fe31 +/emonospace 16#ff45 +/emphasismarkarmenian 16#055b +/emptyset 16#2205 +/enbopomofo 16#3123 +/encyrillic 16#043d +/endash 16#2013 +/endashvertical 16#fe32 +/endescendercyrillic 16#04a3 +/eng 16#014b +/engbopomofo 16#3125 +/enghecyrillic 16#04a5 +/enhookcyrillic 16#04c8 +/enspace 16#2002 +/eogonek 16#0119 +/eokorean 16#3153 +/eopen 16#025b +/eopenclosed 16#029a +/eopenreversed 16#025c +/eopenreversedclosed 16#025e +/eopenreversedhook 16#025d +/eparen 16#24a0 +/epsilon 16#03b5 +/epsilontonos 16#03ad +/equal 16#003d +/equalmonospace 16#ff1d +/equalsmall 16#fe66 +/equalsuperior 16#207c +/equivalence 16#2261 +/erbopomofo 16#3126 +/ercyrillic 16#0440 +/ereversed 16#0258 +/ereversedcyrillic 16#044d +/escyrillic 16#0441 +/esdescendercyrillic 16#04ab +/esh 16#0283 +/eshcurl 16#0286 +/eshortdeva 16#090e +/eshortvowelsigndeva 16#0946 +/eshreversedloop 16#01aa +/eshsquatreversed 16#0285 +/esmallhiragana 16#3047 +/esmallkatakana 16#30a7 +/esmallkatakanahalfwidth 16#ff6a +/estimated 16#212e +/esuperior 16#f6ec +/eta 16#03b7 +/etarmenian 16#0568 +/etatonos 16#03ae +/eth 16#00f0 +/etilde 16#1ebd +/etildebelow 16#1e1b +/etnahtafoukhhebrew 16#0591 +/etnahtafoukhlefthebrew 16#0591 +/etnahtahebrew 16#0591 +/etnahtalefthebrew 16#0591 +/eturned 16#01dd +/eukorean 16#3161 +/euro 16#20ac +/evowelsignbengali 16#09c7 +/evowelsigndeva 16#0947 +/evowelsigngujarati 16#0ac7 +/exclam 16#0021 +/exclamarmenian 16#055c +/exclamdbl 16#203c +/exclamdown 16#00a1 +/exclamdownsmall 16#f7a1 +/exclammonospace 16#ff01 +/exclamsmall 16#f721 +/existential 16#2203 +/ezh 16#0292 +/ezhcaron 16#01ef +/ezhcurl 16#0293 +/ezhreversed 16#01b9 +/ezhtail 16#01ba +/f 16#0066 +/fadeva 16#095e +/fagurmukhi 16#0a5e +/fahrenheit 16#2109 +/fathaarabic 16#064e +/fathalowarabic 16#064e +/fathatanarabic 16#064b +/fbopomofo 16#3108 +/fcircle 16#24d5 +/fdotaccent 16#1e1f +/feharabic 16#0641 +/feharmenian 16#0586 +/fehfinalarabic 16#fed2 +/fehinitialarabic 16#fed3 +/fehmedialarabic 16#fed4 +/feicoptic 16#03e5 +/female 16#2640 +/ff 16#fb00 +/ffi 16#fb03 +/ffl 16#fb04 +/fi 16#fb01 +/fifteencircle 16#246e +/fifteenparen 16#2482 +/fifteenperiod 16#2496 +/figuredash 16#2012 +/filledbox 16#25a0 +/filledrect 16#25ac +/finalkaf 16#05da +/finalkafdagesh 16#fb3a +/finalkafdageshhebrew 16#fb3a +/finalkafhebrew 16#05da +/finalmem 16#05dd +/finalmemhebrew 16#05dd +/finalnun 16#05df +/finalnunhebrew 16#05df +/finalpe 16#05e3 +/finalpehebrew 16#05e3 +/finaltsadi 16#05e5 +/finaltsadihebrew 16#05e5 +/firsttonechinese 16#02c9 +/fisheye 16#25c9 +/fitacyrillic 16#0473 +/five 16#0035 +/fivearabic 16#0665 +/fivebengali 16#09eb +/fivecircle 16#2464 +/fivecircleinversesansserif 16#278e +/fivedeva 16#096b +/fiveeighths 16#215d +/fivegujarati 16#0aeb +/fivegurmukhi 16#0a6b +/fivehackarabic 16#0665 +/fivehangzhou 16#3025 +/fiveideographicparen 16#3224 +/fiveinferior 16#2085 +/fivemonospace 16#ff15 +/fiveoldstyle 16#f735 +/fiveparen 16#2478 +/fiveperiod 16#248c +/fivepersian 16#06f5 +/fiveroman 16#2174 +/fivesuperior 16#2075 +/fivethai 16#0e55 +/fl 16#fb02 +/florin 16#0192 +/fmonospace 16#ff46 +/fmsquare 16#3399 +/fofanthai 16#0e1f +/fofathai 16#0e1d +/fongmanthai 16#0e4f +/forall 16#2200 +/four 16#0034 +/fourarabic 16#0664 +/fourbengali 16#09ea +/fourcircle 16#2463 +/fourcircleinversesansserif 16#278d +/fourdeva 16#096a +/fourgujarati 16#0aea +/fourgurmukhi 16#0a6a +/fourhackarabic 16#0664 +/fourhangzhou 16#3024 +/fourideographicparen 16#3223 +/fourinferior 16#2084 +/fourmonospace 16#ff14 +/fournumeratorbengali 16#09f7 +/fouroldstyle 16#f734 +/fourparen 16#2477 +/fourperiod 16#248b +/fourpersian 16#06f4 +/fourroman 16#2173 +/foursuperior 16#2074 +/fourteencircle 16#246d +/fourteenparen 16#2481 +/fourteenperiod 16#2495 +/fourthai 16#0e54 +/fourthtonechinese 16#02cb +/fparen 16#24a1 +/fraction 16#2044 +/franc 16#20a3 +/g 16#0067 +/gabengali 16#0997 +/gacute 16#01f5 +/gadeva 16#0917 +/gafarabic 16#06af +/gaffinalarabic 16#fb93 +/gafinitialarabic 16#fb94 +/gafmedialarabic 16#fb95 +/gagujarati 16#0a97 +/gagurmukhi 16#0a17 +/gahiragana 16#304c +/gakatakana 16#30ac +/gamma 16#03b3 +/gammalatinsmall 16#0263 +/gammasuperior 16#02e0 +/gangiacoptic 16#03eb +/gbopomofo 16#310d +/gbreve 16#011f +/gcaron 16#01e7 +/gcedilla 16#0123 +/gcircle 16#24d6 +/gcircumflex 16#011d +/gcommaaccent 16#0123 +/gdot 16#0121 +/gdotaccent 16#0121 +/gecyrillic 16#0433 +/gehiragana 16#3052 +/gekatakana 16#30b2 +/geometricallyequal 16#2251 +/gereshaccenthebrew 16#059c +/gereshhebrew 16#05f3 +/gereshmuqdamhebrew 16#059d +/germandbls 16#00df +/gershayimaccenthebrew 16#059e +/gershayimhebrew 16#05f4 +/getamark 16#3013 +/ghabengali 16#0998 +/ghadarmenian 16#0572 +/ghadeva 16#0918 +/ghagujarati 16#0a98 +/ghagurmukhi 16#0a18 +/ghainarabic 16#063a +/ghainfinalarabic 16#fece +/ghaininitialarabic 16#fecf +/ghainmedialarabic 16#fed0 +/ghemiddlehookcyrillic 16#0495 +/ghestrokecyrillic 16#0493 +/gheupturncyrillic 16#0491 +/ghhadeva 16#095a +/ghhagurmukhi 16#0a5a +/ghook 16#0260 +/ghzsquare 16#3393 +/gihiragana 16#304e +/gikatakana 16#30ae +/gimarmenian 16#0563 +/gimel 16#05d2 +/gimeldagesh 16#fb32 +/gimeldageshhebrew 16#fb32 +/gimelhebrew 16#05d2 +/gjecyrillic 16#0453 +/glottalinvertedstroke 16#01be +/glottalstop 16#0294 +/glottalstopinverted 16#0296 +/glottalstopmod 16#02c0 +/glottalstopreversed 16#0295 +/glottalstopreversedmod 16#02c1 +/glottalstopreversedsuperior 16#02e4 +/glottalstopstroke 16#02a1 +/glottalstopstrokereversed 16#02a2 +/gmacron 16#1e21 +/gmonospace 16#ff47 +/gohiragana 16#3054 +/gokatakana 16#30b4 +/gparen 16#24a2 +/gpasquare 16#33ac +/gradient 16#2207 +/grave 16#0060 +/gravebelowcmb 16#0316 +/gravecmb 16#0300 +/gravecomb 16#0300 +/gravedeva 16#0953 +/gravelowmod 16#02ce +/gravemonospace 16#ff40 +/gravetonecmb 16#0340 +/greater 16#003e +/greaterequal 16#2265 +/greaterequalorless 16#22db +/greatermonospace 16#ff1e +/greaterorequivalent 16#2273 +/greaterorless 16#2277 +/greateroverequal 16#2267 +/greatersmall 16#fe65 +/gscript 16#0261 +/gstroke 16#01e5 +/guhiragana 16#3050 +/guillemotleft 16#00ab +/guillemotright 16#00bb +/guilsinglleft 16#2039 +/guilsinglright 16#203a +/gukatakana 16#30b0 +/guramusquare 16#3318 +/gysquare 16#33c9 +/h 16#0068 +/haabkhasiancyrillic 16#04a9 +/haaltonearabic 16#06c1 +/habengali 16#09b9 +/hadescendercyrillic 16#04b3 +/hadeva 16#0939 +/hagujarati 16#0ab9 +/hagurmukhi 16#0a39 +/haharabic 16#062d +/hahfinalarabic 16#fea2 +/hahinitialarabic 16#fea3 +/hahiragana 16#306f +/hahmedialarabic 16#fea4 +/haitusquare 16#332a +/hakatakana 16#30cf +/hakatakanahalfwidth 16#ff8a +/halantgurmukhi 16#0a4d +/hamzaarabic 16#0621 +/hamzalowarabic 16#0621 +/hangulfiller 16#3164 +/hardsigncyrillic 16#044a +/harpoonleftbarbup 16#21bc +/harpoonrightbarbup 16#21c0 +/hasquare 16#33ca +/hatafpatah 16#05b2 +/hatafpatah16 16#05b2 +/hatafpatah23 16#05b2 +/hatafpatah2f 16#05b2 +/hatafpatahhebrew 16#05b2 +/hatafpatahnarrowhebrew 16#05b2 +/hatafpatahquarterhebrew 16#05b2 +/hatafpatahwidehebrew 16#05b2 +/hatafqamats 16#05b3 +/hatafqamats1b 16#05b3 +/hatafqamats28 16#05b3 +/hatafqamats34 16#05b3 +/hatafqamatshebrew 16#05b3 +/hatafqamatsnarrowhebrew 16#05b3 +/hatafqamatsquarterhebrew 16#05b3 +/hatafqamatswidehebrew 16#05b3 +/hatafsegol 16#05b1 +/hatafsegol17 16#05b1 +/hatafsegol24 16#05b1 +/hatafsegol30 16#05b1 +/hatafsegolhebrew 16#05b1 +/hatafsegolnarrowhebrew 16#05b1 +/hatafsegolquarterhebrew 16#05b1 +/hatafsegolwidehebrew 16#05b1 +/hbar 16#0127 +/hbopomofo 16#310f +/hbrevebelow 16#1e2b +/hcedilla 16#1e29 +/hcircle 16#24d7 +/hcircumflex 16#0125 +/hdieresis 16#1e27 +/hdotaccent 16#1e23 +/hdotbelow 16#1e25 +/he 16#05d4 +/heart 16#2665 +/heartsuitblack 16#2665 +/heartsuitwhite 16#2661 +/hedagesh 16#fb34 +/hedageshhebrew 16#fb34 +/hehaltonearabic 16#06c1 +/heharabic 16#0647 +/hehebrew 16#05d4 +/hehfinalaltonearabic 16#fba7 +/hehfinalalttwoarabic 16#feea +/hehfinalarabic 16#feea +/hehhamzaabovefinalarabic 16#fba5 +/hehhamzaaboveisolatedarabic 16#fba4 +/hehinitialaltonearabic 16#fba8 +/hehinitialarabic 16#feeb +/hehiragana 16#3078 +/hehmedialaltonearabic 16#fba9 +/hehmedialarabic 16#feec +/heiseierasquare 16#337b +/hekatakana 16#30d8 +/hekatakanahalfwidth 16#ff8d +/hekutaarusquare 16#3336 +/henghook 16#0267 +/herutusquare 16#3339 +/het 16#05d7 +/hethebrew 16#05d7 +/hhook 16#0266 +/hhooksuperior 16#02b1 +/hieuhacirclekorean 16#327b +/hieuhaparenkorean 16#321b +/hieuhcirclekorean 16#326d +/hieuhkorean 16#314e +/hieuhparenkorean 16#320d +/hihiragana 16#3072 +/hikatakana 16#30d2 +/hikatakanahalfwidth 16#ff8b +/hiriq 16#05b4 +/hiriq14 16#05b4 +/hiriq21 16#05b4 +/hiriq2d 16#05b4 +/hiriqhebrew 16#05b4 +/hiriqnarrowhebrew 16#05b4 +/hiriqquarterhebrew 16#05b4 +/hiriqwidehebrew 16#05b4 +/hlinebelow 16#1e96 +/hmonospace 16#ff48 +/hoarmenian 16#0570 +/hohipthai 16#0e2b +/hohiragana 16#307b +/hokatakana 16#30db +/hokatakanahalfwidth 16#ff8e +/holam 16#05b9 +/holam19 16#05b9 +/holam26 16#05b9 +/holam32 16#05b9 +/holamhebrew 16#05b9 +/holamnarrowhebrew 16#05b9 +/holamquarterhebrew 16#05b9 +/holamwidehebrew 16#05b9 +/honokhukthai 16#0e2e +/hookabovecomb 16#0309 +/hookcmb 16#0309 +/hookpalatalizedbelowcmb 16#0321 +/hookretroflexbelowcmb 16#0322 +/hoonsquare 16#3342 +/horicoptic 16#03e9 +/horizontalbar 16#2015 +/horncmb 16#031b +/hotsprings 16#2668 +/house 16#2302 +/hparen 16#24a3 +/hsuperior 16#02b0 +/hturned 16#0265 +/huhiragana 16#3075 +/huiitosquare 16#3333 +/hukatakana 16#30d5 +/hukatakanahalfwidth 16#ff8c +/hungarumlaut 16#02dd +/hungarumlautcmb 16#030b +/hv 16#0195 +/hyphen 16#002d +/hypheninferior 16#f6e5 +/hyphenmonospace 16#ff0d +/hyphensmall 16#fe63 +/hyphensuperior 16#f6e6 +/hyphentwo 16#2010 +/i 16#0069 +/iacute 16#00ed +/iacyrillic 16#044f +/ibengali 16#0987 +/ibopomofo 16#3127 +/ibreve 16#012d +/icaron 16#01d0 +/icircle 16#24d8 +/icircumflex 16#00ee +/icyrillic 16#0456 +/idblgrave 16#0209 +/ideographearthcircle 16#328f +/ideographfirecircle 16#328b +/ideographicallianceparen 16#323f +/ideographiccallparen 16#323a +/ideographiccentrecircle 16#32a5 +/ideographicclose 16#3006 +/ideographiccomma 16#3001 +/ideographiccommaleft 16#ff64 +/ideographiccongratulationparen 16#3237 +/ideographiccorrectcircle 16#32a3 +/ideographicearthparen 16#322f +/ideographicenterpriseparen 16#323d +/ideographicexcellentcircle 16#329d +/ideographicfestivalparen 16#3240 +/ideographicfinancialcircle 16#3296 +/ideographicfinancialparen 16#3236 +/ideographicfireparen 16#322b +/ideographichaveparen 16#3232 +/ideographichighcircle 16#32a4 +/ideographiciterationmark 16#3005 +/ideographiclaborcircle 16#3298 +/ideographiclaborparen 16#3238 +/ideographicleftcircle 16#32a7 +/ideographiclowcircle 16#32a6 +/ideographicmedicinecircle 16#32a9 +/ideographicmetalparen 16#322e +/ideographicmoonparen 16#322a +/ideographicnameparen 16#3234 +/ideographicperiod 16#3002 +/ideographicprintcircle 16#329e +/ideographicreachparen 16#3243 +/ideographicrepresentparen 16#3239 +/ideographicresourceparen 16#323e +/ideographicrightcircle 16#32a8 +/ideographicsecretcircle 16#3299 +/ideographicselfparen 16#3242 +/ideographicsocietyparen 16#3233 +/ideographicspace 16#3000 +/ideographicspecialparen 16#3235 +/ideographicstockparen 16#3231 +/ideographicstudyparen 16#323b +/ideographicsunparen 16#3230 +/ideographicsuperviseparen 16#323c +/ideographicwaterparen 16#322c +/ideographicwoodparen 16#322d +/ideographiczero 16#3007 +/ideographmetalcircle 16#328e +/ideographmooncircle 16#328a +/ideographnamecircle 16#3294 +/ideographsuncircle 16#3290 +/ideographwatercircle 16#328c +/ideographwoodcircle 16#328d +/ideva 16#0907 +/idieresis 16#00ef +/idieresisacute 16#1e2f +/idieresiscyrillic 16#04e5 +/idotbelow 16#1ecb +/iebrevecyrillic 16#04d7 +/iecyrillic 16#0435 +/ieungacirclekorean 16#3275 +/ieungaparenkorean 16#3215 +/ieungcirclekorean 16#3267 +/ieungkorean 16#3147 +/ieungparenkorean 16#3207 +/igrave 16#00ec +/igujarati 16#0a87 +/igurmukhi 16#0a07 +/ihiragana 16#3044 +/ihookabove 16#1ec9 +/iibengali 16#0988 +/iicyrillic 16#0438 +/iideva 16#0908 +/iigujarati 16#0a88 +/iigurmukhi 16#0a08 +/iimatragurmukhi 16#0a40 +/iinvertedbreve 16#020b +/iishortcyrillic 16#0439 +/iivowelsignbengali 16#09c0 +/iivowelsigndeva 16#0940 +/iivowelsigngujarati 16#0ac0 +/ij 16#0133 +/ikatakana 16#30a4 +/ikatakanahalfwidth 16#ff72 +/ikorean 16#3163 +/ilde 16#02dc +/iluyhebrew 16#05ac +/imacron 16#012b +/imacroncyrillic 16#04e3 +/imageorapproximatelyequal 16#2253 +/imatragurmukhi 16#0a3f +/imonospace 16#ff49 +/increment 16#2206 +/infinity 16#221e +/iniarmenian 16#056b +/integral 16#222b +/integralbottom 16#2321 +/integralbt 16#2321 +/integralex 16#f8f5 +/integraltop 16#2320 +/integraltp 16#2320 +/intersection 16#2229 +/intisquare 16#3305 +/invbullet 16#25d8 +/invcircle 16#25d9 +/invsmileface 16#263b +/iocyrillic 16#0451 +/iogonek 16#012f +/iota 16#03b9 +/iotadieresis 16#03ca +/iotadieresistonos 16#0390 +/iotalatin 16#0269 +/iotatonos 16#03af +/iparen 16#24a4 +/irigurmukhi 16#0a72 +/ismallhiragana 16#3043 +/ismallkatakana 16#30a3 +/ismallkatakanahalfwidth 16#ff68 +/issharbengali 16#09fa +/istroke 16#0268 +/isuperior 16#f6ed +/iterationhiragana 16#309d +/iterationkatakana 16#30fd +/itilde 16#0129 +/itildebelow 16#1e2d +/iubopomofo 16#3129 +/iucyrillic 16#044e +/ivowelsignbengali 16#09bf +/ivowelsigndeva 16#093f +/ivowelsigngujarati 16#0abf +/izhitsacyrillic 16#0475 +/izhitsadblgravecyrillic 16#0477 +/j 16#006a +/jaarmenian 16#0571 +/jabengali 16#099c +/jadeva 16#091c +/jagujarati 16#0a9c +/jagurmukhi 16#0a1c +/jbopomofo 16#3110 +/jcaron 16#01f0 +/jcircle 16#24d9 +/jcircumflex 16#0135 +/jcrossedtail 16#029d +/jdotlessstroke 16#025f +/jecyrillic 16#0458 +/jeemarabic 16#062c +/jeemfinalarabic 16#fe9e +/jeeminitialarabic 16#fe9f +/jeemmedialarabic 16#fea0 +/jeharabic 16#0698 +/jehfinalarabic 16#fb8b +/jhabengali 16#099d +/jhadeva 16#091d +/jhagujarati 16#0a9d +/jhagurmukhi 16#0a1d +/jheharmenian 16#057b +/jis 16#3004 +/jmonospace 16#ff4a +/jparen 16#24a5 +/jsuperior 16#02b2 +/k 16#006b +/kabashkircyrillic 16#04a1 +/kabengali 16#0995 +/kacute 16#1e31 +/kacyrillic 16#043a +/kadescendercyrillic 16#049b +/kadeva 16#0915 +/kaf 16#05db +/kafarabic 16#0643 +/kafdagesh 16#fb3b +/kafdageshhebrew 16#fb3b +/kaffinalarabic 16#feda +/kafhebrew 16#05db +/kafinitialarabic 16#fedb +/kafmedialarabic 16#fedc +/kafrafehebrew 16#fb4d +/kagujarati 16#0a95 +/kagurmukhi 16#0a15 +/kahiragana 16#304b +/kahookcyrillic 16#04c4 +/kakatakana 16#30ab +/kakatakanahalfwidth 16#ff76 +/kappa 16#03ba +/kappasymbolgreek 16#03f0 +/kapyeounmieumkorean 16#3171 +/kapyeounphieuphkorean 16#3184 +/kapyeounpieupkorean 16#3178 +/kapyeounssangpieupkorean 16#3179 +/karoriisquare 16#330d +/kashidaautoarabic 16#0640 +/kashidaautonosidebearingarabic 16#0640 +/kasmallkatakana 16#30f5 +/kasquare 16#3384 +/kasraarabic 16#0650 +/kasratanarabic 16#064d +/kastrokecyrillic 16#049f +/katahiraprolongmarkhalfwidth 16#ff70 +/kaverticalstrokecyrillic 16#049d +/kbopomofo 16#310e +/kcalsquare 16#3389 +/kcaron 16#01e9 +/kcedilla 16#0137 +/kcircle 16#24da +/kcommaaccent 16#0137 +/kdotbelow 16#1e33 +/keharmenian 16#0584 +/kehiragana 16#3051 +/kekatakana 16#30b1 +/kekatakanahalfwidth 16#ff79 +/kenarmenian 16#056f +/kesmallkatakana 16#30f6 +/kgreenlandic 16#0138 +/khabengali 16#0996 +/khacyrillic 16#0445 +/khadeva 16#0916 +/khagujarati 16#0a96 +/khagurmukhi 16#0a16 +/khaharabic 16#062e +/khahfinalarabic 16#fea6 +/khahinitialarabic 16#fea7 +/khahmedialarabic 16#fea8 +/kheicoptic 16#03e7 +/khhadeva 16#0959 +/khhagurmukhi 16#0a59 +/khieukhacirclekorean 16#3278 +/khieukhaparenkorean 16#3218 +/khieukhcirclekorean 16#326a +/khieukhkorean 16#314b +/khieukhparenkorean 16#320a +/khokhaithai 16#0e02 +/khokhonthai 16#0e05 +/khokhuatthai 16#0e03 +/khokhwaithai 16#0e04 +/khomutthai 16#0e5b +/khook 16#0199 +/khorakhangthai 16#0e06 +/khzsquare 16#3391 +/kihiragana 16#304d +/kikatakana 16#30ad +/kikatakanahalfwidth 16#ff77 +/kiroguramusquare 16#3315 +/kiromeetorusquare 16#3316 +/kirosquare 16#3314 +/kiyeokacirclekorean 16#326e +/kiyeokaparenkorean 16#320e +/kiyeokcirclekorean 16#3260 +/kiyeokkorean 16#3131 +/kiyeokparenkorean 16#3200 +/kiyeoksioskorean 16#3133 +/kjecyrillic 16#045c +/klinebelow 16#1e35 +/klsquare 16#3398 +/kmcubedsquare 16#33a6 +/kmonospace 16#ff4b +/kmsquaredsquare 16#33a2 +/kohiragana 16#3053 +/kohmsquare 16#33c0 +/kokaithai 16#0e01 +/kokatakana 16#30b3 +/kokatakanahalfwidth 16#ff7a +/kooposquare 16#331e +/koppacyrillic 16#0481 +/koreanstandardsymbol 16#327f +/koroniscmb 16#0343 +/kparen 16#24a6 +/kpasquare 16#33aa +/ksicyrillic 16#046f +/ktsquare 16#33cf +/kturned 16#029e +/kuhiragana 16#304f +/kukatakana 16#30af +/kukatakanahalfwidth 16#ff78 +/kvsquare 16#33b8 +/kwsquare 16#33be +/l 16#006c +/labengali 16#09b2 +/lacute 16#013a +/ladeva 16#0932 +/lagujarati 16#0ab2 +/lagurmukhi 16#0a32 +/lakkhangyaothai 16#0e45 +/lamaleffinalarabic 16#fefc +/lamalefhamzaabovefinalarabic 16#fef8 +/lamalefhamzaaboveisolatedarabic 16#fef7 +/lamalefhamzabelowfinalarabic 16#fefa +/lamalefhamzabelowisolatedarabic 16#fef9 +/lamalefisolatedarabic 16#fefb +/lamalefmaddaabovefinalarabic 16#fef6 +/lamalefmaddaaboveisolatedarabic 16#fef5 +/lamarabic 16#0644 +/lambda 16#03bb +/lambdastroke 16#019b +/lamed 16#05dc +/lameddagesh 16#fb3c +/lameddageshhebrew 16#fb3c +/lamedhebrew 16#05dc +/lamfinalarabic 16#fede +/lamhahinitialarabic 16#fcca +/laminitialarabic 16#fedf +/lamjeeminitialarabic 16#fcc9 +/lamkhahinitialarabic 16#fccb +/lamlamhehisolatedarabic 16#fdf2 +/lammedialarabic 16#fee0 +/lammeemhahinitialarabic 16#fd88 +/lammeeminitialarabic 16#fccc +/largecircle 16#25ef +/lbar 16#019a +/lbelt 16#026c +/lbopomofo 16#310c +/lcaron 16#013e +/lcedilla 16#013c +/lcircle 16#24db +/lcircumflexbelow 16#1e3d +/lcommaaccent 16#013c +/ldot 16#0140 +/ldotaccent 16#0140 +/ldotbelow 16#1e37 +/ldotbelowmacron 16#1e39 +/leftangleabovecmb 16#031a +/lefttackbelowcmb 16#0318 +/less 16#003c +/lessequal 16#2264 +/lessequalorgreater 16#22da +/lessmonospace 16#ff1c +/lessorequivalent 16#2272 +/lessorgreater 16#2276 +/lessoverequal 16#2266 +/lesssmall 16#fe64 +/lezh 16#026e +/lfblock 16#258c +/lhookretroflex 16#026d +/lira 16#20a4 +/liwnarmenian 16#056c +/lj 16#01c9 +/ljecyrillic 16#0459 +/ll 16#f6c0 +/lladeva 16#0933 +/llagujarati 16#0ab3 +/llinebelow 16#1e3b +/llladeva 16#0934 +/llvocalicbengali 16#09e1 +/llvocalicdeva 16#0961 +/llvocalicvowelsignbengali 16#09e3 +/llvocalicvowelsigndeva 16#0963 +/lmiddletilde 16#026b +/lmonospace 16#ff4c +/lmsquare 16#33d0 +/lochulathai 16#0e2c +/logicaland 16#2227 +/logicalnot 16#00ac +/logicalnotreversed 16#2310 +/logicalor 16#2228 +/lolingthai 16#0e25 +/longs 16#017f +/lowlinecenterline 16#fe4e +/lowlinecmb 16#0332 +/lowlinedashed 16#fe4d +/lozenge 16#25ca +/lparen 16#24a7 +/lslash 16#0142 +/lsquare 16#2113 +/lsuperior 16#f6ee +/ltshade 16#2591 +/luthai 16#0e26 +/lvocalicbengali 16#098c +/lvocalicdeva 16#090c +/lvocalicvowelsignbengali 16#09e2 +/lvocalicvowelsigndeva 16#0962 +/lxsquare 16#33d3 +/m 16#006d +/mabengali 16#09ae +/macron 16#00af +/macronbelowcmb 16#0331 +/macroncmb 16#0304 +/macronlowmod 16#02cd +/macronmonospace 16#ffe3 +/macute 16#1e3f +/madeva 16#092e +/magujarati 16#0aae +/magurmukhi 16#0a2e +/mahapakhhebrew 16#05a4 +/mahapakhlefthebrew 16#05a4 +/mahiragana 16#307e +/maichattawalowleftthai 16#f895 +/maichattawalowrightthai 16#f894 +/maichattawathai 16#0e4b +/maichattawaupperleftthai 16#f893 +/maieklowleftthai 16#f88c +/maieklowrightthai 16#f88b +/maiekthai 16#0e48 +/maiekupperleftthai 16#f88a +/maihanakatleftthai 16#f884 +/maihanakatthai 16#0e31 +/maitaikhuleftthai 16#f889 +/maitaikhuthai 16#0e47 +/maitholowleftthai 16#f88f +/maitholowrightthai 16#f88e +/maithothai 16#0e49 +/maithoupperleftthai 16#f88d +/maitrilowleftthai 16#f892 +/maitrilowrightthai 16#f891 +/maitrithai 16#0e4a +/maitriupperleftthai 16#f890 +/maiyamokthai 16#0e46 +/makatakana 16#30de +/makatakanahalfwidth 16#ff8f +/male 16#2642 +/mansyonsquare 16#3347 +/maqafhebrew 16#05be +/mars 16#2642 +/masoracirclehebrew 16#05af +/masquare 16#3383 +/mbopomofo 16#3107 +/mbsquare 16#33d4 +/mcircle 16#24dc +/mcubedsquare 16#33a5 +/mdotaccent 16#1e41 +/mdotbelow 16#1e43 +/meemarabic 16#0645 +/meemfinalarabic 16#fee2 +/meeminitialarabic 16#fee3 +/meemmedialarabic 16#fee4 +/meemmeeminitialarabic 16#fcd1 +/meemmeemisolatedarabic 16#fc48 +/meetorusquare 16#334d +/mehiragana 16#3081 +/meizierasquare 16#337e +/mekatakana 16#30e1 +/mekatakanahalfwidth 16#ff92 +/mem 16#05de +/memdagesh 16#fb3e +/memdageshhebrew 16#fb3e +/memhebrew 16#05de +/menarmenian 16#0574 +/merkhahebrew 16#05a5 +/merkhakefulahebrew 16#05a6 +/merkhakefulalefthebrew 16#05a6 +/merkhalefthebrew 16#05a5 +/mhook 16#0271 +/mhzsquare 16#3392 +/middledotkatakanahalfwidth 16#ff65 +/middot 16#00b7 +/mieumacirclekorean 16#3272 +/mieumaparenkorean 16#3212 +/mieumcirclekorean 16#3264 +/mieumkorean 16#3141 +/mieumpansioskorean 16#3170 +/mieumparenkorean 16#3204 +/mieumpieupkorean 16#316e +/mieumsioskorean 16#316f +/mihiragana 16#307f +/mikatakana 16#30df +/mikatakanahalfwidth 16#ff90 +/minus 16#2212 +/minusbelowcmb 16#0320 +/minuscircle 16#2296 +/minusmod 16#02d7 +/minusplus 16#2213 +/minute 16#2032 +/miribaarusquare 16#334a +/mirisquare 16#3349 +/mlonglegturned 16#0270 +/mlsquare 16#3396 +/mmcubedsquare 16#33a3 +/mmonospace 16#ff4d +/mmsquaredsquare 16#339f +/mohiragana 16#3082 +/mohmsquare 16#33c1 +/mokatakana 16#30e2 +/mokatakanahalfwidth 16#ff93 +/molsquare 16#33d6 +/momathai 16#0e21 +/moverssquare 16#33a7 +/moverssquaredsquare 16#33a8 +/mparen 16#24a8 +/mpasquare 16#33ab +/mssquare 16#33b3 +/msuperior 16#f6ef +/mturned 16#026f +/mu 16#00b5 +/mu1 16#00b5 +/muasquare 16#3382 +/muchgreater 16#226b +/muchless 16#226a +/mufsquare 16#338c +/mugreek 16#03bc +/mugsquare 16#338d +/muhiragana 16#3080 +/mukatakana 16#30e0 +/mukatakanahalfwidth 16#ff91 +/mulsquare 16#3395 +/multiply 16#00d7 +/mumsquare 16#339b +/munahhebrew 16#05a3 +/munahlefthebrew 16#05a3 +/musicalnote 16#266a +/musicalnotedbl 16#266b +/musicflatsign 16#266d +/musicsharpsign 16#266f +/mussquare 16#33b2 +/muvsquare 16#33b6 +/muwsquare 16#33bc +/mvmegasquare 16#33b9 +/mvsquare 16#33b7 +/mwmegasquare 16#33bf +/mwsquare 16#33bd +/n 16#006e +/nabengali 16#09a8 +/nabla 16#2207 +/nacute 16#0144 +/nadeva 16#0928 +/nagujarati 16#0aa8 +/nagurmukhi 16#0a28 +/nahiragana 16#306a +/nakatakana 16#30ca +/nakatakanahalfwidth 16#ff85 +/napostrophe 16#0149 +/nasquare 16#3381 +/nbopomofo 16#310b +/nbspace 16#00a0 +/ncaron 16#0148 +/ncedilla 16#0146 +/ncircle 16#24dd +/ncircumflexbelow 16#1e4b +/ncommaaccent 16#0146 +/ndotaccent 16#1e45 +/ndotbelow 16#1e47 +/nehiragana 16#306d +/nekatakana 16#30cd +/nekatakanahalfwidth 16#ff88 +/newsheqelsign 16#20aa +/nfsquare 16#338b +/ngabengali 16#0999 +/ngadeva 16#0919 +/ngagujarati 16#0a99 +/ngagurmukhi 16#0a19 +/ngonguthai 16#0e07 +/nhiragana 16#3093 +/nhookleft 16#0272 +/nhookretroflex 16#0273 +/nieunacirclekorean 16#326f +/nieunaparenkorean 16#320f +/nieuncieuckorean 16#3135 +/nieuncirclekorean 16#3261 +/nieunhieuhkorean 16#3136 +/nieunkorean 16#3134 +/nieunpansioskorean 16#3168 +/nieunparenkorean 16#3201 +/nieunsioskorean 16#3167 +/nieuntikeutkorean 16#3166 +/nihiragana 16#306b +/nikatakana 16#30cb +/nikatakanahalfwidth 16#ff86 +/nikhahitleftthai 16#f899 +/nikhahitthai 16#0e4d +/nine 16#0039 +/ninearabic 16#0669 +/ninebengali 16#09ef +/ninecircle 16#2468 +/ninecircleinversesansserif 16#2792 +/ninedeva 16#096f +/ninegujarati 16#0aef +/ninegurmukhi 16#0a6f +/ninehackarabic 16#0669 +/ninehangzhou 16#3029 +/nineideographicparen 16#3228 +/nineinferior 16#2089 +/ninemonospace 16#ff19 +/nineoldstyle 16#f739 +/nineparen 16#247c +/nineperiod 16#2490 +/ninepersian 16#06f9 +/nineroman 16#2178 +/ninesuperior 16#2079 +/nineteencircle 16#2472 +/nineteenparen 16#2486 +/nineteenperiod 16#249a +/ninethai 16#0e59 +/nj 16#01cc +/njecyrillic 16#045a +/nkatakana 16#30f3 +/nkatakanahalfwidth 16#ff9d +/nlegrightlong 16#019e +/nlinebelow 16#1e49 +/nmonospace 16#ff4e +/nmsquare 16#339a +/nnabengali 16#09a3 +/nnadeva 16#0923 +/nnagujarati 16#0aa3 +/nnagurmukhi 16#0a23 +/nnnadeva 16#0929 +/nohiragana 16#306e +/nokatakana 16#30ce +/nokatakanahalfwidth 16#ff89 +/nonbreakingspace 16#00a0 +/nonenthai 16#0e13 +/nonuthai 16#0e19 +/noonarabic 16#0646 +/noonfinalarabic 16#fee6 +/noonghunnaarabic 16#06ba +/noonghunnafinalarabic 16#fb9f +/nooninitialarabic 16#fee7 +/noonjeeminitialarabic 16#fcd2 +/noonjeemisolatedarabic 16#fc4b +/noonmedialarabic 16#fee8 +/noonmeeminitialarabic 16#fcd5 +/noonmeemisolatedarabic 16#fc4e +/noonnoonfinalarabic 16#fc8d +/notcontains 16#220c +/notelement 16#2209 +/notelementof 16#2209 +/notequal 16#2260 +/notgreater 16#226f +/notgreaternorequal 16#2271 +/notgreaternorless 16#2279 +/notidentical 16#2262 +/notless 16#226e +/notlessnorequal 16#2270 +/notparallel 16#2226 +/notprecedes 16#2280 +/notsubset 16#2284 +/notsucceeds 16#2281 +/notsuperset 16#2285 +/nowarmenian 16#0576 +/nparen 16#24a9 +/nssquare 16#33b1 +/nsuperior 16#207f +/ntilde 16#00f1 +/nu 16#03bd +/nuhiragana 16#306c +/nukatakana 16#30cc +/nukatakanahalfwidth 16#ff87 +/nuktabengali 16#09bc +/nuktadeva 16#093c +/nuktagujarati 16#0abc +/nuktagurmukhi 16#0a3c +/numbersign 16#0023 +/numbersignmonospace 16#ff03 +/numbersignsmall 16#fe5f +/numeralsigngreek 16#0374 +/numeralsignlowergreek 16#0375 +/numero 16#2116 +/nun 16#05e0 +/nundagesh 16#fb40 +/nundageshhebrew 16#fb40 +/nunhebrew 16#05e0 +/nvsquare 16#33b5 +/nwsquare 16#33bb +/nyabengali 16#099e +/nyadeva 16#091e +/nyagujarati 16#0a9e +/nyagurmukhi 16#0a1e +/o 16#006f +/oacute 16#00f3 +/oangthai 16#0e2d +/obarred 16#0275 +/obarredcyrillic 16#04e9 +/obarreddieresiscyrillic 16#04eb +/obengali 16#0993 +/obopomofo 16#311b +/obreve 16#014f +/ocandradeva 16#0911 +/ocandragujarati 16#0a91 +/ocandravowelsigndeva 16#0949 +/ocandravowelsigngujarati 16#0ac9 +/ocaron 16#01d2 +/ocircle 16#24de +/ocircumflex 16#00f4 +/ocircumflexacute 16#1ed1 +/ocircumflexdotbelow 16#1ed9 +/ocircumflexgrave 16#1ed3 +/ocircumflexhookabove 16#1ed5 +/ocircumflextilde 16#1ed7 +/ocyrillic 16#043e +/odblacute 16#0151 +/odblgrave 16#020d +/odeva 16#0913 +/odieresis 16#00f6 +/odieresiscyrillic 16#04e7 +/odotbelow 16#1ecd +/oe 16#0153 +/oekorean 16#315a +/ogonek 16#02db +/ogonekcmb 16#0328 +/ograve 16#00f2 +/ogujarati 16#0a93 +/oharmenian 16#0585 +/ohiragana 16#304a +/ohookabove 16#1ecf +/ohorn 16#01a1 +/ohornacute 16#1edb +/ohorndotbelow 16#1ee3 +/ohorngrave 16#1edd +/ohornhookabove 16#1edf +/ohorntilde 16#1ee1 +/ohungarumlaut 16#0151 +/oi 16#01a3 +/oinvertedbreve 16#020f +/okatakana 16#30aa +/okatakanahalfwidth 16#ff75 +/okorean 16#3157 +/olehebrew 16#05ab +/omacron 16#014d +/omacronacute 16#1e53 +/omacrongrave 16#1e51 +/omdeva 16#0950 +/omega 16#03c9 +/omega1 16#03d6 +/omegacyrillic 16#0461 +/omegalatinclosed 16#0277 +/omegaroundcyrillic 16#047b +/omegatitlocyrillic 16#047d +/omegatonos 16#03ce +/omgujarati 16#0ad0 +/omicron 16#03bf +/omicrontonos 16#03cc +/omonospace 16#ff4f +/one 16#0031 +/onearabic 16#0661 +/onebengali 16#09e7 +/onecircle 16#2460 +/onecircleinversesansserif 16#278a +/onedeva 16#0967 +/onedotenleader 16#2024 +/oneeighth 16#215b +/onefitted 16#f6dc +/onegujarati 16#0ae7 +/onegurmukhi 16#0a67 +/onehackarabic 16#0661 +/onehalf 16#00bd +/onehangzhou 16#3021 +/oneideographicparen 16#3220 +/oneinferior 16#2081 +/onemonospace 16#ff11 +/onenumeratorbengali 16#09f4 +/oneoldstyle 16#f731 +/oneparen 16#2474 +/oneperiod 16#2488 +/onepersian 16#06f1 +/onequarter 16#00bc +/oneroman 16#2170 +/onesuperior 16#00b9 +/onethai 16#0e51 +/onethird 16#2153 +/oogonek 16#01eb +/oogonekmacron 16#01ed +/oogurmukhi 16#0a13 +/oomatragurmukhi 16#0a4b +/oopen 16#0254 +/oparen 16#24aa +/openbullet 16#25e6 +/option 16#2325 +/ordfeminine 16#00aa +/ordmasculine 16#00ba +/orthogonal 16#221f +/oshortdeva 16#0912 +/oshortvowelsigndeva 16#094a +/oslash 16#00f8 +/oslashacute 16#01ff +/osmallhiragana 16#3049 +/osmallkatakana 16#30a9 +/osmallkatakanahalfwidth 16#ff6b +/ostrokeacute 16#01ff +/osuperior 16#f6f0 +/otcyrillic 16#047f +/otilde 16#00f5 +/otildeacute 16#1e4d +/otildedieresis 16#1e4f +/oubopomofo 16#3121 +/overline 16#203e +/overlinecenterline 16#fe4a +/overlinecmb 16#0305 +/overlinedashed 16#fe49 +/overlinedblwavy 16#fe4c +/overlinewavy 16#fe4b +/overscore 16#00af +/ovowelsignbengali 16#09cb +/ovowelsigndeva 16#094b +/ovowelsigngujarati 16#0acb +/p 16#0070 +/paampssquare 16#3380 +/paasentosquare 16#332b +/pabengali 16#09aa +/pacute 16#1e55 +/padeva 16#092a +/pagedown 16#21df +/pageup 16#21de +/pagujarati 16#0aaa +/pagurmukhi 16#0a2a +/pahiragana 16#3071 +/paiyannoithai 16#0e2f +/pakatakana 16#30d1 +/palatalizationcyrilliccmb 16#0484 +/palochkacyrillic 16#04c0 +/pansioskorean 16#317f +/paragraph 16#00b6 +/parallel 16#2225 +/parenleft 16#0028 +/parenleftaltonearabic 16#fd3e +/parenleftbt 16#f8ed +/parenleftex 16#f8ec +/parenleftinferior 16#208d +/parenleftmonospace 16#ff08 +/parenleftsmall 16#fe59 +/parenleftsuperior 16#207d +/parenlefttp 16#f8eb +/parenleftvertical 16#fe35 +/parenright 16#0029 +/parenrightaltonearabic 16#fd3f +/parenrightbt 16#f8f8 +/parenrightex 16#f8f7 +/parenrightinferior 16#208e +/parenrightmonospace 16#ff09 +/parenrightsmall 16#fe5a +/parenrightsuperior 16#207e +/parenrighttp 16#f8f6 +/parenrightvertical 16#fe36 +/partialdiff 16#2202 +/paseqhebrew 16#05c0 +/pashtahebrew 16#0599 +/pasquare 16#33a9 +/patah 16#05b7 +/patah11 16#05b7 +/patah1d 16#05b7 +/patah2a 16#05b7 +/patahhebrew 16#05b7 +/patahnarrowhebrew 16#05b7 +/patahquarterhebrew 16#05b7 +/patahwidehebrew 16#05b7 +/pazerhebrew 16#05a1 +/pbopomofo 16#3106 +/pcircle 16#24df +/pdotaccent 16#1e57 +/pe 16#05e4 +/pecyrillic 16#043f +/pedagesh 16#fb44 +/pedageshhebrew 16#fb44 +/peezisquare 16#333b +/pefinaldageshhebrew 16#fb43 +/peharabic 16#067e +/peharmenian 16#057a +/pehebrew 16#05e4 +/pehfinalarabic 16#fb57 +/pehinitialarabic 16#fb58 +/pehiragana 16#307a +/pehmedialarabic 16#fb59 +/pekatakana 16#30da +/pemiddlehookcyrillic 16#04a7 +/perafehebrew 16#fb4e +/percent 16#0025 +/percentarabic 16#066a +/percentmonospace 16#ff05 +/percentsmall 16#fe6a +/period 16#002e +/periodarmenian 16#0589 +/periodcentered 16#00b7 +/periodhalfwidth 16#ff61 +/periodinferior 16#f6e7 +/periodmonospace 16#ff0e +/periodsmall 16#fe52 +/periodsuperior 16#f6e8 +/perispomenigreekcmb 16#0342 +/perpendicular 16#22a5 +/perthousand 16#2030 +/peseta 16#20a7 +/pfsquare 16#338a +/phabengali 16#09ab +/phadeva 16#092b +/phagujarati 16#0aab +/phagurmukhi 16#0a2b +/phi 16#03c6 +/phi1 16#03d5 +/phieuphacirclekorean 16#327a +/phieuphaparenkorean 16#321a +/phieuphcirclekorean 16#326c +/phieuphkorean 16#314d +/phieuphparenkorean 16#320c +/philatin 16#0278 +/phinthuthai 16#0e3a +/phisymbolgreek 16#03d5 +/phook 16#01a5 +/phophanthai 16#0e1e +/phophungthai 16#0e1c +/phosamphaothai 16#0e20 +/pi 16#03c0 +/pieupacirclekorean 16#3273 +/pieupaparenkorean 16#3213 +/pieupcieuckorean 16#3176 +/pieupcirclekorean 16#3265 +/pieupkiyeokkorean 16#3172 +/pieupkorean 16#3142 +/pieupparenkorean 16#3205 +/pieupsioskiyeokkorean 16#3174 +/pieupsioskorean 16#3144 +/pieupsiostikeutkorean 16#3175 +/pieupthieuthkorean 16#3177 +/pieuptikeutkorean 16#3173 +/pihiragana 16#3074 +/pikatakana 16#30d4 +/pisymbolgreek 16#03d6 +/piwrarmenian 16#0583 +/plus 16#002b +/plusbelowcmb 16#031f +/pluscircle 16#2295 +/plusminus 16#00b1 +/plusmod 16#02d6 +/plusmonospace 16#ff0b +/plussmall 16#fe62 +/plussuperior 16#207a +/pmonospace 16#ff50 +/pmsquare 16#33d8 +/pohiragana 16#307d +/pointingindexdownwhite 16#261f +/pointingindexleftwhite 16#261c +/pointingindexrightwhite 16#261e +/pointingindexupwhite 16#261d +/pokatakana 16#30dd +/poplathai 16#0e1b +/postalmark 16#3012 +/postalmarkface 16#3020 +/pparen 16#24ab +/precedes 16#227a +/prescription 16#211e +/primemod 16#02b9 +/primereversed 16#2035 +/product 16#220f +/projective 16#2305 +/prolongedkana 16#30fc +/propellor 16#2318 +/propersubset 16#2282 +/propersuperset 16#2283 +/proportion 16#2237 +/proportional 16#221d +/psi 16#03c8 +/psicyrillic 16#0471 +/psilipneumatacyrilliccmb 16#0486 +/pssquare 16#33b0 +/puhiragana 16#3077 +/pukatakana 16#30d7 +/pvsquare 16#33b4 +/pwsquare 16#33ba +/q 16#0071 +/qadeva 16#0958 +/qadmahebrew 16#05a8 +/qafarabic 16#0642 +/qaffinalarabic 16#fed6 +/qafinitialarabic 16#fed7 +/qafmedialarabic 16#fed8 +/qamats 16#05b8 +/qamats10 16#05b8 +/qamats1a 16#05b8 +/qamats1c 16#05b8 +/qamats27 16#05b8 +/qamats29 16#05b8 +/qamats33 16#05b8 +/qamatsde 16#05b8 +/qamatshebrew 16#05b8 +/qamatsnarrowhebrew 16#05b8 +/qamatsqatanhebrew 16#05b8 +/qamatsqatannarrowhebrew 16#05b8 +/qamatsqatanquarterhebrew 16#05b8 +/qamatsqatanwidehebrew 16#05b8 +/qamatsquarterhebrew 16#05b8 +/qamatswidehebrew 16#05b8 +/qarneyparahebrew 16#059f +/qbopomofo 16#3111 +/qcircle 16#24e0 +/qhook 16#02a0 +/qmonospace 16#ff51 +/qof 16#05e7 +/qofdagesh 16#fb47 +/qofdageshhebrew 16#fb47 +/qofhebrew 16#05e7 +/qparen 16#24ac +/quarternote 16#2669 +/qubuts 16#05bb +/qubuts18 16#05bb +/qubuts25 16#05bb +/qubuts31 16#05bb +/qubutshebrew 16#05bb +/qubutsnarrowhebrew 16#05bb +/qubutsquarterhebrew 16#05bb +/qubutswidehebrew 16#05bb +/question 16#003f +/questionarabic 16#061f +/questionarmenian 16#055e +/questiondown 16#00bf +/questiondownsmall 16#f7bf +/questiongreek 16#037e +/questionmonospace 16#ff1f +/questionsmall 16#f73f +/quotedbl 16#0022 +/quotedblbase 16#201e +/quotedblleft 16#201c +/quotedblmonospace 16#ff02 +/quotedblprime 16#301e +/quotedblprimereversed 16#301d +/quotedblright 16#201d +/quoteleft 16#2018 +/quoteleftreversed 16#201b +/quotereversed 16#201b +/quoteright 16#2019 +/quoterightn 16#0149 +/quotesinglbase 16#201a +/quotesingle 16#0027 +/quotesinglemonospace 16#ff07 +/r 16#0072 +/raarmenian 16#057c +/rabengali 16#09b0 +/racute 16#0155 +/radeva 16#0930 +/radical 16#221a +/radicalex 16#f8e5 +/radoverssquare 16#33ae +/radoverssquaredsquare 16#33af +/radsquare 16#33ad +/rafe 16#05bf +/rafehebrew 16#05bf +/ragujarati 16#0ab0 +/ragurmukhi 16#0a30 +/rahiragana 16#3089 +/rakatakana 16#30e9 +/rakatakanahalfwidth 16#ff97 +/ralowerdiagonalbengali 16#09f1 +/ramiddlediagonalbengali 16#09f0 +/ramshorn 16#0264 +/ratio 16#2236 +/rbopomofo 16#3116 +/rcaron 16#0159 +/rcedilla 16#0157 +/rcircle 16#24e1 +/rcommaaccent 16#0157 +/rdblgrave 16#0211 +/rdotaccent 16#1e59 +/rdotbelow 16#1e5b +/rdotbelowmacron 16#1e5d +/referencemark 16#203b +/reflexsubset 16#2286 +/reflexsuperset 16#2287 +/registered 16#00ae +/registersans 16#f8e8 +/registerserif 16#f6da +/reharabic 16#0631 +/reharmenian 16#0580 +/rehfinalarabic 16#feae +/rehiragana 16#308c +/rekatakana 16#30ec +/rekatakanahalfwidth 16#ff9a +/resh 16#05e8 +/reshdageshhebrew 16#fb48 +/reshhebrew 16#05e8 +/reversedtilde 16#223d +/reviahebrew 16#0597 +/reviamugrashhebrew 16#0597 +/revlogicalnot 16#2310 +/rfishhook 16#027e +/rfishhookreversed 16#027f +/rhabengali 16#09dd +/rhadeva 16#095d +/rho 16#03c1 +/rhook 16#027d +/rhookturned 16#027b +/rhookturnedsuperior 16#02b5 +/rhosymbolgreek 16#03f1 +/rhotichookmod 16#02de +/rieulacirclekorean 16#3271 +/rieulaparenkorean 16#3211 +/rieulcirclekorean 16#3263 +/rieulhieuhkorean 16#3140 +/rieulkiyeokkorean 16#313a +/rieulkiyeoksioskorean 16#3169 +/rieulkorean 16#3139 +/rieulmieumkorean 16#313b +/rieulpansioskorean 16#316c +/rieulparenkorean 16#3203 +/rieulphieuphkorean 16#313f +/rieulpieupkorean 16#313c +/rieulpieupsioskorean 16#316b +/rieulsioskorean 16#313d +/rieulthieuthkorean 16#313e +/rieultikeutkorean 16#316a +/rieulyeorinhieuhkorean 16#316d +/rightangle 16#221f +/righttackbelowcmb 16#0319 +/righttriangle 16#22bf +/rihiragana 16#308a +/rikatakana 16#30ea +/rikatakanahalfwidth 16#ff98 +/ring 16#02da +/ringbelowcmb 16#0325 +/ringcmb 16#030a +/ringhalfleft 16#02bf +/ringhalfleftarmenian 16#0559 +/ringhalfleftbelowcmb 16#031c +/ringhalfleftcentered 16#02d3 +/ringhalfright 16#02be +/ringhalfrightbelowcmb 16#0339 +/ringhalfrightcentered 16#02d2 +/rinvertedbreve 16#0213 +/rittorusquare 16#3351 +/rlinebelow 16#1e5f +/rlongleg 16#027c +/rlonglegturned 16#027a +/rmonospace 16#ff52 +/rohiragana 16#308d +/rokatakana 16#30ed +/rokatakanahalfwidth 16#ff9b +/roruathai 16#0e23 +/rparen 16#24ad +/rrabengali 16#09dc +/rradeva 16#0931 +/rragurmukhi 16#0a5c +/rreharabic 16#0691 +/rrehfinalarabic 16#fb8d +/rrvocalicbengali 16#09e0 +/rrvocalicdeva 16#0960 +/rrvocalicgujarati 16#0ae0 +/rrvocalicvowelsignbengali 16#09c4 +/rrvocalicvowelsigndeva 16#0944 +/rrvocalicvowelsigngujarati 16#0ac4 +/rsuperior 16#f6f1 +/rtblock 16#2590 +/rturned 16#0279 +/rturnedsuperior 16#02b4 +/ruhiragana 16#308b +/rukatakana 16#30eb +/rukatakanahalfwidth 16#ff99 +/rupeemarkbengali 16#09f2 +/rupeesignbengali 16#09f3 +/rupiah 16#f6dd +/ruthai 16#0e24 +/rvocalicbengali 16#098b +/rvocalicdeva 16#090b +/rvocalicgujarati 16#0a8b +/rvocalicvowelsignbengali 16#09c3 +/rvocalicvowelsigndeva 16#0943 +/rvocalicvowelsigngujarati 16#0ac3 +/s 16#0073 +/sabengali 16#09b8 +/sacute 16#015b +/sacutedotaccent 16#1e65 +/sadarabic 16#0635 +/sadeva 16#0938 +/sadfinalarabic 16#feba +/sadinitialarabic 16#febb +/sadmedialarabic 16#febc +/sagujarati 16#0ab8 +/sagurmukhi 16#0a38 +/sahiragana 16#3055 +/sakatakana 16#30b5 +/sakatakanahalfwidth 16#ff7b +/sallallahoualayhewasallamarabic 16#fdfa +/samekh 16#05e1 +/samekhdagesh 16#fb41 +/samekhdageshhebrew 16#fb41 +/samekhhebrew 16#05e1 +/saraaathai 16#0e32 +/saraaethai 16#0e41 +/saraaimaimalaithai 16#0e44 +/saraaimaimuanthai 16#0e43 +/saraamthai 16#0e33 +/saraathai 16#0e30 +/saraethai 16#0e40 +/saraiileftthai 16#f886 +/saraiithai 16#0e35 +/saraileftthai 16#f885 +/saraithai 16#0e34 +/saraothai 16#0e42 +/saraueeleftthai 16#f888 +/saraueethai 16#0e37 +/saraueleftthai 16#f887 +/sarauethai 16#0e36 +/sarauthai 16#0e38 +/sarauuthai 16#0e39 +/sbopomofo 16#3119 +/scaron 16#0161 +/scarondotaccent 16#1e67 +/scedilla 16#015f +/schwa 16#0259 +/schwacyrillic 16#04d9 +/schwadieresiscyrillic 16#04db +/schwahook 16#025a +/scircle 16#24e2 +/scircumflex 16#015d +/scommaaccent 16#0219 +/sdotaccent 16#1e61 +/sdotbelow 16#1e63 +/sdotbelowdotaccent 16#1e69 +/seagullbelowcmb 16#033c +/second 16#2033 +/secondtonechinese 16#02ca +/section 16#00a7 +/seenarabic 16#0633 +/seenfinalarabic 16#feb2 +/seeninitialarabic 16#feb3 +/seenmedialarabic 16#feb4 +/segol 16#05b6 +/segol13 16#05b6 +/segol1f 16#05b6 +/segol2c 16#05b6 +/segolhebrew 16#05b6 +/segolnarrowhebrew 16#05b6 +/segolquarterhebrew 16#05b6 +/segoltahebrew 16#0592 +/segolwidehebrew 16#05b6 +/seharmenian 16#057d +/sehiragana 16#305b +/sekatakana 16#30bb +/sekatakanahalfwidth 16#ff7e +/semicolon 16#003b +/semicolonarabic 16#061b +/semicolonmonospace 16#ff1b +/semicolonsmall 16#fe54 +/semivoicedmarkkana 16#309c +/semivoicedmarkkanahalfwidth 16#ff9f +/sentisquare 16#3322 +/sentosquare 16#3323 +/seven 16#0037 +/sevenarabic 16#0667 +/sevenbengali 16#09ed +/sevencircle 16#2466 +/sevencircleinversesansserif 16#2790 +/sevendeva 16#096d +/seveneighths 16#215e +/sevengujarati 16#0aed +/sevengurmukhi 16#0a6d +/sevenhackarabic 16#0667 +/sevenhangzhou 16#3027 +/sevenideographicparen 16#3226 +/seveninferior 16#2087 +/sevenmonospace 16#ff17 +/sevenoldstyle 16#f737 +/sevenparen 16#247a +/sevenperiod 16#248e +/sevenpersian 16#06f7 +/sevenroman 16#2176 +/sevensuperior 16#2077 +/seventeencircle 16#2470 +/seventeenparen 16#2484 +/seventeenperiod 16#2498 +/seventhai 16#0e57 +/sfthyphen 16#00ad +/shaarmenian 16#0577 +/shabengali 16#09b6 +/shacyrillic 16#0448 +/shaddaarabic 16#0651 +/shaddadammaarabic 16#fc61 +/shaddadammatanarabic 16#fc5e +/shaddafathaarabic 16#fc60 +/shaddakasraarabic 16#fc62 +/shaddakasratanarabic 16#fc5f +/shade 16#2592 +/shadedark 16#2593 +/shadelight 16#2591 +/shademedium 16#2592 +/shadeva 16#0936 +/shagujarati 16#0ab6 +/shagurmukhi 16#0a36 +/shalshelethebrew 16#0593 +/shbopomofo 16#3115 +/shchacyrillic 16#0449 +/sheenarabic 16#0634 +/sheenfinalarabic 16#feb6 +/sheeninitialarabic 16#feb7 +/sheenmedialarabic 16#feb8 +/sheicoptic 16#03e3 +/sheqel 16#20aa +/sheqelhebrew 16#20aa +/sheva 16#05b0 +/sheva115 16#05b0 +/sheva15 16#05b0 +/sheva22 16#05b0 +/sheva2e 16#05b0 +/shevahebrew 16#05b0 +/shevanarrowhebrew 16#05b0 +/shevaquarterhebrew 16#05b0 +/shevawidehebrew 16#05b0 +/shhacyrillic 16#04bb +/shimacoptic 16#03ed +/shin 16#05e9 +/shindagesh 16#fb49 +/shindageshhebrew 16#fb49 +/shindageshshindot 16#fb2c +/shindageshshindothebrew 16#fb2c +/shindageshsindot 16#fb2d +/shindageshsindothebrew 16#fb2d +/shindothebrew 16#05c1 +/shinhebrew 16#05e9 +/shinshindot 16#fb2a +/shinshindothebrew 16#fb2a +/shinsindot 16#fb2b +/shinsindothebrew 16#fb2b +/shook 16#0282 +/sigma 16#03c3 +/sigma1 16#03c2 +/sigmafinal 16#03c2 +/sigmalunatesymbolgreek 16#03f2 +/sihiragana 16#3057 +/sikatakana 16#30b7 +/sikatakanahalfwidth 16#ff7c +/siluqhebrew 16#05bd +/siluqlefthebrew 16#05bd +/similar 16#223c +/sindothebrew 16#05c2 +/siosacirclekorean 16#3274 +/siosaparenkorean 16#3214 +/sioscieuckorean 16#317e +/sioscirclekorean 16#3266 +/sioskiyeokkorean 16#317a +/sioskorean 16#3145 +/siosnieunkorean 16#317b +/siosparenkorean 16#3206 +/siospieupkorean 16#317d +/siostikeutkorean 16#317c +/six 16#0036 +/sixarabic 16#0666 +/sixbengali 16#09ec +/sixcircle 16#2465 +/sixcircleinversesansserif 16#278f +/sixdeva 16#096c +/sixgujarati 16#0aec +/sixgurmukhi 16#0a6c +/sixhackarabic 16#0666 +/sixhangzhou 16#3026 +/sixideographicparen 16#3225 +/sixinferior 16#2086 +/sixmonospace 16#ff16 +/sixoldstyle 16#f736 +/sixparen 16#2479 +/sixperiod 16#248d +/sixpersian 16#06f6 +/sixroman 16#2175 +/sixsuperior 16#2076 +/sixteencircle 16#246f +/sixteencurrencydenominatorbengali 16#09f9 +/sixteenparen 16#2483 +/sixteenperiod 16#2497 +/sixthai 16#0e56 +/slash 16#002f +/slashmonospace 16#ff0f +/slong 16#017f +/slongdotaccent 16#1e9b +/smileface 16#263a +/smonospace 16#ff53 +/sofpasuqhebrew 16#05c3 +/softhyphen 16#00ad +/softsigncyrillic 16#044c +/sohiragana 16#305d +/sokatakana 16#30bd +/sokatakanahalfwidth 16#ff7f +/soliduslongoverlaycmb 16#0338 +/solidusshortoverlaycmb 16#0337 +/sorusithai 16#0e29 +/sosalathai 16#0e28 +/sosothai 16#0e0b +/sosuathai 16#0e2a +/space 16#0020 +/spacehackarabic 16#0020 +/spade 16#2660 +/spadesuitblack 16#2660 +/spadesuitwhite 16#2664 +/sparen 16#24ae +/squarebelowcmb 16#033b +/squarecc 16#33c4 +/squarecm 16#339d +/squarediagonalcrosshatchfill 16#25a9 +/squarehorizontalfill 16#25a4 +/squarekg 16#338f +/squarekm 16#339e +/squarekmcapital 16#33ce +/squareln 16#33d1 +/squarelog 16#33d2 +/squaremg 16#338e +/squaremil 16#33d5 +/squaremm 16#339c +/squaremsquared 16#33a1 +/squareorthogonalcrosshatchfill 16#25a6 +/squareupperlefttolowerrightfill 16#25a7 +/squareupperrighttolowerleftfill 16#25a8 +/squareverticalfill 16#25a5 +/squarewhitewithsmallblack 16#25a3 +/srsquare 16#33db +/ssabengali 16#09b7 +/ssadeva 16#0937 +/ssagujarati 16#0ab7 +/ssangcieuckorean 16#3149 +/ssanghieuhkorean 16#3185 +/ssangieungkorean 16#3180 +/ssangkiyeokkorean 16#3132 +/ssangnieunkorean 16#3165 +/ssangpieupkorean 16#3143 +/ssangsioskorean 16#3146 +/ssangtikeutkorean 16#3138 +/ssuperior 16#f6f2 +/sterling 16#00a3 +/sterlingmonospace 16#ffe1 +/strokelongoverlaycmb 16#0336 +/strokeshortoverlaycmb 16#0335 +/subset 16#2282 +/subsetnotequal 16#228a +/subsetorequal 16#2286 +/succeeds 16#227b +/suchthat 16#220b +/suhiragana 16#3059 +/sukatakana 16#30b9 +/sukatakanahalfwidth 16#ff7d +/sukunarabic 16#0652 +/summation 16#2211 +/sun 16#263c +/superset 16#2283 +/supersetnotequal 16#228b +/supersetorequal 16#2287 +/svsquare 16#33dc +/syouwaerasquare 16#337c +/t 16#0074 +/tabengali 16#09a4 +/tackdown 16#22a4 +/tackleft 16#22a3 +/tadeva 16#0924 +/tagujarati 16#0aa4 +/tagurmukhi 16#0a24 +/taharabic 16#0637 +/tahfinalarabic 16#fec2 +/tahinitialarabic 16#fec3 +/tahiragana 16#305f +/tahmedialarabic 16#fec4 +/taisyouerasquare 16#337d +/takatakana 16#30bf +/takatakanahalfwidth 16#ff80 +/tatweelarabic 16#0640 +/tau 16#03c4 +/tav 16#05ea +/tavdages 16#fb4a +/tavdagesh 16#fb4a +/tavdageshhebrew 16#fb4a +/tavhebrew 16#05ea +/tbar 16#0167 +/tbopomofo 16#310a +/tcaron 16#0165 +/tccurl 16#02a8 +/tcedilla 16#0163 +/tcheharabic 16#0686 +/tchehfinalarabic 16#fb7b +/tchehinitialarabic 16#fb7c +/tchehmedialarabic 16#fb7d +/tcircle 16#24e3 +/tcircumflexbelow 16#1e71 +/tcommaaccent 16#0163 +/tdieresis 16#1e97 +/tdotaccent 16#1e6b +/tdotbelow 16#1e6d +/tecyrillic 16#0442 +/tedescendercyrillic 16#04ad +/teharabic 16#062a +/tehfinalarabic 16#fe96 +/tehhahinitialarabic 16#fca2 +/tehhahisolatedarabic 16#fc0c +/tehinitialarabic 16#fe97 +/tehiragana 16#3066 +/tehjeeminitialarabic 16#fca1 +/tehjeemisolatedarabic 16#fc0b +/tehmarbutaarabic 16#0629 +/tehmarbutafinalarabic 16#fe94 +/tehmedialarabic 16#fe98 +/tehmeeminitialarabic 16#fca4 +/tehmeemisolatedarabic 16#fc0e +/tehnoonfinalarabic 16#fc73 +/tekatakana 16#30c6 +/tekatakanahalfwidth 16#ff83 +/telephone 16#2121 +/telephoneblack 16#260e +/telishagedolahebrew 16#05a0 +/telishaqetanahebrew 16#05a9 +/tencircle 16#2469 +/tenideographicparen 16#3229 +/tenparen 16#247d +/tenperiod 16#2491 +/tenroman 16#2179 +/tesh 16#02a7 +/tet 16#05d8 +/tetdagesh 16#fb38 +/tetdageshhebrew 16#fb38 +/tethebrew 16#05d8 +/tetsecyrillic 16#04b5 +/tevirhebrew 16#059b +/tevirlefthebrew 16#059b +/thabengali 16#09a5 +/thadeva 16#0925 +/thagujarati 16#0aa5 +/thagurmukhi 16#0a25 +/thalarabic 16#0630 +/thalfinalarabic 16#feac +/thanthakhatlowleftthai 16#f898 +/thanthakhatlowrightthai 16#f897 +/thanthakhatthai 16#0e4c +/thanthakhatupperleftthai 16#f896 +/theharabic 16#062b +/thehfinalarabic 16#fe9a +/thehinitialarabic 16#fe9b +/thehmedialarabic 16#fe9c +/thereexists 16#2203 +/therefore 16#2234 +/theta 16#03b8 +/theta1 16#03d1 +/thetasymbolgreek 16#03d1 +/thieuthacirclekorean 16#3279 +/thieuthaparenkorean 16#3219 +/thieuthcirclekorean 16#326b +/thieuthkorean 16#314c +/thieuthparenkorean 16#320b +/thirteencircle 16#246c +/thirteenparen 16#2480 +/thirteenperiod 16#2494 +/thonangmonthothai 16#0e11 +/thook 16#01ad +/thophuthaothai 16#0e12 +/thorn 16#00fe +/thothahanthai 16#0e17 +/thothanthai 16#0e10 +/thothongthai 16#0e18 +/thothungthai 16#0e16 +/thousandcyrillic 16#0482 +/thousandsseparatorarabic 16#066c +/thousandsseparatorpersian 16#066c +/three 16#0033 +/threearabic 16#0663 +/threebengali 16#09e9 +/threecircle 16#2462 +/threecircleinversesansserif 16#278c +/threedeva 16#0969 +/threeeighths 16#215c +/threegujarati 16#0ae9 +/threegurmukhi 16#0a69 +/threehackarabic 16#0663 +/threehangzhou 16#3023 +/threeideographicparen 16#3222 +/threeinferior 16#2083 +/threemonospace 16#ff13 +/threenumeratorbengali 16#09f6 +/threeoldstyle 16#f733 +/threeparen 16#2476 +/threeperiod 16#248a +/threepersian 16#06f3 +/threequarters 16#00be +/threequartersemdash 16#f6de +/threeroman 16#2172 +/threesuperior 16#00b3 +/threethai 16#0e53 +/thzsquare 16#3394 +/tihiragana 16#3061 +/tikatakana 16#30c1 +/tikatakanahalfwidth 16#ff81 +/tikeutacirclekorean 16#3270 +/tikeutaparenkorean 16#3210 +/tikeutcirclekorean 16#3262 +/tikeutkorean 16#3137 +/tikeutparenkorean 16#3202 +/tilde 16#02dc +/tildebelowcmb 16#0330 +/tildecmb 16#0303 +/tildecomb 16#0303 +/tildedoublecmb 16#0360 +/tildeoperator 16#223c +/tildeoverlaycmb 16#0334 +/tildeverticalcmb 16#033e +/timescircle 16#2297 +/tipehahebrew 16#0596 +/tipehalefthebrew 16#0596 +/tippigurmukhi 16#0a70 +/titlocyrilliccmb 16#0483 +/tiwnarmenian 16#057f +/tlinebelow 16#1e6f +/tmonospace 16#ff54 +/toarmenian 16#0569 +/tohiragana 16#3068 +/tokatakana 16#30c8 +/tokatakanahalfwidth 16#ff84 +/tonebarextrahighmod 16#02e5 +/tonebarextralowmod 16#02e9 +/tonebarhighmod 16#02e6 +/tonebarlowmod 16#02e8 +/tonebarmidmod 16#02e7 +/tonefive 16#01bd +/tonesix 16#0185 +/tonetwo 16#01a8 +/tonos 16#0384 +/tonsquare 16#3327 +/topatakthai 16#0e0f +/tortoiseshellbracketleft 16#3014 +/tortoiseshellbracketleftsmall 16#fe5d +/tortoiseshellbracketleftvertical 16#fe39 +/tortoiseshellbracketright 16#3015 +/tortoiseshellbracketrightsmall 16#fe5e +/tortoiseshellbracketrightvertical 16#fe3a +/totaothai 16#0e15 +/tpalatalhook 16#01ab +/tparen 16#24af +/trademark 16#2122 +/trademarksans 16#f8ea +/trademarkserif 16#f6db +/tretroflexhook 16#0288 +/triagdn 16#25bc +/triaglf 16#25c4 +/triagrt 16#25ba +/triagup 16#25b2 +/ts 16#02a6 +/tsadi 16#05e6 +/tsadidagesh 16#fb46 +/tsadidageshhebrew 16#fb46 +/tsadihebrew 16#05e6 +/tsecyrillic 16#0446 +/tsere 16#05b5 +/tsere12 16#05b5 +/tsere1e 16#05b5 +/tsere2b 16#05b5 +/tserehebrew 16#05b5 +/tserenarrowhebrew 16#05b5 +/tserequarterhebrew 16#05b5 +/tserewidehebrew 16#05b5 +/tshecyrillic 16#045b +/tsuperior 16#f6f3 +/ttabengali 16#099f +/ttadeva 16#091f +/ttagujarati 16#0a9f +/ttagurmukhi 16#0a1f +/tteharabic 16#0679 +/ttehfinalarabic 16#fb67 +/ttehinitialarabic 16#fb68 +/ttehmedialarabic 16#fb69 +/tthabengali 16#09a0 +/tthadeva 16#0920 +/tthagujarati 16#0aa0 +/tthagurmukhi 16#0a20 +/tturned 16#0287 +/tuhiragana 16#3064 +/tukatakana 16#30c4 +/tukatakanahalfwidth 16#ff82 +/tusmallhiragana 16#3063 +/tusmallkatakana 16#30c3 +/tusmallkatakanahalfwidth 16#ff6f +/twelvecircle 16#246b +/twelveparen 16#247f +/twelveperiod 16#2493 +/twelveroman 16#217b +/twentycircle 16#2473 +/twentyhangzhou 16#5344 +/twentyparen 16#2487 +/twentyperiod 16#249b +/two 16#0032 +/twoarabic 16#0662 +/twobengali 16#09e8 +/twocircle 16#2461 +/twocircleinversesansserif 16#278b +/twodeva 16#0968 +/twodotenleader 16#2025 +/twodotleader 16#2025 +/twodotleadervertical 16#fe30 +/twogujarati 16#0ae8 +/twogurmukhi 16#0a68 +/twohackarabic 16#0662 +/twohangzhou 16#3022 +/twoideographicparen 16#3221 +/twoinferior 16#2082 +/twomonospace 16#ff12 +/twonumeratorbengali 16#09f5 +/twooldstyle 16#f732 +/twoparen 16#2475 +/twoperiod 16#2489 +/twopersian 16#06f2 +/tworoman 16#2171 +/twostroke 16#01bb +/twosuperior 16#00b2 +/twothai 16#0e52 +/twothirds 16#2154 +/u 16#0075 +/uacute 16#00fa +/ubar 16#0289 +/ubengali 16#0989 +/ubopomofo 16#3128 +/ubreve 16#016d +/ucaron 16#01d4 +/ucircle 16#24e4 +/ucircumflex 16#00fb +/ucircumflexbelow 16#1e77 +/ucyrillic 16#0443 +/udattadeva 16#0951 +/udblacute 16#0171 +/udblgrave 16#0215 +/udeva 16#0909 +/udieresis 16#00fc +/udieresisacute 16#01d8 +/udieresisbelow 16#1e73 +/udieresiscaron 16#01da +/udieresiscyrillic 16#04f1 +/udieresisgrave 16#01dc +/udieresismacron 16#01d6 +/udotbelow 16#1ee5 +/ugrave 16#00f9 +/ugujarati 16#0a89 +/ugurmukhi 16#0a09 +/uhiragana 16#3046 +/uhookabove 16#1ee7 +/uhorn 16#01b0 +/uhornacute 16#1ee9 +/uhorndotbelow 16#1ef1 +/uhorngrave 16#1eeb +/uhornhookabove 16#1eed +/uhorntilde 16#1eef +/uhungarumlaut 16#0171 +/uhungarumlautcyrillic 16#04f3 +/uinvertedbreve 16#0217 +/ukatakana 16#30a6 +/ukatakanahalfwidth 16#ff73 +/ukcyrillic 16#0479 +/ukorean 16#315c +/umacron 16#016b +/umacroncyrillic 16#04ef +/umacrondieresis 16#1e7b +/umatragurmukhi 16#0a41 +/umonospace 16#ff55 +/underscore 16#005f +/underscoredbl 16#2017 +/underscoremonospace 16#ff3f +/underscorevertical 16#fe33 +/underscorewavy 16#fe4f +/union 16#222a +/universal 16#2200 +/uogonek 16#0173 +/uparen 16#24b0 +/upblock 16#2580 +/upperdothebrew 16#05c4 +/upsilon 16#03c5 +/upsilondieresis 16#03cb +/upsilondieresistonos 16#03b0 +/upsilonlatin 16#028a +/upsilontonos 16#03cd +/uptackbelowcmb 16#031d +/uptackmod 16#02d4 +/uragurmukhi 16#0a73 +/uring 16#016f +/ushortcyrillic 16#045e +/usmallhiragana 16#3045 +/usmallkatakana 16#30a5 +/usmallkatakanahalfwidth 16#ff69 +/ustraightcyrillic 16#04af +/ustraightstrokecyrillic 16#04b1 +/utilde 16#0169 +/utildeacute 16#1e79 +/utildebelow 16#1e75 +/uubengali 16#098a +/uudeva 16#090a +/uugujarati 16#0a8a +/uugurmukhi 16#0a0a +/uumatragurmukhi 16#0a42 +/uuvowelsignbengali 16#09c2 +/uuvowelsigndeva 16#0942 +/uuvowelsigngujarati 16#0ac2 +/uvowelsignbengali 16#09c1 +/uvowelsigndeva 16#0941 +/uvowelsigngujarati 16#0ac1 +/v 16#0076 +/vadeva 16#0935 +/vagujarati 16#0ab5 +/vagurmukhi 16#0a35 +/vakatakana 16#30f7 +/vav 16#05d5 +/vavdagesh 16#fb35 +/vavdagesh65 16#fb35 +/vavdageshhebrew 16#fb35 +/vavhebrew 16#05d5 +/vavholam 16#fb4b +/vavholamhebrew 16#fb4b +/vavvavhebrew 16#05f0 +/vavyodhebrew 16#05f1 +/vcircle 16#24e5 +/vdotbelow 16#1e7f +/vecyrillic 16#0432 +/veharabic 16#06a4 +/vehfinalarabic 16#fb6b +/vehinitialarabic 16#fb6c +/vehmedialarabic 16#fb6d +/vekatakana 16#30f9 +/venus 16#2640 +/verticalbar 16#007c +/verticallineabovecmb 16#030d +/verticallinebelowcmb 16#0329 +/verticallinelowmod 16#02cc +/verticallinemod 16#02c8 +/vewarmenian 16#057e +/vhook 16#028b +/vikatakana 16#30f8 +/viramabengali 16#09cd +/viramadeva 16#094d +/viramagujarati 16#0acd +/visargabengali 16#0983 +/visargadeva 16#0903 +/visargagujarati 16#0a83 +/vmonospace 16#ff56 +/voarmenian 16#0578 +/voicediterationhiragana 16#309e +/voicediterationkatakana 16#30fe +/voicedmarkkana 16#309b +/voicedmarkkanahalfwidth 16#ff9e +/vokatakana 16#30fa +/vparen 16#24b1 +/vtilde 16#1e7d +/vturned 16#028c +/vuhiragana 16#3094 +/vukatakana 16#30f4 +/w 16#0077 +/wacute 16#1e83 +/waekorean 16#3159 +/wahiragana 16#308f +/wakatakana 16#30ef +/wakatakanahalfwidth 16#ff9c +/wakorean 16#3158 +/wasmallhiragana 16#308e +/wasmallkatakana 16#30ee +/wattosquare 16#3357 +/wavedash 16#301c +/wavyunderscorevertical 16#fe34 +/wawarabic 16#0648 +/wawfinalarabic 16#feee +/wawhamzaabovearabic 16#0624 +/wawhamzaabovefinalarabic 16#fe86 +/wbsquare 16#33dd +/wcircle 16#24e6 +/wcircumflex 16#0175 +/wdieresis 16#1e85 +/wdotaccent 16#1e87 +/wdotbelow 16#1e89 +/wehiragana 16#3091 +/weierstrass 16#2118 +/wekatakana 16#30f1 +/wekorean 16#315e +/weokorean 16#315d +/wgrave 16#1e81 +/whitebullet 16#25e6 +/whitecircle 16#25cb +/whitecircleinverse 16#25d9 +/whitecornerbracketleft 16#300e +/whitecornerbracketleftvertical 16#fe43 +/whitecornerbracketright 16#300f +/whitecornerbracketrightvertical 16#fe44 +/whitediamond 16#25c7 +/whitediamondcontainingblacksmalldiamond 16#25c8 +/whitedownpointingsmalltriangle 16#25bf +/whitedownpointingtriangle 16#25bd +/whiteleftpointingsmalltriangle 16#25c3 +/whiteleftpointingtriangle 16#25c1 +/whitelenticularbracketleft 16#3016 +/whitelenticularbracketright 16#3017 +/whiterightpointingsmalltriangle 16#25b9 +/whiterightpointingtriangle 16#25b7 +/whitesmallsquare 16#25ab +/whitesmilingface 16#263a +/whitesquare 16#25a1 +/whitestar 16#2606 +/whitetelephone 16#260f +/whitetortoiseshellbracketleft 16#3018 +/whitetortoiseshellbracketright 16#3019 +/whiteuppointingsmalltriangle 16#25b5 +/whiteuppointingtriangle 16#25b3 +/wihiragana 16#3090 +/wikatakana 16#30f0 +/wikorean 16#315f +/wmonospace 16#ff57 +/wohiragana 16#3092 +/wokatakana 16#30f2 +/wokatakanahalfwidth 16#ff66 +/won 16#20a9 +/wonmonospace 16#ffe6 +/wowaenthai 16#0e27 +/wparen 16#24b2 +/wring 16#1e98 +/wsuperior 16#02b7 +/wturned 16#028d +/wynn 16#01bf +/x 16#0078 +/xabovecmb 16#033d +/xbopomofo 16#3112 +/xcircle 16#24e7 +/xdieresis 16#1e8d +/xdotaccent 16#1e8b +/xeharmenian 16#056d +/xi 16#03be +/xmonospace 16#ff58 +/xparen 16#24b3 +/xsuperior 16#02e3 +/y 16#0079 +/yaadosquare 16#334e +/yabengali 16#09af +/yacute 16#00fd +/yadeva 16#092f +/yaekorean 16#3152 +/yagujarati 16#0aaf +/yagurmukhi 16#0a2f +/yahiragana 16#3084 +/yakatakana 16#30e4 +/yakatakanahalfwidth 16#ff94 +/yakorean 16#3151 +/yamakkanthai 16#0e4e +/yasmallhiragana 16#3083 +/yasmallkatakana 16#30e3 +/yasmallkatakanahalfwidth 16#ff6c +/yatcyrillic 16#0463 +/ycircle 16#24e8 +/ycircumflex 16#0177 +/ydieresis 16#00ff +/ydotaccent 16#1e8f +/ydotbelow 16#1ef5 +/yeharabic 16#064a +/yehbarreearabic 16#06d2 +/yehbarreefinalarabic 16#fbaf +/yehfinalarabic 16#fef2 +/yehhamzaabovearabic 16#0626 +/yehhamzaabovefinalarabic 16#fe8a +/yehhamzaaboveinitialarabic 16#fe8b +/yehhamzaabovemedialarabic 16#fe8c +/yehinitialarabic 16#fef3 +/yehmedialarabic 16#fef4 +/yehmeeminitialarabic 16#fcdd +/yehmeemisolatedarabic 16#fc58 +/yehnoonfinalarabic 16#fc94 +/yehthreedotsbelowarabic 16#06d1 +/yekorean 16#3156 +/yen 16#00a5 +/yenmonospace 16#ffe5 +/yeokorean 16#3155 +/yeorinhieuhkorean 16#3186 +/yerahbenyomohebrew 16#05aa +/yerahbenyomolefthebrew 16#05aa +/yericyrillic 16#044b +/yerudieresiscyrillic 16#04f9 +/yesieungkorean 16#3181 +/yesieungpansioskorean 16#3183 +/yesieungsioskorean 16#3182 +/yetivhebrew 16#059a +/ygrave 16#1ef3 +/yhook 16#01b4 +/yhookabove 16#1ef7 +/yiarmenian 16#0575 +/yicyrillic 16#0457 +/yikorean 16#3162 +/yinyang 16#262f +/yiwnarmenian 16#0582 +/ymonospace 16#ff59 +/yod 16#05d9 +/yoddagesh 16#fb39 +/yoddageshhebrew 16#fb39 +/yodhebrew 16#05d9 +/yodyodhebrew 16#05f2 +/yodyodpatahhebrew 16#fb1f +/yohiragana 16#3088 +/yoikorean 16#3189 +/yokatakana 16#30e8 +/yokatakanahalfwidth 16#ff96 +/yokorean 16#315b +/yosmallhiragana 16#3087 +/yosmallkatakana 16#30e7 +/yosmallkatakanahalfwidth 16#ff6e +/yotgreek 16#03f3 +/yoyaekorean 16#3188 +/yoyakorean 16#3187 +/yoyakthai 16#0e22 +/yoyingthai 16#0e0d +/yparen 16#24b4 +/ypogegrammeni 16#037a +/ypogegrammenigreekcmb 16#0345 +/yr 16#01a6 +/yring 16#1e99 +/ysuperior 16#02b8 +/ytilde 16#1ef9 +/yturned 16#028e +/yuhiragana 16#3086 +/yuikorean 16#318c +/yukatakana 16#30e6 +/yukatakanahalfwidth 16#ff95 +/yukorean 16#3160 +/yusbigcyrillic 16#046b +/yusbigiotifiedcyrillic 16#046d +/yuslittlecyrillic 16#0467 +/yuslittleiotifiedcyrillic 16#0469 +/yusmallhiragana 16#3085 +/yusmallkatakana 16#30e5 +/yusmallkatakanahalfwidth 16#ff6d +/yuyekorean 16#318b +/yuyeokorean 16#318a +/yyabengali 16#09df +/yyadeva 16#095f +/z 16#007a +/zaarmenian 16#0566 +/zacute 16#017a +/zadeva 16#095b +/zagurmukhi 16#0a5b +/zaharabic 16#0638 +/zahfinalarabic 16#fec6 +/zahinitialarabic 16#fec7 +/zahiragana 16#3056 +/zahmedialarabic 16#fec8 +/zainarabic 16#0632 +/zainfinalarabic 16#feb0 +/zakatakana 16#30b6 +/zaqefgadolhebrew 16#0595 +/zaqefqatanhebrew 16#0594 +/zarqahebrew 16#0598 +/zayin 16#05d6 +/zayindagesh 16#fb36 +/zayindageshhebrew 16#fb36 +/zayinhebrew 16#05d6 +/zbopomofo 16#3117 +/zcaron 16#017e +/zcircle 16#24e9 +/zcircumflex 16#1e91 +/zcurl 16#0291 +/zdot 16#017c +/zdotaccent 16#017c +/zdotbelow 16#1e93 +/zecyrillic 16#0437 +/zedescendercyrillic 16#0499 +/zedieresiscyrillic 16#04df +/zehiragana 16#305c +/zekatakana 16#30bc +/zero 16#0030 +/zeroarabic 16#0660 +/zerobengali 16#09e6 +/zerodeva 16#0966 +/zerogujarati 16#0ae6 +/zerogurmukhi 16#0a66 +/zerohackarabic 16#0660 +/zeroinferior 16#2080 +/zeromonospace 16#ff10 +/zerooldstyle 16#f730 +/zeropersian 16#06f0 +/zerosuperior 16#2070 +/zerothai 16#0e50 +/zerowidthjoiner 16#feff +/zerowidthnonjoiner 16#200c +/zerowidthspace 16#200b +/zeta 16#03b6 +/zhbopomofo 16#3113 +/zhearmenian 16#056a +/zhebrevecyrillic 16#04c2 +/zhecyrillic 16#0436 +/zhedescendercyrillic 16#0497 +/zhedieresiscyrillic 16#04dd +/zihiragana 16#3058 +/zikatakana 16#30b8 +/zinorhebrew 16#05ae +/zlinebelow 16#1e95 +/zmonospace 16#ff5a +/zohiragana 16#305e +/zokatakana 16#30be +/zparen 16#24b5 +/zretroflexhook 16#0290 +/zstroke 16#01b6 +/zuhiragana 16#305a +/zukatakana 16#30ba +.dicttomark readonly def +/currentglobal where +{pop currentglobal{setglobal}true setglobal} +{{}} +ifelse +/MacRomanEncoding .findencoding +/MacGlyphEncoding +/.notdef/.null/CR +4 index 32 95 getinterval aload pop +99 index 128 45 getinterval aload pop +/notequal/AE +/Oslash/infinity/plusminus/lessequal/greaterequal +/yen/mu1/partialdiff/summation/product +/pi/integral/ordfeminine/ordmasculine/Ohm +/ae/oslash/questiondown/exclamdown/logicalnot +/radical/florin/approxequal/increment/guillemotleft +/guillemotright/ellipsis/nbspace +174 index 203 12 getinterval aload pop +/lozenge +187 index 216 24 getinterval aload pop +/applelogo +212 index 241 7 getinterval aload pop +/overscore +220 index 249 7 getinterval aload pop +/Lslash/lslash/Scaron/scaron +/Zcaron/zcaron/brokenbar/Eth/eth +/Yacute/yacute/Thorn/thorn/minus +/multiply/onesuperior/twosuperior/threesuperior/onehalf +/onequarter/threequarters/franc/Gbreve/gbreve +/Idotaccent/Scedilla/scedilla/Cacute/cacute +/Ccaron/ccaron/dmacron +260 -1 roll pop +258 packedarray +7 1 index .registerencoding +.defineencoding +exec + +%%BeginResource: procset (PDF Font obj_9) +9 0 obj +<> +endobj +%%EndResource +%%BeginResource: file (PDF CharProc obj_6) +6 0 obj +<>stream +J.)Pl,9Xc90Gb-%0K +endstream +endobj +%%EndResource +%%BeginResource: procset (PDF Font obj_7) +7 0 obj +<> +endobj +%%EndResource +%%BeginResource: file (PDF FontDescriptor obj_8) +8 0 obj +<> +endobj +%%EndResource +%%BeginResource: file (PDF FontFile obj_10) +10 0 obj +<>stream +J-eUh@sPTJ<'6ZZUCh1X>VI0(#\;AF`*`ohN'mk;8W;jU0c<[IPAhpW"G7=]`aa7DCE&;Sb_K,M +JjgrK'920Gl'+F]+e-a^PEDZdBTJJ`"u>,45VX7=7cM5aaC$\AL^5?V/JQ352[B_e';;)AD6cZ* +-G]^VhIAC1:5=r8.!\?E*!\h#H/AHO0UkgHj*A_k5,Qj?k*mcLUh)R<:<$3TJl*U;?t[kHW5)\6 +1)Wd6@Q"A;CcpiqPPPTmi,q(^Ce"@0aPEN:Bb%#XAu^8EMFFum1F]pcE\a"UOBk7mF!lR12o5TE +E\5SaMo_dU*n!SlWf9Kb!t!'0@6B-r'`STSpDT3.:BCVIi5THq$]WWK$(+!0Ne?O\Q4?/XAt"I* +_l,^KWGDS;-@l/1XG,jfI0[RPF4M.fK=i2V64'T*Q\Fda-EtB!Mo=lhW\T\9`":"))nLZB8INNl +LhuU8iY5c]6m2PI)EP/$JfDd_ClJjY&D3[&_BH^,l3_Q`C8j7H%'1!F0Ml(1959B`1!=791`+1F +">Yl>:8@b%VPpl!(VCE5Set,DAF,4@K5A]rL%.QP3/05&?CK2KLhQ&`;uB0Hcgg!Z4bclrkm+a/ +;jb-\XWsRFNf#@L/[NU22"fB)G[XO(!J%`m;_D2-k0pJb$m>mDHfdJFTqg0SL-_lkM^nf.Ddbn2 +NAg-%a>+<\4dDt=0VjLc&t_=0W2EIOj&jGSN?]YeCptW+lLR!1U,\2tN\D2h>mN..e#1)6l8"<+ +:lH9JTt$%$L12io(^NEpL)Ypl`0qYMfD';&#XP,;&VrarKGY!2OGsk1'W>qXLk3fO,)0NnC`%o$ +&9&VFU(E*/#_5a))[:kdL*6hrLhn6T@@2ii#6CcD)$]&mL_pD,-tFO\JgcD*#U.9J)s92LU_j@o +/i@-pU!><"#_FsM-A\7++CunO_1mZ,L7>,\+bl\`N$3pX +BSDH>iQ,[9#6DQ8TSR&(;BlH,L3sqRq@JF4#ft9p)9KPcDBfB-0SgmPqX&u:q``!o+i8Y0M6_m4 +:N=_c(dN`lC#PG#dti;[U*9#N-F'I:FB0[1*lIbem5]YSd"9(QE%u*EM5:f>1?/D>6"8'&+YkCM +&6UbZ@)0i'ZA2lug"Z!#/C"Dc0SBj;1\c.;6lJ&9QG11c7!.ifN'[d<#o5V&#d2_+22>YZfAi$. +NHc?DG!cTSUV^Rp\f3ufU=;es1d"QMW.:b*P@@InAK_e<],"/o=t>b\bXqu"B6M:"6OX0YAV`Zr +1Bh''6>D&haKb5H&4*>mNpN2-`)(L0JdYCnO56V;@#2Z;#W2mfq-6IfO:`WTY'c:Ik^tco_La$' +@I+sO!2qm_>uN)2AQTqb<1:qcA*JEC(#;!#2-fIMbECrrRW)"8*j9Z_.r3OZPf/C%IOraN$^CInb?N8<7W?gJO78SJ7S+%mPQCp8jY(\$X[ +j;<149Gsk=1t\?$ItqoETY6Y_kub>..PQ#[3\XuCT&TWLm4M/.3Aq- +;S!X6s$q)MN4d8YdbL%*BC#P]2UGj` +?K8J,dWM_$p6OXQZ_=2>&Uhj$'FZ(I4ArN-Ikpc`$skG8KFXkk3/._RQts&5Z=Z@aAn;T[`;=aq +!SFqb:=0n)9lqq?j#X^%4lt`ZD%j1o2c+DP$A4LbF)@;NqU&7dURZ#ujj%!''_C*r-s%1[WF!Yh +%:I(@A,tUO3Q[Hqh7RM//Nb2=LFX3D3>l9!iF'/`-Cg]$QF#p\_7Z0;mFaR=88O`JV:?8Z;4AnG +No1igKbP+:inHsl +qWf$Tq-d(8^Y/>>Jt!Fp5qMP75]h2'!OJ2PKOV-[Y\Ms%P=;R0+kJe7!LdLUQ:n>m#dS]"_56H4 +@^XaFYP*Xh!)lIijB:Fs-38uOQ=4`(%Ls_lVT,Hm`&ha8# +#-,hCqhT36m?P&'jMfrohis4&gqq_r"gZoES[QP=S&*R>II_R=eeNTeta-EB27\6__,Y`c*(T2=XI/VqdNNn +n%6$Q%@Oh-LM^0_4*F>J*R:s!&)#Yuk3i%(/B)#.6+BIdL;]R^pg:@b>-V^?E9d?+K(7a0@90S$mJqGq!11QH.iL%soe.WOl;W +% +%(Q"N_^PVjr&M<.YBOr%Q:2hp-\XX9)l,V[G//:$LJh8m$]ZfU+njhbFshdgoM+U2#(UfjCVu&jCBWW +HRE4?U+UD"Y.k-ZON(UMaf%O@RVO7S.gD`=%E^0?,%F0R@#$dQ#7,Y_o]Z^OLnZMHa`q9mE,$^: +,;,9na$t-i<.SXtXTs66kWo$b;I\d)dq2]pL&-9(k'!__!D$Bc#.g7XZ@-BBo0T7",nt#=AonJ7[4Ca%`^G3/1;sFi"%Z0u`_:KN'\8;?kN8(dVm:*9Nkk8JB_C6n,F@:ta;jtL[BA!/#$Q*s._c1BEsM2AB.I. +,7,K(,arC:)II;3A;ATHAs@)/N,-HXkbNrT#,lUb9-"0c@-<\l-D#!'W`Us6RYCO6kFuet9R-rc +K*Y:bV^-]V7V/T.#q!NXN5C]HhNUNNRZHO;?tRb9-:DE=D5L!N793gdLgiki)YSk!O9V\4GR@b[qJ5tHB2kB=9>?AEP<@]51BlO"jg.Ube7,0:V/$:&1BOZMWd +?qY+9NP^ju_Q2pIR4!<-#l]E%'P`N>kcT#?o$WUUY9F0;r(#P^kDZ8H$I^W!B.>II$h.OOP%/$G +Q_sn2-_bIui+Q`>BnmP;n;qaiiBq(YR)q-#TRqCp8N)@S7hIXeb3j3u$@@`'BFBPEd!HQr`0eV* +K]u)uB3_1.Z>`8g!CEMl#qU*@U=H4'BP+,`Cr2c:@!9lO5plkKa\%n1G3ihFAHk'8B.RX)M?`I! +$Nd.X`mn@,Sp?'L,0?#K:TST6rdqIoGXeQZYS^cD-LPLNl#ciD"tF/\gn;`"1F8']Y$bqm/t%M\ +`^`""8EA5S,\PZ3TkT["80Y4Se:?iS;?o2D->QXNHtOq=f2<52#-e<4JJV9f=R*,B.?%YIT#$.+ +>K0'jjoZs.aD+6qLrZ7Ini1/4FGS=dKPZ`Z-YL_@#_I5(9iDsMXRtj +Ro,i(kqXJ1:4Z%4BG?,/.5I\7@i^M#j_OY8joC31?tcO@@1r<='lYt7ZSXL/=1!I +@IG&sV%f,F/n3rj(M=;W-@f`![WH/6I\m$YBNf]BL>V*4-AiSNk][L&#B!"/E_^(T9n65Z^1$Wo +Mhac2$5/JDD`\ZG.L+99We?)/9YZ;n[3giAjG+$6#%l;P@n9(<7llMs_KT]FV(]&a$Lictg2"g) +?m]qdM'/-^dU3:cE_%Ij^l4/r@S[9*Q`Q,q8J[9qb&UMA?me*C[c[%_H86ZG/iK07JMb[D!si[G%?>T@nMuH#Z-tE"G?r4+D3iD:*.XA)5S(^U]i5dkMVN0i0L.V +jf>.4k$DI73E="nVi=#cB^V\9&qNUQnBU$?-9e%l,1>_S?c\3/28k*FUm&6cE%(49P(;H`glM$E +qCL9GB?;Vp12-(cNpicg9_5B@EDO]&CWm2XAeb(g13U[5M<:Uqi[f9">%.q-@bW+Q`klUpH?NrR +H\UJ6kXI^>3HjM;LMmcj#$uU'38MR[8k^XZ`a4_i.W@n;H)U4!G1Q"AFD!:eE:h6(8@;I=0TPB! +E&u>q6ENFB:Rk1X8S1rM8ThWl""u$44\2B!jsXf,T(Gm*,SAJ"8//QI<,><>"JeMC`XI,"kZ>um +]pM=kZ$_C`'4!fmkR +N6%Z:1[@+Y1l/"Q?`@gua(f"*p7f+aGSMqqa3s7>^mTu>%k&sFl5N&gj@KT0:CC'9-VQ!X7V/Ou +3GXLrm*?ZK!n@KO@i&s*-ZsNG'PT4a(%2B9L44h5*suU4IX*)o'0^(`?t?H=Ut&jsa),lee9mCF +Gj*4_oZi4bLU-*sC5_Yl$MJFn#@e0qAObIh#&(efS!g0AZ3Mk$8p`P=;!>L+AWd3[jtMJT_(Wr# +`=)rf#AS427gs*HSjk)tW"bC!\nEF+,6G+EQiT;33.n.ke'if/lcZ)g0PAJno#@V:Z:)1L&9hlt +.d8KtRUE,:%[%'L6V:%[/:-JuN;CU@Q\G?sE+!f^ZL2O@L.oIF#;!Ad=Ehb!@J?@?k;Zt,B!JI+ +F=-Ql`h-:r7_1PZ=@XXDIB\ap)J.p2@-.reL+^U23.#:t,R3Of$jr*[NGCX9>dGD==bi/[DB +bVejRVtRWR%/bP5a/P[@5?@P^ZMYIser(j/MK=@d]d3n/)2K!C'8 +_i?URM(G6Sri)q(A?@t57OE5;G!&9J=2q#$B;:bA*cX7kH7M3kM3sZIUPkIDdbo.^f`#Jp2J!4D +fWgbZ7dnROp9t6hBZt9^CoMF0aE,t\2+eSd-TMINnJs:(@>Y'`VRnnU@/>VU9X'@OV`h=48H27L +L.j\2B-jr75\C?`CULnBZ##k`H0GOuR)ETqVMJ\Ln7(UL_9!O7WT&;]N8O4l^CGj-R-82r6?rX+ +&S>":VFZ+0E1&'#WTQZW7LF2@pp]aA,0WtmJhi3X.39=)_H-`CfH-b!rdtF]`Kge(7n@6[*-2?[ +ICt:W";.==A-sLi#"+bD&9:)UOTo;M_$Gc^)%FRf&W#@a2^/h<,:LYNBLNgOL-lRbk"J2eN/5ou +^r2*a)N]^V6)`_#2&VAX@rP8kL,g(Z#/`2-fRbmu:T))rYo3a)),uA937N%g*1IAq6tnps%jr9T +`oL#H$8b3l%>KL%%$62NRh5oO;7(B%0XjH),>kP+OQ"/3/HFU7`sS.Ud9Oo&4;:ILD5T@-T;P>8KlVN +.2nF+)Na@(+rn+JU)QF=\BRF>.GpNA3.$*.aCdH2k'!um\C?F'l]FYI@M$Cb*?p63MtlrLmcna1 +U5`9,d_'E(-DiKuOG*a$#r#bE14gb3*!gHG'UC@CgL_EKXS@\W#4)n[)l/T_*>O;>C(eZ8ONV5R +04H][(bi33L5m[BQu!oH9%+!-WpL(0P,JF#"rKkOHN&mp8[)qJ,]%j3m0H>bD$r%7Y\p3_.uZJ3 +YY*?6aY[G%,_io=3m\b#+rf_iVIBl`)8(ODA0HaPK&jV`CtOR4[uJUrloJRMSf"B..X\6gdfUWen]4K-A*7+80Y,"N]0c7ii6 +:92`\]e&bS;!k\^dPd%Q:*Lh$Zq3:_YZ)"I<.uMNo&cUMM^,J(JP!_.%(D1Ck(cn/)H;NmlMtrE +eAt5eD5E1)[5qR"$gQ4b#i^)h-3>ZOp;HAl4rK(_>opC&TtlJUZAEg@L9%=)1HDZo;Q/QU![I.9 +UF[nW`DNee)C$TFZ7q(\_4i:\.0t!0_Rf0bY*:jth<+#HMt@:LLFA]i[74pCN!1n6o`!H$Q1OS6 +66UkuTW,O%Q$jupOq2(^d5'RSUsAf]-+77*.$%jFEZ5t#)H'6K]PEcRT=a@YN2,o78jN>&Cf6M? +VdY0"TVO@8:kYYTHrc`C]N!V?Vs.aV(G?H(3$K[<4m<'RchNHc:^Gi7VG4Dofs%0:Z?q^6aohFu +/<1u-5;o4(#dQ*9)_0IIBtf%gjQt_%WprbjK++J40Tq\6fEQl]NJ',r?4?("G%b]M-USkaNNG)0o0=TkrKTE6*VeE`YY8GK/*_^OnD.3M9C!1+RFF6\`T$9Jc^N1HpTCp-74uJhFiDC +;@!Ss/ltf?c`5_Z8?MlDG/A'Q#t?c[d@?qaKN:n%4YC9$r]Lqc6-3=*.E`.Q(cZp\a[pMhT\/[r +`5L%raU169)?>FI%XUiQcnXu[fbi1S9%j=Y:^<*LTVq^3bc.6ajH47+R6Xpk5s.\VJ>4YPqJ_"K +Q8IZQY*5["WYWF99sD;@*cg7.cETM<7S5lu7]HkkaY"oofYe?>Y/EkF.RrLd'Tg0ogWV-S,(psG +P@@F%:s)h.7I8\Cj5,_$8^C5PSEZk\"q$a1++pZ&>79pPkNP;Q-E:Ii +Z-N\GSCX6c,eOb:5-K8])kX_#UAg7"AES5!=]%X!4%CS,5[E8%>!LPa$Xu_/$VQsZqO-N"?0pD,FRF5'q*B\R] +8VtBQV<1HTqV*B/n>qtPKLC`WQ:sr?0q7aTcg?_07KlZ@'''=Qc7t###*q64<4k_$&RKSBeWZpJ +FHc"5$8,uLr@8QF7O8,5-EkdWWeBTfb*'A?'fRYDQ;LlWX"Z;,s.!RM@L92AADBd9HiH7Ymnl +,r9.n&M,h^1`V`:P[)coMCg;\'GK#Y?ta75CZVMZ[>b<2QGhl3#8sP6Z`70i6`48T>qRLF:/BIt +m7:&I[4#MeUQ;Ao,hK,m11XGad'[C`8I;]<@MD9Co=;`<4r<-D>^k'd:+9qOWM9Lu!Hp$JX=;[^ +U9s--c:J*]$G#I>LkN<=#ntj%FTjb)&4K<7]U36FqFoNMrN]!CEKE@V4.c4-Y@V1P>_eiJW>+2(pOc:6`*57S%^B8E0;<]7Vi_0#ZM't +dARH\o?5fT+QWHhkPVZ`E;Eg0!mNOcWiLr +OBGtAbN[Fg_U$??Xm@75'Y1+U/6.YpJhX#]D5##Y&WPJ#-bJ;)Dgr>Z#"A5L+AB;#D5Uf$M6oj%&k51l9UD4s)g`X5Ug@5 +Y`\Yn1ZoW&,6^M]%S6m\PVrT:`Um2TjMRajd16j6Un1Mu`&:"<^`.;.L/"ZTS@#5k)AD7DTJH.U +]jbE667Qob6:>NfSK+7i2U[Kq1fOZ79-$BuUYd\I0pFOV.$tI(U]IbK-X`qj+E:E(8kqeYiO]C^ +5`:1'$@%C5i9lFN`>!T-MNLFP5`gm)%>[nsMg2N?i>"FQXf=AAR%ffQ6%P6@S!m0HP#39[=J?)* +XGGOg`R +fJnta5rrb[;,\g@/!_>,#pC7Di]I_'QjH$7Lj5E!R%=Ze:^QtTVQc[6G``fS;MoL#&c,$7R&l`+ +!tgYM$*2pJ0_W6UUC.(dN@;7D6ms+$l3tbI!Ju0)bW-.-PFo\tMD>;@Z$HfIf><'Z[/u!lUb3#= +'9GeTOH?F\$f;6jP+)a/%1/(*Z3.6T9F>HpQ5L&A+^teh+"!sJOGS#ko1]V_5`b.Q,dJRG=fR@a +P6bR0PN_-G+f,Uant#;9XD!^4\Yt?>V%$Q82R&5KJB!QcK.D2>M#ck+^u$N'M3*OO$#t8%B]9:h +5/<%IM0U`bf[,];^D9B\N'`Q*R.^eGfaMB[NNiBi@rVn*L(s^)Lp?7Z&?6['lkF]g$37Gh&PsWt +,`BT"Rq]$^\NHVfbaQ(#M\0S90S0dMV@L-6U`iACnh%g_gPaOMN)8[_0g1]hs.D)3PQ8ib@?[un +[M_1IRA;aKRBMU=Q,Bi#%2c;Xgb/u'5H6u1(k@[3Ysbp4Wk+m&PgQ655nTaYaGm^$RUGBXk^a!^ +]FH*mN6)S<349,ir=fReN3ME0,7+anY8%'(,g'sAqlNs8Z'm&OZGeFs0St]WkOGL`3JF!E2Et6I5m]b#scubWE0SB#cd.(N*r1uRVIgb[1E*:[kNjRX-T8PjH!ujPNp0ofU.TD +c__RW+N&\bKD`%W,E5$,%IfdCR/NWP%#toM`mR[5&l]7Sr0%^&9:6A"BWfAfTSaUgO9ksp=PG?G +H*L9r-a^Jn@8Y#&WJh]/$nBU\W/_U6ck_/tORZC3R-U;'!"Cq%/c_*PlL^Cq:_J%3[BRK).0)u/ +$Dsj+S[j^dW0`j%eu!>TSO!.KWL:F^):sIK#NXG +R7'uY$(/r\ka78o4;(34Vg1WJ"(_gQl%rl@%->aLRBU+WK)3[0+R4@iD!sFcoj_L_1I1;G;rBSs.jSX.c:!RY)f_ +M@t0*#hbcUn[8q[UW.Qua]S5'Gb>CX#q3XdOZ>qU5`h!CPFhQ7Nqj/VYlBN8RuZ^1Mt,L2qt_m# +((`19NUVcn=_!9ISI!%6O^+[fRZ&V65)JK]M%]XqX<4Y(hFOqK3O5%[\nqndSVVk9)td)7Lt[e* +6Q%>uV#eOD_q,G#V^JTIMCFMqiM.8I`el5QX,*.kZ[+A>@EhW-%!hr4,qFrP?d/%*&#nkLRKSQu +)j?MVQZ1`NBi,iAMQ1,)M"ClW)(mQ;)j'#+ME%bpH3(`fU9sFo#sBCXMs`H0oB)[OH^3<\^R!& +SJ2rt$TS,(4Kp'HV$RH.3LR\u#b;>,%Z/E/Q6<%5=K@N>$5NUF$@B(;HQ%p_8fk?=+>A]%3S^1H +^7-O'Ne@WKb#G$AiXOjJ%EdQEZ/^]:jW[+a?YLXc(RgWN#k+J;B/bBre42*Q]XdgX(q2 +!`5u[PH/./+J'0"hP#3fa11]'@B,,/GD`3\Mics.OJWuFWY&666%in'+1Y`Trt&[8VKW\cE.S.)?Q$UIEj&E47:6kcMZRIJGREk.p[ +\"$$,f1@ugfUE65D&&^q0BiWu\m^E3^S,o^gi9lfHdiKE[0Vl+%Me::R/C)8Kf+,TM(u3S=R?S] +bnL0*^Z^ienu]rX5K'l#*\iUK=Ta^md_ikq@3L,X62rYOp5O%i'+0C>bl\6qkR15:$[Dn&=Y:)d +LPd4I+>*[q$5a*?[>^4"e9KrCE4c^kQ`irn(Bl`j!>2LY/%Ch!MEq;6`*1Gkp,l0\8GO)?;$dl0 +fpq9IXMp_:@`FpBD,l+L9hG\TZH(:*&=!4gn&b`*9pW8@"aMc'F95cQ\`K9aL]g3GEafA*DhNJYb2O]U59/l;I)AdE]c!@ +.nU.ZM1Pes.*>=C,+j41&I>.!.#Wu(Ul=N,S3YW"`,?F-+IW#hb([)Iju$IB`NJ_A,^Ijr3`hoE +B9FuE>Lfag+K5nI;[lmB,RPq7Q)NH3h2t+Cfu\56%'5Y3`CKaBTA[?f[K<:92[XPIM,;r_d&d\C +XGE+o3Sn_D!V[#YmG"BU-BA\+))9'9`kb1o:1e!(Fm'7_a5&;H78!7#r,`L+LJa']JP1Jr2QMWj<'5qrX1r=%\8M4joNJ\U>53K'(%%$AF63nfY9nHj`\#UMaTmCJ/$aH*&_$XHeDRA,Lg +^H96U%^hTSOHD\AGR[o.4Re7OG`U/BM^[hoN-LUIeUtnPXbe0Q*QZUC7AZFYs,jm@:ahGRl=H3m +_8]lajZ5ton:uf[5i<4aR[DV3_X?;2?'CZcm?"`B/cqG(N@,"bMFq^k^r")naNVI8f)o;$.qkHQ +fF]g-$!AQ!^o:@SA/G/"%)A5:@H&DMl/&`cX,s42]cs*nD!&8`#cSQNHpA6'D2^%d.D)rs\egup +V@@d5PlY=L-?UJYM&:m"/ZKJt!`#tRn\)`4(]"8T//sEOrL'r-)q6tkS1h"'8r]+9RD#jL/T!4*\:D706r" +*f[^WOGK6s,*&"Y!gs=FN)8e*#_j(7X[1m?^]ouDIFss!MkU5MS]SVnaZcMrgPnO7]g9Yt&[3Fg +lm6rW)Dp%sTh!k1p5?H#P%a6ER@K'N,n+cVj-Z!hCZL]^Xbc3"N26ELWK%EUmu^lLZ4R-NAb$-e +b]Js8Zk1t#CX"bJG=TMiOB)4JYfDdNT+hDOOgqeIka7e?kSf;0$[TAqW1?F&?3pH$7A:\;$tRd` +!0-d$VpRjq%Ta&MN>2SmTo$"01>Ad4m4>dReY=+uGf0n9=];VZ%Bfqm.E*utP6uc'N7b`L5F6YF +R!Fa\H(UONYF9`TRsE>NH/G.fa.@SVTR#.]H4Q`r0:-u,UO#_.qF@B.Dju&cVg=DkqM2%rXWiB7 +2?P?]K9/+rGW,EN21SMRJ,~> +endstream +endobj +%%EndResource +%%BeginResource: file (PDF object obj_1) +1 0 obj +<<>>endobj +%%EndResource +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +4 0 obj +<> +/Contents 5 0 R +>> +endobj +%%EndPageSetup +5 0 obj +<>stream +q 0.012 0 0 0.012 0 0 cm +q +0 28770 38400 -28770 re +W n +1 G +1 g +0 -30 38400 28800 re +f +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +2657.57 4084.29 m +4324.24 4084.29 l +3490.91 3250.95 m +3490.91 4917.62 l +f +41.6667 w +1 j +2657.57 4084.29 m +4324.24 4084.29 l +3490.91 3250.95 m +3490.91 4917.62 l +S +2657.57 4084.29 m +4324.24 4084.29 l +3490.91 3250.95 m +3490.91 4917.62 l +f +2657.57 4084.29 m +4324.24 4084.29 l +3490.91 3250.95 m +3490.91 4917.62 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +6148.48 8198.57 m +7815.15 8198.57 l +6981.82 7365.24 m +6981.82 9031.91 l +f +6148.48 8198.57 m +7815.15 8198.57 l +6981.82 7365.24 m +6981.82 9031.91 l +S +6148.48 8198.57 m +7815.15 8198.57 l +6981.82 7365.24 m +6981.82 9031.91 l +f +6148.48 8198.57 m +7815.15 8198.57 l +6981.82 7365.24 m +6981.82 9031.91 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +2657.57 12312.8 m +4324.24 12312.8 l +3490.91 11479.5 m +3490.91 13146.2 l +f +2657.57 12312.8 m +4324.24 12312.8 l +3490.91 11479.5 m +3490.91 13146.2 l +S +2657.57 12312.8 m +4324.24 12312.8 l +3490.91 11479.5 m +3490.91 13146.2 l +f +2657.57 12312.8 m +4324.24 12312.8 l +3490.91 11479.5 m +3490.91 13146.2 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +6148.48 16427.2 m +7815.15 16427.2 l +6981.82 15593.8 m +6981.82 17260.5 l +f +6148.48 16427.2 m +7815.15 16427.2 l +6981.82 15593.8 m +6981.82 17260.5 l +S +6148.48 16427.2 m +7815.15 16427.2 l +6981.82 15593.8 m +6981.82 17260.5 l +f +6148.48 16427.2 m +7815.15 16427.2 l +6981.82 15593.8 m +6981.82 17260.5 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +2657.57 20541.4 m +4324.24 20541.4 l +3490.91 19708.1 m +3490.91 21374.8 l +f +2657.57 20541.4 m +4324.24 20541.4 l +3490.91 19708.1 m +3490.91 21374.8 l +S +2657.57 20541.4 m +4324.24 20541.4 l +3490.91 19708.1 m +3490.91 21374.8 l +f +2657.57 20541.4 m +4324.24 20541.4 l +3490.91 19708.1 m +3490.91 21374.8 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +6148.48 24655.8 m +7815.15 24655.8 l +6981.82 23822.4 m +6981.82 25489.1 l +f +6148.48 24655.8 m +7815.15 24655.8 l +6981.82 23822.4 m +6981.82 25489.1 l +S +6148.48 24655.8 m +7815.15 24655.8 l +6981.82 23822.4 m +6981.82 25489.1 l +f +6148.48 24655.8 m +7815.15 24655.8 l +6981.82 23822.4 m +6981.82 25489.1 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +9639.41 4084.29 m +11306.1 4084.29 l +10472.8 3250.95 m +10472.8 4917.62 l +f +9639.41 4084.29 m +11306.1 4084.29 l +10472.8 3250.95 m +10472.8 4917.62 l +S +9639.41 4084.29 m +11306.1 4084.29 l +10472.8 3250.95 m +10472.8 4917.62 l +f +9639.41 4084.29 m +11306.1 4084.29 l +10472.8 3250.95 m +10472.8 4917.62 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +13130.3 8198.57 m +14797 8198.57 l +13963.7 7365.24 m +13963.7 9031.91 l +f +13130.3 8198.57 m +14797 8198.57 l +13963.7 7365.24 m +13963.7 9031.91 l +S +13130.3 8198.57 m +14797 8198.57 l +13963.7 7365.24 m +13963.7 9031.91 l +f +13130.3 8198.57 m +14797 8198.57 l +13963.7 7365.24 m +13963.7 9031.91 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +9639.41 12312.8 m +11306.1 12312.8 l +10472.8 11479.5 m +10472.8 13146.2 l +f +9639.41 12312.8 m +11306.1 12312.8 l +10472.8 11479.5 m +10472.8 13146.2 l +S +9639.41 12312.8 m +11306.1 12312.8 l +10472.8 11479.5 m +10472.8 13146.2 l +f +9639.41 12312.8 m +11306.1 12312.8 l +10472.8 11479.5 m +10472.8 13146.2 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +13130.3 16427.2 m +14797 16427.2 l +13963.7 15593.8 m +13963.7 17260.5 l +f +13130.3 16427.2 m +14797 16427.2 l +13963.7 15593.8 m +13963.7 17260.5 l +S +13130.3 16427.2 m +14797 16427.2 l +13963.7 15593.8 m +13963.7 17260.5 l +f +13130.3 16427.2 m +14797 16427.2 l +13963.7 15593.8 m +13963.7 17260.5 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +9639.41 20541.4 m +11306.1 20541.4 l +10472.8 19708.1 m +10472.8 21374.8 l +f +9639.41 20541.4 m +11306.1 20541.4 l +10472.8 19708.1 m +10472.8 21374.8 l +S +9639.41 20541.4 m +11306.1 20541.4 l +10472.8 19708.1 m +10472.8 21374.8 l +f +9639.41 20541.4 m +11306.1 20541.4 l +10472.8 19708.1 m +10472.8 21374.8 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +13130.3 24655.8 m +14797 24655.8 l +13963.7 23822.4 m +13963.7 25489.1 l +f +13130.3 24655.8 m +14797 24655.8 l +13963.7 23822.4 m +13963.7 25489.1 l +S +13130.3 24655.8 m +14797 24655.8 l +13963.7 23822.4 m +13963.7 25489.1 l +f +13130.3 24655.8 m +14797 24655.8 l +13963.7 23822.4 m +13963.7 25489.1 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +16621.3 4084.29 m +18287.9 4084.29 l +17454.6 3250.95 m +17454.6 4917.62 l +f +16621.3 4084.29 m +18287.9 4084.29 l +17454.6 3250.95 m +17454.6 4917.62 l +S +16621.3 4084.29 m +18287.9 4084.29 l +17454.6 3250.95 m +17454.6 4917.62 l +f +16621.3 4084.29 m +18287.9 4084.29 l +17454.6 3250.95 m +17454.6 4917.62 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +20112.1 8198.57 m +21778.8 8198.57 l +20945.4 7365.24 m +20945.4 9031.91 l +f +20112.1 8198.57 m +21778.8 8198.57 l +20945.4 7365.24 m +20945.4 9031.91 l +S +20112.1 8198.57 m +21778.8 8198.57 l +20945.4 7365.24 m +20945.4 9031.91 l +f +20112.1 8198.57 m +21778.8 8198.57 l +20945.4 7365.24 m +20945.4 9031.91 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +16621.3 12312.8 m +18287.9 12312.8 l +17454.6 11479.5 m +17454.6 13146.2 l +f +16621.3 12312.8 m +18287.9 12312.8 l +17454.6 11479.5 m +17454.6 13146.2 l +S +16621.3 12312.8 m +18287.9 12312.8 l +17454.6 11479.5 m +17454.6 13146.2 l +f +16621.3 12312.8 m +18287.9 12312.8 l +17454.6 11479.5 m +17454.6 13146.2 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +20112.1 16427.2 m +21778.8 16427.2 l +20945.4 15593.8 m +20945.4 17260.5 l +f +20112.1 16427.2 m +21778.8 16427.2 l +20945.4 15593.8 m +20945.4 17260.5 l +S +20112.1 16427.2 m +21778.8 16427.2 l +20945.4 15593.8 m +20945.4 17260.5 l +f +20112.1 16427.2 m +21778.8 16427.2 l +20945.4 15593.8 m +20945.4 17260.5 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +16621.3 20541.4 m +18287.9 20541.4 l +17454.6 19708.1 m +17454.6 21374.8 l +f +16621.3 20541.4 m +18287.9 20541.4 l +17454.6 19708.1 m +17454.6 21374.8 l +S +16621.3 20541.4 m +18287.9 20541.4 l +17454.6 19708.1 m +17454.6 21374.8 l +f +16621.3 20541.4 m +18287.9 20541.4 l +17454.6 19708.1 m +17454.6 21374.8 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +20112.1 24655.8 m +21778.8 24655.8 l +20945.4 23822.4 m +20945.4 25489.1 l +f +20112.1 24655.8 m +21778.8 24655.8 l +20945.4 23822.4 m +20945.4 25489.1 l +S +20112.1 24655.8 m +21778.8 24655.8 l +20945.4 23822.4 m +20945.4 25489.1 l +f +20112.1 24655.8 m +21778.8 24655.8 l +20945.4 23822.4 m +20945.4 25489.1 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +23603 4084.29 m +25269.7 4084.29 l +24436.3 3250.95 m +24436.3 4917.62 l +f +23603 4084.29 m +25269.7 4084.29 l +24436.3 3250.95 m +24436.3 4917.62 l +S +23603 4084.29 m +25269.7 4084.29 l +24436.3 3250.95 m +24436.3 4917.62 l +f +23603 4084.29 m +25269.7 4084.29 l +24436.3 3250.95 m +24436.3 4917.62 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +27093.9 8198.57 m +28760.6 8198.57 l +27927.3 7365.24 m +27927.3 9031.91 l +f +27093.9 8198.57 m +28760.6 8198.57 l +27927.3 7365.24 m +27927.3 9031.91 l +S +27093.9 8198.57 m +28760.6 8198.57 l +27927.3 7365.24 m +27927.3 9031.91 l +f +27093.9 8198.57 m +28760.6 8198.57 l +27927.3 7365.24 m +27927.3 9031.91 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +23603 12312.8 m +25269.7 12312.8 l +24436.3 11479.5 m +24436.3 13146.2 l +f +23603 12312.8 m +25269.7 12312.8 l +24436.3 11479.5 m +24436.3 13146.2 l +S +23603 12312.8 m +25269.7 12312.8 l +24436.3 11479.5 m +24436.3 13146.2 l +f +23603 12312.8 m +25269.7 12312.8 l +24436.3 11479.5 m +24436.3 13146.2 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +27093.9 16427.2 m +28760.6 16427.2 l +27927.3 15593.8 m +27927.3 17260.5 l +f +27093.9 16427.2 m +28760.6 16427.2 l +27927.3 15593.8 m +27927.3 17260.5 l +S +27093.9 16427.2 m +28760.6 16427.2 l +27927.3 15593.8 m +27927.3 17260.5 l +f +27093.9 16427.2 m +28760.6 16427.2 l +27927.3 15593.8 m +27927.3 17260.5 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +23603 20541.4 m +25269.7 20541.4 l +24436.3 19708.1 m +24436.3 21374.8 l +f +23603 20541.4 m +25269.7 20541.4 l +24436.3 19708.1 m +24436.3 21374.8 l +S +23603 20541.4 m +25269.7 20541.4 l +24436.3 19708.1 m +24436.3 21374.8 l +f +23603 20541.4 m +25269.7 20541.4 l +24436.3 19708.1 m +24436.3 21374.8 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +27093.9 24655.8 m +28760.6 24655.8 l +27927.3 23822.4 m +27927.3 25489.1 l +f +27093.9 24655.8 m +28760.6 24655.8 l +27927.3 23822.4 m +27927.3 25489.1 l +S +27093.9 24655.8 m +28760.6 24655.8 l +27927.3 23822.4 m +27927.3 25489.1 l +f +27093.9 24655.8 m +28760.6 24655.8 l +27927.3 23822.4 m +27927.3 25489.1 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +30584.8 4084.29 m +32251.5 4084.29 l +31418.2 3250.95 m +31418.2 4917.62 l +f +30584.8 4084.29 m +32251.5 4084.29 l +31418.2 3250.95 m +31418.2 4917.62 l +S +30584.8 4084.29 m +32251.5 4084.29 l +31418.2 3250.95 m +31418.2 4917.62 l +f +30584.8 4084.29 m +32251.5 4084.29 l +31418.2 3250.95 m +31418.2 4917.62 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +34075.8 8198.57 m +35742.4 8198.57 l +34909.1 7365.24 m +34909.1 9031.91 l +f +34075.8 8198.57 m +35742.4 8198.57 l +34909.1 7365.24 m +34909.1 9031.91 l +S +34075.8 8198.57 m +35742.4 8198.57 l +34909.1 7365.24 m +34909.1 9031.91 l +f +34075.8 8198.57 m +35742.4 8198.57 l +34909.1 7365.24 m +34909.1 9031.91 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +30584.8 12312.8 m +32251.5 12312.8 l +31418.2 11479.5 m +31418.2 13146.2 l +f +30584.8 12312.8 m +32251.5 12312.8 l +31418.2 11479.5 m +31418.2 13146.2 l +S +30584.8 12312.8 m +32251.5 12312.8 l +31418.2 11479.5 m +31418.2 13146.2 l +f +30584.8 12312.8 m +32251.5 12312.8 l +31418.2 11479.5 m +31418.2 13146.2 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +34075.8 16427.2 m +35742.4 16427.2 l +34909.1 15593.8 m +34909.1 17260.5 l +f +34075.8 16427.2 m +35742.4 16427.2 l +34909.1 15593.8 m +34909.1 17260.5 l +S +34075.8 16427.2 m +35742.4 16427.2 l +34909.1 15593.8 m +34909.1 17260.5 l +f +34075.8 16427.2 m +35742.4 16427.2 l +34909.1 15593.8 m +34909.1 17260.5 l +S +0.12207 0.467041 0.705078 RG +0.12207 0.467041 0.705078 rg +30584.8 20541.4 m +32251.5 20541.4 l +31418.2 19708.1 m +31418.2 21374.8 l +f +30584.8 20541.4 m +32251.5 20541.4 l +31418.2 19708.1 m +31418.2 21374.8 l +S +30584.8 20541.4 m +32251.5 20541.4 l +31418.2 19708.1 m +31418.2 21374.8 l +f +30584.8 20541.4 m +32251.5 20541.4 l +31418.2 19708.1 m +31418.2 21374.8 l +S +1 0.498047 0.0549927 RG +1 0.498047 0.0549927 rg +34075.8 24655.8 m +35742.4 24655.8 l +34909.1 23822.4 m +34909.1 25489.1 l +f +34075.8 24655.8 m +35742.4 24655.8 l +34909.1 23822.4 m +34909.1 25489.1 l +S +34075.8 24655.8 m +35742.4 24655.8 l +34909.1 23822.4 m +34909.1 25489.1 l +f +34075.8 24655.8 m +35742.4 24655.8 l +34909.1 23822.4 m +34909.1 25489.1 l +S +Q +q +0 0 m +W n +0 0 0 1 K +0 0 0 1 k +q 7410 0 0 -40 -7401 59819 cm +BI +/IM true +/W 1 +/H 1 +/BPC 1 +/F/A85 +ID +!!~> +EI Q +Q +0 0 0 RG +0 0 0 rg +q +83.3333 0 0 83.3333 0 0 cm BT +/R7 9.96264 Tf +1 0 0 1 41.891 42.076 Tm +[(M)0.579901(y)-2.98008(lt)2.56015(0)-5.89017]TJ +0 1 -1 0 48.827 18.016 Tm +[(M)0.579901(y)-2.98008(lt)2.55986(9)-5.88958(0)-5.88988]TJ +-1 0 0 -1 119.758 96.343 Tm +[(M)0.579901(y)-2.97949(lt)2.55956(1)-5.89017(8)-5.89017(0)-5.89017]TJ +0 -1 1 0 85.702 98.383 Tm +[(M)0.579901(y)-2.98008(lt)2.56015(2)-5.89017(7)-5.89017(0)-5.89017]TJ +1 0 0 1 28.054 140.819 Tm +[(M)0.579901(y)-2.98008(c)-1.66501(t)2.56015(0)-5.89017]TJ +0 1 -1 0 44.279 115.099 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(t)2.55956(9)-5.88958(0)-5.88958]TJ +-1 0 0 -1 102.6 195.206 Tm +[(M)0.579901(y)-2.98008(c)-1.66501(t)2.56015(1)-5.89017(8)-5.89017(0)-5.89017]TJ +0 -1 1 0 81.274 197.126 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(t)2.55956(2)-5.88958(7)-5.88958(0)-5.88958]TJ +1 0 0 1 14.743 239.561 Tm +[(M)0.580048(y)-2.98008(r)-6.48507(t)2.55986(0)-5.88958]TJ +0 1 -1 0 39.971 214.368 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(t)2.55956(9)-5.89076(0)-5.89076]TJ +-1 0 0 -1 83.782 293.829 Tm +[(M)0.579901(y)-2.98008(r)-6.48477(t)2.56015(1)-5.89017(8)-5.89017(0)-5.88958]TJ +0 -1 1 0 76.846 295.869 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(t)2.55956(2)-5.89076(7)-5.89076(0)-5.89076]TJ +1 0 0 1 125.673 46.504 Tm +[(M)0.579901(y)-2.97949(lc)-1.66442(0)-5.88958]TJ +0 1 -1 0 132.488 33.237 Tm +[(M)0.579901(y)-2.98008(lc)-1.66501(9)-5.89017(0)-5.89017]TJ +-1 0 0 -1 204.093 100.891 Tm +[(M)0.579901(y)-2.97949(lc)-1.66442(1)-5.88958(8)-5.88958(0)-5.88958]TJ +0 -1 1 0 169.484 116.648 Tm +[(M)0.579901(y)-2.97949(lc)-1.66501(2)-5.89017(7)-5.89017(0)-5.89017]TJ +1 0 0 1 111.559 145.246 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(c)-1.66442(0)-5.88958]TJ +0 1 -1 0 128.181 131.15 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(c)-1.66442(9)-5.88958(0)-5.88958]TJ +-1 0 0 -1 186.659 199.634 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(c)-1.66442(1)-5.88958(8)-5.88958(0)-5.88958]TJ +0 -1 1 0 165.056 216.221 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(c)-1.66442(2)-5.88958(7)-5.88958(0)-5.88958]TJ +1 0 0 1 97.971 243.989 Tm +[(M)0.579901(y)-2.97949(r)-6.48477(c)-1.66442(0)-5.88958]TJ +0 1 -1 0 123.633 230.156 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(c)-1.6656(9)-5.89076(0)-5.89076]TJ +-1 0 0 -1 167.564 298.376 Tm +[(M)0.579901(y)-2.97949(r)-6.48477(c)-1.66442(1)-5.88958(8)-5.88958(0)-5.88958]TJ +0 -1 1 0 160.628 314.701 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(c)-1.6656(2)-5.89076(7)-5.89076(0)-5.89076]TJ +1 0 0 1 209.455 50.931 Tm +[(M)0.581077(y)-2.98067(lb)0.929253(0)-5.89076]TJ +0 1 -1 0 216.39 49.011 Tm +[(M)0.579901(y)-2.98008(lb)0.929841(9)-5.89017(0)-5.89017]TJ +-1 0 0 -1 288.982 105.319 Tm +[(M)0.581077(y)-2.98067(lb)0.929253(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 253.265 136.02 Tm +[(M)0.579901(y)-2.97949(lb)0.930429(2)-5.88958(7)-5.88958(0)-5.88958]TJ +1 0 0 1 194.787 149.674 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(b)0.929253(0)-5.89076]TJ +0 1 -1 0 211.842 147.754 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(b)0.930429(9)-5.88958(0)-5.88958]TJ +-1 0 0 -1 270.994 204.061 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(b)0.929253(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 248.838 236.423 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(b)0.929253(2)-5.89076(7)-5.89076(0)-5.89076]TJ +1 0 0 1 180.646 248.417 Tm +[(M)0.579901(y)-2.97949(r)-6.48595(b)0.929253(0)-5.89076]TJ +0 1 -1 0 207.535 246.497 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(b)0.929253(9)-5.89076(0)-5.89076]TJ +-1 0 0 -1 251.345 302.804 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(b)0.929253(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 244.41 334.64 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(b)0.929253(2)-5.89076(7)-5.89076(0)-5.89076]TJ +1 0 0 1 293.236 48.994 Tm +[(M)0.581077(y)-2.98067(lB)-2.65602(0)-5.89076]TJ +0 1 -1 0 300.052 47.074 Tm +[(M)0.579901(y)-2.98008(lB)-2.65484(9)-5.89017(0)-5.89017]TJ +-1 0 0 -1 374.286 103.381 Tm +[(M)0.581077(y)-2.98067(lB)-2.65602(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 337.047 135.605 Tm +[(M)0.579901(y)-2.97949(lB)-2.65484(2)-5.88958(7)-5.88958(0)-5.88958]TJ +1 0 0 1 277.808 147.737 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(B)-2.65602(0)-5.89076]TJ +0 1 -1 0 295.744 145.817 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(B)-2.65484(9)-5.88958(0)-5.88958]TJ +-1 0 0 -1 355.537 202.124 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(B)-2.65602(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 332.619 236.008 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(B)-2.65602(2)-5.89076(7)-5.89076(0)-5.88958]TJ +1 0 0 1 262.906 246.48 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(B)-2.65602(0)-5.89076]TJ +0 1 -1 0 291.196 244.56 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(B)-2.65602(9)-5.89076(0)-5.89076]TJ +-1 0 0 -1 335.127 300.867 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(B)-2.65602(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 328.192 334.225 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(B)-2.65602(2)-5.89076(7)-5.89076(0)-5.89076]TJ +1 0 0 1 377.018 45.535 Tm +[(M)0.581077(y)-2.98067(lC)-0.701057(0)-5.89076]TJ +0 1 -1 0 383.954 18.155 Tm +[(M)0.579901(y)-2.98008(lC)-0.699586(9)-5.88988(0)-5.89017]TJ +-1 0 0 -1 458.206 99.922 Tm +[(M)0.578725(y)-2.97831(lC)-0.701057(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 420.829 101.842 Tm +[(M)0.579901(y)-2.98008(lC)-0.69988(2)-5.89017(7)-5.89017(0)-5.89017]TJ +1 0 0 1 361.521 144.278 Tm +[(M)0.581077(y)-2.98067(c)-1.6656(C)-0.701057(0)-5.89076]TJ +0 1 -1 0 379.406 115.237 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(C)-0.69988(9)-5.88958(0)-5.88958]TJ +-1 0 0 -1 439.388 198.665 Tm +[(M)0.578725(y)-2.97831(c)-1.6656(C)-0.701057(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 416.401 200.585 Tm +[(M)0.579901(y)-2.97949(c)-1.66442(C)-0.69988(2)-5.88958(7)-5.88958(0)-5.88958]TJ +1 0 0 1 346.549 243.021 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(C)-0.701057(0)-5.89076]TJ +0 1 -1 0 375.098 214.506 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(C)-0.701057(9)-5.89076(0)-5.89076]TJ +-1 0 0 -1 418.909 297.408 Tm +[(M)0.578725(y)-2.97831(r)-6.4836(C)-0.701057(1)-5.89076(8)-5.89076(0)-5.89076]TJ +0 -1 1 0 411.973 299.328 Tm +[(M)0.581077(y)-2.98067(r)-6.48595(C)-0.701057(2)-5.89076(7)-5.89076(0)-5.89076]TJ +ET +Q +Q + +endstream +endobj +%%PageTrailer +%%Trailer +end +cleartomark +countdictstack +exch sub { end } repeat +restore +showpage +%%EOF diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/rotation.pdf b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.pdf new file mode 100644 index 000000000000..5b7d5cacfc69 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/rotation.png b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.png new file mode 100644 index 000000000000..99bab74390b8 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/rotation.svg b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.svg new file mode 100644 index 000000000000..c68bffefde86 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_usetex/rotation.svg @@ -0,0 +1,1387 @@ + + + + + + + + 2023-04-27T20:38:40.258942 + image/svg+xml + + + Matplotlib v3.8.0.dev964+g2e2d2d5f57.d20230428, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png deleted file mode 100644 index e071860dfde6..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png index e96085d9bffd..f0d5023008ca 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png and b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png differ diff --git a/lib/matplotlib/tests/conftest.py b/lib/matplotlib/tests/conftest.py index 722a7ff91484..6afd566750e9 100644 --- a/lib/matplotlib/tests/conftest.py +++ b/lib/matplotlib/tests/conftest.py @@ -1,4 +1,2 @@ -from matplotlib.testing.conftest import (mpl_test_settings, - mpl_image_comparison_parameters, - pytest_configure, pytest_unconfigure, - pd) +from matplotlib.testing.conftest import ( # noqa + mpl_test_settings, pytest_configure, pytest_unconfigure, pd, text_placeholders, xr) diff --git a/lib/matplotlib/tests/data/Courier10PitchBT-Bold.pfb b/lib/matplotlib/tests/data/Courier10PitchBT-Bold.pfb new file mode 100644 index 000000000000..88d9af2af701 Binary files /dev/null and b/lib/matplotlib/tests/data/Courier10PitchBT-Bold.pfb differ diff --git a/lib/matplotlib/tests/cmr10.pfb b/lib/matplotlib/tests/data/cmr10.pfb similarity index 100% rename from lib/matplotlib/tests/cmr10.pfb rename to lib/matplotlib/tests/data/cmr10.pfb diff --git a/lib/matplotlib/tests/mpltest.ttf b/lib/matplotlib/tests/data/mpltest.ttf similarity index 100% rename from lib/matplotlib/tests/mpltest.ttf rename to lib/matplotlib/tests/data/mpltest.ttf diff --git a/lib/matplotlib/tests/data/test_inline_01.ipynb b/lib/matplotlib/tests/data/test_inline_01.ipynb new file mode 100644 index 000000000000..b87ae095bdbe --- /dev/null +++ b/lib/matplotlib/tests/data/test_inline_01.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(3, 2))\n", + "ax.plot([1, 3, 2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib\n", + "matplotlib.get_backend()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + }, + "toc": { + "colors": { + "hover_highlight": "#DAA520", + "running_highlight": "#FF0000", + "selected_highlight": "#FFD700" + }, + "moveMenuLeft": true, + "nav_menu": { + "height": "12px", + "width": "252px" + }, + "navigate_menu": true, + "number_sections": true, + "sideBar": true, + "threshold": 4, + "toc_cell": false, + "toc_section_display": "block", + "toc_window_display": false, + "widenNotebook": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/lib/matplotlib/tests/test_nbagg_01.ipynb b/lib/matplotlib/tests/data/test_nbagg_01.ipynb similarity index 99% rename from lib/matplotlib/tests/test_nbagg_01.ipynb rename to lib/matplotlib/tests/data/test_nbagg_01.ipynb index c8839afe8ddd..bd18aa4192b7 100644 --- a/lib/matplotlib/tests/test_nbagg_01.ipynb +++ b/lib/matplotlib/tests/data/test_nbagg_01.ipynb @@ -8,9 +8,8 @@ }, "outputs": [], "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib notebook\n" + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" ] }, { @@ -473,7 +472,7 @@ " };\n", "}\n", "\n", - "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", "mpl.findpos = function(e) {\n", " //this section is from http://www.quirksmode.org/js/events_properties.html\n", " var targ;\n", @@ -498,7 +497,7 @@ "/*\n", " * return a copy of an object with only non-object keys\n", " * we need this to avoid circular references\n", - " * http://stackoverflow.com/a/24161582/3208463\n", + " * https://stackoverflow.com/a/24161582/3208463\n", " */\n", "function simpleKeys (original) {\n", " return Object.keys(original).reduce(function (obj, key) {\n", @@ -826,17 +825,31 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.plot(range(10))\n" + "ax.plot(range(10))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "'notebook'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import matplotlib\n", + "matplotlib.get_backend()" + ] } ], "metadata": { diff --git a/lib/matplotlib/tests/tinypages/.gitignore b/lib/matplotlib/tests/data/tinypages/.gitignore similarity index 100% rename from lib/matplotlib/tests/tinypages/.gitignore rename to lib/matplotlib/tests/data/tinypages/.gitignore diff --git a/lib/matplotlib/tests/tinypages/README.md b/lib/matplotlib/tests/data/tinypages/README.md similarity index 100% rename from lib/matplotlib/tests/tinypages/README.md rename to lib/matplotlib/tests/data/tinypages/README.md diff --git a/lib/matplotlib/tests/data/tinypages/_static/.gitignore b/lib/matplotlib/tests/data/tinypages/_static/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/matplotlib/tests/tinypages/_static/README.txt b/lib/matplotlib/tests/data/tinypages/_static/README.txt similarity index 100% rename from lib/matplotlib/tests/tinypages/_static/README.txt rename to lib/matplotlib/tests/data/tinypages/_static/README.txt diff --git a/lib/matplotlib/tests/data/tinypages/conf.py b/lib/matplotlib/tests/data/tinypages/conf.py new file mode 100644 index 000000000000..6a1820d9f546 --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/conf.py @@ -0,0 +1,25 @@ +import sphinx +from packaging.version import parse as parse_version + +# -- General configuration ------------------------------------------------ + +extensions = ['matplotlib.sphinxext.plot_directive', + 'matplotlib.sphinxext.figmpl_directive'] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = 'tinypages' +copyright = '2014, Matplotlib developers' +version = '0.1' +release = '0.1' +exclude_patterns = ['_build'] +pygments_style = 'sphinx' + +# -- Options for HTML output ---------------------------------------------- + +if parse_version(sphinx.__version__) >= parse_version('1.3'): + html_theme = 'classic' +else: + html_theme = 'default' + +html_static_path = ['_static'] diff --git a/lib/matplotlib/tests/data/tinypages/included_plot_21.rst b/lib/matplotlib/tests/data/tinypages/included_plot_21.rst new file mode 100644 index 000000000000..761beae6c02d --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/included_plot_21.rst @@ -0,0 +1,6 @@ +Plot 21 has length 6 + +.. plot:: + + plt.plot(range(6)) + diff --git a/lib/matplotlib/tests/data/tinypages/index.rst b/lib/matplotlib/tests/data/tinypages/index.rst new file mode 100644 index 000000000000..33e1bf79cde8 --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/index.rst @@ -0,0 +1,24 @@ +.. tinypages documentation master file, created by + sphinx-quickstart on Tue Mar 18 11:58:34 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to tinypages's documentation! +===================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + some_plots + nestedpage/index + nestedpage2/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/lib/matplotlib/tests/data/tinypages/nestedpage/index.rst b/lib/matplotlib/tests/data/tinypages/nestedpage/index.rst new file mode 100644 index 000000000000..59c41902fa7f --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/nestedpage/index.rst @@ -0,0 +1,20 @@ +################# +Nested page plots +################# + +Plot 1 does not use context: + +.. plot:: + + plt.plot(range(10)) + plt.title('FIRST NESTED 1') + a = 10 + +Plot 2 doesn't use context either; has length 6: + +.. plot:: + + plt.plot(range(6)) + plt.title('FIRST NESTED 2') + + diff --git a/lib/matplotlib/tests/data/tinypages/nestedpage2/index.rst b/lib/matplotlib/tests/data/tinypages/nestedpage2/index.rst new file mode 100644 index 000000000000..b7d21b581a89 --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/nestedpage2/index.rst @@ -0,0 +1,25 @@ +##################### +Nested page plots TWO +##################### + +Plot 1 does not use context: + +.. plot:: + + plt.plot(range(10)) + plt.title('NESTED2 Plot 1') + a = 10 + +Plot 2 doesn't use context either; has length 6: + + +.. plot:: + + plt.plot(range(6)) + plt.title('NESTED2 Plot 2') + + +.. plot:: + + plt.plot(range(6)) + plt.title('NESTED2 PlotP 3') diff --git a/lib/matplotlib/tests/tinypages/range4.py b/lib/matplotlib/tests/data/tinypages/range4.py similarity index 100% rename from lib/matplotlib/tests/tinypages/range4.py rename to lib/matplotlib/tests/data/tinypages/range4.py diff --git a/lib/matplotlib/tests/data/tinypages/range6.py b/lib/matplotlib/tests/data/tinypages/range6.py new file mode 100644 index 000000000000..b6655ae07e1f --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/range6.py @@ -0,0 +1,20 @@ +from matplotlib import pyplot as plt + + +def range4(): + """Never called if plot_directive works as expected.""" + raise NotImplementedError + + +def range6(): + """The function that should be executed.""" + plt.figure() + plt.plot(range(6)) + plt.show() + + +def range10(): + """The function that should be executed.""" + plt.figure() + plt.plot(range(10)) + plt.show() diff --git a/lib/matplotlib/tests/data/tinypages/some_plots.rst b/lib/matplotlib/tests/data/tinypages/some_plots.rst new file mode 100644 index 000000000000..cb56c5b3b8d5 --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/some_plots.rst @@ -0,0 +1,181 @@ +########## +Some plots +########## + +Plot 1 does not use context: + +.. plot:: + + plt.plot(range(10)) + a = 10 + +Plot 2 doesn't use context either; has length 6: + +.. plot:: + + plt.plot(range(6)) + +Plot 3 has length 4, and uses doctest syntax: + +.. plot:: + :format: doctest + + This is a doctest... + + >>> plt.plot(range(4)) + + ... isn't it? + +Plot 4 shows that a new block with context does not see the variable defined +in the no-context block: + +.. plot:: + :context: + + assert 'a' not in globals() + +Plot 5 defines ``a`` in a context block: + +.. plot:: + :context: + + plt.plot(range(6)) + a = 10 + +Plot 6 shows that a block with context sees the new variable. It also uses +``:nofigs:``: + +.. plot:: + :context: + :nofigs: + + assert a == 10 + b = 4 + +Plot 7 uses a variable previously defined in previous ``nofigs`` context. It +also closes any previous figures to create a fresh figure: + +.. plot:: + :context: close-figs + + assert b == 4 + plt.plot(range(b)) + +Plot 8 shows that a non-context block still doesn't have ``a``: + +.. plot:: + :nofigs: + + assert 'a' not in globals() + +Plot 9 has a context block, and does have ``a``: + +.. plot:: + :context: + :nofigs: + + assert a == 10 + +Plot 10 resets context, and ``a`` has gone again: + +.. plot:: + :context: reset + :nofigs: + + assert 'a' not in globals() + c = 10 + +Plot 11 continues the context, we have the new value, but not the old: + +.. plot:: + :context: + + assert c == 10 + assert 'a' not in globals() + plt.plot(range(c)) + +Plot 12 opens a new figure. By default the directive will plot both the first +and the second figure: + +.. plot:: + :context: + + plt.figure() + plt.plot(range(6)) + +Plot 13 shows ``close-figs`` in action. ``close-figs`` closes all figures +previous to this plot directive, so we get always plot the figure we create in +the directive: + +.. plot:: + :context: close-figs + + plt.figure() + plt.plot(range(4)) + +Plot 14 uses ``include-source``: + +.. plot:: + :include-source: + + # Only a comment + +Plot 15 uses an external file with the plot commands and a caption: + +.. plot:: range4.py + + This is the caption for plot 15. + + +Plot 16 uses a specific function in a file with plot commands: + +.. plot:: range6.py range6 + + +Plot 17 gets a caption specified by the :caption: option: + +.. plot:: + :caption: + Plot 17 uses the caption option, + with multi-line input. + :alt: + Plot 17 uses the alt option, + with multi-line input. + + plt.figure() + plt.plot(range(6)) + + +Plot 18 uses an external file with the plot commands and a caption +using the :caption: option: + +.. plot:: range4.py + :caption: This is the caption for plot 18. + +Plot 19 uses shows that the "plot-directive" class is still appended, even if +we request other custom classes: + +.. plot:: range4.py + :class: my-class my-other-class + + Should also have a caption. + +Plot 20 shows that the default template correctly prints the multi-image +scenario: + +.. plot:: + :caption: This caption applies to both plots. + + plt.figure() + plt.plot(range(6)) + + plt.figure() + plt.plot(range(4)) + +Plot 21 is generated via an include directive: + +.. include:: included_plot_21.rst + +Plot 22 uses a different specific function in a file with plot commands: + +.. plot:: range6.py range10 diff --git a/lib/matplotlib/tests/meson.build b/lib/matplotlib/tests/meson.build new file mode 100644 index 000000000000..48b97a1d4b3d --- /dev/null +++ b/lib/matplotlib/tests/meson.build @@ -0,0 +1,112 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_afm.py', + 'test_agg.py', + 'test_agg_filter.py', + 'test_animation.py', + 'test_api.py', + 'test_arrow_patches.py', + 'test_artist.py', + 'test_axes.py', + 'test_axis.py', + 'test_backend_bases.py', + 'test_backend_cairo.py', + 'test_backend_gtk3.py', + 'test_backend_inline.py', + 'test_backend_macosx.py', + 'test_backend_nbagg.py', + 'test_backend_pdf.py', + 'test_backend_pgf.py', + 'test_backend_ps.py', + 'test_backend_qt.py', + 'test_backend_registry.py', + 'test_backend_svg.py', + 'test_backend_template.py', + 'test_backend_tk.py', + 'test_backend_tools.py', + 'test_backend_webagg.py', + 'test_backends_interactive.py', + 'test_basic.py', + 'test_bbox_tight.py', + 'test_bezier.py', + 'test_category.py', + 'test_cbook.py', + 'test_collections.py', + 'test_colorbar.py', + 'test_colors.py', + 'test_compare_images.py', + 'test_constrainedlayout.py', + 'test_container.py', + 'test_contour.py', + 'test_cycles.py', + 'test_dates.py', + 'test_datetime.py', + 'test_determinism.py', + 'test_doc.py', + 'test_dviread.py', + 'test_figure.py', + 'test_font_manager.py', + 'test_fontconfig_pattern.py', + 'test_ft2font.py', + 'test_getattr.py', + 'test_gridspec.py', + 'test_image.py', + 'test_legend.py', + 'test_lines.py', + 'test_marker.py', + 'test_mathtext.py', + 'test_matplotlib.py', + 'test_multivariate_colormaps.py', + 'test_mlab.py', + 'test_offsetbox.py', + 'test_patches.py', + 'test_path.py', + 'test_patheffects.py', + 'test_pickle.py', + 'test_png.py', + 'test_polar.py', + 'test_preprocess_data.py', + 'test_pyplot.py', + 'test_quiver.py', + 'test_rcparams.py', + 'test_sankey.py', + 'test_scale.py', + 'test_simplification.py', + 'test_skew.py', + 'test_sphinxext.py', + 'test_spines.py', + 'test_streamplot.py', + 'test_style.py', + 'test_subplots.py', + 'test_table.py', + 'test_testing.py', + 'test_texmanager.py', + 'test_text.py', + 'test_textpath.py', + 'test_ticker.py', + 'test_tightlayout.py', + 'test_transforms.py', + 'test_triangulation.py', + 'test_type1font.py', + 'test_units.py', + 'test_usetex.py', + 'test_widgets.py', +] + +py3.install_sources(python_sources, + subdir: 'matplotlib/tests') + +install_data( + 'README', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests/')) + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests')) +install_subdir( + 'data', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests/')) diff --git a/lib/matplotlib/tests/test_afm.py b/lib/matplotlib/tests/test_afm.py index 2bbbe1f454a5..80cf8ac60feb 100644 --- a/lib/matplotlib/tests/test_afm.py +++ b/lib/matplotlib/tests/test_afm.py @@ -2,7 +2,7 @@ import pytest import logging -from matplotlib import afm +from matplotlib import _afm from matplotlib import font_manager as fm @@ -39,13 +39,13 @@ def test_nonascii_str(): inp_str = "привет" byte_str = inp_str.encode("utf8") - ret = afm._to_str(byte_str) + ret = _afm._to_str(byte_str) assert ret == inp_str def test_parse_header(): fh = BytesIO(AFM_TEST_DATA) - header = afm._parse_header(fh) + header = _afm._parse_header(fh) assert header == { b'StartFontMetrics': 2.0, b'FontName': 'MyFont-Bold', @@ -66,8 +66,8 @@ def test_parse_header(): def test_parse_char_metrics(): fh = BytesIO(AFM_TEST_DATA) - afm._parse_header(fh) # position - metrics = afm._parse_char_metrics(fh) + _afm._parse_header(fh) # position + metrics = _afm._parse_char_metrics(fh) assert metrics == ( {0: (250.0, 'space', [0, 0, 0, 0]), 42: (1141.0, 'foo', [40, 60, 800, 360]), @@ -81,13 +81,13 @@ def test_parse_char_metrics(): def test_get_familyname_guessed(): fh = BytesIO(AFM_TEST_DATA) - font = afm.AFM(fh) + font = _afm.AFM(fh) del font._header[b'FamilyName'] # remove FamilyName, so we have to guess assert font.get_familyname() == 'My Font' def test_font_manager_weight_normalization(): - font = afm.AFM(BytesIO( + font = _afm.AFM(BytesIO( AFM_TEST_DATA.replace(b"Weight Bold\n", b"Weight Custom\n"))) assert fm.afmFontProperty("", font).weight == "normal" @@ -107,7 +107,7 @@ def test_font_manager_weight_normalization(): def test_bad_afm(afm_data): fh = BytesIO(afm_data) with pytest.raises(RuntimeError): - header = afm._parse_header(fh) + _afm._parse_header(fh) @pytest.mark.parametrize( @@ -132,6 +132,14 @@ def test_bad_afm(afm_data): def test_malformed_header(afm_data, caplog): fh = BytesIO(afm_data) with caplog.at_level(logging.ERROR): - header = afm._parse_header(fh) + _afm._parse_header(fh) assert len(caplog.records) == 1 + + +def test_afm_kerning(): + fn = fm.findfont("Helvetica", fontext="afm") + with open(fn, 'rb') as fh: + afm = _afm.AFM(fh) + assert afm.get_kern_dist_from_name('A', 'V') == -70.0 + assert afm.get_kern_dist_from_name('V', 'A') == -80.0 diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index b425887e91b0..56b26904d041 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -2,15 +2,19 @@ import numpy as np from numpy.testing import assert_array_almost_equal -from PIL import Image, TiffTags +from PIL import features, Image, TiffTags import pytest from matplotlib import ( - collections, path, pyplot as plt, transforms as mtransforms, rcParams) -from matplotlib.image import imread + collections, patheffects, pyplot as plt, transforms as mtransforms, + rcParams, rc_context) +from matplotlib.backends.backend_agg import RendererAgg from matplotlib.figure import Figure +from matplotlib.image import imread +from matplotlib.path import Path from matplotlib.testing.decorators import image_comparison +from matplotlib.transforms import IdentityTransform def test_repeated_save_with_alpha(): @@ -52,7 +56,7 @@ def test_large_single_path_collection(): # applied. f, ax = plt.subplots() collection = collections.PathCollection( - [path.Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])]) + [Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])]) ax.add_artist(collection) ax.set_xlim(10**-3, 1) plt.savefig(buff) @@ -72,10 +76,10 @@ def test_marker_with_nan(): def test_long_path(): buff = io.BytesIO() - - fig, ax = plt.subplots() - np.random.seed(0) - points = np.random.rand(70000) + fig = Figure() + ax = fig.subplots() + points = np.ones(100_000) + points[::2] *= -1 ax.plot(points) fig.savefig(buff, format='png') @@ -83,7 +87,7 @@ def test_long_path(): @image_comparison(['agg_filter.png'], remove_text=True) def test_agg_filter(): def smooth1d(x, window_len): - # copied from http://www.scipy.org/Cookbook/SignalSmooth + # copied from https://scipy-cookbook.readthedocs.io/ s = np.r_[ 2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]] w = np.hanning(window_len) @@ -161,30 +165,29 @@ def process_image(self, padded_src, dpi): fig, ax = plt.subplots() # draw lines - l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", - mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1") - l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-", - mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1") + line1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", + mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1") + line2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-", + mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1") gauss = DropShadowFilter(4) - for l in [l1, l2]: + for line in [line1, line2]: # draw shadows with same lines with slight offset. - xx = l.get_xdata() - yy = l.get_ydata() + xx = line.get_xdata() + yy = line.get_ydata() shadow, = ax.plot(xx, yy) - shadow.update_from(l) + shadow.update_from(line) # offset transform - ot = mtransforms.offset_copy(l.get_transform(), ax.figure, - x=4.0, y=-6.0, units='points') - - shadow.set_transform(ot) + transform = mtransforms.offset_copy( + line.get_transform(), fig, x=4.0, y=-6.0, units='points') + shadow.set_transform(transform) # adjust zorder of the shadow lines so that it is drawn below the # original lines - shadow.set_zorder(l.get_zorder() - 0.5) + shadow.set_zorder(line.get_zorder() - 0.5) shadow.set_agg_filter(gauss) shadow.set_rasterized(True) # to support mixed-mode renderers @@ -196,7 +199,7 @@ def process_image(self, padded_src, dpi): def test_too_large_image(): - fig = plt.figure(figsize=(300, 1000)) + fig = plt.figure(figsize=(300, 2**25)) buff = io.BytesIO() with pytest.raises(ValueError): fig.savefig(buff) @@ -244,3 +247,112 @@ def test_pil_kwargs_tiff(): im = Image.open(buf) tags = {TiffTags.TAGS_V2[k].name: v for k, v in im.tag_v2.items()} assert tags["ImageDescription"] == "test image" + + +@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available") +def test_pil_kwargs_webp(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf_small = io.BytesIO() + pil_kwargs_low = {"quality": 1} + plt.savefig(buf_small, format="webp", pil_kwargs=pil_kwargs_low) + assert len(pil_kwargs_low) == 1 + buf_large = io.BytesIO() + pil_kwargs_high = {"quality": 100} + plt.savefig(buf_large, format="webp", pil_kwargs=pil_kwargs_high) + assert len(pil_kwargs_high) == 1 + assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes + + +def test_gif_no_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="gif", transparent=False) + im = Image.open(buf) + assert im.mode == "P" + assert im.info["transparency"] >= len(im.palette.colors) + + +def test_gif_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="gif", transparent=True) + im = Image.open(buf) + assert im.mode == "P" + assert im.info["transparency"] < len(im.palette.colors) + + +@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available") +def test_webp_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="webp", transparent=True) + im = Image.open(buf) + assert im.mode == "RGBA" + + +def test_draw_path_collection_error_handling(): + fig, ax = plt.subplots() + ax.scatter([1], [1]).set_paths(Path([(0, 1), (2, 3)])) + with pytest.raises(TypeError): + fig.canvas.draw() + + +def test_chunksize_fails(): + # NOTE: This test covers multiple independent test scenarios in a single + # function, because each scenario uses ~2GB of memory and we don't + # want parallel test executors to accidentally run multiple of these + # at the same time. + + N = 100_000 + dpi = 500 + w = 5*dpi + h = 6*dpi + + # make a Path that spans the whole w-h rectangle + x = np.linspace(0, w, N) + y = np.ones(N) * h + y[::2] = 0 + path = Path(np.vstack((x, y)).T) + # effectively disable path simplification (but leaving it "on") + path.simplify_threshold = 0 + + # setup the minimal GraphicsContext to draw a Path + ra = RendererAgg(w, h, dpi) + gc = ra.new_gc() + gc.set_linewidth(1) + gc.set_foreground('r') + + gc.set_hatch('/') + with pytest.raises(OverflowError, match='cannot split hatched path'): + ra.draw_path(gc, path, IdentityTransform()) + gc.set_hatch(None) + + with pytest.raises(OverflowError, match='cannot split filled path'): + ra.draw_path(gc, path, IdentityTransform(), (1, 0, 0)) + + # Set to zero to disable, currently defaults to 0, but let's be sure. + with rc_context({'agg.path.chunksize': 0}): + with pytest.raises(OverflowError, match='Please set'): + ra.draw_path(gc, path, IdentityTransform()) + + # Set big enough that we do not try to chunk. + with rc_context({'agg.path.chunksize': 1_000_000}): + with pytest.raises(OverflowError, match='Please reduce'): + ra.draw_path(gc, path, IdentityTransform()) + + # Small enough we will try to chunk, but big enough we will fail to render. + with rc_context({'agg.path.chunksize': 90_000}): + with pytest.raises(OverflowError, match='Please reduce'): + ra.draw_path(gc, path, IdentityTransform()) + + path.should_simplify = False + with pytest.raises(OverflowError, match="should_simplify is False"): + ra.draw_path(gc, path, IdentityTransform()) + + +def test_non_tuple_rgbaface(): + # This passes rgbaFace as a ndarray to draw_path. + fig = plt.figure() + fig.add_subplot(projection="3d").scatter( + [0, 1, 2], [0, 1, 2], path_effects=[patheffects.Stroke(linewidth=4)]) + fig.canvas.draw() diff --git a/lib/matplotlib/tests/test_agg_filter.py b/lib/matplotlib/tests/test_agg_filter.py index fd54a6b4aa9e..545e62d20d7c 100644 --- a/lib/matplotlib/tests/test_agg_filter.py +++ b/lib/matplotlib/tests/test_agg_filter.py @@ -5,7 +5,7 @@ @image_comparison(baseline_images=['agg_filter_alpha'], - extensions=['png', 'pdf']) + extensions=['gif', 'png', 'pdf']) def test_agg_filter_alpha(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -23,7 +23,7 @@ def manual_alpha(im, dpi): # Note: Doing alpha like this is not the same as setting alpha on # the mesh itself. Currently meshes are drawn as independent patches, # and we see fine borders around the blocks of color. See the SO - # question for an example: https://stackoverflow.com/questions/20678817 + # question for an example: https://stackoverflow.com/q/20678817/ mesh.set_agg_filter(manual_alpha) # Currently we must enable rasterization for this to have an effect in diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index de46a79d4621..114e38996a10 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -1,5 +1,8 @@ import os from pathlib import Path +import platform +import re +import shutil import subprocess import sys import weakref @@ -10,6 +13,35 @@ import matplotlib as mpl from matplotlib import pyplot as plt from matplotlib import animation +from matplotlib.animation import PillowWriter +from matplotlib.testing.decorators import check_figures_equal + + +@pytest.fixture() +def anim(request): + """Create a simple animation (with options).""" + fig, ax = plt.subplots() + line, = ax.plot([], []) + + ax.set_xlim(0, 10) + ax.set_ylim(-1, 1) + + def init(): + line.set_data([], []) + return line, + + def animate(i): + x = np.linspace(0, 10, 100) + y = np.sin(x + i) + line.set_data(x, y) + return line, + + # "klass" can be passed to determine the class returned by the fixture + kwargs = dict(getattr(request, 'param', {})) # make a copy + klass = kwargs.pop('klass', animation.FuncAnimation) + if 'frames' not in kwargs: + kwargs['frames'] = 5 + return klass(fig=fig, func=animate, init_func=init, **kwargs) class NullMovieWriter(animation.AbstractMovieWriter): @@ -32,6 +64,8 @@ def setup(self, fig, outfile, dpi, *args): self._count = 0 def grab_frame(self, **savefig_kwargs): + from matplotlib.animation import _validate_grabframe_kwargs + _validate_grabframe_kwargs(savefig_kwargs) self.savefig_kwargs = savefig_kwargs self._count += 1 @@ -39,26 +73,9 @@ def finish(self): pass -def make_animation(**kwargs): - fig, ax = plt.subplots() - line, = ax.plot([]) - - def init(): - pass - - def animate(i): - line.set_data([0, 1], [0, i]) - return line, - - return animation.FuncAnimation(fig, animate, **kwargs) - - -def test_null_movie_writer(): +def test_null_movie_writer(anim): # Test running an animation with NullMovieWriter. - - num_frames = 5 - anim = make_animation(frames=num_frames) - + plt.rcParams["savefig.facecolor"] = "auto" filename = "unused.null" dpi = 50 savefig_kwargs = dict(foo=0) @@ -67,12 +84,28 @@ def test_null_movie_writer(): anim.save(filename, dpi=dpi, writer=writer, savefig_kwargs=savefig_kwargs) - assert writer.fig == plt.figure(1) # The figure used by make_animation. + assert writer.fig == plt.figure(1) # The figure used by anim fixture assert writer.outfile == filename assert writer.dpi == dpi assert writer.args == () - assert writer.savefig_kwargs == savefig_kwargs - assert writer._count == num_frames + # we enrich the savefig kwargs to ensure we composite transparent + # output to an opaque background + for k, v in savefig_kwargs.items(): + assert writer.savefig_kwargs[k] == v + assert writer._count == anim._save_count + + +@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) +def test_animation_delete(anim): + if platform.python_implementation() == 'PyPy': + # Something in the test setup fixture lingers around into the test and + # breaks pytest.warns on PyPy. This garbage collection fixes it. + # https://foss.heptapod.net/pypy/pypy/-/issues/3536 + np.testing.break_cycles() + anim = animation.FuncAnimation(**anim) + with pytest.warns(Warning, match='Animation was deleted'): + del anim + np.testing.break_cycles() def test_movie_writer_dpi_default(): @@ -115,30 +148,59 @@ def isAvailable(cls): WRITER_OUTPUT = [ ('ffmpeg', 'movie.mp4'), ('ffmpeg_file', 'movie.mp4'), - ('avconv', 'movie.mp4'), - ('avconv_file', 'movie.mp4'), ('imagemagick', 'movie.gif'), ('imagemagick_file', 'movie.gif'), ('pillow', 'movie.gif'), ('html', 'movie.html'), ('null', 'movie.null') ] -WRITER_OUTPUT += [ - (writer, Path(output)) for writer, output in WRITER_OUTPUT] + + +def gen_writers(): + for writer, output in WRITER_OUTPUT: + if not animation.writers.is_available(writer): + mark = pytest.mark.skip(f"writer '{writer}' not available on this system") + yield pytest.param(writer, None, output, marks=[mark]) + yield pytest.param(writer, None, Path(output), marks=[mark]) + continue + + writer_class = animation.writers[writer] + for frame_format in getattr(writer_class, 'supported_formats', [None]): + yield writer, frame_format, output + yield writer, frame_format, Path(output) # Smoke test for saving animations. In the future, we should probably # design more sophisticated tests which compare resulting frames a-la # matplotlib.testing.image_comparison -@pytest.mark.parametrize('writer, output', WRITER_OUTPUT) -def test_save_animation_smoketest(tmpdir, writer, output): - if not animation.writers.is_available(writer): - pytest.skip("writer '%s' not available on this system" % writer) - fig, ax = plt.subplots() - line, = ax.plot([], []) +@pytest.mark.parametrize('writer, frame_format, output', gen_writers()) +@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) +def test_save_animation_smoketest(tmp_path, writer, frame_format, output, anim): + if frame_format is not None: + plt.rcParams["animation.frame_format"] = frame_format + anim = animation.FuncAnimation(**anim) + dpi = None + codec = None + if writer == 'ffmpeg': + # Issue #8253 + anim._fig.set_size_inches((10.85, 9.21)) + dpi = 100. + codec = 'h264' - ax.set_xlim(0, 10) - ax.set_ylim(-1, 1) + anim.save(tmp_path / output, fps=30, writer=writer, bitrate=500, dpi=dpi, + codec=codec) + + del anim + + +@pytest.mark.parametrize('writer, frame_format, output', gen_writers()) +def test_grabframe(tmp_path, writer, frame_format, output): + WriterClass = animation.writers[writer] + + if frame_format is not None: + plt.rcParams["animation.frame_format"] = frame_format + + fig, ax = plt.subplots() dpi = None codec = None @@ -148,27 +210,65 @@ def test_save_animation_smoketest(tmpdir, writer, output): dpi = 100. codec = 'h264' - def init(): - line.set_data([], []) - return line, - - def animate(i): - x = np.linspace(0, 10, 100) - y = np.sin(x + i) - line.set_data(x, y) - return line, - - # Use temporary directory for the file-based writers, which produce a file - # per frame with known names. - with tmpdir.as_cwd(): - anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) - anim.save(output, fps=30, writer=writer, bitrate=500, dpi=dpi, - codec=codec) + test_writer = WriterClass() + with test_writer.saving(fig, tmp_path / output, dpi): + # smoke test it works + test_writer.grab_frame() + for k in {'dpi', 'bbox_inches', 'format'}: + with pytest.raises( + TypeError, + match=f"grab_frame got an unexpected keyword argument {k!r}"): + test_writer.grab_frame(**{k: object()}) + + +@pytest.mark.parametrize('writer', [ + pytest.param( + 'ffmpeg', marks=pytest.mark.skipif( + not animation.FFMpegWriter.isAvailable(), + reason='Requires FFMpeg')), + pytest.param( + 'imagemagick', marks=pytest.mark.skipif( + not animation.ImageMagickWriter.isAvailable(), + reason='Requires ImageMagick')), +]) +@pytest.mark.parametrize('html, want', [ + ('none', None), + ('html5', '. The ' + 'SIL License is a free and open source license specifically designed for ' + 'fonts and related software. The basic terms are that the recipient will ' + 'not remove the copyright and trademark statements from the fonts and ' + 'that, if the person decides to create a derivative work based on the STIX ' + 'Fonts but incorporating some changes or enhancements, the derivative work ' + '("Modified Version") will carry a different name. The copyright and ' + 'trademark restrictions are part of the agreement between the STI Pub ' + 'companies and the typeface designer. The "renaming" restriction results ' + 'from the desire of the STI Pub companies to assure that the STIX Fonts ' + 'will continue to function in a predictable fashion for all that use them. ' + 'No copy of one or more of the individual Font typefaces that form the ' + 'STIX Fonts(TM) set may be sold by itself, but other than this one ' + 'restriction, licensees are free to sell the fonts either separately or as ' + 'part of a package that combines other software or fonts with this font ' + 'set.', + 14: 'http://www.stixfonts.org/user_license.html', + }, +} + + +@pytest.mark.parametrize('font_name, expected', _expected_sfnt_names.items(), + ids=_expected_sfnt_names.keys()) +def test_ft2font_get_sfnt(font_name, expected): + file = fm.findfont(font_name) + font = ft2font.FT2Font(file) + sfnt = font.get_sfnt() + for name, value in expected.items(): + # Macintosh, Unicode 1.0, English, name. + assert sfnt.pop((1, 0, 0, name)) == value.encode('ascii') + # Microsoft, Unicode, English United States, name. + assert sfnt.pop((3, 1, 1033, name)) == value.encode('utf-16be') + assert sfnt == {} + + +_expected_sfnt_tables = { + 'DejaVu Sans': { + 'invalid': None, + 'head': { + 'version': (1, 0), + 'fontRevision': (2, 22937), + 'checkSumAdjustment': -175678572, + 'magicNumber': 0x5F0F3CF5, + 'flags': 31, + 'unitsPerEm': 2048, + 'created': (0, 3514699492), 'modified': (0, 3514699492), + 'xMin': -2090, 'yMin': -948, 'xMax': 3673, 'yMax': 2524, + 'macStyle': 0, + 'lowestRecPPEM': 8, + 'fontDirectionHint': 0, + 'indexToLocFormat': 1, + 'glyphDataFormat': 0, + }, + 'maxp': { + 'version': (1, 0), + 'numGlyphs': 6241, + 'maxPoints': 852, 'maxComponentPoints': 104, 'maxTwilightPoints': 16, + 'maxContours': 43, 'maxComponentContours': 12, + 'maxZones': 2, + 'maxStorage': 153, + 'maxFunctionDefs': 64, + 'maxInstructionDefs': 0, + 'maxStackElements': 1045, + 'maxSizeOfInstructions': 534, + 'maxComponentElements': 8, + 'maxComponentDepth': 4, + }, + 'OS/2': { + 'version': 1, + 'xAvgCharWidth': 1038, + 'usWeightClass': 400, 'usWidthClass': 5, + 'fsType': 0, + 'ySubscriptXSize': 1331, 'ySubscriptYSize': 1433, + 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 286, + 'ySuperscriptXSize': 1331, 'ySuperscriptYSize': 1433, + 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 983, + 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530, + 'sFamilyClass': 0, + 'panose': b'\x02\x0b\x06\x03\x03\x08\x04\x02\x02\x04', + 'ulCharRange': (3875565311, 3523280383, 170156073, 67117068), + 'achVendID': b'PfEd', + 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 65535, + }, + 'hhea': { + 'version': (1, 0), + 'ascent': 1901, 'descent': -483, 'lineGap': 0, + 'advanceWidthMax': 3838, + 'minLeftBearing': -2090, 'minRightBearing': -1455, + 'xMaxExtent': 3673, + 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0, + 'metricDataFormat': 0, 'numOfLongHorMetrics': 6226, + }, + 'vhea': None, + 'post': { + 'format': (2, 0), + 'isFixedPitch': 0, 'italicAngle': (0, 0), + 'underlinePosition': -130, 'underlineThickness': 90, + 'minMemType42': 0, 'maxMemType42': 0, + 'minMemType1': 0, 'maxMemType1': 0, + }, + 'pclt': None, + }, + 'cmtt10': { + 'invalid': None, + 'head': { + 'version': (1, 0), + 'fontRevision': (1, 0), + 'checkSumAdjustment': 555110277, + 'magicNumber': 0x5F0F3CF5, + 'flags': 3, + 'unitsPerEm': 2048, + 'created': (0, 0), 'modified': (0, 0), + 'xMin': -12, 'yMin': -477, 'xMax': 1280, 'yMax': 1430, + 'macStyle': 0, + 'lowestRecPPEM': 6, + 'fontDirectionHint': 2, + 'indexToLocFormat': 1, + 'glyphDataFormat': 0, + }, + 'maxp': { + 'version': (1, 0), + 'numGlyphs': 133, + 'maxPoints': 94, 'maxComponentPoints': 0, 'maxTwilightPoints': 12, + 'maxContours': 5, 'maxComponentContours': 0, + 'maxZones': 2, + 'maxStorage': 6, + 'maxFunctionDefs': 64, + 'maxInstructionDefs': 0, + 'maxStackElements': 200, + 'maxSizeOfInstructions': 100, + 'maxComponentElements': 4, + 'maxComponentDepth': 1, + }, + 'OS/2': { + 'version': 0, + 'xAvgCharWidth': 1075, + 'usWeightClass': 400, 'usWidthClass': 5, + 'fsType': 0, + 'ySubscriptXSize': 410, 'ySubscriptYSize': 369, + 'ySubscriptXOffset': 0, 'ySubscriptYOffset': -469, + 'ySuperscriptXSize': 410, 'ySuperscriptYSize': 369, + 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 1090, + 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530, + 'sFamilyClass': 0, + 'panose': b'\x02\x0b\x05\x00\x00\x00\x00\x00\x00\x00', + 'ulCharRange': (0, 0, 0, 0), + 'achVendID': b'\x00\x00\x00\x00', + 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 9835, + }, + 'hhea': { + 'version': (1, 0), + 'ascent': 1276, 'descent': -489, 'lineGap': 0, + 'advanceWidthMax': 1536, + 'minLeftBearing': -12, 'minRightBearing': -29, + 'xMaxExtent': 1280, + 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0, + 'metricDataFormat': 0, 'numOfLongHorMetrics': 133, + }, + 'vhea': None, + 'post': { + 'format': (2, 0), + 'isFixedPitch': 0, 'italicAngle': (0, 0), + 'underlinePosition': -133, 'underlineThickness': 20, + 'minMemType42': 0, 'maxMemType42': 0, + 'minMemType1': 0, 'maxMemType1': 0, + }, + 'pclt': { + 'version': (1, 0), + 'fontNumber': 2147483648, + 'pitch': 1075, + 'xHeight': 905, + 'style': 0, + 'typeFamily': 0, + 'capHeight': 1276, + 'symbolSet': 0, + 'typeFace': b'cmtt10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'characterComplement': b'\xff\xff\xff\xff7\xff\xff\xfe', + 'strokeWeight': 0, + 'widthType': -5, + 'serifStyle': 64, + }, + }, + 'STIXSizeTwoSym:bold': { + 'invalid': None, + 'head': { + 'version': (1, 0), + 'fontRevision': (1, 0), + 'checkSumAdjustment': 1803408080, + 'magicNumber': 0x5F0F3CF5, + 'flags': 11, + 'unitsPerEm': 1000, + 'created': (0, 3359035786), 'modified': (0, 3359035786), + 'xMin': 4, 'yMin': -355, 'xMax': 1185, 'yMax': 2095, + 'macStyle': 1, + 'lowestRecPPEM': 8, + 'fontDirectionHint': 2, + 'indexToLocFormat': 0, + 'glyphDataFormat': 0, + }, + 'maxp': { + 'version': (1, 0), + 'numGlyphs': 20, + 'maxPoints': 37, 'maxComponentPoints': 0, 'maxTwilightPoints': 0, + 'maxContours': 1, 'maxComponentContours': 0, + 'maxZones': 2, + 'maxStorage': 1, + 'maxFunctionDefs': 64, + 'maxInstructionDefs': 0, + 'maxStackElements': 64, + 'maxSizeOfInstructions': 0, + 'maxComponentElements': 0, + 'maxComponentDepth': 0, + }, + 'OS/2': { + 'version': 2, + 'xAvgCharWidth': 598, + 'usWeightClass': 700, 'usWidthClass': 5, + 'fsType': 0, + 'ySubscriptXSize': 500, 'ySubscriptYSize': 500, + 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 250, + 'ySuperscriptXSize': 500, 'ySuperscriptYSize': 500, + 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 500, + 'yStrikeoutSize': 20, 'yStrikeoutPosition': 1037, + 'sFamilyClass': 0, + 'panose': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'ulCharRange': (3, 192, 0, 0), + 'achVendID': b'STIX', + 'fsSelection': 32, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 10217, + }, + 'hhea': { + 'version': (1, 0), + 'ascent': 2095, 'descent': -404, 'lineGap': 0, + 'advanceWidthMax': 1130, + 'minLeftBearing': 0, 'minRightBearing': -55, + 'xMaxExtent': 1185, + 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0, + 'metricDataFormat': 0, 'numOfLongHorMetrics': 19, + }, + 'vhea': None, + 'post': { + 'format': (2, 0), + 'isFixedPitch': 0, 'italicAngle': (0, 0), + 'underlinePosition': -123, 'underlineThickness': 20, + 'minMemType42': 0, 'maxMemType42': 0, + 'minMemType1': 0, 'maxMemType1': 0, + }, + 'pclt': None, + }, +} + + +@pytest.mark.parametrize('font_name', _expected_sfnt_tables.keys()) +@pytest.mark.parametrize('header', _expected_sfnt_tables['DejaVu Sans'].keys()) +def test_ft2font_get_sfnt_table(font_name, header): + file = fm.findfont(font_name) + font = ft2font.FT2Font(file) + assert font.get_sfnt_table(header) == _expected_sfnt_tables[font_name][header] + + +@pytest.mark.parametrize('left, right, unscaled, unfitted, default', [ + # These are all the same class. + ('A', 'A', 57, 248, 256), ('A', 'À', 57, 248, 256), ('A', 'Ã', 57, 248, 256), + ('A', 'Â', 57, 248, 256), ('A', 'Ã', 57, 248, 256), ('A', 'Ä', 57, 248, 256), + # And a few other random ones. + ('D', 'A', -36, -156, -128), ('T', '.', -243, -1056, -1024), + ('X', 'C', -149, -647, -640), ('-', 'J', 114, 495, 512), +]) +def test_ft2font_get_kerning(left, right, unscaled, unfitted, default): + file = fm.findfont('DejaVu Sans') + # With unscaled, these settings should produce exact values found in FontForge. + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font.set_size(100, 100) + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + ft2font.Kerning.UNSCALED) == unscaled + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + ft2font.Kerning.UNFITTED) == unfitted + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + ft2font.Kerning.DEFAULT) == default + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning.UNSCALED instead'): + k = ft2font.KERNING_UNSCALED + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning enum values instead'): + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + int(k)) == unscaled + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning.UNFITTED instead'): + k = ft2font.KERNING_UNFITTED + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning enum values instead'): + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + int(k)) == unfitted + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning.DEFAULT instead'): + k = ft2font.KERNING_DEFAULT + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='Use Kerning enum values instead'): + assert font.get_kerning(font.get_char_index(ord(left)), + font.get_char_index(ord(right)), + int(k)) == default + + +def test_ft2font_set_text(): + file = fm.findfont('DejaVu Sans') + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + xys = font.set_text('') + np.testing.assert_array_equal(xys, np.empty((0, 2))) + assert font.get_width_height() == (0, 0) + assert font.get_num_glyphs() == 0 + assert font.get_descent() == 0 + assert font.get_bitmap_offset() == (0, 0) + # This string uses all the kerning pairs defined for test_ft2font_get_kerning. + xys = font.set_text('AADAT.XC-J') + np.testing.assert_array_equal( + xys, + [(0, 0), (512, 0), (1024, 0), (1600, 0), (2112, 0), (2496, 0), (2688, 0), + (3200, 0), (3712, 0), (4032, 0)]) + assert font.get_width_height() == (4288, 768) + assert font.get_num_glyphs() == 10 + assert font.get_descent() == 192 + assert font.get_bitmap_offset() == (6, 0) + + +def test_ft2font_loading(): + file = fm.findfont('DejaVu Sans') + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + for glyph in [font.load_char(ord('M')), + font.load_glyph(font.get_char_index(ord('M')))]: + assert glyph is not None + assert glyph.width == 576 + assert glyph.height == 576 + assert glyph.horiBearingX == 0 + assert glyph.horiBearingY == 576 + assert glyph.horiAdvance == 640 + assert glyph.linearHoriAdvance == 678528 + assert glyph.vertBearingX == -384 + assert glyph.vertBearingY == 64 + assert glyph.vertAdvance == 832 + assert glyph.bbox == (54, 0, 574, 576) + assert font.get_num_glyphs() == 2 # Both count as loaded. + # But neither has been placed anywhere. + assert font.get_width_height() == (0, 0) + assert font.get_descent() == 0 + assert font.get_bitmap_offset() == (0, 0) + + +def test_ft2font_drawing(): + expected_str = ( + ' ', + '11 11 ', + '11 11 ', + '1 1 1 1 ', + '1 1 1 1 ', + '1 1 1 1 ', + '1 11 1 ', + '1 11 1 ', + '1 1 ', + '1 1 ', + ' ', + ) + expected = np.array([ + [int(c) for c in line.replace(' ', '0')] for line in expected_str + ]) + expected *= 255 + file = fm.findfont('DejaVu Sans') + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + font.set_text('M') + font.draw_glyphs_to_bitmap(antialiased=False) + image = font.get_image() + np.testing.assert_array_equal(image, expected) + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + glyph = font.load_char(ord('M')) + image = np.zeros(expected.shape, np.uint8) + font.draw_glyph_to_bitmap(image, -1, 1, glyph, antialiased=False) + np.testing.assert_array_equal(image, expected) + + +def test_ft2font_get_path(): + file = fm.findfont('DejaVu Sans') + font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) + vertices, codes = font.get_path() + assert vertices.shape == (0, 2) + assert codes.shape == (0, ) + font.load_char(ord('M')) + vertices, codes = font.get_path() + expected_vertices = np.array([ + (0.843750, 9.000000), (2.609375, 9.000000), # Top left. + (4.906250, 2.875000), # Top of midpoint. + (7.218750, 9.000000), (8.968750, 9.000000), # Top right. + (8.968750, 0.000000), (7.843750, 0.000000), # Bottom right. + (7.843750, 7.906250), # Point under top right. + (5.531250, 1.734375), (4.296875, 1.734375), # Bar under midpoint. + (1.984375, 7.906250), # Point under top left. + (1.984375, 0.000000), (0.843750, 0.000000), # Bottom left. + (0.843750, 9.000000), # Back to top left corner. + (0.000000, 0.000000), + ]) + np.testing.assert_array_equal(vertices, expected_vertices) + expected_codes = np.full(expected_vertices.shape[0], mpath.Path.LINETO, + dtype=mpath.Path.code_type) + expected_codes[0] = mpath.Path.MOVETO + expected_codes[-1] = mpath.Path.CLOSEPOLY + np.testing.assert_array_equal(codes, expected_codes) + + +@pytest.mark.parametrize('fmt', ['pdf', 'png', 'ps', 'raw', 'svg']) +def test_fallback_smoke(fmt): + fonts, test_str = _gen_multi_font_text() + plt.rcParams['font.size'] = 16 + fig = plt.figure(figsize=(4.75, 1.85)) + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') + + fig.savefig(io.BytesIO(), format=fmt) + + +@pytest.mark.parametrize("font_list", + [['DejaVu Serif', 'DejaVu Sans'], + ['DejaVu Sans Mono']], + ids=["two fonts", "one font"]) +def test_fallback_missing(recwarn, font_list): + fig = plt.figure() + fig.text(.5, .5, "Hello 🙃 World!", family=font_list) + fig.canvas.draw() + assert all(isinstance(warn.message, UserWarning) for warn in recwarn) + # not sure order is guaranteed on the font listing so + assert recwarn[0].message.args[0].startswith( + "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)") + assert all([font in recwarn[0].message.args[0] for font in font_list]) + + +@image_comparison(['last_resort']) +def test_fallback_last_resort(recwarn): + fig = plt.figure(figsize=(3, 0.5)) + fig.text(.5, .5, "Hello 🙃 World!", size=24, + horizontalalignment='center', verticalalignment='center') + fig.canvas.draw() + assert all(isinstance(warn.message, UserWarning) for warn in recwarn) + assert recwarn[0].message.args[0].startswith( + "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)") + + +def test__get_fontmap(): + fonts, test_str = _gen_multi_font_text() + # Add some glyphs that don't exist in either font to check the Last Resort fallback. + missing_glyphs = '\n几个汉字' + test_str += missing_glyphs + + ft = fm.get_font( + fm.fontManager._find_fonts_by_props(fm.FontProperties(family=fonts)) + ) + fontmap = ft._get_fontmap(test_str) + for char, font in fontmap.items(): + if char in missing_glyphs: + assert Path(font.fname).name == 'LastResortHE-Regular.ttf' + elif ord(char) > 127: + assert Path(font.fname).name == 'DejaVuSans.ttf' + else: + assert Path(font.fname).name == 'cmr10.ttf' diff --git a/lib/matplotlib/tests/test_getattr.py b/lib/matplotlib/tests/test_getattr.py new file mode 100644 index 000000000000..f0f5823600ca --- /dev/null +++ b/lib/matplotlib/tests/test_getattr.py @@ -0,0 +1,35 @@ +from importlib import import_module +from pkgutil import walk_packages + +import matplotlib +import pytest + +# Get the names of all matplotlib submodules, +# except for the unit tests and private modules. +module_names = [ + m.name + for m in walk_packages( + path=matplotlib.__path__, prefix=f'{matplotlib.__name__}.' + ) + if not m.name.startswith(__package__) + and not any(x.startswith('_') for x in m.name.split('.')) +] + + +@pytest.mark.parametrize('module_name', module_names) +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +@pytest.mark.filterwarnings('ignore::ImportWarning') +def test_getattr(module_name): + """ + Test that __getattr__ methods raise AttributeError for unknown keys. + See #20822, #20855. + """ + try: + module = import_module(module_name) + except (ImportError, RuntimeError, OSError) as e: + # Skip modules that cannot be imported due to missing dependencies + pytest.skip(f'Cannot import {module_name} due to {e}') + + key = 'THIS_SYMBOL_SHOULD_NOT_EXIST' + if hasattr(module, key): + delattr(module, key) diff --git a/lib/matplotlib/tests/test_gridspec.py b/lib/matplotlib/tests/test_gridspec.py index 5d5d7523a2ed..625a79816ed3 100644 --- a/lib/matplotlib/tests/test_gridspec.py +++ b/lib/matplotlib/tests/test_gridspec.py @@ -1,4 +1,6 @@ +import matplotlib import matplotlib.gridspec as gridspec +import matplotlib.pyplot as plt import pytest @@ -8,6 +10,13 @@ def test_equal(): assert gs[:, 0] == gs[:, 0] +def test_update(): + gs = gridspec.GridSpec(2, 1) + + gs.update(left=.1) + assert gs.left == .1 + + def test_width_ratios(): """ Addresses issue #5835. @@ -26,6 +35,23 @@ def test_height_ratios(): gridspec.GridSpec(1, 1, height_ratios=[2, 1, 3]) +def test_SubplotParams(): + s = gridspec.SubplotParams(.1, .1, .9, .9) + assert s.left == 0.1 + + s.reset() + assert s.left == matplotlib.rcParams['figure.subplot.left'] + + with pytest.raises(ValueError, match='left cannot be >= right'): + s.update(left=s.right + .01) + + with pytest.raises(ValueError, match='bottom cannot be >= top'): + s.update(bottom=s.top + .01) + + with pytest.raises(ValueError, match='left cannot be >= right'): + gridspec.SubplotParams(.1, .1, .09, .9) + + def test_repr(): ss = gridspec.GridSpec(3, 3)[2, 1:3] assert repr(ss) == "GridSpec(3, 3)[2:3, 1:3]" @@ -35,3 +61,15 @@ def test_repr(): width_ratios=(1, 3)) assert repr(ss) == \ "GridSpec(2, 2, height_ratios=(3, 1), width_ratios=(1, 3))" + + +def test_subplotspec_args(): + fig, axs = plt.subplots(1, 2) + # should work: + gs = gridspec.GridSpecFromSubplotSpec(2, 1, + subplot_spec=axs[0].get_subplotspec()) + assert gs.get_topmost_subplotspec() == axs[0].get_subplotspec() + with pytest.raises(TypeError, match="subplot_spec must be type SubplotSpec"): + gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=axs[0]) + with pytest.raises(TypeError, match="subplot_spec must be type SubplotSpec"): + gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=axs) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 1b9dd306a4dc..1c89bc5e7912 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1,5 +1,6 @@ from contextlib import ExitStack from copy import copy +import functools import io import os from pathlib import Path @@ -8,43 +9,21 @@ import urllib.request import numpy as np -from numpy.testing import assert_array_equal +from numpy.testing import assert_allclose, assert_array_equal from PIL import Image +import matplotlib as mpl from matplotlib import ( colors, image as mimage, patches, pyplot as plt, style, rcParams) from matplotlib.image import (AxesImage, BboxImage, FigureImage, NonUniformImage, PcolorImage) from matplotlib.testing.decorators import check_figures_equal, image_comparison -from matplotlib.transforms import Bbox, Affine2D, TransformedBbox +from matplotlib.transforms import Bbox, Affine2D, Transform, TransformedBbox +import matplotlib.ticker as mticker import pytest -@image_comparison(['image_interps'], style='mpl20') -def test_image_interps(): - """Make the basic nearest, bilinear and bicubic interps.""" - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - X = np.arange(100) - X = X.reshape(5, 20) - - fig = plt.figure() - ax1 = fig.add_subplot(311) - ax1.imshow(X, interpolation='nearest') - ax1.set_title('three interpolations') - ax1.set_ylabel('nearest') - - ax2 = fig.add_subplot(312) - ax2.imshow(X, interpolation='bilinear') - ax2.set_ylabel('bilinear') - - ax3 = fig.add_subplot(313) - ax3.imshow(X, interpolation='bicubic') - ax3.set_ylabel('bicubic') - - @image_comparison(['interp_alpha.png'], remove_text=True) def test_alpha_interp(): """Test the interpolation of the alpha channel on RGBA images""" @@ -69,11 +48,9 @@ def test_interp_nearest_vs_none(): rcParams['savefig.dpi'] = 3 X = np.array([[[218, 165, 32], [122, 103, 238]], [[127, 255, 0], [255, 99, 71]]], dtype=np.uint8) - fig = plt.figure() - ax1 = fig.add_subplot(121) + fig, (ax1, ax2) = plt.subplots(1, 2) ax1.imshow(X, interpolation='none') ax1.set_title('interpolation none') - ax2 = fig.add_subplot(122) ax2.imshow(X, interpolation='nearest') ax2.set_title('interpolation nearest') @@ -111,7 +88,7 @@ def test_image_python_io(): (3, 2.9, "hanning"), # <3 upsample. (3, 9.1, "nearest"), # >3 upsample. ]) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_imshow_antialiased(fig_test, fig_ref, img_size, fig_size, interpolation): np.random.seed(19680801) @@ -119,15 +96,15 @@ def test_imshow_antialiased(fig_test, fig_ref, A = np.random.rand(int(dpi * img_size), int(dpi * img_size)) for fig in [fig_test, fig_ref]: fig.set_size_inches(fig_size, fig_size) - axs = fig_test.subplots() - axs.set_position([0, 0, 1, 1]) - axs.imshow(A, interpolation='antialiased') - axs = fig_ref.subplots() - axs.set_position([0, 0, 1, 1]) - axs.imshow(A, interpolation=interpolation) + ax = fig_test.subplots() + ax.set_position([0, 0, 1, 1]) + ax.imshow(A, interpolation='auto') + ax = fig_ref.subplots() + ax.set_position([0, 0, 1, 1]) + ax.imshow(A, interpolation=interpolation) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_imshow_zoom(fig_test, fig_ref): # should be less than 3 upsample, so should be nearest... np.random.seed(19680801) @@ -135,14 +112,14 @@ def test_imshow_zoom(fig_test, fig_ref): A = np.random.rand(int(dpi * 3), int(dpi * 3)) for fig in [fig_test, fig_ref]: fig.set_size_inches(2.9, 2.9) - axs = fig_test.subplots() - axs.imshow(A, interpolation='antialiased') - axs.set_xlim([10, 20]) - axs.set_ylim([10, 20]) - axs = fig_ref.subplots() - axs.imshow(A, interpolation='nearest') - axs.set_xlim([10, 20]) - axs.set_ylim([10, 20]) + ax = fig_test.subplots() + ax.imshow(A, interpolation='auto') + ax.set_xlim([10, 20]) + ax.set_ylim([10, 20]) + ax = fig_ref.subplots() + ax.imshow(A, interpolation='nearest') + ax.set_xlim([10, 20]) + ax.set_ylim([10, 20]) @check_figures_equal() @@ -207,6 +184,36 @@ def test_imsave(fmt): assert_array_equal(arr_dpi1, arr_dpi100) +def test_imsave_python_sequences(): + # Tests saving an image with data passed using Python sequence types + # such as lists or tuples. + + # RGB image: 3 rows × 2 columns, with float values in [0.0, 1.0] + img_data = [ + [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], + [(0.0, 0.0, 1.0), (1.0, 1.0, 0.0)], + [(0.0, 1.0, 1.0), (1.0, 0.0, 1.0)], + ] + + buff = io.BytesIO() + plt.imsave(buff, img_data, format="png") + buff.seek(0) + read_img = plt.imread(buff) + + assert_array_equal( + np.array(img_data), + read_img[:, :, :3] # Drop alpha if present + ) + + +@pytest.mark.parametrize("origin", ["upper", "lower"]) +def test_imsave_rgba_origin(origin): + # test that imsave always passes c-contiguous arrays down to pillow + buf = io.BytesIO() + result = np.zeros((10, 10, 4), dtype='uint8') + mimage.imsave(buf, arr=result, format="png", origin=origin) + + @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"]) def test_imsave_fspath(fmt): plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt) @@ -253,6 +260,7 @@ def test_imsave_pil_kwargs_tiff(): buf = io.BytesIO() pil_kwargs = {"description": "test image"} plt.imsave(buf, [[0, 1], [2, 3]], format="tiff", pil_kwargs=pil_kwargs) + assert len(pil_kwargs) == 1 im = Image.open(buf) tags = {TAGS[k].name: v for k, v in im.tag_v2.items()} assert tags["ImageDescription"] == "test image" @@ -260,19 +268,66 @@ def test_imsave_pil_kwargs_tiff(): @image_comparison(['image_alpha'], remove_text=True) def test_image_alpha(): - plt.figure() - np.random.seed(0) Z = np.random.rand(6, 6) - plt.subplot(131) - plt.imshow(Z, alpha=1.0, interpolation='none') + fig, (ax1, ax2, ax3) = plt.subplots(1, 3) + ax1.imshow(Z, alpha=1.0, interpolation='none') + ax2.imshow(Z, alpha=0.5, interpolation='none') + ax3.imshow(Z, alpha=0.5, interpolation='nearest') + + +@mpl.style.context('mpl20') +@check_figures_equal() +def test_imshow_alpha(fig_test, fig_ref): + np.random.seed(19680801) + + rgbf = np.random.rand(6, 6, 3).astype(np.float32) + rgbu = np.uint8(rgbf * 255) + ((ax0, ax1), (ax2, ax3)) = fig_test.subplots(2, 2) + ax0.imshow(rgbf, alpha=0.5) + ax1.imshow(rgbf, alpha=0.75) + ax2.imshow(rgbu, alpha=127/255) + ax3.imshow(rgbu, alpha=191/255) + + rgbaf = np.concatenate((rgbf, np.ones((6, 6, 1))), axis=2).astype(np.float32) + rgbau = np.concatenate((rgbu, np.full((6, 6, 1), 255, np.uint8)), axis=2) + ((ax0, ax1), (ax2, ax3)) = fig_ref.subplots(2, 2) + rgbaf[:, :, 3] = 0.5 + ax0.imshow(rgbaf) + rgbaf[:, :, 3] = 0.75 + ax1.imshow(rgbaf) + rgbau[:, :, 3] = 127 + ax2.imshow(rgbau) + rgbau[:, :, 3] = 191 + ax3.imshow(rgbau) + + +@pytest.mark.parametrize('n_channels, is_int, alpha_arr, opaque', + [(3, False, False, False), # RGB float + (4, False, False, False), # RGBA float + (4, False, True, False), # RGBA float with alpha array + (4, False, False, True), # RGBA float with solid color + (4, True, False, False)]) # RGBA unint8 +def test_imshow_multi_draw(n_channels, is_int, alpha_arr, opaque): + if is_int: + array = np.random.randint(0, 256, (2, 2, n_channels)) + else: + array = np.random.random((2, 2, n_channels)) + if opaque: + array[:, :, 3] = 1 - plt.subplot(132) - plt.imshow(Z, alpha=0.5, interpolation='none') + if alpha_arr: + alpha = np.array([[0.3, 0.5], [1, 0.8]]) + else: + alpha = None + + fig, ax = plt.subplots() + im = ax.imshow(array, alpha=alpha) + fig.draw_without_rendering() - plt.subplot(133) - plt.imshow(Z, alpha=0.5, interpolation='nearest') + # Draw should not modify original array + np.testing.assert_array_equal(array, im._A) def test_cursor_data(): @@ -297,11 +352,11 @@ def test_cursor_data(): # Hmm, something is wrong here... I get 0, not None... # But, this works further down in the tests with extents flipped - #x, y = 0.1, -0.1 - #xdisp, ydisp = ax.transData.transform([x, y]) - #event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) - #z = im.get_cursor_data(event) - #assert z is None, "Did not get None, got %d" % z + # x, y = 0.1, -0.1 + # xdisp, ydisp = ax.transData.transform([x, y]) + # event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + # z = im.get_cursor_data(event) + # assert z is None, "Did not get None, got %d" % z ax.clear() # Now try with the extents flipped. @@ -336,13 +391,58 @@ def test_cursor_data(): event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) is None + # Now try with additional transform applied to the image artist + trans = Affine2D().scale(2).rotate(0.5) + im = ax.imshow(np.arange(100).reshape(10, 10), + transform=trans + ax.transData) + x, y = 3, 10 + xdisp, ydisp = ax.transData.transform([x, y]) + event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + assert im.get_cursor_data(event) == 44 + + +@pytest.mark.parametrize("xy, data", [ + # x/y coords chosen to be 0.5 above boundaries so they lie within image pixels + [[0.5, 0.5], 0 + 0], + [[0.5, 1.5], 0 + 1], + [[4.5, 0.5], 16 + 0], + [[8.5, 0.5], 16 + 0], + [[9.5, 2.5], 81 + 4], + [[-1, 0.5], None], + [[0.5, -1], None], + ] +) +def test_cursor_data_nonuniform(xy, data): + from matplotlib.backend_bases import MouseEvent + + # Non-linear set of x-values + x = np.array([0, 1, 4, 9, 16]) + y = np.array([0, 1, 2, 3, 4]) + z = x[np.newaxis, :]**2 + y[:, np.newaxis]**2 + + fig, ax = plt.subplots() + im = NonUniformImage(ax, extent=(x.min(), x.max(), y.min(), y.max())) + im.set_data(x, y, z) + ax.add_image(im) + # Set lower min lim so we can test cursor outside image + ax.set_xlim(x.min() - 2, x.max()) + ax.set_ylim(y.min() - 2, y.max()) + + xdisp, ydisp = ax.transData.transform(xy) + event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + assert im.get_cursor_data(event) == data, (im.get_cursor_data(event), data) + @pytest.mark.parametrize( - "data, text_without_colorbar, text_with_colorbar", [ - ([[10001, 10000]], "[1e+04]", "[10001]"), - ([[.123, .987]], "[0.123]", "[0.123]"), -]) -def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar): + "data, text", [ + ([[10001, 10000]], "[10001.000]"), + ([[.123, .987]], "[0.123]"), + ([[np.nan, 1, 2]], "[]"), + ([[1, 1+1e-15]], "[1.0000000000000000]"), + ([[-1, -1]], "[-1.0]"), + ([[0, 0]], "[0.00]"), + ]) +def test_format_cursor_data(data, text): from matplotlib.backend_bases import MouseEvent fig, ax = plt.subplots() @@ -350,16 +450,7 @@ def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar): xdisp, ydisp = ax.transData.transform([0, 0]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) - assert im.get_cursor_data(event) == data[0][0] - assert im.format_cursor_data(im.get_cursor_data(event)) \ - == text_without_colorbar - - fig.colorbar(im) - fig.canvas.draw() # This is necessary to set up the colorbar formatter. - - assert im.get_cursor_data(event) == data[0][0] - assert im.format_cursor_data(im.get_cursor_data(event)) \ - == text_with_colorbar + assert im.format_cursor_data(im.get_cursor_data(event)) == text @image_comparison(['image_clip'], style='mpl20') @@ -384,16 +475,7 @@ def test_image_cliprect(): im.set_clip_path(rect) -@image_comparison(['imshow'], remove_text=True, style='mpl20') -def test_imshow(): - fig, ax = plt.subplots() - arr = np.arange(100).reshape((10, 10)) - ax.imshow(arr, interpolation="bilinear", extent=(1, 2, 1, 2)) - ax.set_xlim(0, 3) - ax.set_ylim(0, 3) - - -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_imshow_10_10_1(fig_test, fig_ref): # 10x10x1 should be the same as 10x10 arr = np.arange(100).reshape((10, 10, 1)) @@ -481,7 +563,7 @@ def test_image_composite_background(): ax.set_xlim([0, 12]) -@image_comparison(['image_composite_alpha'], remove_text=True) +@image_comparison(['image_composite_alpha'], remove_text=True, tol=0.07) def test_image_composite_alpha(): """ Tests that the alpha value is recognized and correctly applied in the @@ -508,6 +590,18 @@ def test_image_composite_alpha(): ax.set_ylim([5, 0]) +@check_figures_equal(extensions=["pdf"]) +def test_clip_path_disables_compositing(fig_test, fig_ref): + t = np.arange(9).reshape((3, 3)) + for fig in [fig_test, fig_ref]: + ax = fig.add_subplot() + ax.imshow(t, clip_path=(mpl.path.Path([(0, 0), (0, 1), (1, 0)]), + ax.transData)) + ax.imshow(t, clip_path=(mpl.path.Path([(1, 1), (1, 2), (2, 1)]), + ax.transData)) + fig_ref.suppressComposite = True + + @image_comparison(['rasterize_10dpi'], extensions=['pdf', 'svg'], remove_text=True, style='mpl20') def test_rasterize_dpi(): @@ -534,8 +628,7 @@ def test_rasterize_dpi(): for ax in axs: ax.set_xticks([]) ax.set_yticks([]) - for spine in ax.spines.values(): - spine.set_visible(False) + ax.spines[:].set_visible(False) rcParams['savefig.dpi'] = 10 @@ -558,7 +651,7 @@ def test_bbox_image_inverted(): image = np.identity(10) bbox_im = BboxImage(TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]), - ax.figure.transFigure), + ax.get_figure().transFigure), interpolation='nearest') bbox_im.set_data(image) bbox_im.set_clip_on(False) @@ -585,6 +678,20 @@ def test_get_window_extent_for_AxisImage(): assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]]) + fig, ax = plt.subplots(figsize=(10, 10), dpi=100) + ax.set_position([0, 0, 1, 1]) + ax.set_xlim(1, 2) + ax.set_ylim(0, 1) + im_obj = ax.imshow( + im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest', + transform=ax.transAxes) + + fig.canvas.draw() + renderer = fig.canvas.renderer + im_bbox = im_obj.get_window_extent(renderer) + + assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]]) + @image_comparison(['zoom_and_clip_upper_origin.png'], remove_text=True, style='mpl20') @@ -639,7 +746,7 @@ def test_jpeg_alpha(): # If this fails, there will be only one color (all black). If this # is working, we should have all 256 shades of grey represented. num_colors = len(image.getcolors(256)) - assert 175 <= num_colors <= 185 + assert 175 <= num_colors <= 230 # The fully transparent part should be red. corner_pixel = image.getpixel((0, 0)) assert corner_pixel == (254, 0, 0) @@ -717,8 +824,10 @@ def test_load_from_url(): url = ('file:' + ('///' if sys.platform == 'win32' else '') + path.resolve().as_posix()) - plt.imread(url) - plt.imread(urllib.request.urlopen(url)) + with pytest.raises(ValueError, match="Please open the URL"): + plt.imread(url) + with urllib.request.urlopen(url) as file: + plt.imread(file) @image_comparison(['log_scale_image'], remove_text=True) @@ -732,11 +841,7 @@ def test_log_scale_image(): ax.set(yscale='log') -# Increased tolerance is needed for PDF test to avoid failure. After the PDF -# backend was modified to use indexed color, there are ten pixels that differ -# due to how the subpixel calculation is done when converting the PDF files to -# PNG images. -@image_comparison(['rotate_image'], remove_text=True, tol=0.35) +@image_comparison(['rotate_image'], remove_text=True) def test_rotate_image(): delta = 0.25 x = y = np.arange(-3.0, 3.0, delta) @@ -781,10 +886,8 @@ def test_image_preserve_size2(): data = np.identity(n, float) fig = plt.figure(figsize=(n, n), frameon=False) - - ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0]) + ax = fig.add_axes((0.0, 0.0, 1.0, 1.0)) ax.set_axis_off() - fig.add_axes(ax) ax.imshow(data, interpolation='nearest', origin='lower', aspect='auto') buff = io.BytesIO() fig.savefig(buff, dpi=1) @@ -798,10 +901,8 @@ def test_image_preserve_size2(): np.identity(n, bool)[::-1]) -@image_comparison(['mask_image_over_under.png'], remove_text=True) +@image_comparison(['mask_image_over_under.png'], remove_text=True, tol=1.0) def test_mask_image_over_under(): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False delta = 0.025 x = y = np.arange(-3.0, 3.0, delta) @@ -811,10 +912,7 @@ def test_mask_image_over_under(): (2 * np.pi * 0.5 * 1.5)) Z = 10*(Z2 - Z1) # difference of Gaussians - palette = copy(plt.cm.gray) - palette.set_over('r', 1.0) - palette.set_under('g', 1.0) - palette.set_bad('b', 1.0) + palette = plt.colormaps["gray"].with_extremes(over='r', under='g', bad='b') Zm = np.ma.masked_where(Z > 1.2, Z) fig, (ax1, ax2) = plt.subplots(1, 2) im = ax1.imshow(Zm, interpolation='bilinear', @@ -853,6 +951,14 @@ def test_mask_image(): ax2.imshow(A, interpolation='nearest') +def test_mask_image_all(): + # Test behavior with an image that is entirely masked does not warn + data = np.full((2, 2), np.nan) + fig, ax = plt.subplots() + ax.imshow(data) + fig.canvas.draw_idle() # would emit a warning + + @image_comparison(['imshow_endianess.png'], remove_text=True) def test_imshow_endianess(): x = np.arange(10) @@ -872,10 +978,7 @@ def test_imshow_endianess(): remove_text=True, style='mpl20') def test_imshow_masked_interpolation(): - cm = copy(plt.get_cmap('viridis')) - cm.set_over('r') - cm.set_under('b') - cm.set_bad('k') + cmap = mpl.colormaps['viridis'].with_extremes(over='r', under='b', bad='k') N = 20 n = colors.Normalize(vmin=0, vmax=N*N-1) @@ -898,11 +1001,12 @@ def test_imshow_masked_interpolation(): fig, ax_grid = plt.subplots(3, 6) interps = sorted(mimage._interpd_) + interps.remove('auto') interps.remove('antialiased') for interp, ax in zip(interps, ax_grid.ravel()): ax.set_title(interp) - ax.imshow(data, norm=n, cmap=cm, interpolation=interp) + ax.imshow(data, norm=n, cmap=cmap, interpolation=interp) ax.axis('off') @@ -970,13 +1074,20 @@ def test_imshow_bignumbers_real(): def test_empty_imshow(make_norm): fig, ax = plt.subplots() with pytest.warns(UserWarning, - match="Attempting to set identical left == right"): + match="Attempting to set identical low and high xlims"): im = ax.imshow([[]], norm=make_norm()) im.set_extent([-5, 5, -5, 5]) fig.canvas.draw() with pytest.raises(RuntimeError): - im.make_image(fig._cachedRenderer) + im.make_image(fig.canvas.get_renderer()) + + +def test_imshow_float16(): + fig, ax = plt.subplots() + ax.imshow(np.zeros((3, 3), dtype=np.float16)) + # Ensure that drawing doesn't cause crash. + fig.canvas.draw() def test_imshow_float128(): @@ -996,8 +1107,8 @@ def test_imshow_bool(): def test_full_invalid(): fig, ax = plt.subplots() ax.imshow(np.full((10, 10), np.nan)) - with pytest.warns(UserWarning): - fig.canvas.draw() + + fig.canvas.draw() @pytest.mark.parametrize("fmt,counted", @@ -1072,7 +1183,7 @@ def test_image_cursor_formatting(): assert im.format_cursor_data(data) == '[nan]' -@check_figures_equal() +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_image_array_alpha(fig_test, fig_ref): """Per-pixel alpha channel test.""" x = np.linspace(0, 1) @@ -1081,19 +1192,24 @@ def test_image_array_alpha(fig_test, fig_ref): zz = np.exp(- 3 * ((xx - 0.5) ** 2) + (yy - 0.7 ** 2)) alpha = zz / zz.max() - cmap = plt.get_cmap('viridis') - ax = fig_test.add_subplot(111) + cmap = mpl.colormaps['viridis'] + ax = fig_test.add_subplot() ax.imshow(zz, alpha=alpha, cmap=cmap, interpolation='nearest') - ax = fig_ref.add_subplot(111) + ax = fig_ref.add_subplot() rgba = cmap(colors.Normalize()(zz)) rgba[..., -1] = alpha ax.imshow(rgba, interpolation='nearest') -@pytest.mark.style('mpl20') +def test_image_array_alpha_validation(): + with pytest.raises(TypeError, match="alpha must be a float, two-d"): + plt.imshow(np.zeros((2, 2)), alpha=[1, 1]) + + +@mpl.style.context('mpl20') def test_exact_vmin(): - cmap = copy(plt.cm.get_cmap("autumn_r")) + cmap = copy(mpl.colormaps["autumn_r"]) cmap.set_under(color="lightgrey") # make the image exactly 190 pixels wide @@ -1120,10 +1236,19 @@ def test_exact_vmin(): assert np.all(from_image == direct_computation) -@pytest.mark.network -@pytest.mark.flaky -def test_https_imread_smoketest(): - v = mimage.imread('https://matplotlib.org/1.5.0/_static/logo2.png') +@image_comparison(['image_placement'], extensions=['svg', 'pdf'], + remove_text=True, style='mpl20') +def test_image_placement(): + """ + The red box should line up exactly with the outside of the image. + """ + fig, ax = plt.subplots() + ax.plot([0, 0, 1, 1, 0], [0, 1, 1, 0, 0], color='r', lw=0.1) + np.random.seed(19680801) + ax.imshow(np.random.randn(16, 16), cmap='Blues', extent=(0, 1, 0, 1), + interpolation='none', vmin=-1, vmax=1) + ax.set_xlim(-0.1, 1+0.1) + ax.set_ylim(-0.1, 1+0.1) # A basic ndarray subclass that implements a quantity @@ -1141,7 +1266,7 @@ def __array_finalize__(self, obj): def __getitem__(self, item): units = getattr(self, "units", None) - ret = super(QuantityND, self).__getitem__(item) + ret = super().__getitem__(item) if isinstance(ret, QuantityND) or units is not None: ret = QuantityND(ret, units) return ret @@ -1149,7 +1274,7 @@ def __getitem__(self, item): def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): func = getattr(ufunc, method) if "out" in kwargs: - raise NotImplementedError + return NotImplemented if len(inputs) == 1: i0 = inputs[0] unit = getattr(i0, "units", "dimensionless") @@ -1169,11 +1294,16 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): unit = f"{u0}*{u1}" elif ufunc == np.divide: unit = f"{u0}/({u1})" + elif ufunc in (np.greater, np.greater_equal, + np.equal, np.not_equal, + np.less, np.less_equal): + # Comparisons produce unitless booleans for output + unit = None else: - raise NotImplementedError + return NotImplemented out_arr = func(i0.view(np.ndarray), i1.view(np.ndarray), **kwargs) else: - raise NotImplementedError + return NotImplemented if unit is None: out_arr = np.array(out_arr) else: @@ -1204,3 +1334,509 @@ def test_imshow_quantitynd(): ax.imshow(arr) # executing the draw should not raise an exception fig.canvas.draw() + + +@check_figures_equal() +def test_norm_change(fig_test, fig_ref): + # LogNorm should not mask anything invalid permanently. + data = np.full((5, 5), 1, dtype=np.float64) + data[0:2, :] = -1 + + masked_data = np.ma.array(data, mask=False) + masked_data.mask[0:2, 0:2] = True + + cmap = mpl.colormaps['viridis'].with_extremes(under='w') + + ax = fig_test.subplots() + im = ax.imshow(data, norm=colors.LogNorm(vmin=0.5, vmax=1), + extent=(0, 5, 0, 5), interpolation='nearest', cmap=cmap) + im.set_norm(colors.Normalize(vmin=-2, vmax=2)) + im = ax.imshow(masked_data, norm=colors.LogNorm(vmin=0.5, vmax=1), + extent=(5, 10, 5, 10), interpolation='nearest', cmap=cmap) + im.set_norm(colors.Normalize(vmin=-2, vmax=2)) + ax.set(xlim=(0, 10), ylim=(0, 10)) + + ax = fig_ref.subplots() + ax.imshow(data, norm=colors.Normalize(vmin=-2, vmax=2), + extent=(0, 5, 0, 5), interpolation='nearest', cmap=cmap) + ax.imshow(masked_data, norm=colors.Normalize(vmin=-2, vmax=2), + extent=(5, 10, 5, 10), interpolation='nearest', cmap=cmap) + ax.set(xlim=(0, 10), ylim=(0, 10)) + + +@pytest.mark.parametrize('x', [-1, 1]) +@check_figures_equal() +def test_huge_range_log(fig_test, fig_ref, x): + # parametrize over bad lognorm -1 values and large range 1 -> 1e20 + data = np.full((5, 5), x, dtype=np.float64) + data[0:2, :] = 1E20 + + ax = fig_test.subplots() + ax.imshow(data, norm=colors.LogNorm(vmin=1, vmax=data.max()), + interpolation='nearest', cmap='viridis') + + data = np.full((5, 5), x, dtype=np.float64) + data[0:2, :] = 1000 + + ax = fig_ref.subplots() + cmap = mpl.colormaps['viridis'].with_extremes(under='w') + ax.imshow(data, norm=colors.Normalize(vmin=1, vmax=data.max()), + interpolation='nearest', cmap=cmap) + + +@check_figures_equal() +def test_spy_box(fig_test, fig_ref): + # setting up reference and test + ax_test = fig_test.subplots(1, 3) + ax_ref = fig_ref.subplots(1, 3) + + plot_data = ( + [[1, 1], [1, 1]], + [[0, 0], [0, 0]], + [[0, 1], [1, 0]], + ) + plot_titles = ["ones", "zeros", "mixed"] + + for i, (z, title) in enumerate(zip(plot_data, plot_titles)): + ax_test[i].set_title(title) + ax_test[i].spy(z) + ax_ref[i].set_title(title) + ax_ref[i].imshow(z, interpolation='nearest', + aspect='equal', origin='upper', cmap='Greys', + vmin=0, vmax=1) + ax_ref[i].set_xlim(-0.5, 1.5) + ax_ref[i].set_ylim(1.5, -0.5) + ax_ref[i].xaxis.tick_top() + ax_ref[i].title.set_y(1.05) + ax_ref[i].xaxis.set_ticks_position('both') + ax_ref[i].xaxis.set_major_locator( + mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True) + ) + ax_ref[i].yaxis.set_major_locator( + mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True) + ) + + +@image_comparison(["nonuniform_and_pcolor.png"], style="mpl20") +def test_nonuniform_and_pcolor(): + axs = plt.figure(figsize=(3, 3)).subplots(3, sharex=True, sharey=True) + for ax, interpolation in zip(axs, ["nearest", "bilinear"]): + im = NonUniformImage(ax, interpolation=interpolation) + im.set_data(np.arange(3) ** 2, np.arange(3) ** 2, + np.arange(9).reshape((3, 3))) + ax.add_image(im) + axs[2].pcolorfast( # PcolorImage + np.arange(4) ** 2, np.arange(4) ** 2, np.arange(9).reshape((3, 3))) + for ax in axs: + ax.set_axis_off() + # NonUniformImage "leaks" out of extents, not PColorImage. + ax.set(xlim=(0, 10)) + + +@image_comparison(["nonuniform_logscale.png"], style="mpl20") +def test_nonuniform_logscale(): + _, axs = plt.subplots(ncols=3, nrows=1) + + for i in range(3): + ax = axs[i] + im = NonUniformImage(ax) + im.set_data(np.arange(1, 4) ** 2, np.arange(1, 4) ** 2, + np.arange(9).reshape((3, 3))) + ax.set_xlim(1, 16) + ax.set_ylim(1, 16) + ax.set_box_aspect(1) + if i == 1: + ax.set_xscale("log", base=2) + ax.set_yscale("log", base=2) + if i == 2: + ax.set_xscale("log", base=4) + ax.set_yscale("log", base=4) + ax.add_image(im) + + +@image_comparison(['rgba_antialias.png'], style='mpl20', remove_text=True, tol=0.02) +def test_rgba_antialias(): + fig, axs = plt.subplots(2, 2, figsize=(3.5, 3.5), sharex=False, + sharey=False, constrained_layout=True) + N = 250 + aa = np.ones((N, N)) + aa[::2, :] = -1 + + x = np.arange(N) / N - 0.5 + y = np.arange(N) / N - 0.5 + + X, Y = np.meshgrid(x, y) + R = np.sqrt(X**2 + Y**2) + f0 = 10 + k = 75 + # aliased concentric circles + a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2)) + + # stripes on lhs + a[:int(N/2), :][R[:int(N/2), :] < 0.4] = -1 + a[:int(N/2), :][R[:int(N/2), :] < 0.3] = 1 + aa[:, int(N/2):] = a[:, int(N/2):] + + # set some over/unders and NaNs + aa[20:50, 20:50] = np.nan + aa[70:90, 70:90] = 1e6 + aa[70:90, 20:30] = -1e6 + aa[70:90, 195:215] = 1e6 + aa[20:30, 195:215] = -1e6 + + cmap = plt.colormaps["RdBu_r"] + cmap.set_over('yellow') + cmap.set_under('cyan') + + axs = axs.flatten() + # zoom in + axs[0].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2) + axs[0].set_xlim([N/2-25, N/2+25]) + axs[0].set_ylim([N/2+50, N/2-10]) + + # no anti-alias + axs[1].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2) + + # data antialias: Note no purples, and white in circle. Note + # that alternating red and blue stripes become white. + axs[2].imshow(aa, interpolation='auto', interpolation_stage='data', + cmap=cmap, vmin=-1.2, vmax=1.2) + + # rgba antialias: Note purples at boundary with circle. Note that + # alternating red and blue stripes become purple + axs[3].imshow(aa, interpolation='auto', interpolation_stage='rgba', + cmap=cmap, vmin=-1.2, vmax=1.2) + + +@check_figures_equal() +def test_upsample_interpolation_stage(fig_test, fig_ref): + """ + Show that interpolation_stage='auto' gives the same as 'data' + for upsampling. + """ + # Fixing random state for reproducibility. This non-standard seed + # gives red splotches for 'rgba'. + np.random.seed(19680801+9) + + grid = np.random.rand(4, 4) + ax = fig_ref.subplots() + ax.imshow(grid, interpolation='bilinear', cmap='viridis', + interpolation_stage='data') + + ax = fig_test.subplots() + ax.imshow(grid, interpolation='bilinear', cmap='viridis', + interpolation_stage='auto') + + +@check_figures_equal() +def test_downsample_interpolation_stage(fig_test, fig_ref): + """ + Show that interpolation_stage='auto' gives the same as 'rgba' + for downsampling. + """ + # Fixing random state for reproducibility + np.random.seed(19680801) + + grid = np.random.rand(4000, 4000) + ax = fig_ref.subplots() + ax.imshow(grid, interpolation='auto', cmap='viridis', + interpolation_stage='rgba') + + ax = fig_test.subplots() + ax.imshow(grid, interpolation='auto', cmap='viridis', + interpolation_stage='auto') + + +def test_rc_interpolation_stage(): + for val in ["data", "rgba"]: + with mpl.rc_context({"image.interpolation_stage": val}): + assert plt.imshow([[1, 2]]).get_interpolation_stage() == val + for val in ["DATA", "foo", None]: + with pytest.raises(ValueError): + mpl.rcParams["image.interpolation_stage"] = val + + +# We check for the warning with a draw() in the test, but we also need to +# filter the warning as it is emitted by the figure test decorator +@pytest.mark.filterwarnings(r'ignore:Data with more than .* ' + 'cannot be accurately displayed') +@pytest.mark.parametrize('origin', ['upper', 'lower']) +@pytest.mark.parametrize( + 'dim, size, msg', [['row', 2**23, r'2\*\*23 columns'], + ['col', 2**24, r'2\*\*24 rows']]) +@check_figures_equal() +def test_large_image(fig_test, fig_ref, dim, size, msg, origin): + # Check that Matplotlib downsamples images that are too big for AGG + # See issue #19276. Currently the fix only works for png output but not + # pdf or svg output. + ax_test = fig_test.subplots() + ax_ref = fig_ref.subplots() + + array = np.zeros((1, size + 2)) + array[:, array.size // 2:] = 1 + if dim == 'col': + array = array.T + im = ax_test.imshow(array, vmin=0, vmax=1, + aspect='auto', extent=(0, 1, 0, 1), + interpolation='none', + origin=origin) + + with pytest.warns(UserWarning, + match=f'Data with more than {msg} cannot be ' + 'accurately displayed.'): + fig_test.canvas.draw() + + array = np.zeros((1, 2)) + array[:, 1] = 1 + if dim == 'col': + array = array.T + im = ax_ref.imshow(array, vmin=0, vmax=1, aspect='auto', + extent=(0, 1, 0, 1), + interpolation='none', + origin=origin) + + +@check_figures_equal() +def test_str_norms(fig_test, fig_ref): + t = np.random.rand(10, 10) * .8 + .1 # between 0 and 1 + axts = fig_test.subplots(1, 5) + axts[0].imshow(t, norm="log") + axts[1].imshow(t, norm="log", vmin=.2) + axts[2].imshow(t, norm="symlog") + axts[3].imshow(t, norm="symlog", vmin=.3, vmax=.7) + axts[4].imshow(t, norm="logit", vmin=.3, vmax=.7) + axrs = fig_ref.subplots(1, 5) + axrs[0].imshow(t, norm=colors.LogNorm()) + axrs[1].imshow(t, norm=colors.LogNorm(vmin=.2)) + # same linthresh as SymmetricalLogScale's default. + axrs[2].imshow(t, norm=colors.SymLogNorm(linthresh=2)) + axrs[3].imshow(t, norm=colors.SymLogNorm(linthresh=2, vmin=.3, vmax=.7)) + axrs[4].imshow(t, norm="logit", clim=(.3, .7)) + + assert type(axts[0].images[0].norm) is colors.LogNorm # Exactly that class + with pytest.raises(ValueError): + axts[0].imshow(t, norm="foobar") + + +def test__resample_valid_output(): + resample = functools.partial(mpl._image.resample, transform=Affine2D()) + with pytest.raises(TypeError, match="incompatible function arguments"): + resample(np.zeros((9, 9)), None) + with pytest.raises(ValueError, match="different dimensionalities"): + resample(np.zeros((9, 9)), np.zeros((9, 9, 4))) + with pytest.raises(ValueError, match="different dimensionalities"): + resample(np.zeros((9, 9, 4)), np.zeros((9, 9))) + with pytest.raises(ValueError, match="3D input array must be RGBA"): + resample(np.zeros((9, 9, 3)), np.zeros((9, 9, 4))) + with pytest.raises(ValueError, match="3D output array must be RGBA"): + resample(np.zeros((9, 9, 4)), np.zeros((9, 9, 3))) + with pytest.raises(ValueError, match="mismatched types"): + resample(np.zeros((9, 9), np.uint8), np.zeros((9, 9))) + with pytest.raises(ValueError, match="must be C-contiguous"): + resample(np.zeros((9, 9)), np.zeros((9, 9)).T) + + out = np.zeros((9, 9)) + out.flags.writeable = False + with pytest.raises(ValueError, match="Output array must be writeable"): + resample(np.zeros((9, 9)), out) + + +@pytest.mark.parametrize("data, interpolation, expected", + [(np.array([[0.1, 0.3, 0.2]]), mimage.NEAREST, + np.array([[0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.3, 0.2, 0.2, 0.2]])), + (np.array([[0.1, 0.3, 0.2]]), mimage.BILINEAR, + np.array([[0.1, 0.1, 0.15078125, 0.21096191, 0.27033691, + 0.28476562, 0.2546875, 0.22460938, 0.20002441, 0.20002441]])), + ] +) +def test_resample_nonaffine(data, interpolation, expected): + # Test that equivalent affine and nonaffine transforms resample the same + + # Create a simple affine transform for scaling the input array + affine_transform = Affine2D().scale(sx=expected.shape[1] / data.shape[1], sy=1) + + affine_result = np.empty_like(expected) + mimage.resample(data, affine_result, affine_transform, interpolation=interpolation) + assert_allclose(affine_result, expected) + + # Create a nonaffine version of the same transform + # by compositing with a nonaffine identity transform + class NonAffineIdentityTransform(Transform): + input_dims = 2 + output_dims = 2 + + def inverted(self): + return self + nonaffine_transform = NonAffineIdentityTransform() + affine_transform + + nonaffine_result = np.empty_like(expected) + mimage.resample(data, nonaffine_result, nonaffine_transform, + interpolation=interpolation) + assert_allclose(nonaffine_result, expected, atol=5e-3) + + +def test_axesimage_get_shape(): + # generate dummy image to test get_shape method + ax = plt.gca() + im = AxesImage(ax) + with pytest.raises(RuntimeError, match="You must first set the image array"): + im.get_shape() + z = np.arange(12, dtype=float).reshape((4, 3)) + im.set_data(z) + assert im.get_shape() == (4, 3) + assert im.get_size() == im.get_shape() + + +def test_non_transdata_image_does_not_touch_aspect(): + ax = plt.figure().add_subplot() + im = np.arange(4).reshape((2, 2)) + ax.imshow(im, transform=ax.transAxes) + assert ax.get_aspect() == "auto" + ax.imshow(im, transform=Affine2D().scale(2) + ax.transData) + assert ax.get_aspect() == 1 + ax.imshow(im, transform=ax.transAxes, aspect=2) + assert ax.get_aspect() == 2 + + +@image_comparison( + ['downsampling.png'], style='mpl20', remove_text=True, tol=0.09) +def test_downsampling(): + N = 450 + x = np.arange(N) / N - 0.5 + y = np.arange(N) / N - 0.5 + aa = np.ones((N, N)) + aa[::2, :] = -1 + + X, Y = np.meshgrid(x, y) + R = np.sqrt(X**2 + Y**2) + f0 = 5 + k = 100 + a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2)) + # make the left hand side of this + a[:int(N / 2), :][R[:int(N / 2), :] < 0.4] = -1 + a[:int(N / 2), :][R[:int(N / 2), :] < 0.3] = 1 + aa[:, int(N / 3):] = a[:, int(N / 3):] + a = aa + + fig, axs = plt.subplots(2, 3, figsize=(7, 6), layout='compressed') + axs[0, 0].imshow(a, interpolation='nearest', interpolation_stage='rgba', + cmap='RdBu_r') + axs[0, 0].set_xlim(125, 175) + axs[0, 0].set_ylim(250, 200) + axs[0, 0].set_title('Zoom') + + for ax, interp, space in zip(axs.flat[1:], ['nearest', 'nearest', 'hanning', + 'hanning', 'auto'], + ['data', 'rgba', 'data', 'rgba', 'auto']): + ax.imshow(a, interpolation=interp, interpolation_stage=space, + cmap='RdBu_r') + ax.set_title(f"interpolation='{interp}'\nspace='{space}'") + + +@image_comparison( + ['downsampling_speckle.png'], style='mpl20', remove_text=True, tol=0.09) +def test_downsampling_speckle(): + fig, axs = plt.subplots(1, 2, figsize=(5, 2.7), sharex=True, sharey=True, + layout="compressed") + axs = axs.flatten() + img = ((np.arange(1024).reshape(-1, 1) * np.ones(720)) // 50).T + + cm = plt.get_cmap("viridis") + cm.set_over("m") + norm = colors.LogNorm(vmin=3, vmax=11) + + # old default cannot be tested because it creates over/under speckles + # in the following that are machine dependent. + + axs[0].set_title("interpolation='auto', stage='rgba'") + axs[0].imshow(np.triu(img), cmap=cm, norm=norm, interpolation_stage='rgba') + + # Should be same as previous + axs[1].set_title("interpolation='auto', stage='auto'") + axs[1].imshow(np.triu(img), cmap=cm, norm=norm) + + +@image_comparison( + ['upsampling.png'], style='mpl20', remove_text=True) +def test_upsampling(): + + np.random.seed(19680801+9) # need this seed to get yellow next to blue + a = np.random.rand(4, 4) + + fig, axs = plt.subplots(1, 3, figsize=(6.5, 3), layout='compressed') + im = axs[0].imshow(a, cmap='viridis') + axs[0].set_title( + "interpolation='auto'\nstage='antialaised'\n(default for upsampling)") + + # probably what people want: + axs[1].imshow(a, cmap='viridis', interpolation='sinc') + axs[1].set_title( + "interpolation='sinc'\nstage='auto'\n(default for upsampling)") + + # probably not what people want: + axs[2].imshow(a, cmap='viridis', interpolation='sinc', interpolation_stage='rgba') + axs[2].set_title("interpolation='sinc'\nstage='rgba'") + fig.colorbar(im, ax=axs, shrink=0.7, extend='both') + + +@pytest.mark.parametrize( + 'dtype', + ('float64', 'float32', 'int16', 'uint16', 'int8', 'uint8'), +) +@pytest.mark.parametrize('ndim', (2, 3)) +def test_resample_dtypes(dtype, ndim): + # Issue 28448, incorrect dtype comparisons in C++ image_resample can raise + # ValueError: arrays must be of dtype byte, short, float32 or float64 + rng = np.random.default_rng(4181) + shape = (2, 2) if ndim == 2 else (2, 2, 3) + data = rng.uniform(size=shape).astype(np.dtype(dtype, copy=True)) + fig, ax = plt.subplots() + axes_image = ax.imshow(data) + # Before fix the following raises ValueError for some dtypes. + axes_image.make_image(None)[0] + + +@pytest.mark.parametrize('intp_stage', ('data', 'rgba')) +@check_figures_equal(extensions=['png', 'pdf', 'svg']) +def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_stage): + axs_tst = fig_test.subplots(2, 3) + axs_ref = fig_ref.subplots(2, 3) + ny, nx = 3, 3 + scalar_alpha = 0.5 + array_alpha = np.random.rand(ny, nx) + + # When the image does not have an alpha channel, alpha should be specified + # by the user or default to 1.0 + im_rgb = np.random.rand(ny, nx, 3) + im_concat_default_a = np.ones((ny, nx, 1)) # alpha defaults to 1.0 + im_rgba = np.concatenate( # combine rgb channels with array alpha + (im_rgb, array_alpha.reshape((ny, nx, 1))), axis=-1 + ) + axs_tst[0][0].imshow(im_rgb) + axs_ref[0][0].imshow(np.concatenate((im_rgb, im_concat_default_a), axis=-1)) + axs_tst[0][1].imshow(im_rgb, interpolation_stage=intp_stage, alpha=scalar_alpha) + axs_ref[0][1].imshow( + np.concatenate( # combine rgb channels with broadcasted scalar alpha + (im_rgb, scalar_alpha * im_concat_default_a), axis=-1 + ), interpolation_stage=intp_stage + ) + axs_tst[0][2].imshow(im_rgb, interpolation_stage=intp_stage, alpha=array_alpha) + axs_ref[0][2].imshow(im_rgba, interpolation_stage=intp_stage) + + # When the image already has an alpha channel, multiply it by the + # scalar alpha param, or replace it by the array alpha param + axs_tst[1][0].imshow(im_rgba) + axs_ref[1][0].imshow(im_rgb, alpha=array_alpha) + axs_tst[1][1].imshow(im_rgba, interpolation_stage=intp_stage, alpha=scalar_alpha) + axs_ref[1][1].imshow( + np.concatenate( # combine rgb channels with scaled array alpha + (im_rgb, scalar_alpha * array_alpha.reshape((ny, nx, 1))), axis=-1 + ), interpolation_stage=intp_stage + ) + new_array_alpha = np.random.rand(ny, nx) + axs_tst[1][2].imshow(im_rgba, interpolation_stage=intp_stage, alpha=new_array_alpha) + axs_ref[1][2].imshow( + np.concatenate( # combine rgb channels with new array alpha + (im_rgb, new_array_alpha.reshape((ny, nx, 1))), axis=-1 + ), interpolation_stage=intp_stage + ) diff --git a/lib/matplotlib/tests/test_inset.py b/lib/matplotlib/tests/test_inset.py new file mode 100644 index 000000000000..e368a4af4e1b --- /dev/null +++ b/lib/matplotlib/tests/test_inset.py @@ -0,0 +1,142 @@ +import platform + +import pytest + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import matplotlib.transforms as mtransforms +from matplotlib.testing.decorators import image_comparison, check_figures_equal + + +def test_indicate_inset_no_args(): + fig, ax = plt.subplots() + with pytest.raises(ValueError, match='At least one of bounds or inset_ax'): + ax.indicate_inset() + + +@check_figures_equal() +def test_zoom_inset_update_limits(fig_test, fig_ref): + # Updating the inset axes limits should also update the indicator #19768 + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + for ax in ax_ref, ax_test: + ax.set_xlim([0, 5]) + ax.set_ylim([0, 5]) + + inset_ref = ax_ref.inset_axes([0.6, 0.6, 0.3, 0.3]) + inset_test = ax_test.inset_axes([0.6, 0.6, 0.3, 0.3]) + + inset_ref.set_xlim([1, 2]) + inset_ref.set_ylim([3, 4]) + ax_ref.indicate_inset_zoom(inset_ref) + + ax_test.indicate_inset_zoom(inset_test) + inset_test.set_xlim([1, 2]) + inset_test.set_ylim([3, 4]) + + +def test_inset_indicator_update_styles(): + fig, ax = plt.subplots() + inset = ax.inset_axes([0.6, 0.6, 0.3, 0.3]) + inset.set_xlim([0.2, 0.4]) + inset.set_ylim([0.2, 0.4]) + + indicator = ax.indicate_inset_zoom( + inset, edgecolor='red', alpha=0.5, linewidth=2, linestyle='solid') + + # Changing the rectangle styles should not affect the connectors. + indicator.rectangle.set(color='blue', linestyle='dashed', linewidth=42, alpha=0.2) + for conn in indicator.connectors: + assert mcolors.same_color(conn.get_edgecolor()[:3], 'red') + assert conn.get_alpha() == 0.5 + assert conn.get_linestyle() == 'solid' + assert conn.get_linewidth() == 2 + + # Changing the indicator styles should affect both rectangle and connectors. + indicator.set(color='green', linestyle='dotted', linewidth=7, alpha=0.8) + assert mcolors.same_color(indicator.rectangle.get_facecolor()[:3], 'green') + for patch in (*indicator.connectors, indicator.rectangle): + assert mcolors.same_color(patch.get_edgecolor()[:3], 'green') + assert patch.get_alpha() == 0.8 + assert patch.get_linestyle() == 'dotted' + assert patch.get_linewidth() == 7 + + indicator.set_edgecolor('purple') + for patch in (*indicator.connectors, indicator.rectangle): + assert mcolors.same_color(patch.get_edgecolor()[:3], 'purple') + + # This should also be true if connectors weren't created yet. + indicator._connectors = [] + indicator.set(color='burlywood', linestyle='dashdot', linewidth=4, alpha=0.4) + assert mcolors.same_color(indicator.rectangle.get_facecolor()[:3], 'burlywood') + for patch in (*indicator.connectors, indicator.rectangle): + assert mcolors.same_color(patch.get_edgecolor()[:3], 'burlywood') + assert patch.get_alpha() == 0.4 + assert patch.get_linestyle() == 'dashdot' + assert patch.get_linewidth() == 4 + + indicator._connectors = [] + indicator.set_edgecolor('thistle') + for patch in (*indicator.connectors, indicator.rectangle): + assert mcolors.same_color(patch.get_edgecolor()[:3], 'thistle') + + +def test_inset_indicator_zorder(): + fig, ax = plt.subplots() + rect = [0.2, 0.2, 0.3, 0.4] + + inset = ax.indicate_inset(rect) + assert inset.get_zorder() == 4.99 + + inset = ax.indicate_inset(rect, zorder=42) + assert inset.get_zorder() == 42 + + +@image_comparison(['zoom_inset_connector_styles.png'], remove_text=True, style='mpl20', + tol=0.024 if platform.machine() == 'arm64' else 0) +def test_zoom_inset_connector_styles(): + fig, axs = plt.subplots(2) + for ax in axs: + ax.plot([1, 2, 3]) + + axs[1].set_xlim(0.5, 1.5) + indicator = axs[0].indicate_inset_zoom(axs[1], linewidth=5) + # Make one visible connector a different style + indicator.connectors[1].set_linestyle('dashed') + indicator.connectors[1].set_color('blue') + + +@image_comparison(['zoom_inset_transform.png'], remove_text=True, style='mpl20', + tol=0.01) +def test_zoom_inset_transform(): + fig, ax = plt.subplots() + + ax_ins = ax.inset_axes([0.2, 0.2, 0.3, 0.15]) + ax_ins.set_ylim([0.3, 0.6]) + ax_ins.set_xlim([0.5, 0.9]) + + tr = mtransforms.Affine2D().rotate_deg(30) + indicator = ax.indicate_inset_zoom(ax_ins, transform=tr + ax.transData) + for conn in indicator.connectors: + conn.set_visible(True) + + +def test_zoom_inset_external_transform(): + # Smoke test that an external transform that requires an axes (i.e. + # Cartopy) will work. + class FussyDataTr: + def _as_mpl_transform(self, axes=None): + if axes is None: + raise ValueError("I am a fussy transform that requires an axes") + return axes.transData + + fig, ax = plt.subplots() + + ax_ins = ax.inset_axes([0.2, 0.2, 0.3, 0.15]) + ax_ins.set_xlim([0.7, 0.8]) + ax_ins.set_ylim([0.7, 0.8]) + + ax.indicate_inset_zoom(ax_ins, transform=FussyDataTr()) + + fig.draw_without_rendering() diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 615632c250e3..d80c6b2ed92a 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1,18 +1,27 @@ import collections +import io +import itertools import platform +import time from unittest import mock +import warnings import numpy as np +from numpy.testing import assert_allclose import pytest -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import check_figures_equal, image_comparison +from matplotlib.testing._markers import needs_usetex import matplotlib.pyplot as plt import matplotlib as mpl +import matplotlib.patches as mpatches import matplotlib.transforms as mtransforms import matplotlib.collections as mcollections +import matplotlib.lines as mlines from matplotlib.legend_handler import HandlerTuple import matplotlib.legend as mlegend from matplotlib import rc_context +from matplotlib.font_manager import FontProperties def test_legend_ordereddict(): @@ -33,33 +42,42 @@ def test_legend_ordereddict(): loc='center left', bbox_to_anchor=(1, .5)) -@image_comparison(['legend_auto1'], remove_text=True) +def test_legend_generator(): + # smoketest that generator inputs work + fig, ax = plt.subplots() + ax.plot([0, 1]) + ax.plot([0, 2]) + + handles = (line for line in ax.get_lines()) + labels = (label for label in ['spam', 'eggs']) + + ax.legend(handles, labels, loc='upper left') + + +@image_comparison(['legend_auto1.png'], remove_text=True) def test_legend_auto1(): """Test automatic legend placement""" - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() x = np.arange(100) ax.plot(x, 50 - x, 'o', label='y=1') ax.plot(x, x - 50, 'o', label='y=-1') ax.legend(loc='best') -@image_comparison(['legend_auto2'], remove_text=True) +@image_comparison(['legend_auto2.png'], remove_text=True) def test_legend_auto2(): """Test automatic legend placement""" - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() x = np.arange(100) b1 = ax.bar(x, x, align='edge', color='m') b2 = ax.bar(x, x[::-1], align='edge', color='g') ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best') -@image_comparison(['legend_auto3']) +@image_comparison(['legend_auto3.png']) def test_legend_auto3(): """Test automatic legend placement""" - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5] y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5] ax.plot(x, y, 'o-', label='line') @@ -68,7 +86,61 @@ def test_legend_auto3(): ax.legend(loc='best') -@image_comparison(['legend_various_labels'], remove_text=True) +def test_legend_auto4(): + """ + Check that the legend location with automatic placement is the same, + whatever the histogram type is. Related to issue #9580. + """ + # NB: barstacked is pointless with a single dataset. + fig, axs = plt.subplots(ncols=3, figsize=(6.4, 2.4)) + leg_bboxes = [] + for ax, ht in zip(axs.flat, ('bar', 'step', 'stepfilled')): + ax.set_title(ht) + # A high bar on the left but an even higher one on the right. + ax.hist([0] + 5*[9], bins=range(10), label="Legend", histtype=ht) + leg = ax.legend(loc="best") + fig.canvas.draw() + leg_bboxes.append( + leg.get_window_extent().transformed(ax.transAxes.inverted())) + + # The histogram type "bar" is assumed to be the correct reference. + assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) + assert_allclose(leg_bboxes[2].bounds, leg_bboxes[0].bounds) + + +def test_legend_auto5(): + """ + Check that the automatic placement handle a rather complex + case with non rectangular patch. Related to issue #9580. + """ + fig, axs = plt.subplots(ncols=2, figsize=(9.6, 4.8)) + + leg_bboxes = [] + for ax, loc in zip(axs.flat, ("center", "best")): + # An Ellipse patch at the top, a U-shaped Polygon patch at the + # bottom and a ring-like Wedge patch: the correct placement of + # the legend should be in the center. + for _patch in [ + mpatches.Ellipse( + xy=(0.5, 0.9), width=0.8, height=0.2, fc="C1"), + mpatches.Polygon(np.array([ + [0, 1], [0, 0], [1, 0], [1, 1], [0.9, 1.0], [0.9, 0.1], + [0.1, 0.1], [0.1, 1.0], [0.1, 1.0]]), fc="C1"), + mpatches.Wedge((0.5, 0.5), 0.5, 0, 360, width=0.05, fc="C0") + ]: + ax.add_patch(_patch) + + ax.plot([0.1, 0.9], [0.9, 0.9], label="A segment") # sthg to label + + leg = ax.legend(loc=loc) + fig.canvas.draw() + leg_bboxes.append( + leg.get_window_extent().transformed(ax.transAxes.inverted())) + + assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) + + +@image_comparison(['legend_various_labels.png'], remove_text=True) def test_various_labels(): # tests all sorts of label types fig = plt.figure() @@ -79,22 +151,22 @@ def test_various_labels(): ax.legend(numpoints=1, loc='best') -@image_comparison(['legend_labels_first.png'], remove_text=True) +@image_comparison(['legend_labels_first.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.013) def test_labels_first(): # test labels to left of markers - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() ax.plot(np.arange(10), '-o', label=1) ax.plot(np.ones(10)*5, ':x', label="x") ax.plot(np.arange(20, 10, -1), 'd', label="diamond") ax.legend(loc='best', markerfirst=False) -@image_comparison(['legend_multiple_keys.png'], remove_text=True) +@image_comparison(['legend_multiple_keys.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.013) def test_multiple_keys(): # test legend entries with multiple keys - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() p1, = ax.plot([1, 2, 3], '-o') p2, = ax.plot([2, 3, 4], '-x') p3, = ax.plot([3, 4, 5], '-d') @@ -105,7 +177,7 @@ def test_multiple_keys(): @image_comparison(['rgba_alpha.png'], remove_text=True, - tol=0 if platform.machine() == 'x86_64' else 0.01) + tol=0 if platform.machine() == 'x86_64' else 0.03) def test_alpha_rgba(): fig, ax = plt.subplots() ax.plot(range(10), lw=5) @@ -114,7 +186,7 @@ def test_alpha_rgba(): @image_comparison(['rcparam_alpha.png'], remove_text=True, - tol=0 if platform.machine() == 'x86_64' else 0.01) + tol=0 if platform.machine() == 'x86_64' else 0.03) def test_alpha_rcparam(): fig, ax = plt.subplots() ax.plot(range(10), lw=5) @@ -127,20 +199,22 @@ def test_alpha_rcparam(): leg.legendPatch.set_facecolor([1, 0, 0, 0.5]) -@image_comparison(['fancy'], remove_text=True) +@image_comparison(['fancy.png'], remove_text=True, tol=0.05) def test_fancy(): + # Tolerance caused by changing default shadow "shade" from 0.3 to 1 - 0.7 = + # 0.30000000000000004 # using subplot triggers some offsetbox functionality untested elsewhere plt.subplot(121) - plt.scatter(np.arange(10), np.arange(10, 0, -1), label='XX\nXX') plt.plot([5] * 10, 'o--', label='XX') + plt.scatter(np.arange(10), np.arange(10, 0, -1), label='XX\nXX') plt.errorbar(np.arange(10), np.arange(10), xerr=0.5, yerr=0.5, label='XX') plt.legend(loc="center left", bbox_to_anchor=[1.0, 0.5], - ncol=2, shadow=True, title="My legend", numpoints=1) + ncols=2, shadow=True, title="My legend", numpoints=1) @image_comparison(['framealpha'], remove_text=True, - tol=0 if platform.machine() == 'x86_64' else 0.02) + tol=0 if platform.machine() == 'x86_64' else 0.024) def test_framealpha(): x = np.linspace(1, 100, 100) y = x @@ -148,7 +222,7 @@ def test_framealpha(): plt.legend(framealpha=0.5) -@image_comparison(['scatter_rc3', 'scatter_rc1'], remove_text=True) +@image_comparison(['scatter_rc3.png', 'scatter_rc1.png'], remove_text=True) def test_rc(): # using subplot triggers some offsetbox functionality untested elsewhere plt.figure() @@ -165,24 +239,25 @@ def test_rc(): title="My legend") -@image_comparison(['legend_expand'], remove_text=True) +@image_comparison(['legend_expand.png'], remove_text=True) def test_legend_expand(): """Test expand mode""" legend_modes = [None, "expand"] - fig, axes_list = plt.subplots(len(legend_modes), 1) + fig, axs = plt.subplots(len(legend_modes), 1) x = np.arange(100) - for ax, mode in zip(axes_list, legend_modes): + for ax, mode in zip(axs, legend_modes): ax.plot(x, 50 - x, 'o', label='y=1') l1 = ax.legend(loc='upper left', mode=mode) ax.add_artist(l1) ax.plot(x, x - 50, 'o', label='y=-1') l2 = ax.legend(loc='right', mode=mode) ax.add_artist(l2) - ax.legend(loc='lower left', mode=mode, ncol=2) + ax.legend(loc='lower left', mode=mode, ncols=2) @image_comparison(['hatching'], remove_text=True, style='default') def test_hatching(): + # Remove legend texts when this image is regenerated. # Remove this line when this test image is regenerated. plt.rcParams['text.kerning_factor'] = 6 @@ -215,8 +290,7 @@ def test_hatching(): def test_legend_remove(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() lines = ax.plot(range(10)) leg = fig.legend(lines, "test") leg.remove() @@ -226,6 +300,38 @@ def test_legend_remove(): assert ax.get_legend() is None +def test_reverse_legend_handles_and_labels(): + """Check that the legend handles and labels are reversed.""" + fig, ax = plt.subplots() + x = 1 + y = 1 + labels = ["First label", "Second label", "Third label"] + markers = ['.', ',', 'o'] + + ax.plot(x, y, markers[0], label=labels[0]) + ax.plot(x, y, markers[1], label=labels[1]) + ax.plot(x, y, markers[2], label=labels[2]) + leg = ax.legend(reverse=True) + actual_labels = [t.get_text() for t in leg.get_texts()] + actual_markers = [h.get_marker() for h in leg.legend_handles] + assert actual_labels == list(reversed(labels)) + assert actual_markers == list(reversed(markers)) + + +@check_figures_equal() +def test_reverse_legend_display(fig_test, fig_ref): + """Check that the rendered legend entries are reversed""" + ax = fig_test.subplots() + ax.plot([1], 'ro', label="first") + ax.plot([2], 'bx', label="second") + ax.legend(reverse=True) + + ax = fig_ref.subplots() + ax.plot([2], 'bx', label="second") + ax.plot([1], 'ro', label="first") + ax.legend() + + class TestLegendFunction: # Tests the legend function on the Axes and pyplot. def test_legend_no_args(self): @@ -292,24 +398,21 @@ def test_legend_kwargs_handles_labels(self): lns, = ax.plot(th, np.sin(th), label='sin') lnc, = ax.plot(th, np.cos(th), label='cos') with mock.patch('matplotlib.legend.Legend') as Legend: - # labels of lns, lnc are overwritten with explict ('a', 'b') + # labels of lns, lnc are overwritten with explicit ('a', 'b') ax.legend(labels=('a', 'b'), handles=(lnc, lns)) Legend.assert_called_with(ax, (lnc, lns), ('a', 'b')) - def test_warn_mixed_args_and_kwargs(self): + def test_error_mixed_args_and_kwargs(self): fig, ax = plt.subplots() th = np.linspace(0, 2*np.pi, 1024) lns, = ax.plot(th, np.sin(th), label='sin') lnc, = ax.plot(th, np.cos(th), label='cos') - with pytest.warns(UserWarning) as record: + msg = 'must both be passed positionally or both as keywords' + with pytest.raises(TypeError, match=msg): ax.legend((lnc, lns), labels=('a', 'b')) - assert len(record) == 1 - assert str(record[0].message) == ( - "You have mixed positional and keyword arguments, some input may " - "be discarded.") def test_parasite(self): - from mpl_toolkits.axes_grid1 import host_subplot + from mpl_toolkits.axes_grid1 import host_subplot # type: ignore[import] host = host_subplot(111) par = host.twinx() @@ -351,17 +454,9 @@ def test_legend_label_arg(self): def test_legend_label_three_args(self): fig, ax = plt.subplots() lines = ax.plot(range(10)) - with mock.patch('matplotlib.legend.Legend') as Legend: + with pytest.raises(TypeError, match="0-2"): fig.legend(lines, ['foobar'], 'right') - Legend.assert_called_with(fig, lines, ['foobar'], 'right', - bbox_transform=fig.transFigure) - - def test_legend_label_three_args_pluskw(self): - # test that third argument and loc= called together give - # Exception - fig, ax = plt.subplots() - lines = ax.plot(range(10)) - with pytest.raises(Exception): + with pytest.raises(TypeError, match="0-2"): fig.legend(lines, ['foobar'], 'right', loc='left') def test_legend_kw_args(self): @@ -374,24 +469,62 @@ def test_legend_kw_args(self): fig, (lines, lines2), ('a', 'b'), loc='right', bbox_transform=fig.transFigure) - def test_warn_args_kwargs(self): + def test_error_args_kwargs(self): fig, axs = plt.subplots(1, 2) lines = axs[0].plot(range(10)) lines2 = axs[1].plot(np.arange(10) * 2.) - with pytest.warns(UserWarning) as record: + msg = 'must both be passed positionally or both as keywords' + with pytest.raises(TypeError, match=msg): fig.legend((lines, lines2), labels=('a', 'b')) - assert len(record) == 1 - assert str(record[0].message) == ( - "You have mixed positional and keyword arguments, some input may " - "be discarded.") -@image_comparison(['legend_stackplot.png']) +def test_figure_legend_outside(): + todos = ['upper ' + pos for pos in ['left', 'center', 'right']] + todos += ['lower ' + pos for pos in ['left', 'center', 'right']] + todos += ['left ' + pos for pos in ['lower', 'center', 'upper']] + todos += ['right ' + pos for pos in ['lower', 'center', 'upper']] + + upperext = [20.347556, 27.722556, 790.583, 545.499] + lowerext = [20.347556, 71.056556, 790.583, 588.833] + leftext = [151.681556, 27.722556, 790.583, 588.833] + rightext = [20.347556, 27.722556, 659.249, 588.833] + axbb = [upperext, upperext, upperext, + lowerext, lowerext, lowerext, + leftext, leftext, leftext, + rightext, rightext, rightext] + + legbb = [[10., 555., 133., 590.], # upper left + [338.5, 555., 461.5, 590.], # upper center + [667, 555., 790., 590.], # upper right + [10., 10., 133., 45.], # lower left + [338.5, 10., 461.5, 45.], # lower center + [667., 10., 790., 45.], # lower right + [10., 10., 133., 45.], # left lower + [10., 282.5, 133., 317.5], # left center + [10., 555., 133., 590.], # left upper + [667, 10., 790., 45.], # right lower + [667., 282.5, 790., 317.5], # right center + [667., 555., 790., 590.]] # right upper + + for nn, todo in enumerate(todos): + print(todo) + fig, axs = plt.subplots(constrained_layout=True, dpi=100) + axs.plot(range(10), label='Boo1') + leg = fig.legend(loc='outside ' + todo) + fig.draw_without_rendering() + + assert_allclose(axs.get_window_extent().extents, + axbb[nn]) + assert_allclose(leg.get_window_extent().extents, + legbb[nn]) + + +@image_comparison(['legend_stackplot.png'], + tol=0 if platform.machine() == 'x86_64' else 0.031) def test_legend_stackplot(): """Test legend for PolyCollection using stackplot.""" # related to #1341, #1943, and PR #3303 - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() x = np.linspace(0, 10, 10) y1 = 1.0 * x y2 = 2.0 * x + 1 @@ -483,11 +616,10 @@ def test_linecollection_scaled_dashes(): ax.add_collection(lc3) leg = ax.legend([lc1, lc2, lc3], ["line1", "line2", 'line 3']) - h1, h2, h3 = leg.legendHandles + h1, h2, h3 = leg.legend_handles for oh, lh in zip((lc1, lc2, lc3), (h1, h2, h3)): - assert oh.get_linestyles()[0][1] == lh._dashSeq - assert oh.get_linestyles()[0][0] == lh._dashOffset + assert oh.get_linestyles()[0] == lh._dash_pattern def test_handler_numpoints(): @@ -498,6 +630,23 @@ def test_handler_numpoints(): ax.legend(numpoints=0.5) +def test_text_nohandler_warning(): + """Test that Text artists with labels raise a warning""" + fig, ax = plt.subplots() + ax.plot([0], label="mock data") + ax.text(x=0, y=0, s="text", label="label") + with pytest.warns(UserWarning) as record: + ax.legend() + assert len(record) == 1 + + # this should _not_ warn: + f, ax = plt.subplots() + ax.pcolormesh(np.random.uniform(0, 1, (10, 10))) + with warnings.catch_warnings(): + warnings.simplefilter("error") + ax.get_legend_handles_labels() + + def test_empty_bar_chart_with_legend(): """Test legend when bar chart is empty with a label.""" # related to issue #13003. Calling plt.legend() should not @@ -506,6 +655,38 @@ def test_empty_bar_chart_with_legend(): plt.legend() +@image_comparison(['shadow_argument_types.png'], remove_text=True, style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.028) +def test_shadow_argument_types(): + # Test that different arguments for shadow work as expected + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='test') + + # Test various shadow configurations + # as well as different ways of specifying colors + legs = (ax.legend(loc='upper left', shadow=True), # True + ax.legend(loc='upper right', shadow=False), # False + ax.legend(loc='center left', # string + shadow={'color': 'red', 'alpha': 0.1}), + ax.legend(loc='center right', # tuple + shadow={'color': (0.1, 0.2, 0.5), 'oy': -5}), + ax.legend(loc='lower left', # tab + shadow={'color': 'tab:cyan', 'ox': 10}) + ) + for l in legs: + ax.add_artist(l) + ax.legend(loc='lower right') # default + + +def test_shadow_invalid_argument(): + # Test if invalid argument to legend shadow + # (i.e. not [color|bool]) raises ValueError + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='test') + with pytest.raises(ValueError, match="dict or bool"): + ax.legend(loc="upper left", shadow="aardvark") # Bad argument + + def test_shadow_framealpha(): # Test if framealpha is activated when shadow is True # and framealpha is not explicitly passed''' @@ -520,7 +701,7 @@ def test_legend_title_empty(): # it comes back as an empty string, and that it is not # visible: fig, ax = plt.subplots() - ax.plot(range(10)) + ax.plot(range(10), label="mock data") leg = ax.legend() assert leg.get_title().get_text() == "" assert not leg.get_title().get_visible() @@ -551,24 +732,94 @@ def test_window_extent_cached_renderer(): leg2.get_window_extent() -def test_legend_title_fontsize(): +def test_legend_title_fontprop_fontsize(): # test the title_fontsize kwarg + plt.plot(range(10), label="mock data") + with pytest.raises(ValueError): + plt.legend(title='Aardvark', title_fontsize=22, + title_fontproperties={'family': 'serif', 'size': 22}) + + leg = plt.legend(title='Aardvark', title_fontproperties=FontProperties( + family='serif', size=22)) + assert leg.get_title().get_size() == 22 + + fig, axes = plt.subplots(2, 3, figsize=(10, 6)) + axes = axes.flat + axes[0].plot(range(10), label="mock data") + leg0 = axes[0].legend(title='Aardvark', title_fontsize=22) + assert leg0.get_title().get_fontsize() == 22 + axes[1].plot(range(10), label="mock data") + leg1 = axes[1].legend(title='Aardvark', + title_fontproperties={'family': 'serif', 'size': 22}) + assert leg1.get_title().get_fontsize() == 22 + axes[2].plot(range(10), label="mock data") + mpl.rcParams['legend.title_fontsize'] = None + leg2 = axes[2].legend(title='Aardvark', + title_fontproperties={'family': 'serif'}) + assert leg2.get_title().get_fontsize() == mpl.rcParams['font.size'] + axes[3].plot(range(10), label="mock data") + leg3 = axes[3].legend(title='Aardvark') + assert leg3.get_title().get_fontsize() == mpl.rcParams['font.size'] + axes[4].plot(range(10), label="mock data") + mpl.rcParams['legend.title_fontsize'] = 20 + leg4 = axes[4].legend(title='Aardvark', + title_fontproperties={'family': 'serif'}) + assert leg4.get_title().get_fontsize() == 20 + axes[5].plot(range(10), label="mock data") + leg5 = axes[5].legend(title='Aardvark') + assert leg5.get_title().get_fontsize() == 20 + + +@pytest.mark.parametrize('alignment', ('center', 'left', 'right')) +def test_legend_alignment(alignment): + fig, ax = plt.subplots() + ax.plot(range(10), label='test') + leg = ax.legend(title="Aardvark", alignment=alignment) + assert leg.get_children()[0].align == alignment + assert leg.get_alignment() == alignment + + +@pytest.mark.parametrize('loc', ('center', 'best',)) +def test_ax_legend_set_loc(loc): + fig, ax = plt.subplots() + ax.plot(range(10), label='test') + leg = ax.legend() + leg.set_loc(loc) + assert leg._get_loc() == mlegend.Legend.codes[loc] + + +@pytest.mark.parametrize('loc', ('outside right', 'right',)) +def test_fig_legend_set_loc(loc): + fig, ax = plt.subplots() + ax.plot(range(10), label='test') + leg = fig.legend() + leg.set_loc(loc) + + loc = loc.split()[1] if loc.startswith("outside") else loc + assert leg._get_loc() == mlegend.Legend.codes[loc] + + +@pytest.mark.parametrize('alignment', ('center', 'left', 'right')) +def test_legend_set_alignment(alignment): fig, ax = plt.subplots() - ax.plot(range(10)) - leg = ax.legend(title='Aardvark', title_fontsize=22) - assert leg.get_title().get_fontsize() == 22 + ax.plot(range(10), label='test') + leg = ax.legend() + leg.set_alignment(alignment) + assert leg.get_children()[0].align == alignment + assert leg.get_alignment() == alignment -def test_legend_labelcolor_single(): +@pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5))) +def test_legend_labelcolor_single(color): # test labelcolor for a single color fig, ax = plt.subplots() ax.plot(np.arange(10), np.arange(10)*1, label='#1') ax.plot(np.arange(10), np.arange(10)*2, label='#2') ax.plot(np.arange(10), np.arange(10)*3, label='#3') - leg = ax.legend(labelcolor='red') + leg = ax.legend(labelcolor=color) for text in leg.get_texts(): - assert mpl.colors.same_color(text.get_color(), 'red') + assert mpl.colors.same_color(text.get_color(), color) def test_legend_labelcolor_list(): @@ -595,6 +846,41 @@ def test_legend_labelcolor_linecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_linecolor(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', c='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', c='b') + + leg = ax.legend(labelcolor='linecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_linecolor_iterable(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', c=colors) + + leg = ax.legend(labelcolor='linecolor') + text, = leg.get_texts() + assert mpl.colors.same_color(text.get_color(), 'black') + + +def test_legend_pathcollection_labelcolor_linecolor_cmap(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + # with a colormap + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10), c=np.arange(10), label='#1') + + leg = ax.legend(labelcolor='linecolor') + text, = leg.get_texts() + assert mpl.colors.same_color(text.get_color(), 'black') + + def test_legend_labelcolor_markeredgecolor(): # test the labelcolor for labelcolor='markeredgecolor' fig, ax = plt.subplots() @@ -607,6 +893,49 @@ def test_legend_labelcolor_markeredgecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_markeredgecolor(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', edgecolor='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', edgecolor='b') + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markeredgecolor_iterable(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', edgecolor=colors) + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markeredgecolor_cmap(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + # with a colormap + fig, ax = plt.subplots() + edgecolors = mpl.colormaps["viridis"](np.random.rand(10)) + ax.scatter( + np.arange(10), + np.arange(10), + label='#1', + c=np.arange(10), + edgecolor=edgecolors, + cmap="Reds" + ) + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + def test_legend_labelcolor_markerfacecolor(): # test the labelcolor for labelcolor='markerfacecolor' fig, ax = plt.subplots() @@ -619,6 +948,127 @@ def test_legend_labelcolor_markerfacecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_markerfacecolor(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', facecolor='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', facecolor='b') + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markerfacecolor_iterable(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', facecolor=colors) + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markfacecolor_cmap(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + # with colormaps + fig, ax = plt.subplots() + colors = mpl.colormaps["viridis"](np.random.rand(10)) + ax.scatter( + np.arange(10), + np.arange(10), + label='#1', + c=colors + ) + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +@pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5))) +def test_legend_labelcolor_rcparam_single(color): + # test the rcParams legend.labelcolor for a single color + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1') + ax.plot(np.arange(10), np.arange(10)*2, label='#2') + ax.plot(np.arange(10), np.arange(10)*3, label='#3') + + mpl.rcParams['legend.labelcolor'] = color + leg = ax.legend() + for text in leg.get_texts(): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_linecolor(): + # test the rcParams legend.labelcolor for a linecolor + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b') + + mpl.rcParams['legend.labelcolor'] = 'linecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markeredgecolor(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'markeredgecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markeredgecolor_short(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'mec' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markerfacecolor(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'markerfacecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markerfacecolor_short(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'mfc' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_get_set_draggable(): legend = plt.legend() assert not legend.get_draggable() @@ -628,37 +1078,397 @@ def test_get_set_draggable(): assert not legend.get_draggable() +@pytest.mark.parametrize('draggable', (True, False)) +def test_legend_draggable(draggable): + fig, ax = plt.subplots() + ax.plot(range(10), label='shabnams') + leg = ax.legend(draggable=draggable) + assert leg.get_draggable() is draggable + + def test_alpha_handles(): x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red') legend = plt.legend() - for lh in legend.legendHandles: + for lh in legend.legend_handles: lh.set_alpha(1.0) assert lh.get_facecolor()[:-1] == hh[1].get_facecolor()[:-1] assert lh.get_edgecolor()[:-1] == hh[1].get_edgecolor()[:-1] -def test_warn_big_data_best_loc(): +@needs_usetex +def test_usetex_no_warn(caplog): + mpl.rcParams['font.family'] = 'serif' + mpl.rcParams['font.serif'] = 'Computer Modern' + mpl.rcParams['text.usetex'] = True + + fig, ax = plt.subplots() + ax.plot(0, 0, label='input') + ax.legend(title="My legend") + + fig.canvas.draw() + assert "Font family ['serif'] not found." not in caplog.text + + +def test_warn_big_data_best_loc(monkeypatch): + # Force _find_best_position to think it took a long time. + counter = itertools.count(0, step=1.5) + monkeypatch.setattr(time, 'perf_counter', lambda: next(counter)) + fig, ax = plt.subplots() fig.canvas.draw() # So that we can call draw_artist later. - for idx in range(1000): - ax.plot(np.arange(5000), label=idx) + + # Place line across all possible legend locations. + x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5] + y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5] + ax.plot(x, y, 'o-', label='line') + with rc_context({'legend.loc': 'best'}): legend = ax.legend() - with pytest.warns(UserWarning) as records: + with pytest.warns(UserWarning, + match='Creating legend with loc="best" can be slow with large ' + 'amounts of data.') as records: fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow. # The _find_best_position method of Legend is called twice, duplicating # the warning message. assert len(records) == 2 - for record in records: - assert str(record.message) == ( - 'Creating legend with loc="best" can be slow with large ' - 'amounts of data.') -def test_no_warn_big_data_when_loc_specified(): +def test_no_warn_big_data_when_loc_specified(monkeypatch): + # Force _find_best_position to think it took a long time. + counter = itertools.count(0, step=1.5) + monkeypatch.setattr(time, 'perf_counter', lambda: next(counter)) + fig, ax = plt.subplots() fig.canvas.draw() - for idx in range(1000): - ax.plot(np.arange(5000), label=idx) + + # Place line across all possible legend locations. + x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5] + y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5] + ax.plot(x, y, 'o-', label='line') + legend = ax.legend('best') fig.draw_artist(legend) # Check that no warning is emitted. + + +@pytest.mark.parametrize('label_array', [['low', 'high'], + ('low', 'high'), + np.array(['low', 'high'])]) +def test_plot_multiple_input_multiple_label(label_array): + # test ax.plot() with multidimensional input + # and multiple labels + x = [1, 2, 3] + y = [[1, 2], + [2, 5], + [4, 9]] + + fig, ax = plt.subplots() + ax.plot(x, y, label=label_array) + leg = ax.legend() + legend_texts = [entry.get_text() for entry in leg.get_texts()] + assert legend_texts == ['low', 'high'] + + +@pytest.mark.parametrize('label', ['one', 1, int]) +def test_plot_multiple_input_single_label(label): + # test ax.plot() with multidimensional input + # and single label + x = [1, 2, 3] + y = [[1, 2], + [2, 5], + [4, 9]] + + fig, ax = plt.subplots() + ax.plot(x, y, label=label) + leg = ax.legend() + legend_texts = [entry.get_text() for entry in leg.get_texts()] + assert legend_texts == [str(label)] * 2 + + +def test_plot_single_input_multiple_label(): + # test ax.plot() with 1D array like input + # and iterable label + x = [1, 2, 3] + y = [2, 5, 6] + fig, ax = plt.subplots() + with pytest.raises(ValueError, + match='label must be scalar or have the same length'): + ax.plot(x, y, label=['low', 'high']) + + +def test_plot_single_input_list_label(): + fig, ax = plt.subplots() + line, = ax.plot([[0], [1]], label=['A']) + assert line.get_label() == 'A' + + +def test_plot_multiple_label_incorrect_length_exception(): + # check that exception is raised if multiple labels + # are given, but number of on labels != number of lines + with pytest.raises(ValueError): + x = [1, 2, 3] + y = [[1, 2], + [2, 5], + [4, 9]] + label = ['high', 'low', 'medium'] + fig, ax = plt.subplots() + ax.plot(x, y, label=label) + + +def test_legend_face_edgecolor(): + # Smoke test for PolyCollection legend handler with 'face' edgecolor. + fig, ax = plt.subplots() + ax.fill_between([0, 1, 2], [1, 2, 3], [2, 3, 4], + facecolor='r', edgecolor='face', label='Fill') + ax.legend() + + +def test_legend_text_axes(): + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4], label='line') + leg = ax.legend() + + assert leg.axes is ax + assert leg.get_texts()[0].axes is ax + + +def test_handlerline2d(): + # Test marker consistency for monolithic Line2D legend handler (#11357). + fig, ax = plt.subplots() + ax.scatter([0, 1], [0, 1], marker="v") + handles = [mlines.Line2D([0], [0], marker="v")] + leg = ax.legend(handles, ["Aardvark"], numpoints=1) + assert handles[0].get_marker() == leg.legend_handles[0].get_marker() + + +def test_subfigure_legend(): + # Test that legend can be added to subfigure (#20723) + subfig = plt.figure().subfigures() + ax = subfig.subplots() + ax.plot([0, 1], [0, 1], label="line") + leg = subfig.legend() + assert leg.get_figure(root=False) is subfig + + +def test_setting_alpha_keeps_polycollection_color(): + pc = plt.fill_between([0, 1], [2, 3], color='#123456', label='label') + patch = plt.legend().get_patches()[0] + patch.set_alpha(0.5) + assert patch.get_facecolor()[:3] == tuple(pc.get_facecolor()[0][:3]) + assert patch.get_edgecolor()[:3] == tuple(pc.get_edgecolor()[0][:3]) + + +def test_legend_markers_from_line2d(): + # Test that markers can be copied for legend lines (#17960) + _markers = ['.', '*', 'v'] + fig, ax = plt.subplots() + lines = [mlines.Line2D([0], [0], ls='None', marker=mark) + for mark in _markers] + labels = ["foo", "bar", "xyzzy"] + markers = [line.get_marker() for line in lines] + legend = ax.legend(lines, labels) + + new_markers = [line.get_marker() for line in legend.get_lines()] + new_labels = [text.get_text() for text in legend.get_texts()] + + assert markers == new_markers == _markers + assert labels == new_labels + + +@check_figures_equal() +def test_ncol_ncols(fig_test, fig_ref): + # Test that both ncol and ncols work + strings = ["a", "b", "c", "d", "e", "f"] + ncols = 3 + fig_test.legend(strings, ncol=ncols) + fig_ref.legend(strings, ncols=ncols) + + +def test_loc_invalid_tuple_exception(): + # check that exception is raised if the loc arg + # of legend is not a 2-tuple of numbers + fig, ax = plt.subplots() + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not \\(1.1,\\)')): + ax.legend(loc=(1.1, ), labels=["mock data"]) + + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not \\(0.481, 0.4227, 0.4523\\)')): + ax.legend(loc=(0.481, 0.4227, 0.4523), labels=["mock data"]) + + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not \\(0.481, \'go blue\'\\)')): + ax.legend(loc=(0.481, "go blue"), labels=["mock data"]) + + +def test_loc_valid_tuple(): + fig, ax = plt.subplots() + ax.legend(loc=(0.481, 0.442), labels=["mock data"]) + ax.legend(loc=(1, 2), labels=["mock data"]) + + +def test_loc_valid_list(): + fig, ax = plt.subplots() + ax.legend(loc=[0.481, 0.442], labels=["mock data"]) + ax.legend(loc=[1, 2], labels=["mock data"]) + + +def test_loc_invalid_list_exception(): + fig, ax = plt.subplots() + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not \\[1.1, 2.2, 3.3\\]')): + ax.legend(loc=[1.1, 2.2, 3.3], labels=["mock data"]) + + +def test_loc_invalid_type(): + fig, ax = plt.subplots() + with pytest.raises(ValueError, match=("loc must be string, coordinate " + "tuple, or an integer 0-10, not {'not': True}")): + ax.legend(loc={'not': True}, labels=["mock data"]) + + +def test_loc_validation_numeric_value(): + fig, ax = plt.subplots() + ax.legend(loc=0, labels=["mock data"]) + ax.legend(loc=1, labels=["mock data"]) + ax.legend(loc=5, labels=["mock data"]) + ax.legend(loc=10, labels=["mock data"]) + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not 11')): + ax.legend(loc=11, labels=["mock data"]) + + with pytest.raises(ValueError, match=('loc must be string, coordinate ' + 'tuple, or an integer 0-10, not -1')): + ax.legend(loc=-1, labels=["mock data"]) + + +def test_loc_validation_string_value(): + fig, ax = plt.subplots() + labels = ["mock data"] + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='upper left', labels=labels) + ax.legend(loc='lower left', labels=labels) + ax.legend(loc='lower right', labels=labels) + ax.legend(loc='right', labels=labels) + ax.legend(loc='center left', labels=labels) + ax.legend(loc='center right', labels=labels) + ax.legend(loc='lower center', labels=labels) + ax.legend(loc='upper center', labels=labels) + with pytest.raises(ValueError, match="'wrong' is not a valid value for"): + ax.legend(loc='wrong', labels=labels) + + +def test_legend_handle_label_mismatch(): + pl1, = plt.plot(range(10)) + pl2, = plt.plot(range(10)) + with pytest.warns(UserWarning, match="number of handles and labels"): + legend = plt.legend(handles=[pl1, pl2], labels=["pl1", "pl2", "pl3"]) + assert len(legend.legend_handles) == 2 + assert len(legend.get_texts()) == 2 + + +def test_legend_handle_label_mismatch_no_len(): + pl1, = plt.plot(range(10)) + pl2, = plt.plot(range(10)) + legend = plt.legend(handles=iter([pl1, pl2]), + labels=iter(["pl1", "pl2", "pl3"])) + assert len(legend.legend_handles) == 2 + assert len(legend.get_texts()) == 2 + + +def test_legend_nolabels_warning(): + plt.plot([1, 2, 3]) + with pytest.raises(UserWarning, match="No artists with labels found"): + plt.legend() + + +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") +def test_legend_nolabels_draw(): + plt.plot([1, 2, 3]) + plt.legend() + assert plt.gca().get_legend() is not None + + +def test_legend_loc_polycollection(): + # Test that the legend is placed in the correct + # position for 'best' for polycollection + x = [3, 4, 5] + y1 = [1, 1, 1] + y2 = [5, 5, 5] + leg_bboxes = [] + fig, axs = plt.subplots(ncols=2, figsize=(10, 5)) + for ax, loc in zip(axs.flat, ('best', 'lower left')): + ax.fill_between(x, y1, y2, color='gray', alpha=0.5, label='Shaded Area') + ax.set_xlim(0, 6) + ax.set_ylim(-1, 5) + leg = ax.legend(loc=loc) + fig.canvas.draw() + leg_bboxes.append( + leg.get_window_extent().transformed(ax.transAxes.inverted())) + assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) + + +def test_legend_text(): + # Test that legend is place in the correct + # position for 'best' when there is text in figure + fig, axs = plt.subplots(ncols=2, figsize=(10, 5)) + leg_bboxes = [] + for ax, loc in zip(axs.flat, ('best', 'lower left')): + x = [1, 2] + y = [2, 1] + ax.plot(x, y, label='plot name') + ax.text(1.5, 2, 'some text blahblah', verticalalignment='top') + leg = ax.legend(loc=loc) + fig.canvas.draw() + leg_bboxes.append( + leg.get_window_extent().transformed(ax.transAxes.inverted())) + assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) + + +def test_legend_annotate(): + fig, ax = plt.subplots() + + ax.plot([1, 2, 3], label="Line") + ax.annotate("a", xy=(1, 1)) + ax.legend(loc=0) + + with mock.patch.object( + fig, '_get_renderer', wraps=fig._get_renderer) as mocked_get_renderer: + fig.savefig(io.BytesIO()) + + # Finding the legend position should not require _get_renderer to be called + mocked_get_renderer.assert_not_called() + + +def test_boxplot_legend_labels(): + # Test that legend entries are generated when passing `label`. + np.random.seed(19680801) + data = np.random.random((10, 4)) + fig, axs = plt.subplots(nrows=1, ncols=4) + legend_labels = ['box A', 'box B', 'box C', 'box D'] + + # Testing legend labels and patch passed to legend. + bp1 = axs[0].boxplot(data, patch_artist=True, label=legend_labels) + assert [v.get_label() for v in bp1['boxes']] == legend_labels + handles, labels = axs[0].get_legend_handles_labels() + assert labels == legend_labels + assert all(isinstance(h, mpl.patches.PathPatch) for h in handles) + + # Testing legend without `box`. + bp2 = axs[1].boxplot(data, label=legend_labels, showbox=False) + # Without a box, The legend entries should be passed from the medians. + assert [v.get_label() for v in bp2['medians']] == legend_labels + handles, labels = axs[1].get_legend_handles_labels() + assert labels == legend_labels + assert all(isinstance(h, mpl.lines.Line2D) for h in handles) + + # Testing legend with number of labels different from number of boxes. + with pytest.raises(ValueError, match='values must have same the length'): + bp3 = axs[2].boxplot(data, label=legend_labels[:-1]) + + # Test that for a string label, only the first box gets a label. + bp4 = axs[3].boxplot(data, label='box A') + assert bp4['medians'][0].get_label() == 'box A' + assert all(x.get_label().startswith("_") for x in bp4['medians'][1:]) diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 0fe5b58a0de4..68ee1ff8a9a6 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -3,7 +3,9 @@ """ import itertools +import platform import timeit +from types import SimpleNamespace from cycler import cycler import numpy as np @@ -11,10 +13,13 @@ import pytest import matplotlib +import matplotlib as mpl +from matplotlib import _path import matplotlib.lines as mlines from matplotlib.markers import MarkerStyle from matplotlib.path import Path import matplotlib.pyplot as plt +import matplotlib.transforms as mtransforms from matplotlib.testing.decorators import image_comparison, check_figures_equal @@ -47,7 +52,7 @@ def test_invisible_Line_rendering(): # Create a plot figure: fig = plt.figure() - ax = plt.subplot(111) + ax = plt.subplot() # Create a "big" Line instance: l = mlines.Line2D(x, y) @@ -73,25 +78,37 @@ def test_invisible_Line_rendering(): def test_set_line_coll_dash(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() np.random.seed(0) # Testing setting linestyles for line collections. # This should not produce an error. ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))]) -@image_comparison(['line_dashes'], remove_text=True) +def test_invalid_line_data(): + with pytest.raises(RuntimeError, match='xdata must be'): + mlines.Line2D(0, []) + with pytest.raises(RuntimeError, match='ydata must be'): + mlines.Line2D([], 1) + + line = mlines.Line2D([], []) + with pytest.raises(RuntimeError, match='x must be'): + line.set_xdata(0) + with pytest.raises(RuntimeError, match='y must be'): + line.set_ydata(0) + + +@image_comparison(['line_dashes'], remove_text=True, tol=0.003) def test_line_dashes(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + # Tolerance introduced after reordering of floating-point operations + # Remove when regenerating the images + fig, ax = plt.subplots() ax.plot(range(10), linestyle=(0, (3, 3)), lw=5) def test_line_colors(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.plot(range(10), color='none') ax.plot(range(10), color='r') ax.plot(range(10), color='.3') @@ -107,10 +124,11 @@ def test_valid_colors(): def test_linestyle_variants(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() for ls in ["-", "solid", "--", "dashed", - "-.", "dashdot", ":", "dotted"]: + "-.", "dashdot", ":", "dotted", + (0, None), (0, ()), (0, []), # gh-22930 + ]: ax.plot(range(10), linestyle=ls) fig.canvas.draw() @@ -121,7 +139,8 @@ def test_valid_linestyles(): line.set_linestyle('aardvark') -@image_comparison(['drawstyle_variants.png'], remove_text=True) +@image_comparison(['drawstyle_variants.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.03) def test_drawstyle_variants(): fig, axs = plt.subplots(6) dss = ["default", "steps-mid", "steps-pre", "steps-post", "steps", None] @@ -134,6 +153,17 @@ def test_drawstyle_variants(): ax.set(xlim=(0, 2), ylim=(0, 2)) +@check_figures_equal() +def test_no_subslice_with_transform(fig_ref, fig_test): + ax = fig_ref.add_subplot() + x = np.arange(2000) + ax.plot(x + 2000, x) + + ax = fig_test.add_subplot() + t = mtransforms.Affine2D().translate(2000.0, 0.0) + ax.plot(x, x, transform=t+ax.transData) + + def test_valid_drawstyles(): line = mlines.Line2D([], []) with pytest.raises(ValueError): @@ -153,10 +183,10 @@ def test_set_drawstyle(): assert len(line.get_path().vertices) == len(x) -@image_comparison(['line_collection_dashes'], remove_text=True, style='mpl20') +@image_comparison(['line_collection_dashes'], remove_text=True, style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.65) def test_set_line_coll_dash_image(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() np.random.seed(0) ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))]) @@ -171,7 +201,11 @@ def test_marker_fill_styles(): x = np.array([0, 9]) fig, ax = plt.subplots() - for j, marker in enumerate(mlines.Line2D.filled_markers): + # This hard-coded list of markers correspond to an earlier iteration of + # MarkerStyle.filled_markers; the value of that attribute has changed but + # we kept the old value here to not regenerate the baseline image. + # Replace with mlines.Line2D.filled_markers when the image is regenerated. + for j, marker in enumerate("ov^<>8sp*hHDdPX"): for i, fs in enumerate(mlines.Line2D.fillStyles): color = next(colors) ax.plot(j * 10 + x, y + i + .5 * (j % 2), @@ -208,11 +242,14 @@ def test_lw_scaling(): ax.plot(th, j*np.ones(50) + .1 * lw, linestyle=ls, lw=lw, **sty) -def test_nan_is_sorted(): - line = mlines.Line2D([], []) - assert line._is_sorted(np.array([1, 2, 3])) - assert line._is_sorted(np.array([1, np.nan, 3])) - assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2]) +def test_is_sorted_and_has_non_nan(): + assert _path.is_sorted_and_has_non_nan(np.array([1, 2, 3])) + assert _path.is_sorted_and_has_non_nan(np.array([1, np.nan, 3])) + assert not _path.is_sorted_and_has_non_nan([3, 5] + [np.nan] * 100 + [0, 2]) + # [2, 256] byteswapped: + assert not _path.is_sorted_and_has_non_nan(np.array([33554432, 65536], ">i4")) + n = 2 * mlines.Line2D._subslice_optim_min_size + plt.plot([np.nan] * n, range(n)) @check_figures_equal() @@ -221,28 +258,57 @@ def test_step_markers(fig_test, fig_ref): fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2]) -@check_figures_equal(extensions=('png',)) -def test_markevery(fig_test, fig_ref): +@pytest.mark.parametrize("parent", ["figure", "axes"]) +@check_figures_equal() +def test_markevery(fig_test, fig_ref, parent): np.random.seed(42) - t = np.linspace(0, 3, 14) - y = np.random.rand(len(t)) + x = np.linspace(0, 1, 14) + y = np.random.rand(len(x)) + + cases_test = [None, 4, (2, 5), [1, 5, 11], + [0, -1], slice(5, 10, 2), + np.arange(len(x))[y > 0.5], + 0.3, (0.3, 0.4)] + cases_ref = ["11111111111111", "10001000100010", "00100001000010", + "01000100000100", "10000000000001", "00000101010000", + "01110001110110", "11011011011110", "01010011011101"] + + if parent == "figure": + # float markevery ("relative to axes size") is not supported. + cases_test = cases_test[:-2] + cases_ref = cases_ref[:-2] + + def add_test(x, y, *, markevery): + fig_test.add_artist( + mlines.Line2D(x, y, marker="o", markevery=markevery)) + + def add_ref(x, y, *, markevery): + fig_ref.add_artist( + mlines.Line2D(x, y, marker="o", markevery=markevery)) + + elif parent == "axes": + axs_test = iter(fig_test.subplots(3, 3).flat) + axs_ref = iter(fig_ref.subplots(3, 3).flat) - casesA = [None, 4, (2, 5), [1, 5, 11], - [0, -1], slice(5, 10, 2), 0.3, (0.3, 0.4), - np.arange(len(t))[y > 0.5]] - casesB = ["11111111111111", "10001000100010", "00100001000010", - "01000100000100", "10000000000001", "00000101010000", - "11011011011110", "01010011011101", "01110001110110"] + def add_test(x, y, *, markevery): + next(axs_test).plot(x, y, "-gD", markevery=markevery) - axsA = fig_ref.subplots(3, 3) - axsB = fig_test.subplots(3, 3) + def add_ref(x, y, *, markevery): + next(axs_ref).plot(x, y, "-gD", markevery=markevery) - for ax, case in zip(axsA.flat, casesA): - ax.plot(t, y, "-gD", markevery=case) + for case in cases_test: + add_test(x, y, markevery=case) - for ax, case in zip(axsB.flat, casesB): + for case in cases_ref: me = np.array(list(case)).astype(int).astype(bool) - ax.plot(t, y, "-gD", markevery=me) + add_ref(x, y, markevery=me) + + +def test_markevery_figure_line_unsupported_relsize(): + fig = plt.figure() + fig.add_artist(mlines.Line2D([0, 1], [0, 1], marker="o", markevery=.5)) + with pytest.raises(ValueError): + fig.canvas.draw() def test_marker_as_markerstyle(): @@ -257,7 +323,7 @@ def test_marker_as_markerstyle(): line.set_marker(MarkerStyle("o")) fig.canvas.draw() # test Path roundtrip - triangle1 = Path([[-1., -1.], [1., -1.], [0., 2.], [0., 0.]], closed=True) + triangle1 = Path._create_closed([[-1, -1], [1, -1], [0, 2]]) line2, = ax.plot([1, 3, 2], marker=MarkerStyle(triangle1), ms=22) line3, = ax.plot([0, 2, 1], marker=triangle1, ms=22) @@ -265,7 +331,122 @@ def test_marker_as_markerstyle(): assert_array_equal(line3.get_marker().vertices, triangle1.vertices) +@image_comparison(['striped_line.png'], remove_text=True, style='mpl20') +def test_striped_lines(text_placeholders): + rng = np.random.default_rng(19680801) + _, ax = plt.subplots() + ax.plot(rng.uniform(size=12), color='orange', gapcolor='blue', + linestyle='--', lw=5, label='blue in orange') + ax.plot(rng.uniform(size=12), color='red', gapcolor='black', + linestyle=(0, (2, 5, 4, 2)), lw=5, label='black in red', alpha=0.5) + ax.legend(handlelength=5) + + @check_figures_equal() def test_odd_dashes(fig_test, fig_ref): fig_test.add_subplot().plot([1, 2], dashes=[1, 2, 3]) fig_ref.add_subplot().plot([1, 2], dashes=[1, 2, 3, 1, 2, 3]) + + +def test_picking(): + fig, ax = plt.subplots() + mouse_event = SimpleNamespace(x=fig.bbox.width // 2, + y=fig.bbox.height // 2 + 15) + + # Default pickradius is 5, so event should not pick this line. + l0, = ax.plot([0, 1], [0, 1], picker=True) + found, indices = l0.contains(mouse_event) + assert not found + + # But with a larger pickradius, this should be picked. + l1, = ax.plot([0, 1], [0, 1], picker=True, pickradius=20) + found, indices = l1.contains(mouse_event) + assert found + assert_array_equal(indices['ind'], [0]) + + # And if we modify the pickradius after creation, it should work as well. + l2, = ax.plot([0, 1], [0, 1], picker=True) + found, indices = l2.contains(mouse_event) + assert not found + l2.set_pickradius(20) + found, indices = l2.contains(mouse_event) + assert found + assert_array_equal(indices['ind'], [0]) + + +@check_figures_equal() +def test_input_copy(fig_test, fig_ref): + + t = np.arange(0, 6, 2) + l, = fig_test.add_subplot().plot(t, t, ".-") + t[:] = range(3) + # Trigger cache invalidation + l.set_drawstyle("steps") + fig_ref.add_subplot().plot([0, 2, 4], [0, 2, 4], ".-", drawstyle="steps") + + +@check_figures_equal() +def test_markevery_prop_cycle(fig_test, fig_ref): + """Test that we can set markevery prop_cycle.""" + cases = [None, 8, (30, 8), [16, 24, 30], [0, -1], + slice(100, 200, 3), 0.1, 0.3, 1.5, + (0.0, 0.1), (0.45, 0.1)] + + cmap = mpl.colormaps['jet'] + colors = cmap(np.linspace(0.2, 0.8, len(cases))) + + x = np.linspace(-1, 1) + y = 5 * x**2 + + axs = fig_ref.add_subplot() + for i, markevery in enumerate(cases): + axs.plot(y - i, 'o-', markevery=markevery, color=colors[i]) + + matplotlib.rcParams['axes.prop_cycle'] = cycler(markevery=cases, + color=colors) + + ax = fig_test.add_subplot() + for i, _ in enumerate(cases): + ax.plot(y - i, 'o-') + + +def test_axline_setters(): + fig, ax = plt.subplots() + line1 = ax.axline((.1, .1), slope=0.6) + line2 = ax.axline((.1, .1), (.8, .4)) + # Testing xy1, xy2 and slope setters. + # This should not produce an error. + line1.set_xy1((.2, .3)) + line1.set_slope(2.4) + line2.set_xy1((.3, .2)) + line2.set_xy2((.6, .8)) + # Testing xy1, xy2 and slope getters. + # Should return the modified values. + assert line1.get_xy1() == (.2, .3) + assert line1.get_slope() == 2.4 + assert line2.get_xy1() == (.3, .2) + assert line2.get_xy2() == (.6, .8) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + line1.set_xy1(.2, .3) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + line2.set_xy2(.6, .8) + # Testing setting xy2 and slope together. + # These test should raise a ValueError + with pytest.raises(ValueError, + match="Cannot set an 'xy2' value while 'slope' is set"): + line1.set_xy2(.2, .3) + + with pytest.raises(ValueError, + match="Cannot set a 'slope' value while 'xy2' is set"): + line2.set_slope(3) + + +def test_axline_small_slope(): + """Test that small slopes are not coerced to zero in the transform.""" + line = plt.axline((0, 0), slope=1e-14) + p1 = line.get_transform().transform_point((0, 0)) + p2 = line.get_transform().transform_point((1, 1)) + # y-values must be slightly different + dy = p2[1] - p1[1] + assert dy > 0 + assert dy < 4e-12 diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 1ac7aaa0d0d8..f6e20c148897 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -3,6 +3,7 @@ from matplotlib import markers from matplotlib.path import Path from matplotlib.testing.decorators import check_figures_equal +from matplotlib.transforms import Affine2D import pytest @@ -13,27 +14,41 @@ def test_marker_fillstyle(): assert not marker_style.is_filled() -def test_markers_valid(): - marker_style = markers.MarkerStyle() - mrk_array = np.array([[-0.5, 0], - [0.5, 0]]) +@pytest.mark.parametrize('marker', [ + 'o', + 'x', + '', + 'None', + r'$\frac{1}{2}$', + "$\u266B$", + 1, + markers.TICKLEFT, + [[-1, 0], [1, 0]], + np.array([[-1, 0], [1, 0]]), + Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO]), + (5, 0), # a pentagon + (7, 1), # a 7-pointed star + (5, 2), # asterisk + (5, 0, 10), # a pentagon, rotated by 10 degrees + (7, 1, 10), # a 7-pointed star, rotated by 10 degrees + (5, 2, 10), # asterisk, rotated by 10 degrees + markers.MarkerStyle('o'), +]) +def test_markers_valid(marker): # Checking this doesn't fail. - marker_style.set_marker(mrk_array) + markers.MarkerStyle(marker) -def test_markers_invalid(): - marker_style = markers.MarkerStyle() - mrk_array = np.array([[-0.5, 0, 1, 2, 3]]) - # Checking this does fail. +@pytest.mark.parametrize('marker', [ + 'square', # arbitrary string + np.array([[-0.5, 0, 1, 2, 3]]), # 1D array + (1,), + (5, 3), # second parameter of tuple must be 0, 1, or 2 + (1, 2, 3, 4), +]) +def test_markers_invalid(marker): with pytest.raises(ValueError): - marker_style.set_marker(mrk_array) - - -def test_marker_path(): - marker_style = markers.MarkerStyle() - path = Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) - # Checking this doesn't fail. - marker_style.set_marker(path) + markers.MarkerStyle(marker) class UnsnappedMarkerStyle(markers.MarkerStyle): @@ -48,7 +63,7 @@ def _recache(self): self._snap_threshold = None -@check_figures_equal() +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_poly_marker(fig_test, fig_ref): ax_test = fig_test.add_subplot() ax_ref = fig_ref.add_subplot() @@ -108,7 +123,7 @@ def test_star_marker(): # are corners and get a slight bevel. The reference markers are just singular # lines without corners, so they have no bevel, and we need to add a slight # tolerance. -@check_figures_equal(tol=1.45) +@check_figures_equal(extensions=['png', 'pdf', 'svg'], tol=1.45) def test_asterisk_marker(fig_test, fig_ref, request): ax_test = fig_test.add_subplot() ax_ref = fig_ref.add_subplot() @@ -141,7 +156,19 @@ def draw_ref_marker(y, style, size): ax_ref.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5)) -@check_figures_equal() +# The bullet mathtext marker is not quite a circle, so this is not a perfect match, but +# it is close enough to confirm that the text-based marker is centred correctly. But we +# still need a small tolerance to work around that difference. +@check_figures_equal(tol=1.86) +def test_text_marker(fig_ref, fig_test): + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + ax_ref.plot(0, 0, marker=r'o', markersize=100, markeredgewidth=0) + ax_test.plot(0, 0, marker=r'$\bullet$', markersize=100, markeredgewidth=0) + + +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_marker_clipping(fig_ref, fig_test): # Plotting multiple markers can trigger different optimized paths in # backends, so compare single markers vs multiple to ensure they are @@ -180,3 +207,97 @@ def test_marker_clipping(fig_ref, fig_test): ax_test.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow)) ax_ref.axis('off') ax_test.axis('off') + + +def test_marker_init_transforms(): + """Test that initializing marker with transform is a simple addition.""" + marker = markers.MarkerStyle("o") + t = Affine2D().translate(1, 1) + t_marker = markers.MarkerStyle("o", transform=t) + assert marker.get_transform() + t == t_marker.get_transform() + + +def test_marker_init_joinstyle(): + marker = markers.MarkerStyle("*") + styled_marker = markers.MarkerStyle("*", joinstyle="round") + assert styled_marker.get_joinstyle() == "round" + assert marker.get_joinstyle() != "round" + + +def test_marker_init_captyle(): + marker = markers.MarkerStyle("*") + styled_marker = markers.MarkerStyle("*", capstyle="round") + assert styled_marker.get_capstyle() == "round" + assert marker.get_capstyle() != "round" + + +@pytest.mark.parametrize("marker,transform,expected", [ + (markers.MarkerStyle("o"), Affine2D().translate(1, 1), + Affine2D().translate(1, 1)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), +]) +def test_marker_transformed(marker, transform, expected): + new_marker = marker.transformed(transform) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_rotated_invalid(): + marker = markers.MarkerStyle("o") + with pytest.raises(ValueError): + new_marker = marker.rotated() + with pytest.raises(ValueError): + new_marker = marker.rotated(deg=10, rad=10) + + +@pytest.mark.parametrize("marker,deg,rad,expected", [ + (markers.MarkerStyle("o"), 10, None, Affine2D().rotate_deg(10)), + (markers.MarkerStyle("o"), None, 0.01, Affine2D().rotate(0.01)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + None, 0.01, Affine2D().translate(1, 1).rotate(0.01)), + (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), +]) +def test_marker_rotated(marker, deg, rad, expected): + new_marker = marker.rotated(deg=deg, rad=rad) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_scaled(): + marker = markers.MarkerStyle("1") + new_marker = marker.scaled(2) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2) + assert marker._user_transform is not new_marker._user_transform + + new_marker = marker.scaled(2, 3) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2, 3) + assert marker._user_transform is not new_marker._user_transform + + marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) + new_marker = marker.scaled(2) + assert new_marker is not marker + expected = Affine2D().translate(1, 1).scale(2) + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform + + +def test_alt_transform(): + m1 = markers.MarkerStyle("o", "left") + m2 = markers.MarkerStyle("o", "left", Affine2D().rotate_deg(90)) + assert m1.get_alt_transform().rotate_deg(90) == m2.get_alt_transform() diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 1b4d3336f19e..39c28dc9228c 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -1,20 +1,32 @@ +from __future__ import annotations + import io -import os +from pathlib import Path +import platform import re +import textwrap +from typing import Any +from xml.etree import ElementTree as ET import numpy as np +from packaging.version import parse as parse_version +import pyparsing import pytest + import matplotlib as mpl from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.pyplot as plt -from matplotlib import mathtext +from matplotlib import font_manager as fm, mathtext, _mathtext +from matplotlib.ft2font import LoadFlags + +pyparsing_version = parse_version(pyparsing.__version__) # If test is removed, use None as placeholder math_tests = [ r'$a+b+\dot s+\dot{s}+\ldots$', - r'$x \doteq y$', + r'$x\hspace{-0.2}\doteq\hspace{-0.2}y$', r'\$100.00 $\alpha \_$', r'$\frac{\$100.00}{y}$', r'$x y$', @@ -31,11 +43,13 @@ r'$x^2$', r'$x^2_y$', r'$x_y^2$', - r'$\prod_{i=\alpha_{i+1}}^\infty$', + (r'$\sum _{\genfrac{}{}{0}{}{0\leq i\leq m}{0 M \: M \; M \ M \enspace M \quad M \qquad M \! M$', - r'$\Cup$ $\Cap$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$', - r'$\dotplus$ $\doteq$ $\doteqdot$ $\ddots$', + r'$\Cap$ $\Cup$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$', + r'$\hspace{-0.2}\dotplus\hspace{-0.2}$ $\hspace{-0.2}\doteq\hspace{-0.2}$ $\hspace{-0.2}\doteqdot\hspace{-0.2}$ $\ddots$', r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873 r'${xyz}^k{x}_{k}{x}^{p}{y}^{p-2} {d}_{i}^{j}{b}_{j}{c}_{k}{d} {x}^{j}_{i}{E}^{0}{E}^0_u$', r'${\int}_x^x x\oint_x^x x\int_{X}^{X}x\int_x x \int^x x \int_{x} x\int^{x}{\int}_{x} x{\int}^{x}_{x}x$', r'testing$^{123}$', - ' '.join('$\\' + p + '$' for p in sorted(mathtext.Parser._accentprefixed)), + None, r'$6-2$; $-2$; $ -2$; ${-2}$; ${ -2}$; $20^{+3}_{-2}$', r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444 r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799 r'$\left(X\right)_{a}^{b}$', # github issue 7615 r'$\dfrac{\$100.00}{y}$', # github issue #1888 + r'$a=-b-c$' # github issue #28180 +] +# 'svgastext' tests switch svg output to embed text as text (rather than as +# paths). +svgastext_math_tests = [ + r'$-$-', +] +# 'lightweight' tests test only a single fontset (dejavusans, which is the +# default) and only png outputs, in order to minimize the size of baseline +# images. +lightweight_math_tests = [ + r'$\sqrt[ab]{123}$', # github issue #8665 + r'$x \overset{f}{\rightarrow} \overset{f}{x} \underset{xx}{ff} \overset{xx}{ff} \underset{f}{x} \underset{f}{\leftarrow} x$', # github issue #18241 + r'$\sum x\quad\sum^nx\quad\sum_nx\quad\sum_n^nx\quad\prod x\quad\prod^nx\quad\prod_nx\quad\prod_n^nx$', # GitHub issue 18085 + r'$1.$ $2.$ $19680801.$ $a.$ $b.$ $mpl.$', + r'$\text{text}_{\text{sub}}^{\text{sup}} + \text{\$foo\$} + \frac{\text{num}}{\mathbf{\text{den}}}\text{with space, curly brackets \{\}, and dash -}$', + r'$\boldsymbol{abcde} \boldsymbol{+} \boldsymbol{\Gamma + \Omega} \boldsymbol{01234} \boldsymbol{\alpha * \beta}$', + r'$\left\lbrace\frac{\left\lbrack A^b_c\right\rbrace}{\left\leftbrace D^e_f \right\rbrack}\right\rightbrace\ \left\leftparen\max_{x} \left\lgroup \frac{A}{B}\right\rgroup \right\rightparen$', + r'$\left( a\middle. b \right)$ $\left( \frac{a}{b} \middle\vert x_i \in P^S \right)$ $\left[ 1 - \middle| a\middle| + \left( x - \left\lfloor \dfrac{a}{b}\right\rfloor \right) \right]$', + r'$\sum_{\substack{k = 1\\ k \neq \lfloor n/2\rfloor}}^{n}P(i,j) \sum_{\substack{i \neq 0\\ -1 \leq i \leq 3\\ 1 \leq j \leq 5}} F^i(x,y) \sum_{\substack{\left \lfloor \frac{n}{2} \right\rfloor}} F(n)$', ] digits = "0123456789" @@ -125,7 +161,7 @@ # stub should be of the form (None, N) where N is the number of strings that # used to be tested # Add new tests at the end. -font_test_specs = [ +font_test_specs: list[tuple[None | list[str], Any]] = [ ([], all), (['mathrm'], all), (['mathbf'], all), @@ -146,10 +182,11 @@ (['mathscr'], [uppercase, lowercase]), (['mathsf'], [digits, uppercase, lowercase]), (['mathrm', 'mathsf'], [digits, uppercase, lowercase]), - (['mathbf', 'mathsf'], [digits, uppercase, lowercase]) + (['mathbf', 'mathsf'], [digits, uppercase, lowercase]), + (['mathbfit'], all), ] -font_tests = [] +font_tests: list[None | str] = [] for fonts, chars in font_test_specs: if fonts is None: font_tests.extend([None] * chars) @@ -162,64 +199,106 @@ *('}' for font in fonts), '$', ]) - for set in chars: - font_tests.append(wrapper % set) - -font_tests = list(filter(lambda x: x[1] is not None, enumerate(font_tests))) + for font_set in chars: + font_tests.append(wrapper % font_set) @pytest.fixture -def baseline_images(request, fontset, index): +def baseline_images(request, fontset, index, text): + if text is None: + pytest.skip("test has been removed") return ['%s_%s_%02d' % (request.param, fontset, index)] -cur_math_tests = list(filter(lambda x: x[1] is not None, enumerate(math_tests))) +@pytest.mark.parametrize( + 'index, text', enumerate(math_tests), ids=range(len(math_tests))) +@pytest.mark.parametrize( + 'fontset', ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif']) +@pytest.mark.parametrize('baseline_images', ['mathtext'], indirect=True) +@image_comparison(baseline_images=None, + tol=0.011 if platform.machine() in ('ppc64le', 's390x') else 0) +def test_mathtext_rendering(baseline_images, fontset, index, text): + mpl.rcParams['mathtext.fontset'] = fontset + fig = plt.figure(figsize=(5.25, 0.75)) + fig.text(0.5, 0.5, text, + horizontalalignment='center', verticalalignment='center') -@pytest.mark.parametrize('index, test', cur_math_tests, - ids=[str(index) for index, _ in cur_math_tests]) -@pytest.mark.parametrize('fontset', - ['cm', 'stix', 'stixsans', 'dejavusans', - 'dejavuserif']) -@pytest.mark.parametrize('baseline_images', ['mathtext'], indirect=True) -@image_comparison(baseline_images=None) -def test_mathtext_rendering(baseline_images, fontset, index, test): +@pytest.mark.parametrize('index, text', enumerate(svgastext_math_tests), + ids=range(len(svgastext_math_tests))) +@pytest.mark.parametrize('fontset', ['cm', 'dejavusans']) +@pytest.mark.parametrize('baseline_images', ['mathtext0'], indirect=True) +@image_comparison( + baseline_images=None, extensions=['svg'], + savefig_kwarg={'metadata': { # Minimize image size. + 'Creator': None, 'Date': None, 'Format': None, 'Type': None}}) +def test_mathtext_rendering_svgastext(baseline_images, fontset, index, text): mpl.rcParams['mathtext.fontset'] = fontset + mpl.rcParams['svg.fonttype'] = 'none' # Minimize image size. fig = plt.figure(figsize=(5.25, 0.75)) - fig.text(0.5, 0.5, test, + fig.patch.set(visible=False) # Minimize image size. + fig.text(0.5, 0.5, text, horizontalalignment='center', verticalalignment='center') -@pytest.mark.parametrize('index, test', font_tests, - ids=[str(index) for index, _ in font_tests]) -@pytest.mark.parametrize('fontset', - ['cm', 'stix', 'stixsans', 'dejavusans', - 'dejavuserif']) -@pytest.mark.parametrize('baseline_images', ['mathfont'], indirect=True) +@pytest.mark.parametrize('index, text', enumerate(lightweight_math_tests), + ids=range(len(lightweight_math_tests))) +@pytest.mark.parametrize('fontset', ['dejavusans']) +@pytest.mark.parametrize('baseline_images', ['mathtext1'], indirect=True) @image_comparison(baseline_images=None, extensions=['png']) -def test_mathfont_rendering(baseline_images, fontset, index, test): +def test_mathtext_rendering_lightweight(baseline_images, fontset, index, text): + fig = plt.figure(figsize=(5.25, 0.75)) + fig.text(0.5, 0.5, text, math_fontfamily=fontset, + horizontalalignment='center', verticalalignment='center') + + +@pytest.mark.parametrize( + 'index, text', enumerate(font_tests), ids=range(len(font_tests))) +@pytest.mark.parametrize( + 'fontset', ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif']) +@pytest.mark.parametrize('baseline_images', ['mathfont'], indirect=True) +@image_comparison(baseline_images=None, extensions=['png'], + tol=0.011 if platform.machine() in ('ppc64le', 's390x') else 0) +def test_mathfont_rendering(baseline_images, fontset, index, text): mpl.rcParams['mathtext.fontset'] = fontset fig = plt.figure(figsize=(5.25, 0.75)) - fig.text(0.5, 0.5, test, + fig.text(0.5, 0.5, text, horizontalalignment='center', verticalalignment='center') +@check_figures_equal() +def test_short_long_accents(fig_test, fig_ref): + acc_map = _mathtext.Parser._accent_map + short_accs = [s for s in acc_map if len(s) == 1] + corresponding_long_accs = [] + for s in short_accs: + l, = (l for l in acc_map if len(l) > 1 and acc_map[l] == acc_map[s]) + corresponding_long_accs.append(l) + fig_test.text(0, .5, "$" + "".join(rf"\{s}a" for s in short_accs) + "$") + fig_ref.text( + 0, .5, "$" + "".join(fr"\{l} a" for l in corresponding_long_accs) + "$") + + def test_fontinfo(): fontpath = mpl.font_manager.findfont("DejaVu Sans") font = mpl.ft2font.FT2Font(fontpath) table = font.get_sfnt_table("head") + assert table is not None assert table['version'] == (1, 0) +# See gh-26152 for more context on this xfail +@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0), + reason="Error messages are incorrect for this version") @pytest.mark.parametrize( 'math, msg', [ - (r'$\hspace{}$', r'Expected \hspace{n}'), - (r'$\hspace{foo}$', r'Expected \hspace{n}'), + (r'$\hspace{}$', r'Expected \hspace{space}'), + (r'$\hspace{foo}$', r'Expected \hspace{space}'), + (r'$\sinx$', r'Unknown symbol: \sinx'), + (r'$\dotx$', r'Unknown symbol: \dotx'), (r'$\frac$', r'Expected \frac{num}{den}'), (r'$\frac{}{}$', r'Expected \frac{num}{den}'), - (r'$\stackrel$', r'Expected \stackrel{num}{den}'), - (r'$\stackrel{}{}$', r'Expected \stackrel{num}{den}'), (r'$\binom$', r'Expected \binom{num}{den}'), (r'$\binom{}{}$', r'Expected \binom{num}{den}'), (r'$\genfrac$', @@ -228,22 +307,31 @@ def test_fontinfo(): r'Expected \genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}'), (r'$\sqrt$', r'Expected \sqrt{value}'), (r'$\sqrt f$', r'Expected \sqrt{value}'), - (r'$\overline$', r'Expected \overline{value}'), - (r'$\overline{}$', r'Expected \overline{value}'), + (r'$\overline$', r'Expected \overline{body}'), + (r'$\overline{}$', r'Expected \overline{body}'), (r'$\leftF$', r'Expected a delimiter'), (r'$\rightF$', r'Unknown symbol: \rightF'), (r'$\left(\right$', r'Expected a delimiter'), - (r'$\left($', r'Expected "\right"'), + # PyParsing 2 uses double quotes, PyParsing 3 uses single quotes and an + # extra backslash. + (r'$\left($', re.compile(r'Expected ("|\'\\)\\right["\']')), (r'$\dfrac$', r'Expected \dfrac{num}{den}'), (r'$\dfrac{}{}$', r'Expected \dfrac{num}{den}'), + (r'$\overset$', r'Expected \overset{annotation}{body}'), + (r'$\underset$', r'Expected \underset{annotation}{body}'), + (r'$\foo$', r'Unknown symbol: \foo'), + (r'$a^2^2$', r'Double superscript'), + (r'$a_2_2$', r'Double subscript'), + (r'$a^2_a^2$', r'Double superscript'), + (r'$a = {b$', r"Expected '}'"), ], ids=[ 'hspace without value', 'hspace with invalid value', + 'function without space', + 'accent without space', 'frac without parameters', 'frac with empty parameters', - 'stackrel without parameters', - 'stackrel with empty parameters', 'binom without parameters', 'binom with empty parameters', 'genfrac without parameters', @@ -258,38 +346,42 @@ def test_fontinfo(): 'unclosed parentheses without sizing', 'dfrac without parameters', 'dfrac with empty parameters', + 'overset without parameters', + 'underset without parameters', + 'unknown symbol', + 'double superscript', + 'double subscript', + 'super on sub without braces', + 'unclosed group', ] ) def test_mathtext_exceptions(math, msg): parser = mathtext.MathTextParser('agg') - - with pytest.raises(ValueError, match=re.escape(msg)): + match = re.escape(msg) if isinstance(msg, str) else msg + with pytest.raises(ValueError, match=match): parser.parse(math) -def test_single_minus_sign(): - plt.figure(figsize=(0.3, 0.3)) - plt.text(0.5, 0.5, '$-$') - for spine in plt.gca().spines.values(): - spine.set_visible(False) - plt.gca().set_xticks([]) - plt.gca().set_yticks([]) +def test_get_unicode_index_exception(): + with pytest.raises(ValueError): + _mathtext.get_unicode_index(r'\foo') - buff = io.BytesIO() - plt.savefig(buff, format="rgba", dpi=1000) - array = np.frombuffer(buff.getvalue(), dtype=np.uint8) - # If this fails, it would be all white - assert not np.all(array == 0xff) +def test_single_minus_sign(): + fig = plt.figure() + fig.text(0.5, 0.5, '$-$') + fig.canvas.draw() + t = np.asarray(fig.canvas.renderer.buffer_rgba()) + assert (t != 0xff).any() # assert that canvas is not all white. -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_spaces(fig_test, fig_ref): - fig_test.subplots().set_title(r"$1\,2\>3\ 4$") - fig_ref.subplots().set_title(r"$1\/2\:3~4$") + fig_test.text(.5, .5, r"$1\,2\>3\ 4$") + fig_ref.text(.5, .5, r"$1\/2\:3~4$") -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_operator_space(fig_test, fig_ref): fig_test.text(0.1, 0.1, r"$\log 6$") fig_test.text(0.1, 0.2, r"$\log(6)$") @@ -299,6 +391,7 @@ def test_operator_space(fig_test, fig_ref): fig_test.text(0.1, 0.6, r"$\operatorname{op}[6]$") fig_test.text(0.1, 0.7, r"$\cos^2$") fig_test.text(0.1, 0.8, r"$\log_2$") + fig_test.text(0.1, 0.9, r"$\sin^2 \cos$") # GitHub issue #17852 fig_ref.text(0.1, 0.1, r"$\mathrm{log\,}6$") fig_ref.text(0.1, 0.2, r"$\mathrm{log}(6)$") @@ -308,6 +401,23 @@ def test_operator_space(fig_test, fig_ref): fig_ref.text(0.1, 0.6, r"$\mathrm{op}[6]$") fig_ref.text(0.1, 0.7, r"$\mathrm{cos}^2$") fig_ref.text(0.1, 0.8, r"$\mathrm{log}_2$") + fig_ref.text(0.1, 0.9, r"$\mathrm{sin}^2 \mathrm{\,cos}$") + + +@check_figures_equal() +def test_inverted_delimiters(fig_test, fig_ref): + fig_test.text(.5, .5, r"$\left)\right($", math_fontfamily="dejavusans") + fig_ref.text(.5, .5, r"$)($", math_fontfamily="dejavusans") + + +@check_figures_equal() +def test_genfrac_displaystyle(fig_test, fig_ref): + fig_test.text(0.1, 0.1, r"$\dfrac{2x}{3y}$") + + thickness = _mathtext.TruetypeFonts.get_underline_thickness( + None, None, fontsize=mpl.rcParams["font.size"], + dpi=mpl.rcParams["savefig.dpi"]) + fig_ref.text(0.1, 0.1, r"$\genfrac{}{}{%f}{0}{2x}{3y}$" % thickness) def test_mathtext_fallback_valid(): @@ -315,30 +425,25 @@ def test_mathtext_fallback_valid(): mpl.rcParams['mathtext.fallback'] = fallback -@pytest.mark.xfail def test_mathtext_fallback_invalid(): for fallback in ['abc', '']: - mpl.rcParams['mathtext.fallback'] = fallback - - -@pytest.mark.xfail -def test_mathtext_fallback_to_cm_invalid(): - for fallback in [True, False]: - mpl.rcParams['mathtext.fallback_to_cm'] = fallback + with pytest.raises(ValueError, match="not a valid fallback font name"): + mpl.rcParams['mathtext.fallback'] = fallback @pytest.mark.parametrize( "fallback,fontlist", [("cm", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'cmr10', 'STIXGeneral']), - ("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])]) + ("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'STIXGeneral', 'STIXGeneral'])]) def test_mathtext_fallback(fallback, fontlist): mpl.font_manager.fontManager.addfont( - os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf')) + (Path(__file__).resolve().parent / 'data/mpltest.ttf')) mpl.rcParams["svg.fonttype"] = 'none' mpl.rcParams['mathtext.fontset'] = 'custom' mpl.rcParams['mathtext.rm'] = 'mpltest' mpl.rcParams['mathtext.it'] = 'mpltest:italic' mpl.rcParams['mathtext.bf'] = 'mpltest:bold' + mpl.rcParams['mathtext.bfit'] = 'mpltest:italic:bold' mpl.rcParams['mathtext.fallback'] = fallback test_str = r'a$A\AA\breve\gimel$' @@ -347,23 +452,19 @@ def test_mathtext_fallback(fallback, fontlist): fig, ax = plt.subplots() fig.text(.5, .5, test_str, fontsize=40, ha='center') fig.savefig(buff, format="svg") + tspans = (ET.fromstring(buff.getvalue()) + .findall(".//{http://www.w3.org/2000/svg}tspan[@style]")) char_fonts = [ - line.split("font-family:")[-1].split(";")[0] - for line in str(buff.getvalue()).split(r"\n") if "tspan" in line - ] - assert char_fonts == fontlist - mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1] + re.search(r"font-family: '([\w ]+)'", tspan.attrib["style"]).group(1) + for tspan in tspans] + assert char_fonts == fontlist, f'Expected {fontlist}, got {char_fonts}' + mpl.font_manager.fontManager.ttflist.pop() -def test_math_to_image(tmpdir): - mathtext.math_to_image('$x^2$', str(tmpdir.join('example.png'))) +def test_math_to_image(tmp_path): + mathtext.math_to_image('$x^2$', tmp_path / 'example.png') mathtext.math_to_image('$x^2$', io.BytesIO()) - - -def test_mathtext_to_png(tmpdir): - mt = mathtext.MathTextParser('bitmap') - mt.to_png(str(tmpdir.join('example.png')), '$x^2$') - mt.to_png(io.BytesIO(), '$x^2$') + mathtext.math_to_image('$x^2$', io.BytesIO(), color='Maroon') @image_comparison(baseline_images=['math_fontfamily_image.png'], @@ -374,3 +475,126 @@ def test_math_fontfamily(): size=24, math_fontfamily='dejavusans') fig.text(0.2, 0.3, r"$This\ text\ should\ have\ another$", size=24, math_fontfamily='stix') + + +def test_default_math_fontfamily(): + mpl.rcParams['mathtext.fontset'] = 'cm' + test_str = r'abc$abc\alpha$' + fig, ax = plt.subplots() + + text1 = fig.text(0.1, 0.1, test_str, font='Arial') + prop1 = text1.get_fontproperties() + assert prop1.get_math_fontfamily() == 'cm' + text2 = fig.text(0.2, 0.2, test_str, fontproperties='Arial') + prop2 = text2.get_fontproperties() + assert prop2.get_math_fontfamily() == 'cm' + + fig.draw_without_rendering() + + +def test_argument_order(): + mpl.rcParams['mathtext.fontset'] = 'cm' + test_str = r'abc$abc\alpha$' + fig, ax = plt.subplots() + + text1 = fig.text(0.1, 0.1, test_str, + math_fontfamily='dejavusans', font='Arial') + prop1 = text1.get_fontproperties() + assert prop1.get_math_fontfamily() == 'dejavusans' + text2 = fig.text(0.2, 0.2, test_str, + math_fontfamily='dejavusans', fontproperties='Arial') + prop2 = text2.get_fontproperties() + assert prop2.get_math_fontfamily() == 'dejavusans' + text3 = fig.text(0.3, 0.3, test_str, + font='Arial', math_fontfamily='dejavusans') + prop3 = text3.get_fontproperties() + assert prop3.get_math_fontfamily() == 'dejavusans' + text4 = fig.text(0.4, 0.4, test_str, + fontproperties='Arial', math_fontfamily='dejavusans') + prop4 = text4.get_fontproperties() + assert prop4.get_math_fontfamily() == 'dejavusans' + + fig.draw_without_rendering() + + +def test_mathtext_cmr10_minus_sign(): + # cmr10 does not contain a minus sign and used to issue a warning + # RuntimeWarning: Glyph 8722 missing from current font. + mpl.rcParams['font.family'] = 'cmr10' + mpl.rcParams['axes.formatter.use_mathtext'] = True + fig, ax = plt.subplots() + ax.plot(range(-1, 1), range(-1, 1)) + # draw to make sure we have no warnings + fig.canvas.draw() + + +def test_mathtext_operators(): + test_str = r''' + \increment \smallin \notsmallowns + \smallowns \QED \rightangle + \smallintclockwise \smallvarointclockwise + \smallointctrcclockwise + \ratio \minuscolon \dotsminusdots + \sinewave \simneqq \nlesssim + \ngtrsim \nlessgtr \ngtrless + \cupleftarrow \oequal \rightassert + \rightModels \hermitmatrix \barvee + \measuredrightangle \varlrtriangle + \equalparallel \npreccurlyeq \nsucccurlyeq + \nsqsubseteq \nsqsupseteq \sqsubsetneq + \sqsupsetneq \disin \varisins + \isins \isindot \varisinobar + \isinobar \isinvb \isinE + \nisd \varnis \nis + \varniobar \niobar \bagmember + \triangle'''.split() + + fig = plt.figure() + for x, i in enumerate(test_str): + fig.text(0.5, (x + 0.5)/len(test_str), r'${%s}$' % i) + + fig.draw_without_rendering() + + +@check_figures_equal() +def test_boldsymbol(fig_test, fig_ref): + fig_test.text(0.1, 0.2, r"$\boldsymbol{\mathrm{abc0123\alpha}}$") + fig_ref.text(0.1, 0.2, r"$\mathrm{abc0123\alpha}$") + + +def test_box_repr(): + s = repr(_mathtext.Parser().parse( + r"$\frac{1}{2}$", + _mathtext.DejaVuSansFonts(fm.FontProperties(), LoadFlags.NO_HINTING), + fontsize=12, dpi=100)) + assert s == textwrap.dedent("""\ + Hlist[ + Hlist[], + Hlist[ + Hlist[ + Vlist[ + HCentered[ + Glue, + Hlist[ + `1`, + k2.36, + ], + Glue, + ], + Vbox, + Hrule, + Vbox, + HCentered[ + Glue, + Hlist[ + `2`, + k2.02, + ], + Glue, + ], + ], + Hbox, + ], + ], + Hlist[], + ]""") diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index 8e33217fd3af..37b41fafdb78 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -5,24 +5,43 @@ import pytest import matplotlib +from matplotlib.testing import subprocess_run_for_testing -@pytest.mark.skipif( - os.name == "nt", reason="chmod() doesn't work as is on Windows") -@pytest.mark.skipif(os.name != "nt" and os.geteuid() == 0, +@pytest.mark.parametrize('version_str, version_tuple', [ + ('3.5.0', (3, 5, 0, 'final', 0)), + ('3.5.0rc2', (3, 5, 0, 'candidate', 2)), + ('3.5.0.dev820+g6768ef8c4c', (3, 5, 0, 'alpha', 820)), + ('3.5.0.post820+g6768ef8c4c', (3, 5, 1, 'alpha', 820)), +]) +def test_parse_to_version_info(version_str, version_tuple): + assert matplotlib._parse_to_version_info(version_str) == version_tuple + + +@pytest.mark.skipif(sys.platform == "win32", + reason="chmod() doesn't work as is on Windows") +@pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0, reason="chmod() doesn't work as root") -def test_tmpconfigdir_warning(tmpdir): +def test_tmpconfigdir_warning(tmp_path): """Test that a warning is emitted if a temporary configdir must be used.""" - mode = os.stat(tmpdir).st_mode + mode = os.stat(tmp_path).st_mode try: - os.chmod(tmpdir, 0) - proc = subprocess.run( + os.chmod(tmp_path, 0) + proc = subprocess_run_for_testing( [sys.executable, "-c", "import matplotlib"], - env={**os.environ, "MPLCONFIGDIR": str(tmpdir)}, - stderr=subprocess.PIPE, universal_newlines=True, check=True) + env={**os.environ, "MPLCONFIGDIR": str(tmp_path)}, + stderr=subprocess.PIPE, text=True, check=True) assert "set the MPLCONFIGDIR" in proc.stderr finally: - os.chmod(tmpdir, mode) + os.chmod(tmp_path, mode) + + +def test_importable_with_no_home(tmp_path): + subprocess_run_for_testing( + [sys.executable, "-c", + "import pathlib; pathlib.Path.home = lambda *args: 1/0; " + "import matplotlib.pyplot"], + env={**os.environ, "MPLCONFIGDIR": str(tmp_path)}, check=True) def test_use_doc_standard_backends(): @@ -35,13 +54,15 @@ def parse(key): for line in matplotlib.use.__doc__.split(key)[1].split('\n'): if not line.strip(): break - backends += [e.strip() for e in line.split(',') if e] + backends += [e.strip().lower() for e in line.split(',') if e] return backends + from matplotlib.backends import BackendFilter, backend_registry + assert (set(parse('- interactive backends:\n')) == - set(matplotlib.rcsetup.interactive_bk)) + set(backend_registry.list_builtin(BackendFilter.INTERACTIVE))) assert (set(parse('- non-interactive backends:\n')) == - set(matplotlib.rcsetup.non_interactive_bk)) + set(backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE))) def test_importable_with__OO(): @@ -55,5 +76,7 @@ def test_importable_with__OO(): "import matplotlib.cbook as cbook; " "import matplotlib.patches as mpatches" ) - cmd = [sys.executable, "-OO", "-c", program] - assert subprocess.call(cmd) == 0 + subprocess_run_for_testing( + [sys.executable, "-OO", "-c", program], + env={**os.environ, "MPLBACKEND": ""}, check=True + ) diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index d63d60adfdf2..3b0d2529b5f1 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -3,399 +3,22 @@ import numpy as np import pytest -import matplotlib.mlab as mlab -from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning +from matplotlib import mlab -def _stride_repeat(*args, **kwargs): - with pytest.warns(MatplotlibDeprecationWarning): - return mlab.stride_repeat(*args, **kwargs) - - -class TestStride: - def get_base(self, x): - y = x - while y.base is not None: - y = y.base - return y - - def calc_window_target(self, x, NFFT, noverlap=0, axis=0): - """ - This is an adaptation of the original window extraction algorithm. - This is here to test to make sure the new implementation has the same - result. - """ - step = NFFT - noverlap - ind = np.arange(0, len(x) - NFFT + 1, step) - n = len(ind) - result = np.zeros((NFFT, n)) - - # do the ffts of the slices - for i in range(n): - result[:, i] = x[ind[i]:ind[i]+NFFT] - if axis == 1: - result = result.T - return result - - @pytest.mark.parametrize('shape', [(), (10, 1)], ids=['0D', '2D']) - def test_stride_windows_invalid_input_shape(self, shape): - x = np.arange(np.prod(shape)).reshape(shape) - with pytest.raises(ValueError): - mlab.stride_windows(x, 5) - - @pytest.mark.parametrize('n, noverlap', - [(0, None), (11, None), (2, 2), (2, 3)], - ids=['n less than 1', 'n greater than input', - 'noverlap greater than n', - 'noverlap equal to n']) - def test_stride_windows_invalid_params(self, n, noverlap): - x = np.arange(10) - with pytest.raises(ValueError): - mlab.stride_windows(x, n, noverlap) - - @pytest.mark.parametrize('shape', [(), (10, 1)], ids=['0D', '2D']) - def test_stride_repeat_invalid_input_shape(self, shape): - x = np.arange(np.prod(shape)).reshape(shape) - with pytest.raises(ValueError): - _stride_repeat(x, 5) - - @pytest.mark.parametrize('axis', [-1, 2], - ids=['axis less than 0', - 'axis greater than input shape']) - def test_stride_repeat_invalid_axis(self, axis): - x = np.array(0) - with pytest.raises(ValueError): - _stride_repeat(x, 5, axis=axis) - - def test_stride_repeat_n_lt_1_ValueError(self): - x = np.arange(10) - with pytest.raises(ValueError): - _stride_repeat(x, 0) - - @pytest.mark.parametrize('axis', [0, 1], ids=['axis0', 'axis1']) - @pytest.mark.parametrize('n', [1, 5], ids=['n1', 'n5']) - def test_stride_repeat(self, n, axis): - x = np.arange(10) - y = _stride_repeat(x, n, axis=axis) - - expected_shape = [10, 10] - expected_shape[axis] = n - yr = np.repeat(np.expand_dims(x, axis), n, axis=axis) - - assert yr.shape == y.shape - assert_array_equal(yr, y) - assert tuple(expected_shape) == y.shape - assert self.get_base(y) is x - - @pytest.mark.parametrize('axis', [0, 1], ids=['axis0', 'axis1']) - @pytest.mark.parametrize('n, noverlap', - [(1, 0), (5, 0), (15, 2), (13, -3)], - ids=['n1-noverlap0', 'n5-noverlap0', - 'n15-noverlap2', 'n13-noverlapn3']) - def test_stride_windows(self, n, noverlap, axis): - x = np.arange(100) - y = mlab.stride_windows(x, n, noverlap=noverlap, axis=axis) - - expected_shape = [0, 0] - expected_shape[axis] = n - expected_shape[1 - axis] = 100 // (n - noverlap) - yt = self.calc_window_target(x, n, noverlap=noverlap, axis=axis) - - assert yt.shape == y.shape - assert_array_equal(yt, y) - assert tuple(expected_shape) == y.shape - assert self.get_base(y) is x - - @pytest.mark.parametrize('axis', [0, 1], ids=['axis0', 'axis1']) - def test_stride_windows_n32_noverlap0_unflatten(self, axis): - n = 32 - x = np.arange(n)[np.newaxis] - x1 = np.tile(x, (21, 1)) - x2 = x1.flatten() - y = mlab.stride_windows(x2, n, axis=axis) - - if axis == 0: - x1 = x1.T - assert y.shape == x1.shape - assert_array_equal(y, x1) - - def test_stride_ensure_integer_type(self): - N = 100 - x = np.full(N + 20, np.nan) - y = x[10:-10] - y[:] = 0.3 - # previous to #3845 lead to corrupt access - y_strided = mlab.stride_windows(y, n=33, noverlap=0.6) - assert_array_equal(y_strided, 0.3) - # previous to #3845 lead to corrupt access - y_strided = mlab.stride_windows(y, n=33.3, noverlap=0) - assert_array_equal(y_strided, 0.3) - # even previous to #3845 could not find any problematic - # configuration however, let's be sure it's not accidentally - # introduced - y_strided = _stride_repeat(y, n=33.815) - assert_array_equal(y_strided, 0.3) - - -def _apply_window(*args, **kwargs): - with pytest.warns(MatplotlibDeprecationWarning): - return mlab.apply_window(*args, **kwargs) - - -class TestWindow: - def setup(self): - np.random.seed(0) - n = 1000 - - self.sig_rand = np.random.standard_normal(n) + 100. - self.sig_ones = np.ones(n) - - def check_window_apply_repeat(self, x, window, NFFT, noverlap): - """ - This is an adaptation of the original window application algorithm. - This is here to test to make sure the new implementation has the same - result. - """ - step = NFFT - noverlap - ind = np.arange(0, len(x) - NFFT + 1, step) - n = len(ind) - result = np.zeros((NFFT, n)) - - if np.iterable(window): - windowVals = window - else: - windowVals = window(np.ones(NFFT, x.dtype)) - - # do the ffts of the slices - for i in range(n): - result[:, i] = windowVals * x[ind[i]:ind[i]+NFFT] - return result - - def test_window_none_rand(self): - res = mlab.window_none(self.sig_ones) - assert_array_equal(res, self.sig_ones) - - def test_window_none_ones(self): - res = mlab.window_none(self.sig_rand) - assert_array_equal(res, self.sig_rand) - - def test_window_hanning_rand(self): - targ = np.hanning(len(self.sig_rand)) * self.sig_rand - res = mlab.window_hanning(self.sig_rand) - - assert_allclose(targ, res, atol=1e-06) - - def test_window_hanning_ones(self): - targ = np.hanning(len(self.sig_ones)) - res = mlab.window_hanning(self.sig_ones) - - assert_allclose(targ, res, atol=1e-06) - - def test_apply_window_1D_axis1_ValueError(self): - x = self.sig_rand - window = mlab.window_hanning - with pytest.raises(ValueError): - _apply_window(x, window, axis=1, return_window=False) - - def test_apply_window_1D_els_wrongsize_ValueError(self): - x = self.sig_rand - window = mlab.window_hanning(np.ones(x.shape[0]-1)) - with pytest.raises(ValueError): - _apply_window(x, window) - - def test_apply_window_0D_ValueError(self): - x = np.array(0) - window = mlab.window_hanning - with pytest.raises(ValueError): - _apply_window(x, window, axis=1, return_window=False) - - def test_apply_window_3D_ValueError(self): - x = self.sig_rand[np.newaxis][np.newaxis] - window = mlab.window_hanning - with pytest.raises(ValueError): - _apply_window(x, window, axis=1, return_window=False) - - def test_apply_window_hanning_1D(self): - x = self.sig_rand - window = mlab.window_hanning - window1 = mlab.window_hanning(np.ones(x.shape[0])) - y, window2 = _apply_window(x, window, return_window=True) - yt = window(x) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - assert_array_equal(window1, window2) - - def test_apply_window_hanning_1D_axis0(self): - x = self.sig_rand - window = mlab.window_hanning - y = _apply_window(x, window, axis=0, return_window=False) - yt = window(x) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_els_1D_axis0(self): - x = self.sig_rand - window = mlab.window_hanning(np.ones(x.shape[0])) - window1 = mlab.window_hanning - y = _apply_window(x, window, axis=0, return_window=False) - yt = window1(x) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_2D_axis0(self): - x = np.random.standard_normal([1000, 10]) + 100. - window = mlab.window_hanning - y = _apply_window(x, window, axis=0, return_window=False) - yt = np.zeros_like(x) - for i in range(x.shape[1]): - yt[:, i] = window(x[:, i]) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_els1_2D_axis0(self): - x = np.random.standard_normal([1000, 10]) + 100. - window = mlab.window_hanning(np.ones(x.shape[0])) - window1 = mlab.window_hanning - y = _apply_window(x, window, axis=0, return_window=False) - yt = np.zeros_like(x) - for i in range(x.shape[1]): - yt[:, i] = window1(x[:, i]) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_els2_2D_axis0(self): - x = np.random.standard_normal([1000, 10]) + 100. - window = mlab.window_hanning - window1 = mlab.window_hanning(np.ones(x.shape[0])) - y, window2 = _apply_window(x, window, axis=0, return_window=True) - yt = np.zeros_like(x) - for i in range(x.shape[1]): - yt[:, i] = window1*x[:, i] - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - assert_array_equal(window1, window2) - - def test_apply_window_hanning_els3_2D_axis0(self): - x = np.random.standard_normal([1000, 10]) + 100. - window = mlab.window_hanning - window1 = mlab.window_hanning(np.ones(x.shape[0])) - y, window2 = _apply_window(x, window, axis=0, return_window=True) - yt = _apply_window(x, window1, axis=0, return_window=False) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - assert_array_equal(window1, window2) - - def test_apply_window_hanning_2D_axis1(self): - x = np.random.standard_normal([10, 1000]) + 100. - window = mlab.window_hanning - y = _apply_window(x, window, axis=1, return_window=False) - yt = np.zeros_like(x) - for i in range(x.shape[0]): - yt[i, :] = window(x[i, :]) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_2D_els1_axis1(self): - x = np.random.standard_normal([10, 1000]) + 100. - window = mlab.window_hanning(np.ones(x.shape[1])) - window1 = mlab.window_hanning - y = _apply_window(x, window, axis=1, return_window=False) - yt = np.zeros_like(x) - for i in range(x.shape[0]): - yt[i, :] = window1(x[i, :]) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_2D_els2_axis1(self): - x = np.random.standard_normal([10, 1000]) + 100. - window = mlab.window_hanning - window1 = mlab.window_hanning(np.ones(x.shape[1])) - y, window2 = _apply_window(x, window, axis=1, return_window=True) - yt = np.zeros_like(x) - for i in range(x.shape[0]): - yt[i, :] = window1 * x[i, :] - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - assert_array_equal(window1, window2) - - def test_apply_window_hanning_2D_els3_axis1(self): - x = np.random.standard_normal([10, 1000]) + 100. - window = mlab.window_hanning - window1 = mlab.window_hanning(np.ones(x.shape[1])) - y = _apply_window(x, window, axis=1, return_window=False) - yt = _apply_window(x, window1, axis=1, return_window=False) - assert yt.shape == y.shape - assert x.shape == y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_stride_windows_hanning_2D_n13_noverlapn3_axis0(self): - x = self.sig_rand - window = mlab.window_hanning - yi = mlab.stride_windows(x, n=13, noverlap=2, axis=0) - y = _apply_window(yi, window, axis=0, return_window=False) - yt = self.check_window_apply_repeat(x, window, 13, 2) - assert yt.shape == y.shape - assert x.shape != y.shape - assert_allclose(yt, y, atol=1e-06) - - def test_apply_window_hanning_2D_stack_axis1(self): - ydata = np.arange(32) - ydata1 = ydata+5 - ydata2 = ydata+3.3 - ycontrol1 = _apply_window(ydata1, mlab.window_hanning) - ycontrol2 = mlab.window_hanning(ydata2) - ydata = np.vstack([ydata1, ydata2]) - ycontrol = np.vstack([ycontrol1, ycontrol2]) - ydata = np.tile(ydata, (20, 1)) - ycontrol = np.tile(ycontrol, (20, 1)) - result = _apply_window(ydata, mlab.window_hanning, axis=1, - return_window=False) - assert_allclose(ycontrol, result, atol=1e-08) - - def test_apply_window_hanning_2D_stack_windows_axis1(self): - ydata = np.arange(32) - ydata1 = ydata+5 - ydata2 = ydata+3.3 - ycontrol1 = _apply_window(ydata1, mlab.window_hanning) - ycontrol2 = mlab.window_hanning(ydata2) - ydata = np.vstack([ydata1, ydata2]) - ycontrol = np.vstack([ycontrol1, ycontrol2]) - ydata = np.tile(ydata, (20, 1)) - ycontrol = np.tile(ycontrol, (20, 1)) - result = _apply_window(ydata, mlab.window_hanning, axis=1, - return_window=False) - assert_allclose(ycontrol, result, atol=1e-08) - - def test_apply_window_hanning_2D_stack_windows_axis1_unflatten(self): - n = 32 - ydata = np.arange(n) - ydata1 = ydata+5 - ydata2 = ydata+3.3 - ycontrol1 = _apply_window(ydata1, mlab.window_hanning) - ycontrol2 = mlab.window_hanning(ydata2) - ydata = np.vstack([ydata1, ydata2]) - ycontrol = np.vstack([ycontrol1, ycontrol2]) - ydata = np.tile(ydata, (20, 1)) - ycontrol = np.tile(ycontrol, (20, 1)) - ydata = ydata.flatten() - ydata1 = mlab.stride_windows(ydata, 32, noverlap=0, axis=0) - result = _apply_window(ydata1, mlab.window_hanning, axis=0, - return_window=False) - assert_allclose(ycontrol.T, result, atol=1e-08) +def test_window(): + np.random.seed(0) + n = 1000 + rand = np.random.standard_normal(n) + 100 + ones = np.ones(n) + assert_array_equal(mlab.window_none(ones), ones) + assert_array_equal(mlab.window_none(rand), rand) + assert_array_equal(np.hanning(len(rand)) * rand, mlab.window_hanning(rand)) + assert_array_equal(np.hanning(len(ones)), mlab.window_hanning(ones)) class TestDetrend: - def setup(self): + def setup_method(self): np.random.seed(0) n = 1000 x = np.linspace(0., 100, n) @@ -404,482 +27,147 @@ def setup(self): self.sig_off = self.sig_zeros + 100. self.sig_slope = np.linspace(-10., 90., n) - self.sig_slope_mean = x - x.mean() - sig_rand = np.random.standard_normal(n) - sig_sin = np.sin(x*2*np.pi/(n/100)) - - sig_rand -= sig_rand.mean() - sig_sin -= sig_sin.mean() - - self.sig_base = sig_rand + sig_sin - - self.atol = 1e-08 - - def test_detrend_none_0D_zeros(self): - input = 0. - targ = input - mlab.detrend_none(input) - assert input == targ - - def test_detrend_none_0D_zeros_axis1(self): - input = 0. - targ = input - mlab.detrend_none(input, axis=1) - assert input == targ - - def test_detrend_str_none_0D_zeros(self): - input = 0. - targ = input - mlab.detrend(input, key='none') - assert input == targ - - def test_detrend_detrend_none_0D_zeros(self): - input = 0. - targ = input - mlab.detrend(input, key=mlab.detrend_none) - assert input == targ - - def test_detrend_none_0D_off(self): - input = 5.5 - targ = input - mlab.detrend_none(input) - assert input == targ - - def test_detrend_none_1D_off(self): - input = self.sig_off - targ = input - res = mlab.detrend_none(input) - assert_array_equal(res, targ) - - def test_detrend_none_1D_slope(self): - input = self.sig_slope - targ = input - res = mlab.detrend_none(input) - assert_array_equal(res, targ) - - def test_detrend_none_1D_base(self): - input = self.sig_base - targ = input - res = mlab.detrend_none(input) - assert_array_equal(res, targ) - - def test_detrend_none_1D_base_slope_off_list(self): - input = self.sig_base + self.sig_slope + self.sig_off - targ = input.tolist() - res = mlab.detrend_none(input.tolist()) - assert res == targ - - def test_detrend_none_2D(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - input = np.vstack(arri) - targ = input - res = mlab.detrend_none(input) - assert_array_equal(res, targ) - - def test_detrend_none_2D_T(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - input = np.vstack(arri) - targ = input - res = mlab.detrend_none(input.T) - assert_array_equal(res.T, targ) - - def test_detrend_mean_0D_zeros(self): - input = 0. - targ = 0. - res = mlab.detrend_mean(input) - assert_almost_equal(res, targ) - - def test_detrend_str_mean_0D_zeros(self): - input = 0. - targ = 0. - res = mlab.detrend(input, key='mean') - assert_almost_equal(res, targ) - - def test_detrend_detrend_mean_0D_zeros(self): - input = 0. - targ = 0. - res = mlab.detrend(input, key=mlab.detrend_mean) - assert_almost_equal(res, targ) - - def test_detrend_mean_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend_mean(input) - assert_almost_equal(res, targ) - - def test_detrend_str_mean_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend(input, key='mean') - assert_almost_equal(res, targ) - - def test_detrend_detrend_mean_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend(input, key=mlab.detrend_mean) - assert_almost_equal(res, targ) - - def test_detrend_mean_1D_zeros(self): - input = self.sig_zeros - targ = self.sig_zeros - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_mean_1D_base(self): - input = self.sig_base - targ = self.sig_base - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_mean_1D_base_off(self): - input = self.sig_base + self.sig_off - targ = self.sig_base - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_mean_1D_base_slope(self): - input = self.sig_base + self.sig_slope - targ = self.sig_base + self.sig_slope_mean - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_mean_1D_base_slope_off(self): - input = self.sig_base + self.sig_slope + self.sig_off - targ = self.sig_base + self.sig_slope_mean - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_mean_1D_base_slope_off_axis0(self): - input = self.sig_base + self.sig_slope + self.sig_off - targ = self.sig_base + self.sig_slope_mean - res = mlab.detrend_mean(input, axis=0) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_mean_1D_base_slope_off_list(self): - input = self.sig_base + self.sig_slope + self.sig_off - targ = self.sig_base + self.sig_slope_mean - res = mlab.detrend_mean(input.tolist()) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_mean_1D_base_slope_off_list_axis0(self): + self.sig_base = ( + np.random.standard_normal(n) + np.sin(x*2*np.pi/(n/100))) + self.sig_base -= self.sig_base.mean() + + def allclose(self, *args): + assert_allclose(*args, atol=1e-8) + + def test_detrend_none(self): + assert mlab.detrend_none(0.) == 0. + assert mlab.detrend_none(0., axis=1) == 0. + assert mlab.detrend(0., key="none") == 0. + assert mlab.detrend(0., key=mlab.detrend_none) == 0. + for sig in [ + 5.5, self.sig_off, self.sig_slope, self.sig_base, + (self.sig_base + self.sig_slope + self.sig_off).tolist(), + np.vstack([self.sig_base, # 2D case. + self.sig_base + self.sig_off, + self.sig_base + self.sig_slope, + self.sig_base + self.sig_off + self.sig_slope]), + np.vstack([self.sig_base, # 2D transposed case. + self.sig_base + self.sig_off, + self.sig_base + self.sig_slope, + self.sig_base + self.sig_off + self.sig_slope]).T, + ]: + if isinstance(sig, np.ndarray): + assert_array_equal(mlab.detrend_none(sig), sig) + else: + assert mlab.detrend_none(sig) == sig + + def test_detrend_mean(self): + for sig in [0., 5.5]: # 0D. + assert mlab.detrend_mean(sig) == 0. + assert mlab.detrend(sig, key="mean") == 0. + assert mlab.detrend(sig, key=mlab.detrend_mean) == 0. + # 1D. + self.allclose(mlab.detrend_mean(self.sig_zeros), self.sig_zeros) + self.allclose(mlab.detrend_mean(self.sig_base), self.sig_base) + self.allclose(mlab.detrend_mean(self.sig_base + self.sig_off), + self.sig_base) + self.allclose(mlab.detrend_mean(self.sig_base + self.sig_slope), + self.sig_base + self.sig_slope_mean) + self.allclose( + mlab.detrend_mean(self.sig_base + self.sig_slope + self.sig_off), + self.sig_base + self.sig_slope_mean) + + def test_detrend_mean_1d_base_slope_off_list_andor_axis0(self): input = self.sig_base + self.sig_slope + self.sig_off - targ = self.sig_base + self.sig_slope_mean - res = mlab.detrend_mean(input.tolist(), axis=0) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_mean_2D_default(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend_mean(input) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_mean_2D_none(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend_mean(input, axis=None) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_mean_2D_none_T(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri).T - targ = np.vstack(arrt) - res = mlab.detrend_mean(input, axis=None) - assert_allclose(res.T, targ, - atol=1e-08) - - def test_detrend_mean_2D_axis0(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri).T - targ = np.vstack(arrt).T - res = mlab.detrend_mean(input, axis=0) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_mean_2D_axis1(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend_mean(input, axis=1) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_mean_2D_axism1(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend_mean(input, axis=-1) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_2D_default(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend(input) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_2D_none(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend(input, axis=None) - assert_allclose(res, targ, atol=1e-08) - - def test_detrend_str_mean_2D_axis0(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri).T - targ = np.vstack(arrt).T - res = mlab.detrend(input, key='mean', axis=0) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_str_constant_2D_none_T(self): - arri = [self.sig_off, - self.sig_base + self.sig_off] - arrt = [self.sig_zeros, - self.sig_base] - input = np.vstack(arri).T - targ = np.vstack(arrt) - res = mlab.detrend(input, key='constant', axis=None) - assert_allclose(res.T, targ, - atol=1e-08) - - def test_detrend_str_default_2D_axis1(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend(input, key='default', axis=1) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_detrend_mean_2D_axis0(self): - arri = [self.sig_base, - self.sig_base + self.sig_off, - self.sig_base + self.sig_slope, - self.sig_base + self.sig_off + self.sig_slope] - arrt = [self.sig_base, - self.sig_base, - self.sig_base + self.sig_slope_mean, - self.sig_base + self.sig_slope_mean] - input = np.vstack(arri).T - targ = np.vstack(arrt).T - res = mlab.detrend(input, key=mlab.detrend_mean, axis=0) - assert_allclose(res, targ, - atol=1e-08) - - def test_detrend_bad_key_str_ValueError(self): - input = self.sig_slope[np.newaxis] - with pytest.raises(ValueError): - mlab.detrend(input, key='spam') - - def test_detrend_bad_key_var_ValueError(self): - input = self.sig_slope[np.newaxis] - with pytest.raises(ValueError): - mlab.detrend(input, key=5) - - def test_detrend_mean_0D_d0_ValueError(self): - input = 5.5 - with pytest.raises(ValueError): - mlab.detrend_mean(input, axis=0) - - def test_detrend_0D_d0_ValueError(self): - input = 5.5 - with pytest.raises(ValueError): - mlab.detrend(input, axis=0) - - def test_detrend_mean_1D_d1_ValueError(self): - input = self.sig_slope - with pytest.raises(ValueError): - mlab.detrend_mean(input, axis=1) - - def test_detrend_1D_d1_ValueError(self): - input = self.sig_slope - with pytest.raises(ValueError): - mlab.detrend(input, axis=1) - - def test_detrend_mean_2D_d2_ValueError(self): - input = self.sig_slope[np.newaxis] - with pytest.raises(ValueError): - mlab.detrend_mean(input, axis=2) - - def test_detrend_2D_d2_ValueError(self): - input = self.sig_slope[np.newaxis] - with pytest.raises(ValueError): - mlab.detrend(input, axis=2) - - def test_detrend_linear_0D_zeros(self): - input = 0. - targ = 0. - res = mlab.detrend_linear(input) - assert_almost_equal(res, targ) - - def test_detrend_linear_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend_linear(input) - assert_almost_equal(res, targ) - - def test_detrend_str_linear_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend(input, key='linear') - assert_almost_equal(res, targ) - - def test_detrend_detrend_linear_0D_off(self): - input = 5.5 - targ = 0. - res = mlab.detrend(input, key=mlab.detrend_linear) - assert_almost_equal(res, targ) - - def test_detrend_linear_1d_off(self): - input = self.sig_off - targ = self.sig_zeros - res = mlab.detrend_linear(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_linear_1d_slope(self): - input = self.sig_slope - targ = self.sig_zeros - res = mlab.detrend_linear(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_linear_1d_slope_off(self): - input = self.sig_slope + self.sig_off - targ = self.sig_zeros - res = mlab.detrend_linear(input) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_str_linear_1d_slope_off(self): - input = self.sig_slope + self.sig_off - targ = self.sig_zeros - res = mlab.detrend(input, key='linear') - assert_allclose(res, targ, atol=self.atol) + target = self.sig_base + self.sig_slope_mean + self.allclose(mlab.detrend_mean(input, axis=0), target) + self.allclose(mlab.detrend_mean(input.tolist()), target) + self.allclose(mlab.detrend_mean(input.tolist(), axis=0), target) + + def test_detrend_mean_2d(self): + input = np.vstack([self.sig_off, + self.sig_base + self.sig_off]) + target = np.vstack([self.sig_zeros, + self.sig_base]) + self.allclose(mlab.detrend_mean(input), target) + self.allclose(mlab.detrend_mean(input, axis=None), target) + self.allclose(mlab.detrend_mean(input.T, axis=None).T, target) + self.allclose(mlab.detrend(input), target) + self.allclose(mlab.detrend(input, axis=None), target) + self.allclose( + mlab.detrend(input.T, key="constant", axis=None), target.T) + + input = np.vstack([self.sig_base, + self.sig_base + self.sig_off, + self.sig_base + self.sig_slope, + self.sig_base + self.sig_off + self.sig_slope]) + target = np.vstack([self.sig_base, + self.sig_base, + self.sig_base + self.sig_slope_mean, + self.sig_base + self.sig_slope_mean]) + self.allclose(mlab.detrend_mean(input.T, axis=0), target.T) + self.allclose(mlab.detrend_mean(input, axis=1), target) + self.allclose(mlab.detrend_mean(input, axis=-1), target) + self.allclose(mlab.detrend(input, key="default", axis=1), target) + self.allclose(mlab.detrend(input.T, key="mean", axis=0), target.T) + self.allclose( + mlab.detrend(input.T, key=mlab.detrend_mean, axis=0), target.T) + + def test_detrend_ValueError(self): + for signal, kwargs in [ + (self.sig_slope[np.newaxis], {"key": "spam"}), + (self.sig_slope[np.newaxis], {"key": 5}), + (5.5, {"axis": 0}), + (self.sig_slope, {"axis": 1}), + (self.sig_slope[np.newaxis], {"axis": 2}), + ]: + with pytest.raises(ValueError): + mlab.detrend(signal, **kwargs) - def test_detrend_detrend_linear_1d_slope_off(self): - input = self.sig_slope + self.sig_off - targ = self.sig_zeros - res = mlab.detrend(input, key=mlab.detrend_linear) - assert_allclose(res, targ, atol=self.atol) + def test_detrend_mean_ValueError(self): + for signal, kwargs in [ + (5.5, {"axis": 0}), + (self.sig_slope, {"axis": 1}), + (self.sig_slope[np.newaxis], {"axis": 2}), + ]: + with pytest.raises(ValueError): + mlab.detrend_mean(signal, **kwargs) + + def test_detrend_linear(self): + # 0D. + assert mlab.detrend_linear(0.) == 0. + assert mlab.detrend_linear(5.5) == 0. + assert mlab.detrend(5.5, key="linear") == 0. + assert mlab.detrend(5.5, key=mlab.detrend_linear) == 0. + for sig in [ # 1D. + self.sig_off, + self.sig_slope, + self.sig_slope + self.sig_off, + ]: + self.allclose(mlab.detrend_linear(sig), self.sig_zeros) - def test_detrend_linear_1d_slope_off_list(self): + def test_detrend_str_linear_1d(self): input = self.sig_slope + self.sig_off - targ = self.sig_zeros - res = mlab.detrend_linear(input.tolist()) - assert_allclose(res, targ, atol=self.atol) + target = self.sig_zeros + self.allclose(mlab.detrend(input, key="linear"), target) + self.allclose(mlab.detrend(input, key=mlab.detrend_linear), target) + self.allclose(mlab.detrend_linear(input.tolist()), target) + + def test_detrend_linear_2d(self): + input = np.vstack([self.sig_off, + self.sig_slope, + self.sig_slope + self.sig_off]) + target = np.vstack([self.sig_zeros, + self.sig_zeros, + self.sig_zeros]) + self.allclose( + mlab.detrend(input.T, key="linear", axis=0), target.T) + self.allclose( + mlab.detrend(input.T, key=mlab.detrend_linear, axis=0), target.T) + self.allclose( + mlab.detrend(input, key="linear", axis=1), target) + self.allclose( + mlab.detrend(input, key=mlab.detrend_linear, axis=1), target) - def test_detrend_linear_2D_ValueError(self): - input = self.sig_slope[np.newaxis] with pytest.raises(ValueError): - mlab.detrend_linear(input) - - def test_detrend_str_linear_2d_slope_off_axis0(self): - arri = [self.sig_off, - self.sig_slope, - self.sig_slope + self.sig_off] - arrt = [self.sig_zeros, - self.sig_zeros, - self.sig_zeros] - input = np.vstack(arri).T - targ = np.vstack(arrt).T - res = mlab.detrend(input, key='linear', axis=0) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_detrend_linear_1d_slope_off_axis1(self): - arri = [self.sig_off, - self.sig_slope, - self.sig_slope + self.sig_off] - arrt = [self.sig_zeros, - self.sig_zeros, - self.sig_zeros] - input = np.vstack(arri).T - targ = np.vstack(arrt).T - res = mlab.detrend(input, key=mlab.detrend_linear, axis=0) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_str_linear_2d_slope_off_axis0_notranspose(self): - arri = [self.sig_off, - self.sig_slope, - self.sig_slope + self.sig_off] - arrt = [self.sig_zeros, - self.sig_zeros, - self.sig_zeros] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend(input, key='linear', axis=1) - assert_allclose(res, targ, atol=self.atol) - - def test_detrend_detrend_linear_1d_slope_off_axis1_notranspose(self): - arri = [self.sig_off, - self.sig_slope, - self.sig_slope + self.sig_off] - arrt = [self.sig_zeros, - self.sig_zeros, - self.sig_zeros] - input = np.vstack(arri) - targ = np.vstack(arrt) - res = mlab.detrend(input, key=mlab.detrend_linear, axis=1) - assert_allclose(res, targ, atol=self.atol) + mlab.detrend_linear(self.sig_slope[np.newaxis]) @pytest.mark.parametrize('iscomplex', [False, True], @@ -900,7 +188,7 @@ def test_detrend_detrend_linear_1d_slope_off_axis1_notranspose(self): ([], 255, 33, -1, -1, None), ([], 256, 128, -1, 256, 256), ([], None, -1, 32, -1, -1), - ], + ], ids=[ 'nosig', 'Fs4', @@ -992,7 +280,7 @@ def stim(self, request, fstims, iscomplex, sides, len_x, NFFT_density, num=pad_to_spectrum_real // 2 + 1) else: # frequencies for specgram, psd, and csd - # need to handle even and odd differentl + # need to handle even and odd differently if pad_to_density_real % 2: freqs_density = np.linspace(-Fs / 2, Fs / 2, num=2 * pad_to_density_real, @@ -1220,9 +508,8 @@ def test_psd_window_hanning(self): ydata = np.arange(self.NFFT_density) ydata1 = ydata+5 ydata2 = ydata+3.3 - ycontrol1, windowVals = _apply_window(ydata1, - mlab.window_hanning, - return_window=True) + windowVals = mlab.window_hanning(np.ones_like(ydata1)) + ycontrol1 = ydata1 * windowVals ycontrol2 = mlab.window_hanning(ydata2) ydata = np.vstack([ydata1, ydata2]) ycontrol = np.vstack([ycontrol1, ycontrol2]) @@ -1249,7 +536,7 @@ def test_psd_window_hanning(self): noverlap=0, sides=self.sides, window=mlab.window_none) - spec_c *= len(ycontrol1)/(np.abs(windowVals)**2).sum() + spec_c *= len(ycontrol1)/(windowVals**2).sum() assert_array_equal(fsp_g, fsp_c) assert_array_equal(fsp_b, fsp_c) assert_allclose(spec_g, spec_c, atol=1e-08) @@ -1266,9 +553,8 @@ def test_psd_window_hanning_detrend_linear(self): ydata2 = ydata+3.3 ycontrol1 = ycontrol ycontrol2 = ycontrol - ycontrol1, windowVals = _apply_window(ycontrol1, - mlab.window_hanning, - return_window=True) + windowVals = mlab.window_hanning(np.ones_like(ycontrol1)) + ycontrol1 = ycontrol1 * windowVals ycontrol2 = mlab.window_hanning(ycontrol2) ydata = np.vstack([ydata1, ydata2]) ycontrol = np.vstack([ycontrol1, ycontrol2]) @@ -1297,7 +583,7 @@ def test_psd_window_hanning_detrend_linear(self): noverlap=0, sides=self.sides, window=mlab.window_none) - spec_c *= len(ycontrol1)/(np.abs(windowVals)**2).sum() + spec_c *= len(ycontrol1)/(windowVals**2).sum() assert_array_equal(fsp_g, fsp_c) assert_array_equal(fsp_b, fsp_c) assert_allclose(spec_g, spec_c, atol=1e-08) @@ -1305,6 +591,33 @@ def test_psd_window_hanning_detrend_linear(self): with pytest.raises(AssertionError): assert_allclose(spec_b, spec_c, atol=1e-08) + def test_psd_window_flattop(self): + # flattop window + # adaption from https://github.com/scipy/scipy/blob\ + # /v1.10.0/scipy/signal/windows/_windows.py#L562-L622 + a = [0.21557895, 0.41663158, 0.277263158, 0.083578947, 0.006947368] + fac = np.linspace(-np.pi, np.pi, self.NFFT_density_real) + win = np.zeros(self.NFFT_density_real) + for k in range(len(a)): + win += a[k] * np.cos(k * fac) + + spec, fsp = mlab.psd(x=self.y, + NFFT=self.NFFT_density, + Fs=self.Fs, + noverlap=0, + sides=self.sides, + window=win, + scale_by_freq=False) + spec_a, fsp_a = mlab.psd(x=self.y, + NFFT=self.NFFT_density, + Fs=self.Fs, + noverlap=0, + sides=self.sides, + window=win) + assert_allclose(spec*win.sum()**2, + spec_a*self.Fs*(win**2).sum(), + atol=1e-08) + def test_psd_windowarray(self): freqs = self.freqs_density spec, fsp = mlab.psd(x=self.y, @@ -1498,11 +811,11 @@ def test_cohere(): assert np.isreal(np.mean(cohsq)) -#***************************************************************** -# These Tests where taken from SCIPY with some minor modifications +# ***************************************************************** +# These Tests were taken from SCIPY with some minor modifications # this can be retrieved from: # https://github.com/scipy/scipy/blob/master/scipy/stats/tests/test_kdeoth.py -#***************************************************************** +# ***************************************************************** class TestGaussianKDE: diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py new file mode 100644 index 000000000000..81a2e6adeb35 --- /dev/null +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -0,0 +1,564 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_allclose +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import (image_comparison, + remove_ticks_and_titles) +import matplotlib as mpl +import pytest +from pathlib import Path +from io import BytesIO +from PIL import Image +import base64 + + +@image_comparison(["bivariate_cmap_shapes.png"]) +def test_bivariate_cmap_shapes(): + x_0 = np.repeat(np.linspace(-0.1, 1.1, 10, dtype='float32')[None, :], 10, axis=0) + x_1 = x_0.T + + fig, axes = plt.subplots(1, 4, figsize=(10, 2)) + + # shape = 'square' + cmap = mpl.bivar_colormaps['BiPeak'] + axes[0].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = 'circle' + cmap = mpl.bivar_colormaps['BiCone'] + axes[1].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = 'ignore' + cmap = mpl.bivar_colormaps['BiPeak'] + cmap = cmap.with_extremes(shape='ignore') + axes[2].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = circleignore + cmap = mpl.bivar_colormaps['BiCone'] + cmap = cmap.with_extremes(shape='circleignore') + axes[3].imshow(cmap((x_0, x_1)), interpolation='nearest') + remove_ticks_and_titles(fig) + + +def test_multivar_creation(): + # test creation of a custom multivariate colorbar + blues = mpl.colormaps['Blues'] + cmap = mpl.colors.MultivarColormap((blues, 'Oranges'), 'sRGB_sub') + y, x = np.mgrid[0:3, 0:3]/2 + im = cmap((y, x)) + res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1], + [0.96004614, 0.53504037, 0.23277201, 1], + [0.46666667, 0.1372549, 0.01568627, 1]], + [[0.41708574, 0.64141484, 0.75980008, 1], + [0.40850442, 0.23135717, 0.07100346, 1], + [0, 0, 0, 1]], + [[0.03137255, 0.14901961, 0.34117647, 1], + [0.02279123, 0, 0, 1], + [0, 0, 0, 1]]]) + assert_allclose(im, res, atol=0.01) + + with pytest.raises(ValueError, match="colormaps must be a list of"): + cmap = mpl.colors.MultivarColormap((blues, [blues]), 'sRGB_sub') + with pytest.raises(ValueError, match="A MultivarColormap must"): + cmap = mpl.colors.MultivarColormap('blues', 'sRGB_sub') + with pytest.raises(ValueError, match="A MultivarColormap must"): + cmap = mpl.colors.MultivarColormap((blues), 'sRGB_sub') + + +@image_comparison(["multivar_alpha_mixing.png"]) +def test_multivar_alpha_mixing(): + # test creation of a custom colormap using 'rainbow' + # and a colormap that goes from alpha = 1 to alpha = 0 + rainbow = mpl.colormaps['rainbow'] + alpha = np.zeros((256, 4)) + alpha[:, 3] = np.linspace(1, 0, 256) + alpha_cmap = mpl.colors.LinearSegmentedColormap.from_list('from_list', alpha) + + cmap = mpl.colors.MultivarColormap((rainbow, alpha_cmap), 'sRGB_add') + y, x = np.mgrid[0:10, 0:10]/9 + im = cmap((y, x)) + + fig, ax = plt.subplots() + ax.imshow(im, interpolation='nearest') + remove_ticks_and_titles(fig) + + +def test_multivar_cmap_call(): + cmap = mpl.multivar_colormaps['2VarAddA'] + assert_array_equal(cmap((0.0, 0.0)), (0, 0, 0, 1)) + assert_array_equal(cmap((1.0, 1.0)), (1, 1, 1, 1)) + assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1) + + cmap = mpl.multivar_colormaps['2VarSubA'] + assert_array_equal(cmap((0.0, 0.0)), (1, 1, 1, 1)) + assert_allclose(cmap((1.0, 1.0)), (0, 0, 0, 1), atol=0.1) + + # check outside and bad + cs = cmap([(0., 0., 0., 1.2, np.nan), (0., 1.2, np.nan, 0., 0., )]) + assert_allclose(cs, [[1., 1., 1., 1.], + [0.801, 0.426, 0.119, 1.], + [0., 0., 0., 0.], + [0.199, 0.574, 0.881, 1.], + [0., 0., 0., 0.]]) + + assert_array_equal(cmap((0.0, 0.0), bytes=True), (255, 255, 255, 255)) + + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], alpha=(0.5, 0.3)) + + with pytest.raises(ValueError, match="For the selected colormap the data"): + cs = cmap([(0, 5, 9), (0, 0, 0), (0, 0, 0)]) + + with pytest.raises(ValueError, match="clip cannot be false"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, clip=False) + # Tests calling a multivariate colormap with integer values + cmap = mpl.multivar_colormaps['2VarSubA'] + + # call only integers + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)]) + res = np.array([[1, 1, 1, 1], + [0.85176471, 0.91029412, 0.96023529, 1], + [0.70452941, 0.82764706, 0.93358824, 1], + [0.94358824, 0.88505882, 0.83511765, 1], + [0.89729412, 0.77417647, 0.66823529, 1], + [0, 0, 0, 1]]) + assert_allclose(cs, res, atol=0.01) + + # call only integers, wrong byte order + swapped_dt = np.dtype(int).newbyteorder() + cs = cmap([np.array([0, 50, 100, 0, 0, 300], dtype=swapped_dt), + np.array([0, 0, 0, 50, 100, 300], dtype=swapped_dt)]) + assert_allclose(cs, res, atol=0.01) + + # call mix floats integers + # check calling with bytes = True + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], bytes=True) + res = np.array([[255, 255, 255, 255], + [217, 232, 244, 255], + [179, 211, 238, 255], + [240, 225, 212, 255], + [228, 197, 170, 255], + [0, 0, 0, 255]]) + assert_allclose(cs, res, atol=0.01) + + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], alpha=0.5) + res = np.array([[1, 1, 1, 0.5], + [0.85176471, 0.91029412, 0.96023529, 0.5], + [0.70452941, 0.82764706, 0.93358824, 0.5], + [0.94358824, 0.88505882, 0.83511765, 0.5], + [0.89729412, 0.77417647, 0.66823529, 0.5], + [0, 0, 0, 0.5]]) + assert_allclose(cs, res, atol=0.01) + # call with tuple + assert_allclose(cmap((100, 120), bytes=True, alpha=0.5), + [149, 142, 136, 127], atol=0.01) + + # alpha and bytes + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5) + res = np.array([[0, 0, 255, 127], + [141, 0, 255, 127], + [255, 0, 255, 127], + [0, 115, 255, 127], + [0, 255, 255, 127], + [255, 255, 255, 127]]) + + # bad alpha shape + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3)) + + cmap = cmap.with_extremes(bad=(1, 1, 1, 1)) + cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) + res = np.array([[1., 1., 1., 1.], + [0., 0., 0., 1.], + [1., 1., 1., 1.]]) + assert_allclose(cs, res, atol=0.01) + + # call outside with tuple + assert_allclose(cmap((300, 300), bytes=True, alpha=0.5), + [0, 0, 0, 127], atol=0.01) + with pytest.raises(ValueError, + match="For the selected colormap the data must have"): + cs = cmap((0, 5, 9)) + + # test over/under + cmap = mpl.multivar_colormaps['2VarAddA'] + with pytest.raises(ValueError, match='i.e. be of length 2'): + cmap.with_extremes(over=0) + with pytest.raises(ValueError, match='i.e. be of length 2'): + cmap.with_extremes(under=0) + + cmap = cmap.with_extremes(under=[(0, 0, 0, 0)]*2) + assert_allclose((0, 0, 0, 0), cmap((-1., 0)), atol=1e-2) + cmap = cmap.with_extremes(over=[(0, 0, 0, 0)]*2) + assert_allclose((0, 0, 0, 0), cmap((2., 0)), atol=1e-2) + + +def test_multivar_bad_mode(): + cmap = mpl.multivar_colormaps['2VarSubA'] + with pytest.raises(ValueError, match="is not a valid value for"): + cmap = mpl.colors.MultivarColormap(cmap[:], 'bad') + + +def test_multivar_resample(): + cmap = mpl.multivar_colormaps['3VarAddA'] + cmap_resampled = cmap.resampled((None, 10, 3)) + + assert_allclose(cmap_resampled[1](0.25), (0.093, 0.116, 0.059, 1.0)) + assert_allclose(cmap_resampled((0, 0.25, 0)), (0.093, 0.116, 0.059, 1.0)) + assert_allclose(cmap_resampled((1, 0.25, 1)), (0.417271, 0.264624, 0.274976, 1.), + atol=0.01) + + with pytest.raises(ValueError, match="lutshape must be of length"): + cmap = cmap.resampled(4) + + +def test_bivar_cmap_call_tuple(): + cmap = mpl.bivar_colormaps['BiOrangeBlue'] + assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1), atol=0.01) + assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1), atol=0.1) + assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1) + + +def test_bivar_cmap_call(): + """ + Tests calling a bivariate colormap with integer values + """ + im = np.ones((10, 12, 4)) + im[:, :, 0] = np.linspace(0, 1, 10)[:, np.newaxis] + im[:, :, 1] = np.linspace(0, 1, 12)[np.newaxis, :] + cmap = mpl.colors.BivarColormapFromImage(im) + + # call only integers + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)]) + res = np.array([[0, 0, 1, 1], + [0.556, 0, 1, 1], + [1, 0, 1, 1], + [0, 0.454, 1, 1], + [0, 1, 1, 1], + [1, 1, 1, 1]]) + assert_allclose(cs, res, atol=0.01) + # call only integers, wrong byte order + swapped_dt = np.dtype(int).newbyteorder() + cs = cmap([np.array([0, 5, 9, 0, 0, 10], dtype=swapped_dt), + np.array([0, 0, 0, 5, 11, 12], dtype=swapped_dt)]) + assert_allclose(cs, res, atol=0.01) + + # call mix floats integers + cmap = cmap.with_extremes(outside=(1, 0, 0, 0)) + cs = cmap([(0.5, 0), (0, 3)]) + res = np.array([[0.555, 0, 1, 1], + [0, 0.2727, 1, 1]]) + assert_allclose(cs, res, atol=0.01) + + # check calling with bytes = True + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True) + res = np.array([[0, 0, 255, 255], + [141, 0, 255, 255], + [255, 0, 255, 255], + [0, 115, 255, 255], + [0, 255, 255, 255], + [255, 255, 255, 255]]) + assert_allclose(cs, res, atol=0.01) + + # test alpha + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], alpha=0.5) + res = np.array([[0, 0, 1, 0.5], + [0.556, 0, 1, 0.5], + [1, 0, 1, 0.5], + [0, 0.454, 1, 0.5], + [0, 1, 1, 0.5], + [1, 1, 1, 0.5]]) + assert_allclose(cs, res, atol=0.01) + # call with tuple + assert_allclose(cmap((10, 12), bytes=True, alpha=0.5), + [255, 255, 255, 127], atol=0.01) + + # alpha and bytes + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5) + res = np.array([[0, 0, 255, 127], + [141, 0, 255, 127], + [255, 0, 255, 127], + [0, 115, 255, 127], + [0, 255, 255, 127], + [255, 255, 255, 127]]) + + # bad alpha shape + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3)) + + # set shape to 'ignore'. + # final point is outside colormap and should then receive + # the 'outside' (in this case [1,0,0,0]) + # also test 'bad' (in this case [1,1,1,0]) + cmap = cmap.with_extremes(outside=(1, 0, 0, 0), bad=(1, 1, 1, 0), shape='ignore') + cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) + res = np.array([[0, 0, 1, 1], + [1, 0, 0, 0], + [1, 1, 1, 0]]) + assert_allclose(cs, res, atol=0.01) + # call outside with tuple + assert_allclose(cmap((10, 12), bytes=True, alpha=0.5), + [255, 0, 0, 127], atol=0.01) + # with integers + cs = cmap([(0, 10), (0, 12)]) + res = np.array([[0, 0, 1, 1], + [1, 0, 0, 0]]) + assert_allclose(cs, res, atol=0.01) + + with pytest.raises(ValueError, + match="For a `BivarColormap` the data must have"): + cs = cmap((0, 5, 9)) + + cmap = cmap.with_extremes(shape='circle') + with pytest.raises(NotImplementedError, + match="only implemented for use with with floats"): + cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) + + # test origin + cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(0.5, 0.5)) + assert_allclose(cmap[0](0.5), + (0.50244140625, 0.5024222412109375, 0.50244140625, 1)) + assert_allclose(cmap[1](0.5), + (0.50244140625, 0.5024222412109375, 0.50244140625, 1)) + cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(1, 1)) + assert_allclose(cmap[0](1.), + (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0)) + assert_allclose(cmap[1](1.), + (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0)) + with pytest.raises(KeyError, + match="only 0 or 1 are valid keys"): + cs = cmap[2] + + +def test_bivar_getitem(): + """Test __getitem__ on BivarColormap""" + xA = ([.0, .25, .5, .75, 1., -1, 2], [.5]*7) + xB = ([.5]*7, [.0, .25, .5, .75, 1., -1, 2]) + + cmaps = mpl.bivar_colormaps['BiPeak'] + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + cmaps = cmaps.with_extremes(shape='ignore') + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + xA = ([.0, .25, .5, .75, 1., -1, 2], [.0]*7) + xB = ([.0]*7, [.0, .25, .5, .75, 1., -1, 2]) + cmaps = mpl.bivar_colormaps['BiOrangeBlue'] + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + cmaps = cmaps.with_extremes(shape='ignore') + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + +def test_bivar_cmap_bad_shape(): + """ + Tests calling a bivariate colormap with integer values + """ + cmap = mpl.bivar_colormaps['BiCone'] + _ = cmap.lut + with pytest.raises(ValueError, + match="is not a valid value for shape"): + cmap.with_extremes(shape='bad_shape') + + with pytest.raises(ValueError, + match="is not a valid value for shape"): + mpl.colors.BivarColormapFromImage(np.ones((3, 3, 4)), + shape='bad_shape') + + +def test_bivar_cmap_bad_lut(): + """ + Tests calling a bivariate colormap with integer values + """ + with pytest.raises(ValueError, + match="The lut must be an array of shape"): + cmap = mpl.colors.BivarColormapFromImage(np.ones((3, 3, 5))) + + +def test_bivar_cmap_from_image(): + """ + This tests the creation and use of a bivariate colormap + generated from an image + """ + + data_0 = np.arange(6).reshape((2, 3))/5 + data_1 = np.arange(6).reshape((3, 2)).T/5 + + # bivariate colormap from array + cim = np.ones((10, 12, 3)) + cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10 + cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12 + + cmap = mpl.colors.BivarColormapFromImage(cim) + im = cmap((data_0, data_1)) + res = np.array([[[0, 0, 1, 1], + [0.2, 0.33333333, 1, 1], + [0.4, 0.75, 1, 1]], + [[0.6, 0.16666667, 1, 1], + [0.8, 0.58333333, 1, 1], + [0.9, 0.91666667, 1, 1]]]) + assert_allclose(im, res, atol=0.01) + + # input as unit8 + cim = np.ones((10, 12, 3))*255 + cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10*255 + cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12*255 + + cmap = mpl.colors.BivarColormapFromImage(cim.astype(np.uint8)) + im = cmap((data_0, data_1)) + res = np.array([[[0, 0, 1, 1], + [0.2, 0.33333333, 1, 1], + [0.4, 0.75, 1, 1]], + [[0.6, 0.16666667, 1, 1], + [0.8, 0.58333333, 1, 1], + [0.9, 0.91666667, 1, 1]]]) + assert_allclose(im, res, atol=0.01) + + # bivariate colormap from array + png_path = Path(__file__).parent / "baseline_images/pngsuite/basn2c16.png" + cim = Image.open(png_path) + cim = np.asarray(cim.convert('RGBA')) + + cmap = mpl.colors.BivarColormapFromImage(cim) + im = cmap((data_0, data_1), bytes=True) + res = np.array([[[255, 255, 0, 255], + [156, 206, 0, 255], + [49, 156, 49, 255]], + [[206, 99, 0, 255], + [99, 49, 107, 255], + [0, 0, 255, 255]]]) + assert_allclose(im, res, atol=0.01) + + +def test_bivar_resample(): + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, 2)) + assert_allclose(cmap((0.25, 0.25)), (0, 0, 0, 1), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, 2)) + assert_allclose(cmap((0.25, 0.25)), (1., 0.5, 0., 1.), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, -2)) + assert_allclose(cmap((0.25, 0.25)), (0., 0.5, 1., 1.), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, -2)) + assert_allclose(cmap((0.25, 0.25)), (1, 1, 1, 1), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].reversed() + assert_allclose(cmap((0.25, 0.25)), (0.748535, 0.748547, 0.748535, 1.), atol=1e-2) + cmap = mpl.bivar_colormaps['BiOrangeBlue'].transposed() + assert_allclose(cmap((0.25, 0.25)), (0.252441, 0.252422, 0.252441, 1.), atol=1e-2) + + with pytest.raises(ValueError, match="lutshape must be of length"): + cmap = cmap.resampled(4) + + +def test_bivariate_repr_png(): + cmap = mpl.bivar_colormaps['BiCone'] + png = cmap._repr_png_() + assert len(png) > 0 + img = Image.open(BytesIO(png)) + assert img.width > 0 + assert img.height > 0 + assert 'Title' in img.text + assert 'Description' in img.text + assert 'Author' in img.text + assert 'Software' in img.text + + +def test_bivariate_repr_html(): + cmap = mpl.bivar_colormaps['BiCone'] + html = cmap._repr_html_() + assert len(html) > 0 + png = cmap._repr_png_() + assert base64.b64encode(png).decode('ascii') in html + assert cmap.name in html + assert html.startswith('') + + +def test_multivariate_repr_png(): + cmap = mpl.multivar_colormaps['3VarAddA'] + png = cmap._repr_png_() + assert len(png) > 0 + img = Image.open(BytesIO(png)) + assert img.width > 0 + assert img.height > 0 + assert 'Title' in img.text + assert 'Description' in img.text + assert 'Author' in img.text + assert 'Software' in img.text + + +def test_multivariate_repr_html(): + cmap = mpl.multivar_colormaps['3VarAddA'] + html = cmap._repr_html_() + assert len(html) > 0 + for c in cmap: + png = c._repr_png_() + assert base64.b64encode(png).decode('ascii') in html + assert cmap.name in html + assert html.startswith('') + + +def test_bivar_eq(): + """ + Tests equality between multivariate colormaps + """ + cmap_0 = mpl.bivar_colormaps['BiPeak'] + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + assert (cmap_0 == cmap_1) is True + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiCone'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1 = cmap_1.with_extremes(bad='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1 = cmap_1.with_extremes(outside='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1._init() + cmap_1._lut *= 0.5 + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1 = cmap_1.with_extremes(shape='ignore') + assert (cmap_0 == cmap_1) is False + + +def test_multivar_eq(): + """ + Tests equality between multivariate colormaps + """ + cmap_0 = mpl.multivar_colormaps['2VarAddA'] + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + assert (cmap_0 == cmap_1) is True + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.colors.MultivarColormap([cmap_0[0]]*2, + 'sRGB_add') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['3VarAddA'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + cmap_1 = cmap_1.with_extremes(bad='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + cmap_1 = mpl.colors.MultivarColormap(cmap_1[:], 'sRGB_sub') + assert (cmap_0 == cmap_1) is False diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py index 72fdbdbf2e3b..d9791ff5bc20 100644 --- a/lib/matplotlib/tests/test_offsetbox.py +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -5,15 +5,15 @@ from numpy.testing import assert_allclose import pytest -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.lines as mlines -from matplotlib.backend_bases import MouseButton +from matplotlib.backend_bases import MouseButton, MouseEvent from matplotlib.offsetbox import ( - AnchoredOffsetbox, AnnotationBbox, AnchoredText, DrawingArea, - OffsetImage, TextArea, _get_packed_offsets) + AnchoredOffsetbox, AnnotationBbox, AnchoredText, DrawingArea, HPacker, + OffsetBox, OffsetImage, PaddedBox, TextArea, VPacker, _get_packed_offsets) @image_comparison(['offsetbox_clipping'], remove_text=True) @@ -28,6 +28,7 @@ def test_offsetbox_clipping(): fig, ax = plt.subplots() size = 100 da = DrawingArea(size, size, clip=True) + assert da.clip_children bg = mpatches.Rectangle((0, 0), size, size, facecolor='#CCCCCC', edgecolor='None', @@ -117,76 +118,74 @@ def test_expand_with_tight_layout(): d2 = [2, 1] ax.plot(d1, label='series 1') ax.plot(d2, label='series 2') - ax.legend(ncol=2, mode='expand') + ax.legend(ncols=2, mode='expand') fig.tight_layout() # where the crash used to happen -@pytest.mark.parametrize('wd_list', - ([(150, 1)], [(150, 1)]*3, [(0.1, 1)], [(0.1, 1)]*2)) +@pytest.mark.parametrize('widths', + ([150], [150, 150, 150], [0.1], [0.1, 0.1])) @pytest.mark.parametrize('total', (250, 100, 0, -1, None)) @pytest.mark.parametrize('sep', (250, 1, 0, -1)) @pytest.mark.parametrize('mode', ("expand", "fixed", "equal")) -def test_get_packed_offsets(wd_list, total, sep, mode): +def test_get_packed_offsets(widths, total, sep, mode): # Check a (rather arbitrary) set of parameters due to successive similar # issue tickets (at least #10476 and #10784) related to corner cases # triggered inside this function when calling higher-level functions # (e.g. `Axes.legend`). # These are just some additional smoke tests. The output is untested. - _get_packed_offsets(wd_list, total, sep, mode=mode) + _get_packed_offsets(widths, total, sep, mode=mode) -_Params = namedtuple('_params', 'wd_list, total, sep, expected') +_Params = namedtuple('_Params', 'wd_list, total, sep, expected') -@pytest.mark.parametrize('wd_list, total, sep, expected', [ +@pytest.mark.parametrize('widths, total, sep, expected', [ _Params( # total=None - [(3, 0), (1, 0), (2, 0)], total=None, sep=1, expected=(8, [0, 4, 6])), + [3, 1, 2], total=None, sep=1, expected=(8, [0, 4, 6])), _Params( # total larger than required - [(3, 0), (1, 0), (2, 0)], total=10, sep=1, expected=(10, [0, 4, 6])), + [3, 1, 2], total=10, sep=1, expected=(10, [0, 4, 6])), _Params( # total smaller than required - [(3, 0), (1, 0), (2, 0)], total=5, sep=1, expected=(5, [0, 4, 6])), + [3, 1, 2], total=5, sep=1, expected=(5, [0, 4, 6])), ]) -def test_get_packed_offsets_fixed(wd_list, total, sep, expected): - result = _get_packed_offsets(wd_list, total, sep, mode='fixed') +def test_get_packed_offsets_fixed(widths, total, sep, expected): + result = _get_packed_offsets(widths, total, sep, mode='fixed') assert result[0] == expected[0] assert_allclose(result[1], expected[1]) -@pytest.mark.parametrize('wd_list, total, sep, expected', [ +@pytest.mark.parametrize('widths, total, sep, expected', [ _Params( # total=None (implicit 1) - [(.1, 0)] * 3, total=None, sep=None, expected=(1, [0, .45, .9])), + [.1, .1, .1], total=None, sep=None, expected=(1, [0, .45, .9])), _Params( # total larger than sum of widths - [(3, 0), (1, 0), (2, 0)], total=10, sep=1, expected=(10, [0, 5, 8])), + [3, 1, 2], total=10, sep=1, expected=(10, [0, 5, 8])), _Params( # total smaller sum of widths: overlapping boxes - [(3, 0), (1, 0), (2, 0)], total=5, sep=1, expected=(5, [0, 2.5, 3])), + [3, 1, 2], total=5, sep=1, expected=(5, [0, 2.5, 3])), ]) -def test_get_packed_offsets_expand(wd_list, total, sep, expected): - result = _get_packed_offsets(wd_list, total, sep, mode='expand') +def test_get_packed_offsets_expand(widths, total, sep, expected): + result = _get_packed_offsets(widths, total, sep, mode='expand') assert result[0] == expected[0] assert_allclose(result[1], expected[1]) -@pytest.mark.parametrize('wd_list, total, sep, expected', [ +@pytest.mark.parametrize('widths, total, sep, expected', [ _Params( # total larger than required - [(3, 0), (2, 0), (1, 0)], total=6, sep=None, expected=(6, [0, 2, 4])), + [3, 2, 1], total=6, sep=None, expected=(6, [0, 2, 4])), _Params( # total smaller sum of widths: overlapping boxes - [(3, 0), (2, 0), (1, 0), (.5, 0)], total=2, sep=None, - expected=(2, [0, 0.5, 1, 1.5])), + [3, 2, 1, .5], total=2, sep=None, expected=(2, [0, 0.5, 1, 1.5])), _Params( # total larger than required - [(.5, 0), (1, 0), (.2, 0)], total=None, sep=1, - expected=(6, [0, 2, 4])), + [.5, 1, .2], total=None, sep=1, expected=(6, [0, 2, 4])), # the case total=None, sep=None is tested separately below ]) -def test_get_packed_offsets_equal(wd_list, total, sep, expected): - result = _get_packed_offsets(wd_list, total, sep, mode='equal') +def test_get_packed_offsets_equal(widths, total, sep, expected): + result = _get_packed_offsets(widths, total, sep, mode='equal') assert result[0] == expected[0] assert_allclose(result[1], expected[1]) def test_get_packed_offsets_equal_total_none_sep_none(): with pytest.raises(ValueError): - _get_packed_offsets([(1, 0)] * 3, total=None, sep=None, mode='equal') + _get_packed_offsets([1, 1, 1], total=None, sep=None, mode='equal') @pytest.mark.parametrize('child_type', ['draw', 'image', 'text']) @@ -228,7 +227,8 @@ def test_picking(child_type, boxcoords): x, y = ax.transAxes.transform_point((0.5, 0.5)) fig.canvas.draw() calls.clear() - fig.canvas.button_press_event(x, y, MouseButton.LEFT) + MouseEvent( + "button_press_event", fig.canvas, x, y, MouseButton.LEFT)._process() assert len(calls) == 1 and calls[0].artist == ab # Annotation should *not* be picked by an event at its original center @@ -237,7 +237,8 @@ def test_picking(child_type, boxcoords): ax.set_ylim(-1, 0) fig.canvas.draw() calls.clear() - fig.canvas.button_press_event(x, y, MouseButton.LEFT) + MouseEvent( + "button_press_event", fig.canvas, x, y, MouseButton.LEFT)._process() assert len(calls) == 0 @@ -256,7 +257,8 @@ def test_anchoredtext_horizontal_alignment(): ax.add_artist(text2) -def test_annotationbbox_extents(): +@pytest.mark.parametrize("extent_kind", ["window_extent", "tightbbox"]) +def test_annotationbbox_extents(extent_kind): plt.rcParams.update(plt.rcParamsDefault) fig, ax = plt.subplots(figsize=(4, 3), dpi=100) @@ -283,31 +285,22 @@ def test_annotationbbox_extents(): arrowprops=dict(arrowstyle="->")) ax.add_artist(ab6) - fig.canvas.draw() - renderer = fig.canvas.get_renderer() - # Test Annotation - bb1w = an1.get_window_extent(renderer) - bb1e = an1.get_tightbbox(renderer) + bb1 = getattr(an1, f"get_{extent_kind}")() target1 = [332.9, 242.8, 467.0, 298.9] - assert_allclose(bb1w.extents, target1, atol=2) - assert_allclose(bb1e.extents, target1, atol=2) + assert_allclose(bb1.extents, target1, atol=2) # Test AnnotationBbox - bb3w = ab3.get_window_extent(renderer) - bb3e = ab3.get_tightbbox(renderer) + bb3 = getattr(ab3, f"get_{extent_kind}")() target3 = [-17.6, 129.0, 200.7, 167.9] - assert_allclose(bb3w.extents, target3, atol=2) - assert_allclose(bb3e.extents, target3, atol=2) + assert_allclose(bb3.extents, target3, atol=2) - bb6w = ab6.get_window_extent(renderer) - bb6e = ab6.get_tightbbox(renderer) + bb6 = getattr(ab6, f"get_{extent_kind}")() target6 = [180.0, -32.0, 230.0, 92.9] - assert_allclose(bb6w.extents, target6, atol=2) - assert_allclose(bb6e.extents, target6, atol=2) + assert_allclose(bb6.extents, target6, atol=2) # Test bbox_inches='tight' buf = io.BytesIO() @@ -321,3 +314,159 @@ def test_annotationbbox_extents(): fig.canvas.draw() fig.tight_layout() fig.canvas.draw() + + +def test_zorder(): + assert OffsetBox(zorder=42).zorder == 42 + + +def test_arrowprops_copied(): + da = DrawingArea(20, 20, 0, 0, clip=True) + arrowprops = {"arrowstyle": "->", "relpos": (.3, .7)} + ab = AnnotationBbox(da, [.5, .5], xybox=(-0.2, 0.5), xycoords='data', + boxcoords="axes fraction", box_alignment=(0., .5), + arrowprops=arrowprops) + assert ab.arrowprops is not ab + assert arrowprops["relpos"] == (.3, .7) + + +@pytest.mark.parametrize("align", ["baseline", "bottom", "top", + "left", "right", "center"]) +def test_packers(align): + # set the DPI to match points to make the math easier below + fig = plt.figure(dpi=72) + renderer = fig.canvas.get_renderer() + + x1, y1 = 10, 30 + x2, y2 = 20, 60 + r1 = DrawingArea(x1, y1) + r2 = DrawingArea(x2, y2) + + # HPacker + hpacker = HPacker(children=[r1, r2], align=align) + hpacker.draw(renderer) + bbox = hpacker.get_bbox(renderer) + px, py = hpacker.get_offset(bbox, renderer) + # width, height, xdescent, ydescent + assert_allclose(bbox.bounds, (0, 0, x1 + x2, max(y1, y2))) + # internal element placement + if align in ("baseline", "left", "bottom"): + y_height = 0 + elif align in ("right", "top"): + y_height = y2 - y1 + elif align == "center": + y_height = (y2 - y1) / 2 + # x-offsets, y-offsets + assert_allclose([child.get_offset() for child in hpacker.get_children()], + [(px, py + y_height), (px + x1, py)]) + + # VPacker + vpacker = VPacker(children=[r1, r2], align=align) + vpacker.draw(renderer) + bbox = vpacker.get_bbox(renderer) + px, py = vpacker.get_offset(bbox, renderer) + # width, height, xdescent, ydescent + assert_allclose(bbox.bounds, (0, -max(y1, y2), max(x1, x2), y1 + y2)) + # internal element placement + if align in ("baseline", "left", "bottom"): + x_height = 0 + elif align in ("right", "top"): + x_height = x2 - x1 + elif align == "center": + x_height = (x2 - x1) / 2 + # x-offsets, y-offsets + assert_allclose([child.get_offset() for child in vpacker.get_children()], + [(px + x_height, py), (px, py - y2)]) + + +def test_paddedbox_default_values(): + # smoke test paddedbox for correct default value + fig, ax = plt.subplots() + at = AnchoredText("foo", 'upper left') + pb = PaddedBox(at, patch_attrs={'facecolor': 'r'}, draw_frame=True) + ax.add_artist(pb) + fig.draw_without_rendering() + + +def test_annotationbbox_properties(): + ab = AnnotationBbox(DrawingArea(20, 20, 0, 0, clip=True), (0.5, 0.5), + xycoords='data') + assert ab.xyann == (0.5, 0.5) # xy if xybox not given + assert ab.anncoords == 'data' # xycoords if boxcoords not given + + ab = AnnotationBbox(DrawingArea(20, 20, 0, 0, clip=True), (0.5, 0.5), + xybox=(-0.2, 0.4), xycoords='data', + boxcoords='axes fraction') + assert ab.xyann == (-0.2, 0.4) # xybox if given + assert ab.anncoords == 'axes fraction' # boxcoords if given + + +def test_textarea_properties(): + ta = TextArea('Foo') + assert ta.get_text() == 'Foo' + assert not ta.get_multilinebaseline() + + ta.set_text('Bar') + ta.set_multilinebaseline(True) + assert ta.get_text() == 'Bar' + assert ta.get_multilinebaseline() + + +@check_figures_equal() +def test_textarea_set_text(fig_test, fig_ref): + ax_ref = fig_ref.add_subplot() + text0 = AnchoredText("Foo", "upper left") + ax_ref.add_artist(text0) + + ax_test = fig_test.add_subplot() + text1 = AnchoredText("Bar", "upper left") + ax_test.add_artist(text1) + text1.txt.set_text("Foo") + + +@image_comparison(['paddedbox.png'], remove_text=True, style='mpl20') +def test_paddedbox(): + fig, ax = plt.subplots() + + ta = TextArea("foo") + pb = PaddedBox(ta, pad=5, patch_attrs={'facecolor': 'r'}, draw_frame=True) + ab = AnchoredOffsetbox('upper left', child=pb) + ax.add_artist(ab) + + ta = TextArea("bar") + pb = PaddedBox(ta, pad=10, patch_attrs={'facecolor': 'b'}) + ab = AnchoredOffsetbox('upper right', child=pb) + ax.add_artist(ab) + + ta = TextArea("foobar") + pb = PaddedBox(ta, pad=15, draw_frame=True) + ab = AnchoredOffsetbox('lower right', child=pb) + ax.add_artist(ab) + + +def test_remove_draggable(): + fig, ax = plt.subplots() + an = ax.annotate("foo", (.5, .5)) + an.draggable(True) + an.remove() + MouseEvent("button_release_event", fig.canvas, 1, 1)._process() + + +def test_draggable_in_subfigure(): + fig = plt.figure() + # Put annotation at lower left corner to make it easily pickable below. + ann = fig.subfigures().add_axes([0, 0, 1, 1]).annotate("foo", (0, 0)) + ann.draggable(True) + fig.canvas.draw() # Texts are non-pickable until the first draw. + MouseEvent("button_press_event", fig.canvas, 1, 1)._process() + assert ann._draggable.got_artist + # Stop dragging the annotation. + MouseEvent("button_release_event", fig.canvas, 1, 1)._process() + assert not ann._draggable.got_artist + # A scroll event should not initiate a drag. + MouseEvent("scroll_event", fig.canvas, 1, 1)._process() + assert not ann._draggable.got_artist + # An event outside the annotation should not initiate a drag. + bbox = ann.get_window_extent() + MouseEvent("button_press_event", fig.canvas, bbox.x1+2, bbox.y1+2)._process() + assert not ann._draggable.got_artist diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index baff5dabccaa..4ed9222eb95e 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -1,20 +1,21 @@ """ Tests specific to the patches module. """ +import platform + import numpy as np from numpy.testing import assert_almost_equal, assert_array_equal import pytest -from matplotlib.patches import Patch, Polygon, Rectangle, FancyArrowPatch +import matplotlib as mpl +from matplotlib.patches import (Annulus, Ellipse, Patch, Polygon, Rectangle, + FancyArrowPatch, FancyArrow, BoxStyle, Arc) from matplotlib.testing.decorators import image_comparison, check_figures_equal from matplotlib.transforms import Bbox import matplotlib.pyplot as plt from matplotlib import ( collections as mcollections, colors as mcolors, patches as mpatches, - path as mpath, style as mstyle, transforms as mtransforms, rcParams) - -import sys -on_win = (sys.platform == 'win32') + path as mpath, transforms as mtransforms, rcParams) def test_Polygon_close(): @@ -29,6 +30,7 @@ def test_Polygon_close(): # start with open path and close it: p = Polygon(xy, closed=True) + assert p.get_closed() assert_array_equal(p.get_xy(), xyclosed) p.set_xy(xy) assert_array_equal(p.get_xy(), xyclosed) @@ -41,6 +43,7 @@ def test_Polygon_close(): # start with open path and leave it open: p = Polygon(xy, closed=False) + assert not p.get_closed() assert_array_equal(p.get_xy(), xy) p.set_xy(xy) assert_array_equal(p.get_xy(), xy) @@ -52,6 +55,105 @@ def test_Polygon_close(): assert_array_equal(p.get_xy(), xyclosed) +def test_corner_center(): + loc = [10, 20] + width = 1 + height = 2 + + # Rectangle + # No rotation + corners = ((10, 20), (11, 20), (11, 22), (10, 22)) + rect = Rectangle(loc, width, height) + assert_array_equal(rect.get_corners(), corners) + assert_array_equal(rect.get_center(), (10.5, 21)) + + # 90 deg rotation + corners_rot = ((10, 20), (10, 21), (8, 21), (8, 20)) + rect.set_angle(90) + assert_array_equal(rect.get_corners(), corners_rot) + assert_array_equal(rect.get_center(), (9, 20.5)) + + # Rotation not a multiple of 90 deg + theta = 33 + t = mtransforms.Affine2D().rotate_around(*loc, np.deg2rad(theta)) + corners_rot = t.transform(corners) + rect.set_angle(theta) + assert_almost_equal(rect.get_corners(), corners_rot) + + # Ellipse + loc = [loc[0] + width / 2, + loc[1] + height / 2] + ellipse = Ellipse(loc, width, height) + + # No rotation + assert_array_equal(ellipse.get_corners(), corners) + + # 90 deg rotation + corners_rot = ((11.5, 20.5), (11.5, 21.5), (9.5, 21.5), (9.5, 20.5)) + ellipse.set_angle(90) + assert_array_equal(ellipse.get_corners(), corners_rot) + # Rotation shouldn't change ellipse center + assert_array_equal(ellipse.get_center(), loc) + + # Rotation not a multiple of 90 deg + theta = 33 + t = mtransforms.Affine2D().rotate_around(*loc, np.deg2rad(theta)) + corners_rot = t.transform(corners) + ellipse.set_angle(theta) + assert_almost_equal(ellipse.get_corners(), corners_rot) + + +def test_ellipse_vertices(): + # expect 0 for 0 ellipse width, height + ellipse = Ellipse(xy=(0, 0), width=0, height=0, angle=0) + assert_almost_equal( + ellipse.get_vertices(), + [(0.0, 0.0), (0.0, 0.0)], + ) + assert_almost_equal( + ellipse.get_co_vertices(), + [(0.0, 0.0), (0.0, 0.0)], + ) + + ellipse = Ellipse(xy=(0, 0), width=2, height=1, angle=30) + assert_almost_equal( + ellipse.get_vertices(), + [ + ( + ellipse.center[0] + ellipse.width / 4 * np.sqrt(3), + ellipse.center[1] + ellipse.width / 4, + ), + ( + ellipse.center[0] - ellipse.width / 4 * np.sqrt(3), + ellipse.center[1] - ellipse.width / 4, + ), + ], + ) + assert_almost_equal( + ellipse.get_co_vertices(), + [ + ( + ellipse.center[0] - ellipse.height / 4, + ellipse.center[1] + ellipse.height / 4 * np.sqrt(3), + ), + ( + ellipse.center[0] + ellipse.height / 4, + ellipse.center[1] - ellipse.height / 4 * np.sqrt(3), + ), + ], + ) + v1, v2 = np.array(ellipse.get_vertices()) + np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center) + v1, v2 = np.array(ellipse.get_co_vertices()) + np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center) + + ellipse = Ellipse(xy=(2.252, -10.859), width=2.265, height=1.98, angle=68.78) + v1, v2 = np.array(ellipse.get_vertices()) + np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center) + v1, v2 = np.array(ellipse.get_co_vertices()) + np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center) + + def test_rotate_rect(): loc = np.asarray([1.0, 2.0]) width = 2 @@ -76,6 +178,61 @@ def test_rotate_rect(): assert_almost_equal(rect1.get_verts(), new_verts) +@check_figures_equal() +def test_rotate_rect_draw(fig_test, fig_ref): + ax_test = fig_test.add_subplot() + ax_ref = fig_ref.add_subplot() + + loc = (0, 0) + width, height = (1, 1) + angle = 30 + rect_ref = Rectangle(loc, width, height, angle=angle) + ax_ref.add_patch(rect_ref) + assert rect_ref.get_angle() == angle + + # Check that when the angle is updated after adding to an Axes, that the + # patch is marked stale and redrawn in the correct location + rect_test = Rectangle(loc, width, height) + assert rect_test.get_angle() == 0 + ax_test.add_patch(rect_test) + rect_test.set_angle(angle) + assert rect_test.get_angle() == angle + + +@check_figures_equal() +def test_dash_offset_patch_draw(fig_test, fig_ref): + ax_test = fig_test.add_subplot() + ax_ref = fig_ref.add_subplot() + + loc = (0.1, 0.1) + width, height = (0.8, 0.8) + rect_ref = Rectangle(loc, width, height, linewidth=3, edgecolor='b', + linestyle=(0, [6, 6])) + # fill the line gaps using a linestyle (0, [0, 6, 6, 0]), which is + # equivalent to (6, [6, 6]) but has 0 dash offset + rect_ref2 = Rectangle(loc, width, height, linewidth=3, edgecolor='r', + linestyle=(0, [0, 6, 6, 0])) + assert rect_ref.get_linestyle() == (0, [6, 6]) + assert rect_ref2.get_linestyle() == (0, [0, 6, 6, 0]) + + ax_ref.add_patch(rect_ref) + ax_ref.add_patch(rect_ref2) + + # Check that the dash offset of the rect is the same if we pass it in the + # init method and if we create two rects with appropriate onoff sequence + # of linestyle. + + rect_test = Rectangle(loc, width, height, linewidth=3, edgecolor='b', + linestyle=(0, [6, 6])) + rect_test2 = Rectangle(loc, width, height, linewidth=3, edgecolor='r', + linestyle=(6, [6, 6])) + assert rect_test.get_linestyle() == (0, [6, 6]) + assert rect_test2.get_linestyle() == (6, [6, 6]) + + ax_test.add_patch(rect_test) + ax_test.add_patch(rect_test2) + + def test_negative_rect(): # These two rectangles have the same vertices, but starting from a # different point. (We also drop the last vertex, which is a duplicate.) @@ -84,11 +241,9 @@ def test_negative_rect(): assert_array_equal(np.roll(neg_vertices, 2, 0), pos_vertices) -@image_comparison(['clip_to_bbox']) +@image_comparison(['clip_to_bbox.png']) def test_clip_to_bbox(): - fig = plt.figure() - - ax = fig.add_subplot(111) + fig, ax = plt.subplots() ax.set_xlim([-18, 20]) ax.set_ylim([-150, 100]) @@ -129,20 +284,20 @@ def test_patch_alpha_coloring(): cut_star2 = mpath.Path(verts + 1, codes) ax = plt.axes() - patch = mpatches.PathPatch(cut_star1, - linewidth=5, linestyle='dashdot', - facecolor=(1, 0, 0, 0.5), - edgecolor=(0, 0, 1, 0.75)) - ax.add_patch(patch) - col = mcollections.PathCollection([cut_star2], linewidth=5, linestyles='dashdot', facecolor=(1, 0, 0, 0.5), edgecolor=(0, 0, 1, 0.75)) ax.add_collection(col) - ax.set_xlim([-1, 2]) - ax.set_ylim([-1, 2]) + patch = mpatches.PathPatch(cut_star1, + linewidth=5, linestyle='dashdot', + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_patch(patch) + + ax.set_xlim(-1, 2) + ax.set_ylim(-1, 2) @image_comparison(['patch_alpha_override'], remove_text=True) @@ -159,13 +314,6 @@ def test_patch_alpha_override(): cut_star2 = mpath.Path(verts + 1, codes) ax = plt.axes() - patch = mpatches.PathPatch(cut_star1, - linewidth=5, linestyle='dashdot', - alpha=0.25, - facecolor=(1, 0, 0, 0.5), - edgecolor=(0, 0, 1, 0.75)) - ax.add_patch(patch) - col = mcollections.PathCollection([cut_star2], linewidth=5, linestyles='dashdot', alpha=0.25, @@ -173,11 +321,18 @@ def test_patch_alpha_override(): edgecolor=(0, 0, 1, 0.75)) ax.add_collection(col) - ax.set_xlim([-1, 2]) - ax.set_ylim([-1, 2]) + patch = mpatches.PathPatch(cut_star1, + linewidth=5, linestyle='dashdot', + alpha=0.25, + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_patch(patch) + + ax.set_xlim(-1, 2) + ax.set_ylim(-1, 2) -@pytest.mark.style('default') +@mpl.style.context('default') def test_patch_color_none(): # Make sure the alpha kwarg does not override 'none' facecolor. # Addresses issue #7478. @@ -198,20 +353,20 @@ def test_patch_custom_linestyle(): cut_star2 = mpath.Path(verts + 1, codes) ax = plt.axes() - patch = mpatches.PathPatch( - cut_star1, - linewidth=5, linestyle=(0, (5, 7, 10, 7)), - facecolor=(1, 0, 0), edgecolor=(0, 0, 1)) - ax.add_patch(patch) - col = mcollections.PathCollection( [cut_star2], linewidth=5, linestyles=[(0, (5, 7, 10, 7))], facecolor=(1, 0, 0), edgecolor=(0, 0, 1)) ax.add_collection(col) - ax.set_xlim([-1, 2]) - ax.set_ylim([-1, 2]) + patch = mpatches.PathPatch( + cut_star1, + linewidth=5, linestyle=(0, (5, 7, 10, 7)), + facecolor=(1, 0, 0), edgecolor=(0, 0, 1)) + ax.add_patch(patch) + + ax.set_xlim(-1, 2) + ax.set_ylim(-1, 2) def test_patch_linestyle_accents(): @@ -226,8 +381,7 @@ def test_patch_linestyle_accents(): linestyles = ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"] - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() for i, ls in enumerate(linestyles): star = mpath.Path(verts + i, codes) patch = mpatches.PathPatch(star, @@ -241,6 +395,32 @@ def test_patch_linestyle_accents(): fig.canvas.draw() +@check_figures_equal() +def test_patch_linestyle_none(fig_test, fig_ref): + circle = mpath.Path.unit_circle() + + ax_test = fig_test.add_subplot() + ax_ref = fig_ref.add_subplot() + for i, ls in enumerate(['none', 'None', ' ', '']): + path = mpath.Path(circle.vertices + i, circle.codes) + patch = mpatches.PathPatch(path, + linewidth=3, linestyle=ls, + facecolor=(1, 0, 0), + edgecolor=(0, 0, 1)) + ax_test.add_patch(patch) + + patch = mpatches.PathPatch(path, + linewidth=3, linestyle='-', + facecolor=(1, 0, 0), + edgecolor='none') + ax_ref.add_patch(patch) + + ax_test.set_xlim([-1, i + 1]) + ax_test.set_ylim([-1, i + 1]) + ax_ref.set_xlim([-1, i + 1]) + ax_ref.set_ylim([-1, i + 1]) + + def test_wedge_movement(): param_dict = {'center': ((0, 0), (1, 1), 'set_center'), 'r': (5, 8, 'set_radius'), @@ -257,8 +437,8 @@ def test_wedge_movement(): assert getattr(w, attr) == new_v -# png needs tol>=0.06, pdf tol>=1.617 -@image_comparison(['wedge_range'], remove_text=True, tol=1.65 if on_win else 0) +@image_comparison(['wedge_range'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.009) def test_wedge_range(): ax = plt.axes() @@ -283,8 +463,8 @@ def test_wedge_range(): ax.add_artist(wedge) - ax.set_xlim([-2, 8]) - ax.set_ylim([-2, 9]) + ax.set_xlim(-2, 8) + ax.set_ylim(-2, 9) def test_patch_str(): @@ -310,6 +490,10 @@ def test_patch_str(): expected = 'Arc(xy=(1, 2), width=3, height=4, angle=5, theta1=6, theta2=7)' assert str(p) == expected + p = mpatches.Annulus(xy=(1, 2), r=(3, 4), width=1, angle=2) + expected = "Annulus(xy=(1, 2), r=(3, 4), width=1, angle=2)" + assert str(p) == expected + p = mpatches.RegularPolygon((1, 2), 20, radius=5) assert str(p) == "RegularPolygon((1, 2), 20, radius=5, orientation=0)" @@ -324,6 +508,9 @@ def test_patch_str(): p = mpatches.PathPatch(path) assert str(p) == "PathPatch3((1, 2) ...)" + p = mpatches.Polygon(np.empty((0, 2))) + assert str(p) == "Polygon0()" + data = [[1, 2], [2, 2], [1, 2]] p = mpatches.Polygon(data) assert str(p) == "Polygon3((1, 2) ...)" @@ -351,14 +538,14 @@ def test_multi_color_hatch(): rects = ax.bar(range(5), range(1, 6)) for i, rect in enumerate(rects): rect.set_facecolor('none') - rect.set_edgecolor('C{}'.format(i)) + rect.set_edgecolor(f'C{i}') rect.set_hatch('/') ax.autoscale_view() ax.autoscale(False) for i in range(5): - with mstyle.context({'hatch.color': 'C{}'.format(i)}): + with mpl.style.context({'hatch.color': f'C{i}'}): r = Rectangle((i - .8 / 2, 5), .8, 1, hatch='//', fc='none') ax.add_patch(r) @@ -376,7 +563,8 @@ def test_units_rectangle(): ax.set_ylim([5*U.km, 9*U.km]) -@image_comparison(['connection_patch.png'], style='mpl20', remove_text=True) +@image_comparison(['connection_patch.png'], style='mpl20', remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.024) def test_connection_patch(): fig, (ax1, ax2) = plt.subplots(1, 2) @@ -395,7 +583,7 @@ def test_connection_patch(): ax2.add_artist(con) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_connection_patch_fig(fig_test, fig_ref): # Test that connection patch can be added as figure artist, and that figure # pixels count negative values from the top right corner (this API may be @@ -418,6 +606,28 @@ def test_connection_patch_fig(fig_test, fig_ref): fig_ref.add_artist(con) +@check_figures_equal() +def test_connection_patch_pixel_points(fig_test, fig_ref): + xyA_pts = (.3, .2) + xyB_pts = (-30, -20) + + ax1, ax2 = fig_test.subplots(1, 2) + con = mpatches.ConnectionPatch(xyA=xyA_pts, coordsA="axes points", axesA=ax1, + xyB=xyB_pts, coordsB="figure points", + arrowstyle="->", shrinkB=5) + fig_test.add_artist(con) + + plt.rcParams["savefig.dpi"] = plt.rcParams["figure.dpi"] + + ax1, ax2 = fig_ref.subplots(1, 2) + xyA_pix = (xyA_pts[0]*(fig_ref.dpi/72), xyA_pts[1]*(fig_ref.dpi/72)) + xyB_pix = (xyB_pts[0]*(fig_ref.dpi/72), xyB_pts[1]*(fig_ref.dpi/72)) + con = mpatches.ConnectionPatch(xyA=xyA_pix, coordsA="axes pixels", axesA=ax1, + xyB=xyB_pix, coordsB="figure pixels", + arrowstyle="->", shrinkB=5) + fig_ref.add_artist(con) + + def test_datetime_rectangle(): # Check that creating a rectangle with timedeltas doesn't fail from datetime import datetime, timedelta @@ -434,7 +644,7 @@ def test_datetime_datetime_fails(): from datetime import datetime start = datetime(2017, 1, 1, 0, 0, 0) - dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong + dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong. with pytest.raises(TypeError): mpatches.Rectangle((start, 0), dt_delta, 1) @@ -444,7 +654,7 @@ def test_datetime_datetime_fails(): def test_contains_point(): - ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0, 0) + ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0) points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)] path = ell.get_path() transform = ell.get_transform() @@ -457,7 +667,7 @@ def test_contains_point(): def test_contains_points(): - ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0, 0) + ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0) points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)] path = ell.get_path() transform = ell.get_transform() @@ -468,7 +678,7 @@ def test_contains_points(): # Currently fails with pdf/svg, probably because some parts assume a dpi of 72. -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_shadow(fig_test, fig_ref): xy = np.array([.2, .3]) dxy = np.array([.1, .2]) @@ -499,7 +709,37 @@ def test_fancyarrow_units(): dtime = datetime(2000, 1, 1) fig, ax = plt.subplots() arrow = FancyArrowPatch((0, dtime), (0.01, dtime)) - ax.add_patch(arrow) + + +def test_fancyarrow_setdata(): + fig, ax = plt.subplots() + arrow = ax.arrow(0, 0, 10, 10, head_length=5, head_width=1, width=.5) + expected1 = np.array( + [[13.54, 13.54], + [10.35, 9.65], + [10.18, 9.82], + [0.18, -0.18], + [-0.18, 0.18], + [9.82, 10.18], + [9.65, 10.35], + [13.54, 13.54]] + ) + assert np.allclose(expected1, np.round(arrow.verts, 2)) + + expected2 = np.array( + [[16.71, 16.71], + [16.71, 15.29], + [16.71, 15.29], + [1.71, 0.29], + [0.29, 1.71], + [15.29, 16.71], + [15.29, 16.71], + [16.71, 16.71]] + ) + arrow.set_data( + x=1, y=1, dx=15, dy=15, width=2, head_width=2, head_length=1 + ) + assert np.allclose(expected2, np.round(arrow.verts, 2)) @image_comparison(["large_arc.svg"], style="mpl20") @@ -509,7 +749,7 @@ def test_large_arc(): y = -2115 diameter = 4261 for ax in [ax1, ax2]: - a = mpatches.Arc((x, y), diameter, diameter, lw=2, color='k') + a = Arc((x, y), diameter, diameter, lw=2, color='k') ax.add_patch(a) ax.set_axis_off() ax.set_aspect('equal') @@ -536,7 +776,7 @@ def test_rotated_arcs(): for prescale, centers in zip((1 - .0001, (1 - .0001) / np.sqrt(2)), (on_axis_centers, diag_centers)): for j, (x_sign, y_sign) in enumerate(centers, start=k): - a = mpatches.Arc( + a = Arc( (x_sign * scale * prescale, y_sign * scale * prescale), scale * sx, @@ -559,6 +799,78 @@ def test_rotated_arcs(): ax.set_aspect("equal") +def test_fancyarrow_shape_error(): + with pytest.raises(ValueError, match="Got unknown shape: 'foo'"): + FancyArrow(0, 0, 0.2, 0.2, shape='foo') + + +@pytest.mark.parametrize('fmt, match', ( + ("foo", "Unknown style: 'foo'"), + ("Round,foo", "Incorrect style argument: 'Round,foo'"), +)) +def test_boxstyle_errors(fmt, match): + with pytest.raises(ValueError, match=match): + BoxStyle(fmt) + + +@image_comparison(baseline_images=['annulus'], extensions=['png']) +def test_annulus(): + + fig, ax = plt.subplots() + cir = Annulus((0.5, 0.5), 0.2, 0.05, fc='g') # circular annulus + ell = Annulus((0.5, 0.5), (0.5, 0.3), 0.1, 45, # elliptical + fc='m', ec='b', alpha=0.5, hatch='xxx') + ax.add_patch(cir) + ax.add_patch(ell) + ax.set_aspect('equal') + + +@image_comparison(baseline_images=['annulus'], extensions=['png']) +def test_annulus_setters(): + + fig, ax = plt.subplots() + cir = Annulus((0., 0.), 0.2, 0.01, fc='g') # circular annulus + ell = Annulus((0., 0.), (1, 2), 0.1, 0, # elliptical + fc='m', ec='b', alpha=0.5, hatch='xxx') + ax.add_patch(cir) + ax.add_patch(ell) + ax.set_aspect('equal') + + cir.center = (0.5, 0.5) + cir.radii = 0.2 + cir.width = 0.05 + + ell.center = (0.5, 0.5) + ell.radii = (0.5, 0.3) + ell.width = 0.1 + ell.angle = 45 + + +@image_comparison(baseline_images=['annulus'], extensions=['png']) +def test_annulus_setters2(): + + fig, ax = plt.subplots() + cir = Annulus((0., 0.), 0.2, 0.01, fc='g') # circular annulus + ell = Annulus((0., 0.), (1, 2), 0.1, 0, # elliptical + fc='m', ec='b', alpha=0.5, hatch='xxx') + ax.add_patch(cir) + ax.add_patch(ell) + ax.set_aspect('equal') + + cir.center = (0.5, 0.5) + cir.set_semimajor(0.2) + cir.set_semiminor(0.2) + assert cir.radii == (0.2, 0.2) + cir.width = 0.05 + + ell.center = (0.5, 0.5) + ell.set_semimajor(0.5) + ell.set_semiminor(0.3) + assert ell.radii == (0.5, 0.3) + ell.width = 0.1 + ell.angle = 45 + + def test_degenerate_polygon(): point = [0, 0] correct_extents = Bbox([point, point]).extents @@ -603,3 +915,181 @@ def test_default_capstyle(): def test_default_joinstyle(): patch = Patch() assert patch.get_joinstyle() == 'miter' + + +@image_comparison(["autoscale_arc"], extensions=['png', 'svg'], + style="mpl20", remove_text=True) +def test_autoscale_arc(): + fig, axs = plt.subplots(1, 3, figsize=(4, 1)) + arc_lists = ( + [Arc((0, 0), 1, 1, theta1=0, theta2=90)], + [Arc((0.5, 0.5), 1.5, 0.5, theta1=10, theta2=20)], + [Arc((0.5, 0.5), 1.5, 0.5, theta1=10, theta2=20), + Arc((0.5, 0.5), 2.5, 0.5, theta1=110, theta2=120), + Arc((0.5, 0.5), 3.5, 0.5, theta1=210, theta2=220), + Arc((0.5, 0.5), 4.5, 0.5, theta1=310, theta2=320)]) + + for ax, arcs in zip(axs, arc_lists): + for arc in arcs: + ax.add_patch(arc) + ax.autoscale() + + +@check_figures_equal(extensions=["png", 'svg', 'pdf', 'eps']) +def test_arc_in_collection(fig_test, fig_ref): + arc1 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20) + arc2 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20) + col = mcollections.PatchCollection(patches=[arc2], facecolors='none', + edgecolors='k') + fig_ref.subplots().add_patch(arc1) + fig_test.subplots().add_collection(col) + + +@check_figures_equal(extensions=["png", 'svg', 'pdf', 'eps']) +def test_modifying_arc(fig_test, fig_ref): + arc1 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20) + arc2 = Arc([.5, .5], 1.5, 1, theta1=0, theta2=60, angle=10) + fig_ref.subplots().add_patch(arc1) + fig_test.subplots().add_patch(arc2) + arc2.set_width(.5) + arc2.set_angle(20) + + +def test_arrow_set_data(): + fig, ax = plt.subplots() + arrow = mpl.patches.Arrow(2, 0, 0, 10) + expected1 = np.array( + [[1.9, 0.], + [2.1, -0.], + [2.1, 8.], + [2.3, 8.], + [2., 10.], + [1.7, 8.], + [1.9, 8.], + [1.9, 0.]] + ) + assert np.allclose(expected1, np.round(arrow.get_verts(), 2)) + + expected2 = np.array( + [[0.39, 0.04], + [0.61, -0.04], + [3.01, 6.36], + [3.24, 6.27], + [3.5, 8.], + [2.56, 6.53], + [2.79, 6.44], + [0.39, 0.04]] + ) + arrow.set_data(x=.5, dx=3, dy=8, width=1.2) + assert np.allclose(expected2, np.round(arrow.get_verts(), 2)) + + +@check_figures_equal(extensions=["png", "pdf", "svg", "eps"]) +def test_set_and_get_hatch_linewidth(fig_test, fig_ref): + ax_test = fig_test.add_subplot() + ax_ref = fig_ref.add_subplot() + + lw = 2.0 + + with plt.rc_context({"hatch.linewidth": lw}): + ax_ref.add_patch(mpatches.Rectangle((0, 0), 1, 1, hatch="x")) + + ax_test.add_patch(mpatches.Rectangle((0, 0), 1, 1, hatch="x")) + ax_test.patches[0].set_hatch_linewidth(lw) + + assert ax_ref.patches[0].get_hatch_linewidth() == lw + assert ax_test.patches[0].get_hatch_linewidth() == lw + + +def test_patch_hatchcolor_inherit_logic(): + with mpl.rc_context({'hatch.color': 'edge'}): + # Test for when edgecolor and hatchcolor is set + rect = Rectangle((0, 0), 1, 1, hatch='//', ec='red', + hatchcolor='yellow') + assert mcolors.same_color(rect.get_edgecolor(), 'red') + assert mcolors.same_color(rect.get_hatchcolor(), 'yellow') + + # Test for explicitly setting edgecolor and then hatchcolor + rect = Rectangle((0, 0), 1, 1, hatch='//') + rect.set_edgecolor('orange') + assert mcolors.same_color(rect.get_hatchcolor(), 'orange') + rect.set_hatchcolor('cyan') + assert mcolors.same_color(rect.get_hatchcolor(), 'cyan') + + # Test for explicitly setting hatchcolor and then edgecolor + rect = Rectangle((0, 0), 1, 1, hatch='//') + rect.set_hatchcolor('purple') + assert mcolors.same_color(rect.get_hatchcolor(), 'purple') + rect.set_edgecolor('green') + assert mcolors.same_color(rect.get_hatchcolor(), 'purple') + + # Smoke test for setting with numpy array + rect.set_hatchcolor(np.ones(3)) + + +def test_patch_hatchcolor_fallback_logic(): + # Test for when hatchcolor parameter is passed + rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green') + assert mcolors.same_color(rect.get_hatchcolor(), 'green') + + # Test that hatchcolor parameter takes precedence over rcParam + # When edgecolor is not set + with mpl.rc_context({'hatch.color': 'blue'}): + rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green') + assert mcolors.same_color(rect.get_hatchcolor(), 'green') + # When edgecolor is set + with mpl.rc_context({'hatch.color': 'yellow'}): + rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green', edgecolor='red') + assert mcolors.same_color(rect.get_hatchcolor(), 'green') + + # Test that hatchcolor is not overridden by edgecolor when + # hatchcolor parameter is not passed and hatch.color rcParam is set to a color + # When edgecolor is not set + with mpl.rc_context({'hatch.color': 'blue'}): + rect = Rectangle((0, 0), 1, 1, hatch='//') + assert mcolors.same_color(rect.get_hatchcolor(), 'blue') + # When edgecolor is set + with mpl.rc_context({'hatch.color': 'blue'}): + rect = Rectangle((0, 0), 1, 1, hatch='//', edgecolor='red') + assert mcolors.same_color(rect.get_hatchcolor(), 'blue') + + # Test that hatchcolor matches edgecolor when + # hatchcolor parameter is not passed and hatch.color rcParam is set to 'edge' + with mpl.rc_context({'hatch.color': 'edge'}): + rect = Rectangle((0, 0), 1, 1, hatch='//', edgecolor='red') + assert mcolors.same_color(rect.get_hatchcolor(), 'red') + # hatchcolor parameter is set to 'edge' + rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='edge', edgecolor='orange') + assert mcolors.same_color(rect.get_hatchcolor(), 'orange') + + # Test for default hatchcolor when hatchcolor parameter is not passed and + # hatch.color rcParam is set to 'edge' and edgecolor is not set + rect = Rectangle((0, 0), 1, 1, hatch='//') + assert mcolors.same_color(rect.get_hatchcolor(), mpl.rcParams['patch.edgecolor']) + + +def test_facecolor_none_force_edgecolor_false(): + rcParams['patch.force_edgecolor'] = False # default value + rect = Rectangle((0, 0), 1, 1, facecolor="none") + assert rect.get_edgecolor() == (0.0, 0.0, 0.0, 0.0) + + +def test_facecolor_none_force_edgecolor_true(): + rcParams['patch.force_edgecolor'] = True + rect = Rectangle((0, 0), 1, 1, facecolor="none") + assert rect.get_edgecolor() == (0.0, 0.0, 0.0, 1) + + +def test_facecolor_none_edgecolor_force_edgecolor(): + + # Case 1:force_edgecolor =False -> rcParams['patch.edgecolor'] should NOT be applied + rcParams['patch.force_edgecolor'] = False + rcParams['patch.edgecolor'] = 'red' + rect = Rectangle((0, 0), 1, 1, facecolor="none") + assert not mcolors.same_color(rect.get_edgecolor(), rcParams['patch.edgecolor']) + + # Case 2:force_edgecolor =True -> rcParams['patch.edgecolor'] SHOULD be applied + rcParams['patch.force_edgecolor'] = True + rcParams['patch.edgecolor'] = 'red' + rect = Rectangle((0, 0), 1, 1, facecolor="none") + assert mcolors.same_color(rect.get_edgecolor(), rcParams['patch.edgecolor']) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 2c4844b47f37..21f4c33794af 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -1,4 +1,4 @@ -import copy +import platform import re import numpy as np @@ -54,15 +54,32 @@ def test_path_exceptions(): def test_point_in_path(): # Test #1787 - verts2 = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)] - - path = Path(verts2, closed=True) + path = Path._create_closed([(0, 0), (0, 1), (1, 1), (1, 0)]) points = [(0.5, 0.5), (1.5, 0.5)] ret = path.contains_points(points) assert ret.dtype == 'bool' np.testing.assert_equal(ret, [True, False]) +@pytest.mark.parametrize( + "other_path, inside, inverted_inside", + [(Path([(0.25, 0.25), (0.25, 0.75), (0.75, 0.75), (0.75, 0.25), (0.25, 0.25)], + closed=True), True, False), + (Path([(-0.25, -0.25), (-0.25, 1.75), (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)], + closed=True), False, True), + (Path([(-0.25, -0.25), (-0.25, 1.75), (0.5, 0.5), + (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)], + closed=True), False, False), + (Path([(0.25, 0.25), (0.25, 1.25), (1.25, 1.25), (1.25, 0.25), (0.25, 0.25)], + closed=True), False, False), + (Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True), False, False), + (Path([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)], closed=True), False, False)]) +def test_contains_path(other_path, inside, inverted_inside): + path = Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True) + assert path.contains_path(other_path) is inside + assert other_path.contains_path(path) is inverted_inside + + def test_contains_points_negative_radius(): path = Path.unit_circle() @@ -102,6 +119,16 @@ def test_exact_extents(path, extents): assert np.all(path.get_extents().extents == extents) +@pytest.mark.parametrize('ignored_code', [Path.CLOSEPOLY, Path.STOP]) +def test_extents_with_ignored_codes(ignored_code): + # Check that STOP and CLOSEPOLY points are ignored when calculating extents + # of a path with only straight lines + path = Path([[0, 0], + [1, 1], + [2, 2]], [Path.MOVETO, Path.MOVETO, ignored_code]) + assert np.all(path.get_extents().extents == (0., 0., 1., 1.)) + + def test_point_in_path_nan(): box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]) p = Path(box) @@ -116,15 +143,15 @@ def test_nonlinear_containment(): ax.set(xscale="log", ylim=(0, 1)) polygon = ax.axvspan(1, 10) assert polygon.get_path().contains_point( - ax.transData.transform((5, .5)), ax.transData) + ax.transData.transform((5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((.5, .5)), ax.transData) + ax.transData.transform((.5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((50, .5)), ax.transData) + ax.transData.transform((50, .5)), polygon.get_transform()) -@image_comparison(['arrow_contains_point.png'], - remove_text=True, style='mpl20') +@image_comparison(['arrow_contains_point.png'], remove_text=True, style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.027) def test_arrow_contains_point(): # fix bug (#8384) fig, ax = plt.subplots() @@ -196,8 +223,14 @@ def test_log_transform_with_zero(): def test_make_compound_path_empty(): # We should be able to make a compound path with no arguments. # This makes it easier to write generic path based code. - r = Path.make_compound_path() - assert r.vertices.shape == (0, 2) + empty = Path.make_compound_path() + assert empty.vertices.shape == (0, 2) + r2 = Path.make_compound_path(empty, empty) + assert r2.vertices.shape == (0, 2) + assert r2.codes.shape == (0,) + r3 = Path.make_compound_path(Path([(0, 0)]), empty) + assert r3.vertices.shape == (1, 2) + assert r3.codes.shape == (1,) def test_make_compound_path_stops(): @@ -249,7 +282,8 @@ def test_marker_paths_pdf(): @image_comparison(['nan_path'], style='default', remove_text=True, - extensions=['pdf', 'svg', 'eps', 'png']) + extensions=['pdf', 'svg', 'eps', 'png'], + tol=0 if platform.machine() == 'x86_64' else 0.009) def test_nan_isolated_points(): y0 = [0, np.nan, 2, np.nan, 4, 5, 6] @@ -323,8 +357,28 @@ def test_path_deepcopy(): codes = [Path.MOVETO, Path.LINETO] path1 = Path(verts) path2 = Path(verts, codes) - copy.deepcopy(path1) - copy.deepcopy(path2) + path1_copy = path1.deepcopy() + path2_copy = path2.deepcopy() + assert path1 is not path1_copy + assert path1.vertices is not path1_copy.vertices + assert path2 is not path2_copy + assert path2.vertices is not path2_copy.vertices + assert path2.codes is not path2_copy.codes + + +def test_path_shallowcopy(): + # Should not raise any error + verts = [[0, 0], [1, 1]] + codes = [Path.MOVETO, Path.LINETO] + path1 = Path(verts) + path2 = Path(verts, codes) + path1_copy = path1.copy() + path2_copy = path2.copy() + assert path1 is not path1_copy + assert path1.vertices is path1_copy.vertices + assert path2 is not path2_copy + assert path2.vertices is path2_copy.vertices + assert path2.codes is path2_copy.codes @pytest.mark.parametrize('phi', np.concatenate([ @@ -400,6 +454,16 @@ def test_path_intersect_path(phi): b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)])) assert a.intersects_path(b) and b.intersects_path(a) + # a and b are collinear but do not intersect + a = transform.transform_path(Path([(1, -1), (0, -1)])) + b = transform.transform_path(Path([(0, 1), (0.9, 1)])) + assert not a.intersects_path(b) and not b.intersects_path(a) + + # a and b are collinear but do not intersect + a = transform.transform_path(Path([(0., -5.), (1., -5.)])) + b = transform.transform_path(Path([(1., 5.), (0., 5.)])) + assert not a.intersects_path(b) and not b.intersects_path(a) + @pytest.mark.parametrize('offset', range(-720, 361, 45)) def test_full_arc(offset): @@ -477,3 +541,84 @@ def test_cleanup_closepoly(): cleaned = p.cleaned(remove_nans=True) assert len(cleaned) == 1 assert cleaned.codes[0] == Path.STOP + + +def test_interpolated_moveto(): + # Initial path has two subpaths with two LINETOs each + vertices = np.array([[0, 0], + [0, 1], + [1, 2], + [4, 4], + [4, 5], + [5, 5]]) + codes = [Path.MOVETO, Path.LINETO, Path.LINETO] * 2 + + path = Path(vertices, codes) + result = path.interpolated(3) + + # Result should have two subpaths with six LINETOs each + expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6 + np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2) + + +def test_interpolated_closepoly(): + codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY] + vertices = [(4, 3), (5, 4), (5, 3), (0, 0)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + expected_vertices = np.array([[4, 3], + [4.5, 3.5], + [5, 4], + [5, 3.5], + [5, 3], + [4.5, 3], + [4, 3]]) + expected_codes = [Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY] + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) + + # Usually closepoly is the last vertex but does not have to be. + codes += [Path.LINETO] + vertices += [(2, 1)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + extra_expected_vertices = np.array([[3, 2], + [2, 1]]) + expected_vertices = np.concatenate([expected_vertices, extra_expected_vertices]) + + expected_codes += [Path.LINETO] * 2 + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) + + +def test_interpolated_moveto_closepoly(): + # Initial path has two closed subpaths + codes = ([Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]) * 2 + vertices = [(4, 3), (5, 4), (5, 3), (0, 0), (8, 6), (10, 8), (10, 6), (0, 0)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + expected_vertices1 = np.array([[4, 3], + [4.5, 3.5], + [5, 4], + [5, 3.5], + [5, 3], + [4.5, 3], + [4, 3]]) + expected_vertices = np.concatenate([expected_vertices1, expected_vertices1 * 2]) + expected_codes = ([Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]) * 2 + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) + + +def test_interpolated_empty_path(): + path = Path(np.zeros((0, 2))) + assert path.interpolated(42) is path diff --git a/lib/matplotlib/tests/test_patheffects.py b/lib/matplotlib/tests/test_patheffects.py index 2592796b33af..466754aae383 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -1,3 +1,5 @@ +import platform + import numpy as np from matplotlib.testing.decorators import image_comparison @@ -5,11 +7,13 @@ import matplotlib.patheffects as path_effects from matplotlib.path import Path import matplotlib.patches as patches +from matplotlib.backend_bases import RendererBase +from matplotlib.patheffects import PathEffectRenderer @image_comparison(['patheffect1'], remove_text=True) def test_patheffect1(): - ax1 = plt.subplot(111) + ax1 = plt.subplot() ax1.imshow([[1, 2], [2, 3]]) txt = ax1.annotate("test", (1., 1.), (0., 0), arrowprops=dict(arrowstyle="->", @@ -25,17 +29,15 @@ def test_patheffect1(): ax1.grid(True, linestyle="-", path_effects=pe) -@image_comparison(['patheffect2'], remove_text=True, style='mpl20') +@image_comparison(['patheffect2'], remove_text=True, style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.06) def test_patheffect2(): - ax2 = plt.subplot(111) + ax2 = plt.subplot() arr = np.arange(25).reshape((5, 5)) ax2.imshow(arr, interpolation='nearest') cntr = ax2.contour(arr, colors="k") - - plt.setp(cntr.collections, - path_effects=[path_effects.withStroke(linewidth=3, - foreground="w")]) + cntr.set(path_effects=[path_effects.withStroke(linewidth=3, foreground="w")]) clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True) plt.setp(clbls, @@ -43,7 +45,8 @@ def test_patheffect2(): foreground="w")]) -@image_comparison(['patheffect3']) +@image_comparison(['patheffect3'], + tol=0 if platform.machine() == 'x86_64' else 0.019) def test_patheffect3(): p1, = plt.plot([1, 3, 5, 4, 3], 'o-b', lw=4) p1.set_path_effects([path_effects.SimpleLineShadow(), @@ -84,7 +87,7 @@ def test_patheffects_stroked_text(): ] font_size = 50 - ax = plt.axes([0, 0, 1, 1]) + ax = plt.axes((0, 0, 1, 1)) for i, chunk in enumerate(text_chunks): text = ax.text(x=0.01, y=(0.9 - i * 0.13), s=chunk, fontdict={'ha': 'left', 'va': 'center', @@ -122,13 +125,9 @@ def test_collection(): x, y = np.meshgrid(np.linspace(0, 10, 150), np.linspace(-5, 5, 100)) data = np.sin(x) + np.cos(y) cs = plt.contour(data) - pe = [path_effects.PathPatchEffect(edgecolor='black', facecolor='none', - linewidth=12), - path_effects.Stroke(linewidth=5)] - - for collection in cs.collections: - collection.set_path_effects(pe) - + cs.set(path_effects=[ + path_effects.PathPatchEffect(edgecolor='black', facecolor='none', linewidth=12), + path_effects.Stroke(linewidth=5)]) for text in plt.clabel(cs, colors='white'): text.set_path_effects([path_effects.withStroke(foreground='k', linewidth=3)]) @@ -136,8 +135,8 @@ def test_collection(): 'edgecolor': 'blue'}) -@image_comparison(['tickedstroke'], remove_text=True, extensions=['png']) -def test_tickedstroke(): +@image_comparison(['tickedstroke.png'], remove_text=True, style='mpl20') +def test_tickedstroke(text_placeholders): fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4)) path = Path.unit_circle() patch = patches.PathPatch(path, facecolor='none', lw=2, path_effects=[ @@ -149,13 +148,13 @@ def test_tickedstroke(): ax1.set_xlim(-2, 2) ax1.set_ylim(-2, 2) - ax2.plot([0, 1], [0, 1], label=' ', + ax2.plot([0, 1], [0, 1], label='C0', path_effects=[path_effects.withTickedStroke(spacing=7, angle=135)]) nx = 101 x = np.linspace(0.0, 1.0, nx) y = 0.3 * np.sin(x * 8) + 0.4 - ax2.plot(x, y, label=' ', path_effects=[path_effects.withTickedStroke()]) + ax2.plot(x, y, label='C1', path_effects=[path_effects.withTickedStroke()]) ax2.legend() @@ -175,16 +174,43 @@ def test_tickedstroke(): g3 = .8 + x1 ** -3 - x2 cg1 = ax3.contour(x1, x2, g1, [0], colors=('k',)) - plt.setp(cg1.collections, - path_effects=[path_effects.withTickedStroke(angle=135)]) + cg1.set(path_effects=[path_effects.withTickedStroke(angle=135)]) cg2 = ax3.contour(x1, x2, g2, [0], colors=('r',)) - plt.setp(cg2.collections, - path_effects=[path_effects.withTickedStroke(angle=60, length=2)]) + cg2.set(path_effects=[path_effects.withTickedStroke(angle=60, length=2)]) cg3 = ax3.contour(x1, x2, g3, [0], colors=('b',)) - plt.setp(cg3.collections, - path_effects=[path_effects.withTickedStroke(spacing=7)]) + cg3.set(path_effects=[path_effects.withTickedStroke(spacing=7)]) ax3.set_xlim(0, 4) ax3.set_ylim(0, 4) + + +@image_comparison(['spaces_and_newlines.png'], remove_text=True) +def test_patheffects_spaces_and_newlines(): + ax = plt.subplot() + s1 = " " + s2 = "\nNewline also causes problems" + text1 = ax.text(0.5, 0.75, s1, ha='center', va='center', size=20, + bbox={'color': 'salmon'}) + text2 = ax.text(0.5, 0.25, s2, ha='center', va='center', size=20, + bbox={'color': 'thistle'}) + text1.set_path_effects([path_effects.Normal()]) + text2.set_path_effects([path_effects.Normal()]) + + +def test_patheffects_overridden_methods_open_close_group(): + class CustomRenderer(RendererBase): + def __init__(self): + super().__init__() + + def open_group(self, s, gid=None): + return "open_group overridden" + + def close_group(self, s): + return "close_group overridden" + + renderer = PathEffectRenderer([path_effects.Normal()], CustomRenderer()) + + assert renderer.open_group('s') == "open_group overridden" + assert renderer.close_group('s') == "close_group overridden" diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index e3bd7eeee185..82fc60e186c7 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -1,15 +1,23 @@ from io import BytesIO +import ast +import os +import sys import pickle +import pickletools import numpy as np import pytest +import matplotlib as mpl from matplotlib import cm -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing import subprocess_run_helper, is_ci_environment +from matplotlib.testing.decorators import check_figures_equal from matplotlib.dates import rrulewrapper +from matplotlib.lines import VertexSelector import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms import matplotlib.figure as mfigure +from mpl_toolkits.axes_grid1 import axes_divider, parasite_axes # type: ignore[import] def test_simple(): @@ -39,13 +47,9 @@ def test_simple(): pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL) -@image_comparison( - ['multi_pickle.png'], remove_text=True, style='mpl20', tol=0.082) -def test_complete(): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - - fig = plt.figure('Figure with a label?', figsize=(10, 6)) +def _generate_complete_test_figure(fig_ref): + fig_ref.set_size_inches((10, 6)) + plt.figure(fig_ref) plt.suptitle('Can you fit any more in a figure?') @@ -57,6 +61,7 @@ def test_complete(): # Ensure lists also pickle correctly. plt.subplot(3, 3, 1) plt.plot(list(range(10))) + plt.ylabel("hello") plt.subplot(3, 3, 2) plt.contourf(data, hatches=['//', 'ooo']) @@ -67,6 +72,7 @@ def test_complete(): plt.subplot(3, 3, 4) plt.imshow(data) + plt.ylabel("hello\nworld!") plt.subplot(3, 3, 5) plt.pcolor(data) @@ -82,31 +88,98 @@ def test_complete(): plt.quiver(x, y, u, v) plt.subplot(3, 3, 8) - plt.scatter(x, x**2, label='$x^2$') + plt.scatter(x, x ** 2, label='$x^2$') plt.legend(loc='upper left') plt.subplot(3, 3, 9) - plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4) + plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$') + plt.legend(draggable=True) + + # Ensure subfigure parenting works. + subfigs = fig_ref.subfigures(2) + subfigs[0].subplots(1, 2) + subfigs[1].subplots(1, 2) + + fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers. + - # +@mpl.style.context("default") +@check_figures_equal() +def test_complete(fig_test, fig_ref): + _generate_complete_test_figure(fig_ref) # plotting is done, now test its pickle-ability - # - result_fh = BytesIO() - pickle.dump(fig, result_fh, pickle.HIGHEST_PROTOCOL) + pkl = pickle.dumps(fig_ref, pickle.HIGHEST_PROTOCOL) + # FigureCanvasAgg is picklable and GUI canvases are generally not, but there should + # be no reference to the canvas in the pickle stream in either case. In order to + # keep the test independent of GUI toolkits, run it with Agg and check that there's + # no reference to FigureCanvasAgg in the pickle stream. + assert "FigureCanvasAgg" not in [arg for op, arg, pos in pickletools.genops(pkl)] + loaded = pickle.loads(pkl) + loaded.canvas.draw() + + fig_test.set_size_inches(loaded.get_size_inches()) + fig_test.figimage(loaded.canvas.renderer.buffer_rgba()) + + plt.close(loaded) + + +def _pickle_load_subprocess(): + import os + import pickle + + path = os.environ['PICKLE_FILE_PATH'] + + with open(path, 'rb') as blob: + fig = pickle.load(blob) + + print(str(pickle.dumps(fig))) + + +@mpl.style.context("default") +@check_figures_equal() +def test_pickle_load_from_subprocess(fig_test, fig_ref, tmp_path): + _generate_complete_test_figure(fig_ref) + + fp = tmp_path / 'sinus.pickle' + assert not fp.exists() + + with fp.open('wb') as file: + pickle.dump(fig_ref, file, pickle.HIGHEST_PROTOCOL) + assert fp.exists() + + proc = subprocess_run_helper( + _pickle_load_subprocess, + timeout=60, + extra_env={ + "PICKLE_FILE_PATH": str(fp), + "MPLBACKEND": "Agg", + # subprocess_run_helper will set SOURCE_DATE_EPOCH=0, so for a dirty tree, + # the version will have the date 19700101. As we aren't trying to test the + # version compatibility warning, force setuptools-scm to use the same + # version as us. + "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MATPLOTLIB": mpl.__version__, + }, + ) - plt.close('all') + loaded_fig = pickle.loads(ast.literal_eval(proc.stdout)) - # make doubly sure that there are no figures left - assert plt._pylab_helpers.Gcf.figs == {} + loaded_fig.canvas.draw() - # wind back the fh and load in the figure - result_fh.seek(0) - fig = pickle.load(result_fh) + fig_test.set_size_inches(loaded_fig.get_size_inches()) + fig_test.figimage(loaded_fig.canvas.renderer.buffer_rgba()) - # make sure there is now a figure manager - assert plt._pylab_helpers.Gcf.figs != {} + plt.close(loaded_fig) - assert fig.get_label() == 'Figure with a label?' + +def test_gcf(): + fig = plt.figure("a label") + buf = BytesIO() + pickle.dump(fig, buf, pickle.HIGHEST_PROTOCOL) + plt.close("all") + assert plt._pylab_helpers.Gcf.figs == {} # No figures must be left. + fig = pickle.loads(buf.getbuffer()) + assert plt._pylab_helpers.Gcf.figs != {} # A manager is there again. + assert fig.get_label() == "a label" def test_no_pyplot(): @@ -138,7 +211,7 @@ def test_image(): def test_polar(): - plt.subplot(111, polar=True) + plt.subplot(polar=True) fig = plt.gcf() pf = pickle.dumps(fig) pickle.loads(pf) @@ -199,7 +272,7 @@ def test_inset_and_secondary(): pickle.loads(pickle.dumps(fig)) -@pytest.mark.parametrize("cmap", cm._cmap_registry.values()) +@pytest.mark.parametrize("cmap", cm._colormaps.values()) def test_cmap(cmap): pickle.dumps(cmap) @@ -212,3 +285,55 @@ def test_unpickle_canvas(): out.seek(0) fig2 = pickle.load(out) assert fig2.canvas is not None + + +def test_mpl_toolkits(): + ax = parasite_axes.host_axes([0, 0, 1, 1]) + axes_divider.make_axes_area_auto_adjustable(ax) + assert type(pickle.loads(pickle.dumps(ax))) == parasite_axes.HostAxes + + +def test_standard_norm(): + assert type(pickle.loads(pickle.dumps(mpl.colors.LogNorm()))) \ + == mpl.colors.LogNorm + + +def test_dynamic_norm(): + logit_norm_instance = mpl.colors.make_norm_from_scale( + mpl.scale.LogitScale, mpl.colors.Normalize)() + assert type(pickle.loads(pickle.dumps(logit_norm_instance))) \ + == type(logit_norm_instance) + + +def test_vertexselector(): + line, = plt.plot([0, 1], picker=True) + pickle.loads(pickle.dumps(VertexSelector(line))) + + +def test_cycler(): + ax = plt.figure().add_subplot() + ax.set_prop_cycle(c=["c", "m", "y", "k"]) + ax.plot([1, 2]) + ax = pickle.loads(pickle.dumps(ax)) + l, = ax.plot([3, 4]) + assert l.get_color() == "m" + + +# Run under an interactive backend to test that we don't try to pickle the +# (interactive and non-picklable) canvas. +def _test_axeswidget_interactive(): + ax = plt.figure().add_subplot() + pickle.dumps(mpl.widgets.Button(ax, "button")) + + +@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649 + ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and + sys.platform == 'darwin' and sys.version_info[:2] < (3, 11), + reason='Tk version mismatch on Azure macOS CI' + ) +def test_axeswidget_interactive(): + subprocess_run_helper( + _test_axeswidget_interactive, + timeout=120 if is_ci_environment() else 20, + extra_env={'MPLBACKEND': 'tkagg'} + ) diff --git a/lib/matplotlib/tests/test_png.py b/lib/matplotlib/tests/test_png.py index 133d3954452b..a7677b0d05ac 100644 --- a/lib/matplotlib/tests/test_png.py +++ b/lib/matplotlib/tests/test_png.py @@ -4,11 +4,10 @@ import pytest from matplotlib.testing.decorators import image_comparison -from matplotlib import pyplot as plt -import matplotlib.cm as cm +from matplotlib import cm, pyplot as plt -@image_comparison(['pngsuite.png'], tol=0.03) +@image_comparison(['pngsuite.png'], tol=0.09) def test_pngsuite(): files = sorted( (Path(__file__).parent / "baseline_images/pngsuite").glob("basn*.png")) @@ -21,24 +20,26 @@ def test_pngsuite(): if data.ndim == 2: # keep grayscale images gray cmap = cm.gray - plt.imshow(data, extent=[i, i + 1, 0, 1], cmap=cmap) + # Using the old default data interpolation stage lets us + # continue to use the existing reference image + plt.imshow(data, extent=(i, i + 1, 0, 1), cmap=cmap, + interpolation_stage='data') plt.gca().patch.set_facecolor("#ddffff") plt.gca().set_xlim(0, len(files)) -def test_truncated_file(tmpdir): - d = tmpdir.mkdir('test') - fname = str(d.join('test.png')) - fname_t = str(d.join('test_truncated.png')) - plt.savefig(fname) - with open(fname, 'rb') as fin: +def test_truncated_file(tmp_path): + path = tmp_path / 'test.png' + path_t = tmp_path / 'test_truncated.png' + plt.savefig(path) + with open(path, 'rb') as fin: buf = fin.read() - with open(fname_t, 'wb') as fout: + with open(path_t, 'wb') as fout: fout.write(buf[:20]) with pytest.raises(Exception): - plt.imread(fname_t) + plt.imread(path_t) def test_truncated_buffer(): diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index da9a77c82502..a0969df5de90 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -1,5 +1,3 @@ -import platform - import numpy as np from numpy.testing import assert_allclose import pytest @@ -9,8 +7,7 @@ from matplotlib.testing.decorators import image_comparison, check_figures_equal -@image_comparison(['polar_axes'], style='default', - tol=0 if platform.machine() == 'x86_64' else 0.01) +@image_comparison(['polar_axes.png'], style='default', tol=0.012) def test_polar_annotations(): # You can specify the xypoint and the xytext in different positions and # coordinate systems, and optionally turn on a connecting line and mark the @@ -25,7 +22,7 @@ def test_polar_annotations(): theta = 2.0 * 2.0 * np.pi * r fig = plt.figure() - ax = fig.add_subplot(111, polar=True) + ax = fig.add_subplot(polar=True) line, = ax.plot(theta, r, color='#ee8d18', lw=3) line, = ax.plot((0, 0), (0, 1), color="#0000ff", lw=1) @@ -44,7 +41,8 @@ def test_polar_annotations(): ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out') -@image_comparison(['polar_coords'], style='default', remove_text=True) +@image_comparison(['polar_coords.png'], style='default', remove_text=True, + tol=0.014) def test_polar_coord_annotations(): # You can also use polar notation on a cartesian axes. Here the native # coordinate system ('data') is cartesian, so you need to specify the @@ -52,7 +50,7 @@ def test_polar_coord_annotations(): el = mpl.patches.Ellipse((0, 0), 10, 20, facecolor='r', alpha=0.5) fig = plt.figure() - ax = fig.add_subplot(111, aspect='equal') + ax = fig.add_subplot(aspect='equal') ax.add_artist(el) el.set_clip_box(ax.bbox) @@ -97,7 +95,7 @@ def test_polar_twice(): fig = plt.figure() plt.polar([1, 2], [.1, .2]) plt.polar([3, 4], [.3, .4]) - assert len(fig.axes) == 1, 'More than one polar axes created.' + assert len(fig.axes) == 1, 'More than one polar Axes created.' @check_figures_equal() @@ -137,7 +135,7 @@ def test_polar_units_2(fig_test, fig_ref): plt.figure(fig_test.number) # test {theta,r}units. plt.polar(xs_deg, ys_km, thetaunits="rad", runits="km") - assert isinstance(plt.gca().get_xaxis().get_major_formatter(), + assert isinstance(plt.gca().xaxis.get_major_formatter(), units.UnitDblFormatter) ax = fig_ref.add_subplot(projection="polar") @@ -146,7 +144,7 @@ def test_polar_units_2(fig_test, fig_ref): ax.set(xlabel="rad", ylabel="km") -@image_comparison(['polar_rmin'], style='default') +@image_comparison(['polar_rmin.png'], style='default') def test_polar_rmin(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -158,7 +156,7 @@ def test_polar_rmin(): ax.set_rmin(0.5) -@image_comparison(['polar_negative_rmin'], style='default') +@image_comparison(['polar_negative_rmin.png'], style='default') def test_polar_negative_rmin(): r = np.arange(-3.0, 0.0, 0.01) theta = 2*np.pi*r @@ -170,7 +168,7 @@ def test_polar_negative_rmin(): ax.set_rmin(-3.0) -@image_comparison(['polar_rorigin'], style='default') +@image_comparison(['polar_rorigin.png'], style='default') def test_polar_rorigin(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -202,7 +200,7 @@ def test_polar_invertedylim_rorigin(): ax.set_rorigin(3) -@image_comparison(['polar_theta_position'], style='default') +@image_comparison(['polar_theta_position.png'], style='default') def test_polar_theta_position(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -214,15 +212,22 @@ def test_polar_theta_position(): ax.set_theta_direction('clockwise') -@image_comparison(['polar_rlabel_position'], style='default') +@image_comparison(['polar_rlabel_position.png'], style='default') def test_polar_rlabel_position(): fig = plt.figure() - ax = fig.add_subplot(111, projection='polar') + ax = fig.add_subplot(projection='polar') ax.set_rlabel_position(315) ax.tick_params(rotation='auto') -@image_comparison(['polar_theta_wedge'], style='default') +@image_comparison(['polar_title_position.png'], style='mpl20') +def test_polar_title_position(): + fig = plt.figure() + ax = fig.add_subplot(projection='polar') + ax.set_title('foo') + + +@image_comparison(['polar_theta_wedge.png'], style='default') def test_polar_theta_limits(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -251,9 +256,11 @@ def test_polar_theta_limits(): direction=DIRECTIONS[i % len(DIRECTIONS)], rotation='auto') ax.yaxis.set_tick_params(label2On=True, rotation='auto') + ax.xaxis.get_major_locator().base.set_params( # backcompat + steps=[1, 2, 2.5, 5, 10]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_polar_rlim(fig_test, fig_ref): ax = fig_test.subplots(subplot_kw={'polar': True}) ax.set_rlim(top=10) @@ -264,7 +271,7 @@ def test_polar_rlim(fig_test, fig_ref): ax.set_rmin(.5) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_polar_rlim_bottom(fig_test, fig_ref): ax = fig_test.subplots(subplot_kw={'polar': True}) ax.set_rlim(bottom=[.5, 10]) @@ -291,6 +298,13 @@ def test_polar_no_data(): assert ax.get_rmin() == 0 and ax.get_rmax() == 1 +def test_polar_default_log_lims(): + plt.subplot(projection='polar') + ax = plt.gca() + ax.set_rscale('log') + assert ax.get_rmin() > 0 + + def test_polar_not_datalim_adjustable(): ax = plt.figure().add_subplot(projection="polar") with pytest.raises(ValueError): @@ -299,7 +313,7 @@ def test_polar_not_datalim_adjustable(): def test_polar_gridlines(): fig = plt.figure() - ax = fig.add_subplot(111, polar=True) + ax = fig.add_subplot(polar=True) # make all major grid lines lighter, only x grid lines set in 2.1.0 ax.grid(alpha=0.2) # hide y tick labels, no effect in 2.1.0 @@ -317,23 +331,21 @@ def test_get_tightbbox_polar(): bb.extents, [107.7778, 29.2778, 539.7847, 450.7222], rtol=1e-03) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_polar_interpolation_steps_constant_r(fig_test, fig_ref): # Check that an extra half-turn doesn't make any difference -- modulo # antialiasing, which we disable here. p1 = (fig_test.add_subplot(121, projection="polar") - .bar([0], [1], 3*np.pi, edgecolor="none")) + .bar([0], [1], 3*np.pi, edgecolor="none", antialiased=False)) p2 = (fig_test.add_subplot(122, projection="polar") - .bar([0], [1], -3*np.pi, edgecolor="none")) + .bar([0], [1], -3*np.pi, edgecolor="none", antialiased=False)) p3 = (fig_ref.add_subplot(121, projection="polar") - .bar([0], [1], 2*np.pi, edgecolor="none")) + .bar([0], [1], 2*np.pi, edgecolor="none", antialiased=False)) p4 = (fig_ref.add_subplot(122, projection="polar") - .bar([0], [1], -2*np.pi, edgecolor="none")) - for p in [p1, p2, p3, p4]: - plt.setp(p, antialiased=False) + .bar([0], [1], -2*np.pi, edgecolor="none", antialiased=False)) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_polar_interpolation_steps_variable_r(fig_test, fig_ref): l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2]) l.get_path()._interpolation_steps = 100 @@ -345,8 +357,172 @@ def test_thetalim_valid_invalid(): ax = plt.subplot(projection='polar') ax.set_thetalim(0, 2 * np.pi) # doesn't raise. ax.set_thetalim(thetamin=800, thetamax=440) # doesn't raise. - with pytest.raises(ValueError, match='The angle range must be <= 2 pi'): + with pytest.raises(ValueError, + match='angle range must be less than a full circle'): ax.set_thetalim(0, 3 * np.pi) with pytest.raises(ValueError, - match='The angle range must be <= 360 degrees'): + match='angle range must be less than a full circle'): ax.set_thetalim(thetamin=800, thetamax=400) + + +def test_thetalim_args(): + ax = plt.subplot(projection='polar') + ax.set_thetalim(0, 1) + assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (0, 1) + ax.set_thetalim((2, 3)) + assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (2, 3) + + +def test_default_thetalocator(): + # Ideally we would check AAAABBC, but the smallest axes currently puts a + # single tick at 150° because MaxNLocator doesn't have a way to accept 15° + # while rejecting 150°. + fig, axs = plt.subplot_mosaic( + "AAAABB.", subplot_kw={"projection": "polar"}) + for ax in axs.values(): + ax.set_thetalim(0, np.pi) + for ax in axs.values(): + ticklocs = np.degrees(ax.xaxis.get_majorticklocs()).tolist() + assert pytest.approx(90) in ticklocs + assert pytest.approx(100) not in ticklocs + + +def test_axvspan(): + ax = plt.subplot(projection="polar") + span = ax.axvspan(0, np.pi/4) + assert span.get_path()._interpolation_steps > 1 + + +@check_figures_equal() +def test_remove_shared_polar(fig_ref, fig_test): + # Removing shared polar axes used to crash. Test removing them, keeping in + # both cases just the lower left axes of a grid to avoid running into a + # separate issue (now being fixed) of ticklabel visibility for shared axes. + axs = fig_ref.subplots( + 2, 2, sharex=True, subplot_kw={"projection": "polar"}) + for i in [0, 1, 3]: + axs.flat[i].remove() + axs = fig_test.subplots( + 2, 2, sharey=True, subplot_kw={"projection": "polar"}) + for i in [0, 1, 3]: + axs.flat[i].remove() + + +def test_shared_polar_keeps_ticklabels(): + fig, axs = plt.subplots( + 2, 2, subplot_kw={"projection": "polar"}, sharex=True, sharey=True) + fig.canvas.draw() + assert axs[0, 1].xaxis.majorTicks[0].get_visible() + assert axs[0, 1].yaxis.majorTicks[0].get_visible() + fig, axs = plt.subplot_mosaic( + "ab\ncd", subplot_kw={"projection": "polar"}, sharex=True, sharey=True) + fig.canvas.draw() + assert axs["b"].xaxis.majorTicks[0].get_visible() + assert axs["b"].yaxis.majorTicks[0].get_visible() + + +def test_axvline_axvspan_do_not_modify_rlims(): + ax = plt.subplot(projection="polar") + ax.axvspan(0, 1) + ax.axvline(.5) + ax.plot([.1, .2]) + assert ax.get_ylim() == (0, .2) + + +def test_cursor_precision(): + ax = plt.subplot(projection="polar") + # Higher radii correspond to higher theta-precisions. + assert ax.format_coord(0, 0.005) == "θ=0.0Ï€ (0°), r=0.005" + assert ax.format_coord(0, .1) == "θ=0.00Ï€ (0°), r=0.100" + assert ax.format_coord(0, 1) == "θ=0.000Ï€ (0.0°), r=1.000" + assert ax.format_coord(1, 0.005) == "θ=0.3Ï€ (57°), r=0.005" + assert ax.format_coord(1, .1) == "θ=0.32Ï€ (57°), r=0.100" + assert ax.format_coord(1, 1) == "θ=0.318Ï€ (57.3°), r=1.000" + assert ax.format_coord(2, 0.005) == "θ=0.6Ï€ (115°), r=0.005" + assert ax.format_coord(2, .1) == "θ=0.64Ï€ (115°), r=0.100" + assert ax.format_coord(2, 1) == "θ=0.637Ï€ (114.6°), r=1.000" + + +def test_custom_fmt_data(): + ax = plt.subplot(projection="polar") + def millions(x): + return '$%1.1fM' % (x*1e-6) + + # Test only x formatter + ax.fmt_xdata = None + ax.fmt_ydata = millions + assert ax.format_coord(12, 2e7) == "θ=3.8197186342Ï€ (687.54935416°), r=$20.0M" + assert ax.format_coord(1234, 2e6) == "θ=392.794399551Ï€ (70702.9919191°), r=$2.0M" + assert ax.format_coord(3, 100) == "θ=0.95493Ï€ (171.887°), r=$0.0M" + + # Test only y formatter + ax.fmt_xdata = millions + ax.fmt_ydata = None + assert ax.format_coord(2e5, 1) == "θ=$0.2M, r=1.000" + assert ax.format_coord(1, .1) == "θ=$0.0M, r=0.100" + assert ax.format_coord(1e6, 0.005) == "θ=$1.0M, r=0.005" + + # Test both x and y formatters + ax.fmt_xdata = millions + ax.fmt_ydata = millions + assert ax.format_coord(2e6, 2e4*3e5) == "θ=$2.0M, r=$6000.0M" + assert ax.format_coord(1e18, 12891328123) == "θ=$1000000000000.0M, r=$12891.3M" + assert ax.format_coord(63**7, 1081968*1024) == "θ=$3938980.6M, r=$1107.9M" + + +@image_comparison(['polar_log.png'], style='default') +def test_polar_log(): + fig = plt.figure() + ax = fig.add_subplot(polar=True) + + ax.set_rscale('log') + ax.set_rlim(1, 1000) + + n = 100 + ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n)) + + +def test_polar_neg_theta_lims(): + fig = plt.figure() + ax = fig.add_subplot(projection='polar') + ax.set_thetalim(-np.pi, np.pi) + labels = [l.get_text() for l in ax.xaxis.get_ticklabels()] + assert labels == ['-180°', '-135°', '-90°', '-45°', '0°', '45°', '90°', '135°'] + + +@pytest.mark.parametrize("order", ["before", "after"]) +@image_comparison(baseline_images=['polar_errorbar.png'], remove_text=True, + style='mpl20') +def test_polar_errorbar(order): + theta = np.arange(0, 2 * np.pi, np.pi / 8) + r = theta / np.pi / 2 + 0.5 + fig = plt.figure(figsize=(5, 5)) + ax = fig.add_subplot(projection='polar') + if order == "before": + ax.set_theta_zero_location("N") + ax.set_theta_direction(-1) + ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen") + else: + ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen") + ax.set_theta_zero_location("N") + ax.set_theta_direction(-1) + + +def test_radial_limits_behavior(): + # r=0 is kept as limit if positive data and ticks are used + # negative ticks or data result in negative limits + fig = plt.figure() + ax = fig.add_subplot(projection='polar') + assert ax.get_ylim() == (0, 1) + # upper limit is expanded to include the ticks, but lower limit stays at 0 + ax.set_rticks([1, 2, 3, 4]) + assert ax.get_ylim() == (0, 4) + # upper limit is autoscaled to data, but lower limit limit stays 0 + ax.plot([1, 2], [1, 2]) + assert ax.get_ylim() == (0, 2) + # negative ticks also expand the negative limit + ax.set_rticks([-1, 0, 1, 2]) + assert ax.get_ylim() == (-1, 2) + # negative data also autoscales to negative limits + ax.plot([1, 2], [-1, -2]) + assert ax.get_ylim() == (-2, 2) diff --git a/lib/matplotlib/tests/test_preprocess_data.py b/lib/matplotlib/tests/test_preprocess_data.py index 1f4707679508..c983d78786e1 100644 --- a/lib/matplotlib/tests/test_preprocess_data.py +++ b/lib/matplotlib/tests/test_preprocess_data.py @@ -1,10 +1,12 @@ import re +import sys import numpy as np import pytest from matplotlib import _preprocess_data from matplotlib.axes import Axes +from matplotlib.testing import subprocess_run_for_testing from matplotlib.testing.decorators import check_figures_equal # Notes on testing the plotting functions itself @@ -16,8 +18,7 @@ # this gets used in multiple tests, so define it here @_preprocess_data(replace_names=["x", "y"], label_namer="y") def plot_func(ax, x, y, ls="x", label=None, w="xyz"): - return ("x: %s, y: %s, ls: %s, w: %s, label: %s" % ( - list(x), list(y), ls, w, label)) + return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}" all_funcs = [plot_func] @@ -78,7 +79,7 @@ def test_function_call_without_data(func): def test_function_call_with_dict_input(func): """Tests with dict input, unpacking via preprocess_pipeline""" data = {'a': 1, 'b': 2} - assert(func(None, data.keys(), data.values()) == + assert (func(None, data.keys(), data.values()) == "x: ['a', 'b'], y: [1, 2], ls: x, w: xyz, label: None") @@ -145,8 +146,7 @@ def test_function_call_replace_all(): @_preprocess_data(label_namer="y") def func_replace_all(ax, x, y, ls="x", label=None, w="NOT"): - return "x: %s, y: %s, ls: %s, w: %s, label: %s" % ( - list(x), list(y), ls, w, label) + return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}" assert (func_replace_all(None, "a", "b", w="x", data=data) == "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b") @@ -170,8 +170,7 @@ def test_no_label_replacements(): @_preprocess_data(replace_names=["x", "y"], label_namer=None) def func_no_label(ax, x, y, ls="x", label=None, w="xyz"): - return "x: %s, y: %s, ls: %s, w: %s, label: %s" % ( - list(x), list(y), ls, w, label) + return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}" data = {"a": [1, 2], "b": [8, 9], "w": "NOT"} assert (func_no_label(None, "a", "b", data=data) == @@ -197,41 +196,78 @@ def func(ax, x, y, z=1): def test_docstring_addition(): @_preprocess_data() def funcy(ax, *args, **kwargs): - """Funcy does nothing""" + """ + Parameters + ---------- + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + """ - assert re.search(r"every other argument", funcy.__doc__) - assert not re.search(r"the following arguments", funcy.__doc__) + assert re.search(r"all parameters also accept a string", funcy.__doc__) + assert not re.search(r"the following parameters", funcy.__doc__) @_preprocess_data(replace_names=[]) def funcy(ax, x, y, z, bar=None): - """Funcy does nothing""" + """ + Parameters + ---------- + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + """ - assert not re.search(r"every other argument", funcy.__doc__) - assert not re.search(r"the following arguments", funcy.__doc__) + assert not re.search(r"all parameters also accept a string", funcy.__doc__) + assert not re.search(r"the following parameters", funcy.__doc__) @_preprocess_data(replace_names=["bar"]) def funcy(ax, x, y, z, bar=None): - """Funcy does nothing""" - - assert not re.search(r"every other argument", funcy.__doc__) - assert not re.search(r"the following arguments .*: \*bar\*\.", + """ + Parameters + ---------- + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + """ + + assert not re.search(r"all parameters also accept a string", funcy.__doc__) + assert not re.search(r"the following parameters .*: \*bar\*\.", funcy.__doc__) @_preprocess_data(replace_names=["x", "t"]) def funcy(ax, x, y, z, t=None): - """Funcy does nothing""" - - assert not re.search(r"every other argument", funcy.__doc__) - assert not re.search(r"the following arguments .*: \*x\*, \*t\*\.", + """ + Parameters + ---------- + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + """ + + assert not re.search(r"all parameters also accept a string", funcy.__doc__) + assert not re.search(r"the following parameters .*: \*x\*, \*t\*\.", funcy.__doc__) +def test_data_parameter_replacement(): + """ + Test that the docstring contains the correct *data* parameter stub + for all methods that we run _preprocess_data() on. + """ + program = ( + "import logging; " + "logging.basicConfig(level=logging.DEBUG); " + "import matplotlib.pyplot as plt" + ) + cmd = [sys.executable, "-c", program] + completed_proc = subprocess_run_for_testing( + cmd, text=True, capture_output=True + ) + assert 'data parameter docstring error' not in completed_proc.stderr + + class TestPlotTypes: plotters = [Axes.scatter, Axes.bar, Axes.plot] @pytest.mark.parametrize('plotter', plotters) - @check_figures_equal(extensions=['png']) + @check_figures_equal() def test_dict_unpack(self, plotter, fig_test, fig_ref): x = [1, 2, 3] y = [4, 5, 6] @@ -242,7 +278,7 @@ def test_dict_unpack(self, plotter, fig_test, fig_ref): plotter(fig_ref.subplots(), x, y) @pytest.mark.parametrize('plotter', plotters) - @check_figures_equal(extensions=['png']) + @check_figures_equal() def test_data_kwarg(self, plotter, fig_test, fig_ref): x = [1, 2, 3] y = [4, 5, 6] diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index b52483ea7937..ab713707bace 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,25 +1,29 @@ import difflib -import subprocess + +import numpy as np import sys from pathlib import Path import pytest import matplotlib as mpl +from matplotlib.testing import subprocess_run_for_testing from matplotlib import pyplot as plt -from matplotlib.cbook import MatplotlibDeprecationWarning -def test_pyplot_up_to_date(tmpdir): +def test_pyplot_up_to_date(tmp_path): + pytest.importorskip("black") + gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py" if not gen_script.exists(): pytest.skip("boilerplate.py not found") orig_contents = Path(plt.__file__).read_text() - plt_file = tmpdir.join('pyplot.py') + plt_file = tmp_path / 'pyplot.py' plt_file.write_text(orig_contents, 'utf-8') - subprocess.run([sys.executable, str(gen_script), str(plt_file)], - check=True) + subprocess_run_for_testing( + [sys.executable, str(gen_script), str(plt_file)], + check=True) new_contents = plt_file.read_text('utf-8') if orig_contents != new_contents: @@ -39,8 +43,8 @@ def test_pyplot_up_to_date(tmpdir): def test_copy_docstring_and_deprecators(recwarn): - @mpl.cbook._rename_parameter("(version)", "old", "new") - @mpl.cbook._make_keyword_only("(version)", "kwo") + @mpl._api.rename_parameter(mpl.__version__, "old", "new") + @mpl._api.make_keyword_only(mpl.__version__, "kwo") def func(new, kwo=None): pass @@ -53,9 +57,9 @@ def wrapper_func(new, kwo=None): wrapper_func(None, kwo=None) wrapper_func(new=None, kwo=None) assert not recwarn - with pytest.warns(MatplotlibDeprecationWarning): + with pytest.warns(mpl.MatplotlibDeprecationWarning): wrapper_func(old=None) - with pytest.warns(MatplotlibDeprecationWarning): + with pytest.warns(mpl.MatplotlibDeprecationWarning): wrapper_func(None, None) @@ -144,7 +148,7 @@ def test_nested_ion_ioff(): assert mpl.is_interactive() assert not mpl.is_interactive() - # redunant contexts + # redundant contexts with plt.ion(): with plt.ion(): assert mpl.is_interactive() @@ -153,3 +157,330 @@ def test_nested_ion_ioff(): with plt.ioff(): plt.ion() assert not mpl.is_interactive() + + +def test_close(): + try: + plt.close(1.1) + except TypeError as e: + assert str(e) == ( + "'fig' must be an instance of matplotlib.figure.Figure, int, str " + "or None, not a float") + + +def test_subplot_reuse(): + ax1 = plt.subplot(121) + assert ax1 is plt.gca() + ax2 = plt.subplot(122) + assert ax2 is plt.gca() + ax3 = plt.subplot(121) + assert ax1 is plt.gca() + assert ax1 is ax3 + + +def test_axes_kwargs(): + # plt.axes() always creates new axes, even if axes kwargs differ. + plt.figure() + ax = plt.axes() + ax1 = plt.axes() + assert ax is not None + assert ax1 is not ax + plt.close() + + plt.figure() + ax = plt.axes(projection='polar') + ax1 = plt.axes(projection='polar') + assert ax is not None + assert ax1 is not ax + plt.close() + + plt.figure() + ax = plt.axes(projection='polar') + ax1 = plt.axes() + assert ax is not None + assert ax1.name == 'rectilinear' + assert ax1 is not ax + plt.close() + + +def test_subplot_replace_projection(): + # plt.subplot() searches for axes with the same subplot spec, and if one + # exists, and the kwargs match returns it, create a new one if they do not + fig = plt.figure() + ax = plt.subplot(1, 2, 1) + ax1 = plt.subplot(1, 2, 1) + ax2 = plt.subplot(1, 2, 2) + ax3 = plt.subplot(1, 2, 1, projection='polar') + ax4 = plt.subplot(1, 2, 1, projection='polar') + assert ax is not None + assert ax1 is ax + assert ax2 is not ax + assert ax3 is not ax + assert ax3 is ax4 + + assert ax in fig.axes + assert ax2 in fig.axes + assert ax3 in fig.axes + + assert ax.name == 'rectilinear' + assert ax2.name == 'rectilinear' + assert ax3.name == 'polar' + + +def test_subplot_kwarg_collision(): + ax1 = plt.subplot(projection='polar', theta_offset=0) + ax2 = plt.subplot(projection='polar', theta_offset=0) + assert ax1 is ax2 + ax1.remove() + ax3 = plt.subplot(projection='polar', theta_offset=1) + assert ax1 is not ax3 + assert ax1 not in plt.gcf().axes + + +def test_gca(): + # plt.gca() returns an existing axes, unless there were no axes. + plt.figure() + ax = plt.gca() + ax1 = plt.gca() + assert ax is not None + assert ax1 is ax + plt.close() + + +def test_subplot_projection_reuse(): + # create an Axes + ax1 = plt.subplot(111) + # check that it is current + assert ax1 is plt.gca() + # make sure we get it back if we ask again + assert ax1 is plt.subplot(111) + # remove it + ax1.remove() + # create a polar plot + ax2 = plt.subplot(111, projection='polar') + assert ax2 is plt.gca() + # this should have deleted the first axes + assert ax1 not in plt.gcf().axes + # assert we get it back if no extra parameters passed + assert ax2 is plt.subplot(111) + ax2.remove() + # now check explicitly setting the projection to rectilinear + # makes a new axes + ax3 = plt.subplot(111, projection='rectilinear') + assert ax3 is plt.gca() + assert ax3 is not ax2 + assert ax2 not in plt.gcf().axes + + +def test_subplot_polar_normalization(): + ax1 = plt.subplot(111, projection='polar') + ax2 = plt.subplot(111, polar=True) + ax3 = plt.subplot(111, polar=True, projection='polar') + assert ax1 is ax2 + assert ax1 is ax3 + + with pytest.raises(ValueError, + match="polar=True, yet projection='3d'"): + ax2 = plt.subplot(111, polar=True, projection='3d') + + +def test_subplot_change_projection(): + created_axes = set() + ax = plt.subplot() + created_axes.add(ax) + projections = ('aitoff', 'hammer', 'lambert', 'mollweide', + 'polar', 'rectilinear', '3d') + for proj in projections: + ax.remove() + ax = plt.subplot(projection=proj) + assert ax is plt.subplot() + assert ax.name == proj + created_axes.add(ax) + # Check that each call created a new Axes. + assert len(created_axes) == 1 + len(projections) + + +def test_polar_second_call(): + # the first call creates the axes with polar projection + ln1, = plt.polar(0., 1., 'ro') + assert isinstance(ln1, mpl.lines.Line2D) + # the second call should reuse the existing axes + ln2, = plt.polar(1.57, .5, 'bo') + assert isinstance(ln2, mpl.lines.Line2D) + assert ln1.axes is ln2.axes + + +def test_fallback_position(): + # check that position kwarg works if rect not supplied + axref = plt.axes([0.2, 0.2, 0.5, 0.5]) + axtest = plt.axes(position=[0.2, 0.2, 0.5, 0.5]) + np.testing.assert_allclose(axtest.bbox.get_points(), + axref.bbox.get_points()) + + # check that position kwarg ignored if rect is supplied + axref = plt.axes([0.2, 0.2, 0.5, 0.5]) + axtest = plt.axes([0.2, 0.2, 0.5, 0.5], position=[0.1, 0.1, 0.8, 0.8]) + np.testing.assert_allclose(axtest.bbox.get_points(), + axref.bbox.get_points()) + + +def test_set_current_figure_via_subfigure(): + fig1 = plt.figure() + subfigs = fig1.subfigures(2) + + plt.figure() + assert plt.gcf() != fig1 + + current = plt.figure(subfigs[1]) + assert plt.gcf() == fig1 + assert current == fig1 + + +def test_set_current_axes_on_subfigure(): + fig = plt.figure() + subfigs = fig.subfigures(2) + + ax = subfigs[0].subplots(1, squeeze=True) + subfigs[1].subplots(1, squeeze=True) + + assert plt.gca() != ax + plt.sca(ax) + assert plt.gca() == ax + + +def test_pylab_integration(): + IPython = pytest.importorskip("IPython") + mpl.testing.subprocess_run_helper( + IPython.start_ipython, + "--pylab", + "-c", + ";".join(( + "import matplotlib.pyplot as plt", + "assert plt._REPL_DISPLAYHOOK == plt._ReplDisplayHook.IPYTHON", + )), + timeout=60, + ) + + +def test_doc_pyplot_summary(): + """Test that pyplot_summary lists all the plot functions.""" + pyplot_docs = Path(__file__).parent / '../../../doc/api/pyplot_summary.rst' + if not pyplot_docs.exists(): + pytest.skip("Documentation sources not available") + + def extract_documented_functions(lines): + """ + Return a list of all the functions that are mentioned in the + autosummary blocks contained in *lines*. + + An autosummary block looks like this:: + + .. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + plot + errorbar + + """ + functions = [] + in_autosummary = False + for line in lines: + if not in_autosummary: + if line.startswith(".. autosummary::"): + in_autosummary = True + else: + if not line or line.startswith(" :"): + # empty line or autosummary parameter + continue + if not line[0].isspace(): + # no more indentation: end of autosummary block + in_autosummary = False + continue + functions.append(line.strip()) + return functions + + lines = pyplot_docs.read_text().split("\n") + doc_functions = set(extract_documented_functions(lines)) + plot_commands = set(plt._get_pyplot_commands()) + missing = plot_commands.difference(doc_functions) + if missing: + raise AssertionError( + f"The following pyplot functions are not listed in the " + f"documentation. Please add them to doc/api/pyplot_summary.rst: " + f"{missing!r}") + extra = doc_functions.difference(plot_commands) + if extra: + raise AssertionError( + f"The following functions are listed in the pyplot documentation, " + f"but they do not exist in pyplot. " + f"Please remove them from doc/api/pyplot_summary.rst: {extra!r}") + + +def test_minor_ticks(): + plt.figure() + plt.plot(np.arange(1, 10)) + tick_pos, tick_labels = plt.xticks(minor=True) + assert np.all(tick_labels == np.array([], dtype=np.float64)) + assert tick_labels == [] + + plt.yticks(ticks=[3.5, 6.5], labels=["a", "b"], minor=True) + ax = plt.gca() + tick_pos = ax.get_yticks(minor=True) + tick_labels = ax.get_yticklabels(minor=True) + assert np.all(tick_pos == np.array([3.5, 6.5])) + assert [l.get_text() for l in tick_labels] == ['a', 'b'] + + +def test_switch_backend_no_close(): + plt.switch_backend('agg') + fig = plt.figure() + fig = plt.figure() + assert len(plt.get_fignums()) == 2 + plt.switch_backend('agg') + assert len(plt.get_fignums()) == 2 + plt.switch_backend('svg') + assert len(plt.get_fignums()) == 2 + + +def figure_hook_example(figure): + figure._test_was_here = True + + +def test_figure_hook(): + + test_rc = { + 'figure.hooks': ['matplotlib.tests.test_pyplot:figure_hook_example'] + } + with mpl.rc_context(test_rc): + fig = plt.figure() + + assert fig._test_was_here + + +def test_multiple_same_figure_calls(): + fig = plt.figure(1, figsize=(1, 2)) + with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"): + fig2 = plt.figure(1, figsize=np.array([3, 4])) + with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"): + plt.figure(fig, figsize=np.array([5, 6])) + assert fig is fig2 + fig3 = plt.figure(1) # Checks for false warnings + assert fig is fig3 + + +def test_close_all_warning(): + fig1 = plt.figure() + + # Check that the warning is issued when 'all' is passed to plt.figure + with pytest.warns(UserWarning, match="closes all existing figures"): + fig2 = plt.figure("all") + + +def test_matshow(): + fig = plt.figure() + arr = [[0, 1], [1, 2]] + + # Smoke test that matshow does not ask for a new figsize on the existing figure + plt.matshow(arr, fignum=fig.number) diff --git a/lib/matplotlib/tests/test_quiver.py b/lib/matplotlib/tests/test_quiver.py index 740ad3603a9c..1205487cfe94 100644 --- a/lib/matplotlib/tests/test_quiver.py +++ b/lib/matplotlib/tests/test_quiver.py @@ -1,32 +1,41 @@ +import platform +import sys + import numpy as np import pytest -import sys + from matplotlib import pyplot as plt from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import check_figures_equal -def draw_quiver(ax, **kw): +def draw_quiver(ax, **kwargs): X, Y = np.meshgrid(np.arange(0, 2 * np.pi, 1), np.arange(0, 2 * np.pi, 1)) U = np.cos(X) V = np.sin(Y) - Q = ax.quiver(U, V, **kw) + Q = ax.quiver(U, V, **kwargs) return Q +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason='Requires CPython') def test_quiver_memory_leak(): fig, ax = plt.subplots() Q = draw_quiver(ax) ttX = Q.X + orig_refcount = sys.getrefcount(ttX) Q.remove() del Q - assert sys.getrefcount(ttX) == 2 + assert sys.getrefcount(ttX) < orig_refcount +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason='Requires CPython') def test_quiver_key_memory_leak(): fig, ax = plt.subplots() @@ -35,20 +44,20 @@ def test_quiver_key_memory_leak(): qk = ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$', labelpos='W', fontproperties={'weight': 'bold'}) - assert sys.getrefcount(qk) == 3 + orig_refcount = sys.getrefcount(qk) qk.remove() - assert sys.getrefcount(qk) == 2 + assert sys.getrefcount(qk) < orig_refcount def test_quiver_number_of_args(): X = [1, 2] with pytest.raises( TypeError, - match='takes 2-5 positional arguments but 1 were given'): + match='takes from 2 to 5 positional arguments but 1 were given'): plt.quiver(X) with pytest.raises( TypeError, - match='takes 2-5 positional arguments but 6 were given'): + match='takes from 2 to 5 positional arguments but 6 were given'): plt.quiver(X, X, X, X, X, X) @@ -84,7 +93,7 @@ def test_no_warnings(): def test_zero_headlength(): # Based on report by Doug McNeil: - # http://matplotlib.1069221.n5.nabble.com/quiver-warnings-td28107.html + # https://discourse.matplotlib.org/t/quiver-warnings/16722 fig, ax = plt.subplots() X, Y = np.meshgrid(np.arange(10), np.arange(10)) U, V = np.cos(X), np.sin(Y) @@ -202,6 +211,17 @@ def test_barbs_flip(): flip_barb=Y < 0) +def test_barb_copy(): + fig, ax = plt.subplots() + u = np.array([1.1]) + v = np.array([2.2]) + b0 = ax.barbs([1], [1], u, v) + u[0] = 0 + assert b0.u[0] == 1.1 + v[0] = 0 + assert b0.v[0] == 2.2 + + def test_bad_masked_sizes(): """Test error handling when given differing sized masked arrays.""" x = np.arange(3) @@ -249,6 +269,62 @@ def test_quiverkey_angles(): assert len(qk.verts) == 1 +def test_quiverkey_angles_xy_aitoff(): + # GH 26316 and GH 26748 + # Test that only one arrow will be plotted with non-cartesian + # when angles='xy' and/or scale_units='xy' + + # only for test purpose + # scale_units='xy' may not be a valid use case for non-cartesian + kwargs_list = [ + {'angles': 'xy'}, + {'angles': 'xy', 'scale_units': 'xy'}, + {'scale_units': 'xy'} + ] + + for kwargs_dict in kwargs_list: + + x = np.linspace(-np.pi, np.pi, 11) + y = np.ones_like(x) * np.pi / 6 + vx = np.zeros_like(x) + vy = np.ones_like(x) + + fig = plt.figure() + ax = fig.add_subplot(projection='aitoff') + q = ax.quiver(x, y, vx, vy, **kwargs_dict) + qk = ax.quiverkey(q, 0, 0, 1, '1 units') + + fig.canvas.draw() + assert len(qk.verts) == 1 + + +def test_quiverkey_angles_scale_units_cartesian(): + # GH 26316 + # Test that only one arrow will be plotted with normal cartesian + # when angles='xy' and/or scale_units='xy' + + kwargs_list = [ + {'angles': 'xy'}, + {'angles': 'xy', 'scale_units': 'xy'}, + {'scale_units': 'xy'} + ] + + for kwargs_dict in kwargs_list: + X = [0, -1, 0] + Y = [0, -1, 0] + U = [1, -1, 1] + V = [1, -1, 0] + + fig, ax = plt.subplots() + q = ax.quiver(X, Y, U, V, **kwargs_dict) + ax.quiverkey(q, X=0.3, Y=1.1, U=1, + label='Quiver key, length = 1', labelpos='E') + qk = ax.quiverkey(q, 0, 0, 1, '1 units') + + fig.canvas.draw() + assert len(qk.verts) == 1 + + def test_quiver_setuvc_numbers(): """Check that it is possible to set all arrow UVC to the same numbers""" @@ -259,3 +335,53 @@ def test_quiver_setuvc_numbers(): q = ax.quiver(X, Y, U, V) q.set_UVC(0, 1) + + +def draw_quiverkey_zorder_argument(fig, zorder=None): + """Draw Quiver and QuiverKey using zorder argument""" + x = np.arange(1, 6, 1) + y = np.arange(1, 6, 1) + X, Y = np.meshgrid(x, y) + U, V = 2, 2 + + ax = fig.subplots() + q = ax.quiver(X, Y, U, V, pivot='middle') + ax.set_xlim(0.5, 5.5) + ax.set_ylim(0.5, 5.5) + if zorder is None: + ax.quiverkey(q, 4, 4, 25, coordinates='data', + label='U', color='blue') + ax.quiverkey(q, 5.5, 2, 20, coordinates='data', + label='V', color='blue', angle=90) + else: + ax.quiverkey(q, 4, 4, 25, coordinates='data', + label='U', color='blue', zorder=zorder) + ax.quiverkey(q, 5.5, 2, 20, coordinates='data', + label='V', color='blue', angle=90, zorder=zorder) + + +def draw_quiverkey_setzorder(fig, zorder=None): + """Draw Quiver and QuiverKey using set_zorder""" + x = np.arange(1, 6, 1) + y = np.arange(1, 6, 1) + X, Y = np.meshgrid(x, y) + U, V = 2, 2 + + ax = fig.subplots() + q = ax.quiver(X, Y, U, V, pivot='middle') + ax.set_xlim(0.5, 5.5) + ax.set_ylim(0.5, 5.5) + qk1 = ax.quiverkey(q, 4, 4, 25, coordinates='data', + label='U', color='blue') + qk2 = ax.quiverkey(q, 5.5, 2, 20, coordinates='data', + label='V', color='blue', angle=90) + if zorder is not None: + qk1.set_zorder(zorder) + qk2.set_zorder(zorder) + + +@pytest.mark.parametrize('zorder', [0, 2, 5, None]) +@check_figures_equal() +def test_quiverkey_zorder(fig_test, fig_ref, zorder): + draw_quiverkey_zorder_argument(fig_test, zorder=zorder) + draw_quiverkey_setzorder(fig_ref, zorder=zorder) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 97ae564af1fe..2235f98b720f 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -1,45 +1,47 @@ -from collections import OrderedDict import copy import os -from pathlib import Path import subprocess import sys from unittest import mock from cycler import cycler, Cycler +from packaging.version import parse as parse_version import pytest import matplotlib as mpl -from matplotlib import cbook +from matplotlib import _api, _c_internal_utils import matplotlib.pyplot as plt import matplotlib.colors as mcolors import numpy as np from matplotlib.rcsetup import ( validate_bool, - validate_bool_maybe_none, validate_color, validate_colorlist, + _validate_color_or_linecolor, validate_cycler, validate_float, + validate_fontstretch, validate_fontweight, validate_hatch, validate_hist_bins, validate_int, validate_markevery, validate_stringlist, + validate_sketch, _validate_linestyle, _listify_validator) +from matplotlib.testing import subprocess_run_for_testing -def test_rcparams(tmpdir): +def test_rcparams(tmp_path): mpl.rc('text', usetex=False) mpl.rc('lines', linewidth=22) usetex = mpl.rcParams['text.usetex'] linewidth = mpl.rcParams['lines.linewidth'] - rcpath = Path(tmpdir) / 'test_rcparams.rc' - rcpath.write_text('lines.linewidth: 33') + rcpath = tmp_path / 'test_rcparams.rc' + rcpath.write_text('lines.linewidth: 33', encoding='utf-8') # test context given dictionary with mpl.rc_context(rc={'text.usetex': not usetex}): @@ -106,37 +108,35 @@ def test_rcparams_update(): rc = mpl.RcParams({'figure.figsize': (3.5, 42)}) bad_dict = {'figure.figsize': (3.5, 42, 1)} # make sure validation happens on input - with pytest.raises(ValueError), \ - pytest.warns(UserWarning, match="validate"): + with pytest.raises(ValueError): rc.update(bad_dict) def test_rcparams_init(): - with pytest.raises(ValueError), \ - pytest.warns(UserWarning, match="validate"): + with pytest.raises(ValueError): mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) +def test_nargs_cycler(): + from matplotlib.rcsetup import cycler as ccl + with pytest.raises(TypeError, match='3 were given'): + # cycler() takes 0-2 arguments. + ccl(ccl(color=list('rgb')), 2, 3) + + def test_Bug_2543(): # Test that it possible to add all values to itself / deepcopy - # This was not possible because validate_bool_maybe_none did not - # accept None as an argument. # https://github.com/matplotlib/matplotlib/issues/2543 # We filter warnings at this stage since a number of them are raised # for deprecated rcparams as they should. We don't want these in the # printed in the test suite. - with cbook._suppress_matplotlib_deprecation_warning(): + with _api.suppress_matplotlib_deprecation_warning(): with mpl.rc_context(): _copy = mpl.rcParams.copy() for key in _copy: mpl.rcParams[key] = _copy[key] with mpl.rc_context(): copy.deepcopy(mpl.rcParams) - # real test is that this does not raise - assert validate_bool_maybe_none(None) is None - assert validate_bool_maybe_none("none") is None - with pytest.raises(ValueError): - validate_bool_maybe_none("blah") with pytest.raises(ValueError): validate_bool(None) with pytest.raises(ValueError): @@ -196,9 +196,9 @@ def test_axes_titlecolor_rcparams(): assert title.get_color() == 'r' -def test_Issue_1713(tmpdir): - rcpath = Path(tmpdir) / 'test_rcparams.rc' - rcpath.write_text('timezone: UTC', encoding='UTF-32-BE') +def test_Issue_1713(tmp_path): + rcpath = tmp_path / 'test_rcparams.rc' + rcpath.write_text('timezone: UTC', encoding='utf-8') with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'): rc = mpl.rc_params_from_file(rcpath, True, False) assert rc.get('timezone') == 'UTC' @@ -233,8 +233,6 @@ def generate_validator_testcases(valid): (('a', 'b'), ['a', 'b']), (iter(['a', 'b']), ['a', 'b']), (np.array(['a', 'b']), ['a', 'b']), - ((1, 2), ['1', '2']), - (np.array([1, 2]), ['1', '2']), ), 'fail': ((set(), ValueError), (1, ValueError), @@ -254,13 +252,13 @@ def generate_validator_testcases(valid): for _ in ('1.5, 2.5', [1.5, 2.5], [1.5, 2.5], (1.5, 2.5), np.array((1.5, 2.5)))), 'fail': ((_, ValueError) - for _ in ('aardvark', ('a', 1), - (1, 2, 3) - )) + for _ in ('aardvark', ('a', 1), (1, 2, 3), (None, ), None)) }, {'validator': validate_cycler, 'success': (('cycler("color", "rgb")', cycler("color", 'rgb')), + ('cycler("color", "Dark2")', + cycler("color", mpl.color_sequences["Dark2"])), (cycler('linestyle', ['-', '--']), cycler('linestyle', ['-', '--'])), ("""(cycler("color", ["r", "g", "b"]) + @@ -288,6 +286,17 @@ def generate_validator_testcases(valid): ('cycler("bleh, [])', ValueError), # syntax error ('Cycler("linewidth", [1, 2, 3])', ValueError), # only 'cycler()' function is allowed + # do not allow dunder in string literals + ("cycler('c', [j.__class__(j) for j in ['r', 'b']])", + ValueError), + ("cycler('c', [j. __class__(j) for j in ['r', 'b']])", + ValueError), + ("cycler('c', [j.\t__class__(j) for j in ['r', 'b']])", + ValueError), + ("cycler('c', [j.\u000c__class__(j) for j in ['r', 'b']])", + ValueError), + ("cycler('c', [j.__class__(j).lower() for j in ['r', 'b']])", + ValueError), ('1 + 2', ValueError), # doesn't produce a Cycler object ('os.system("echo Gotcha")', ValueError), # os not available ('import os', ValueError), # should not be able to import @@ -340,6 +349,17 @@ def generate_validator_testcases(valid): ('(0, 1, "0.5")', ValueError), # last one not a float ), }, + {'validator': _validate_color_or_linecolor, + 'success': (('linecolor', 'linecolor'), + ('markerfacecolor', 'markerfacecolor'), + ('mfc', 'markerfacecolor'), + ('markeredgecolor', 'markeredgecolor'), + ('mec', 'markeredgecolor') + ), + 'fail': (('line', ValueError), + ('marker', ValueError) + ) + }, {'validator': validate_hist_bins, 'success': (('auto', 'auto'), ('fd', 'fd'), @@ -400,6 +420,7 @@ def generate_validator_testcases(valid): ([1, 2, 3], ValueError), # sequence with odd length (1.23, ValueError), # not a sequence (("a", [1, 2]), ValueError), # wrong explicit offset + ((None, [1, 2]), ValueError), # wrong explicit offset ((1, [1, 2, 3]), ValueError), # odd length sequence (([1, 2], 1), ValueError), # inverted offset/onoff ) @@ -436,6 +457,12 @@ def test_validator_invalid(validator, arg, exception_type): validator(arg) +def test_validate_cycler_bad_color_string(): + msg = "'foo' is neither a color sequence name nor can it be interpreted as a list" + with pytest.raises(ValueError, match=msg): + validate_cycler("cycler('color', 'foo')") + + @pytest.mark.parametrize('weight, parsed_weight', [ ('bold', 'bold'), ('BOLD', ValueError), # weight is case-sensitive @@ -456,12 +483,39 @@ def test_validate_fontweight(weight, parsed_weight): assert validate_fontweight(weight) == parsed_weight +@pytest.mark.parametrize('stretch, parsed_stretch', [ + ('expanded', 'expanded'), + ('EXPANDED', ValueError), # stretch is case-sensitive + (100, 100), + ('100', 100), + (np.array(100), 100), + # fractional fontweights are not defined. This should actually raise a + # ValueError, but historically did not. + (20.6, 20), + ('20.6', ValueError), + ([100], ValueError), +]) +def test_validate_fontstretch(stretch, parsed_stretch): + if parsed_stretch is ValueError: + with pytest.raises(ValueError): + validate_fontstretch(stretch) + else: + assert validate_fontstretch(stretch) == parsed_stretch + + def test_keymaps(): key_list = [k for k in mpl.rcParams if 'keymap' in k] for k in key_list: assert isinstance(mpl.rcParams[k], list) +def test_no_backend_reset_rccontext(): + assert mpl.rcParams['backend'] != 'module://aardvark' + with mpl.rc_context(): + mpl.rcParams['backend'] = 'module://aardvark' + assert mpl.rcParams['backend'] == 'module://aardvark' + + def test_rcparams_reset_after_fail(): # There was previously a bug that meant that if rc_context failed and # raised an exception due to issues in the supplied rc parameters, the @@ -469,35 +523,152 @@ def test_rcparams_reset_after_fail(): with mpl.rc_context(rc={'text.usetex': False}): assert mpl.rcParams['text.usetex'] is False with pytest.raises(KeyError): - with mpl.rc_context(rc=OrderedDict([('text.usetex', True), - ('test.blah', True)])): + with mpl.rc_context(rc={'text.usetex': True, 'test.blah': True}): pass assert mpl.rcParams['text.usetex'] is False @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") -def test_backend_fallback_headless(tmpdir): +def test_backend_fallback_headless_invalid_backend(tmp_path): env = {**os.environ, - "DISPLAY": "", "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)} + "DISPLAY": "", "WAYLAND_DISPLAY": "", + "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} + # plotting should fail with the tkagg backend selected in a headless environment with pytest.raises(subprocess.CalledProcessError): - subprocess.run( + subprocess_run_for_testing( [sys.executable, "-c", - ("import matplotlib;" + - "matplotlib.use('tkagg');" + - "import matplotlib.pyplot") + "import matplotlib;" + "matplotlib.use('tkagg');" + "import matplotlib.pyplot;" + "matplotlib.pyplot.plot(42);" ], - env=env, check=True) + env=env, check=True, stderr=subprocess.DEVNULL) + +@pytest.mark.skipif(sys.platform != "linux", reason="Linux only") +def test_backend_fallback_headless_auto_backend(tmp_path): + # specify a headless mpl environment, but request a graphical (tk) backend + env = {**os.environ, + "DISPLAY": "", "WAYLAND_DISPLAY": "", + "MPLBACKEND": "TkAgg", "MPLCONFIGDIR": str(tmp_path)} + + # allow fallback to an available interactive backend explicitly in configuration + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text("backend_fallback: true") + + # plotting should succeed, by falling back to use the generic agg backend + backend = subprocess_run_for_testing( + [sys.executable, "-c", + "import matplotlib.pyplot;" + "matplotlib.pyplot.plot(42);" + "print(matplotlib.get_backend());" + ], + env=env, text=True, check=True, capture_output=True).stdout + assert backend.strip().lower() == "agg" + + +@pytest.mark.skipif( + sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), + reason="headless") +def test_backend_fallback_headful(tmp_path): + if parse_version(pytest.__version__) >= parse_version('8.2.0'): + pytest_kwargs = dict(exc_type=ImportError) + else: + pytest_kwargs = {} -@pytest.mark.skipif(sys.platform == "linux" and not os.environ.get("DISPLAY"), - reason="headless") -def test_backend_fallback_headful(tmpdir): - pytest.importorskip("tkinter") - env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)} - backend = subprocess.check_output( + pytest.importorskip("tkinter", **pytest_kwargs) + env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} + backend = subprocess_run_for_testing( [sys.executable, "-c", - "import matplotlib.pyplot; print(matplotlib.get_backend())"], - env=env, universal_newlines=True) + "import matplotlib as mpl; " + "sentinel = mpl.rcsetup._auto_backend_sentinel; " + # Check that access on another instance does not resolve the sentinel. + "assert mpl.RcParams({'backend': sentinel})['backend'] == sentinel; " + "assert mpl.rcParams._get('backend') == sentinel; " + "assert mpl.get_backend(auto_select=False) is None; " + "import matplotlib.pyplot; " + "print(matplotlib.get_backend())"], + env=env, text=True, check=True, capture_output=True).stdout # The actual backend will depend on what's installed, but at least tkagg is # present. assert backend.strip().lower() != "agg" + + +def test_deprecation(monkeypatch): + mpl.rcParams.update(mpl.rcParams.copy()) # Doesn't warn. + # Note that the warning suppression actually arises from the + # iteration over the updater rcParams being protected by + # suppress_matplotlib_deprecation_warning, rather than any explicit check. + + +@pytest.mark.parametrize("value", [ + "best", + 1, + "1", + (0.9, .7), + (-0.9, .7), + "(0.9, .7)" +]) +def test_rcparams_legend_loc(value): + # rcParams['legend.loc'] should allow any of the following formats. + # if any of these are not allowed, an exception will be raised + # test for gh issue #22338 + mpl.rcParams["legend.loc"] = value + + +@pytest.mark.parametrize("value", [ + "best", + 1, + (0.9, .7), + (-0.9, .7), +]) +def test_rcparams_legend_loc_from_file(tmp_path, value): + # rcParams['legend.loc'] should be settable from matplotlibrc. + # if any of these are not allowed, an exception will be raised. + # test for gh issue #22338 + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text(f"legend.loc: {value}") + + with mpl.rc_context(fname=rc_path): + assert mpl.rcParams["legend.loc"] == value + + +@pytest.mark.parametrize("value", [(1, 2, 3), '1, 2, 3', '(1, 2, 3)']) +def test_validate_sketch(value): + mpl.rcParams["path.sketch"] = value + assert mpl.rcParams["path.sketch"] == (1, 2, 3) + assert validate_sketch(value) == (1, 2, 3) + + +@pytest.mark.parametrize("value", [1, '1', '1 2 3']) +def test_validate_sketch_error(value): + with pytest.raises(ValueError, match="scale, length, randomness"): + validate_sketch(value) + with pytest.raises(ValueError, match="scale, length, randomness"): + mpl.rcParams["path.sketch"] = value + + +@pytest.mark.parametrize("value", ['1, 2, 3', '(1,2,3)']) +def test_rcparams_path_sketch_from_file(tmp_path, value): + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text(f"path.sketch: {value}") + with mpl.rc_context(fname=rc_path): + assert mpl.rcParams["path.sketch"] == (1, 2, 3) + + +@pytest.mark.parametrize('group, option, alias, value', [ + ('lines', 'linewidth', 'lw', 3), + ('lines', 'linestyle', 'ls', 'dashed'), + ('lines', 'color', 'c', 'white'), + ('axes', 'facecolor', 'fc', 'black'), + ('figure', 'edgecolor', 'ec', 'magenta'), + ('lines', 'markeredgewidth', 'mew', 1.5), + ('patch', 'antialiased', 'aa', False), + ('font', 'sans-serif', 'sans', ["Verdana"]) +]) +def test_rc_aliases(group, option, alias, value): + rc_kwargs = {alias: value,} + mpl.rc(group, **rc_kwargs) + + rcParams_key = f"{group}.{option}" + assert mpl.rcParams[rcParams_key] == value diff --git a/lib/matplotlib/tests/test_sankey.py b/lib/matplotlib/tests/test_sankey.py index 62c4fce662cb..253bfa4fa093 100644 --- a/lib/matplotlib/tests/test_sankey.py +++ b/lib/matplotlib/tests/test_sankey.py @@ -1,4 +1,8 @@ +import pytest +from numpy.testing import assert_allclose, assert_array_equal + from matplotlib.sankey import Sankey +from matplotlib.testing.decorators import check_figures_equal def test_sankey(): @@ -10,3 +14,92 @@ def test_sankey(): def test_label(): s = Sankey(flows=[0.25], labels=['First'], orientations=[-1]) assert s.diagrams[0].texts[0].get_text() == 'First\n0.25' + + +def test_format_using_callable(): + # test using callable by slightly incrementing above label example + + def show_three_decimal_places(value): + return f'{value:.3f}' + + s = Sankey(flows=[0.25], labels=['First'], orientations=[-1], + format=show_three_decimal_places) + + assert s.diagrams[0].texts[0].get_text() == 'First\n0.250' + + +@pytest.mark.parametrize('kwargs, msg', ( + ({'gap': -1}, "'gap' is negative"), + ({'gap': 1, 'radius': 2}, "'radius' is greater than 'gap'"), + ({'head_angle': -1}, "'head_angle' is negative"), + ({'tolerance': -1}, "'tolerance' is negative"), + ({'flows': [1, -1], 'orientations': [-1, 0, 1]}, + r"The shapes of 'flows' \(2,\) and 'orientations'"), + ({'flows': [1, -1], 'labels': ['a', 'b', 'c']}, + r"The shapes of 'flows' \(2,\) and 'labels'"), + )) +def test_sankey_errors(kwargs, msg): + with pytest.raises(ValueError, match=msg): + Sankey(**kwargs) + + +@pytest.mark.parametrize('kwargs, msg', ( + ({'trunklength': -1}, "'trunklength' is negative"), + ({'flows': [0.2, 0.3], 'prior': 0}, "The scaled sum of the connected"), + ({'prior': -1}, "The index of the prior diagram is negative"), + ({'prior': 1}, "The index of the prior diagram is 1"), + ({'connect': (-1, 1), 'prior': 0}, "At least one of the connection"), + ({'connect': (2, 1), 'prior': 0}, "The connection index to the source"), + ({'connect': (1, 3), 'prior': 0}, "The connection index to this dia"), + ({'connect': (1, 1), 'prior': 0, 'flows': [-0.2, 0.2], + 'orientations': [2]}, "The value of orientations"), + ({'connect': (1, 1), 'prior': 0, 'flows': [-0.2, 0.2], + 'pathlengths': [2]}, "The lengths of 'flows'"), + )) +def test_sankey_add_errors(kwargs, msg): + sankey = Sankey() + with pytest.raises(ValueError, match=msg): + sankey.add(flows=[0.2, -0.2]) + sankey.add(**kwargs) + + +def test_sankey2(): + s = Sankey(flows=[0.25, -0.25, 0.5, -0.5], labels=['Foo'], + orientations=[-1], unit='Bar') + sf = s.finish() + assert_array_equal(sf[0].flows, [0.25, -0.25, 0.5, -0.5]) + assert sf[0].angles == [1, 3, 1, 3] + assert all([text.get_text()[0:3] == 'Foo' for text in sf[0].texts]) + assert all([text.get_text()[-3:] == 'Bar' for text in sf[0].texts]) + assert sf[0].text.get_text() == '' + assert_allclose(sf[0].tips, + [(-1.375, -0.52011255), + (1.375, -0.75506044), + (-0.75, -0.41522509), + (0.75, -0.8599479)]) + + s = Sankey(flows=[0.25, -0.25, 0, 0.5, -0.5], labels=['Foo'], + orientations=[-1], unit='Bar') + sf = s.finish() + assert_array_equal(sf[0].flows, [0.25, -0.25, 0, 0.5, -0.5]) + assert sf[0].angles == [1, 3, None, 1, 3] + assert_allclose(sf[0].tips, + [(-1.375, -0.52011255), + (1.375, -0.75506044), + (0, 0), + (-0.75, -0.41522509), + (0.75, -0.8599479)]) + + +@check_figures_equal() +def test_sankey3(fig_test, fig_ref): + ax_test = fig_test.gca() + s_test = Sankey(ax=ax_test, flows=[0.25, -0.25, -0.25, 0.25, 0.5, -0.5], + orientations=[1, -1, 1, -1, 0, 0]) + s_test.finish() + + ax_ref = fig_ref.gca() + s_ref = Sankey(ax=ax_ref) + s_ref.add(flows=[0.25, -0.25, -0.25, 0.25, 0.5, -0.5], + orientations=[1, -1, 1, -1, 0, 0]) + s_ref.finish() diff --git a/lib/matplotlib/tests/test_scale.py b/lib/matplotlib/tests/test_scale.py index 1bfe12e24deb..b3da951cf464 100644 --- a/lib/matplotlib/tests/test_scale.py +++ b/lib/matplotlib/tests/test_scale.py @@ -1,7 +1,12 @@ +import copy + import matplotlib.pyplot as plt from matplotlib.scale import ( + AsinhScale, AsinhTransform, LogTransform, InvertedLogTransform, SymmetricalLogTransform) +import matplotlib.scale as mscale +from matplotlib.ticker import AsinhLocator, LogFormatterSciNotation from matplotlib.testing.decorators import check_figures_equal, image_comparison import numpy as np @@ -32,22 +37,22 @@ def test_symlog_mask_nan(): x = np.arange(-1.5, 5, 0.5) out = slti.transform_non_affine(slt.transform_non_affine(x)) assert_allclose(out, x) - assert type(out) == type(x) + assert type(out) is type(x) x[4] = np.nan out = slti.transform_non_affine(slt.transform_non_affine(x)) assert_allclose(out, x) - assert type(out) == type(x) + assert type(out) is type(x) x = np.ma.array(x) out = slti.transform_non_affine(slt.transform_non_affine(x)) assert_allclose(out, x) - assert type(out) == type(x) + assert type(out) is type(x) x[3] = np.ma.masked out = slti.transform_non_affine(slt.transform_non_affine(x)) assert_allclose(out, x) - assert type(out) == type(x) + assert type(out) is type(x) @image_comparison(['logit_scales.png'], remove_text=True) @@ -102,7 +107,8 @@ def test_logscale_mask(): fig, ax = plt.subplots() ax.plot(np.exp(-xs**2)) fig.canvas.draw() - ax.set(yscale="log") + ax.set(yscale="log", + yticks=10.**np.arange(-300, 0, 24)) # Backcompat tick selection. def test_extra_kwargs_raise(): @@ -157,6 +163,7 @@ def test_logscale_nonpos_values(): ax4.set_yscale('log') ax4.set_xscale('log') + ax4.set_yticks([1e-2, 1, 1e+2]) # Backcompat tick selection. def test_invalid_log_lims(): @@ -198,3 +205,93 @@ def forward(x): ax.plot(x, x) ax.set_xscale('function', functions=(forward, inverse)) ax.set_xlim(1, 1000) + + +def test_pass_scale(): + # test passing a scale object works... + fig, ax = plt.subplots() + scale = mscale.LogScale(axis=None) + ax.set_xscale(scale) + scale = mscale.LogScale(axis=None) + ax.set_yscale(scale) + assert ax.xaxis.get_scale() == 'log' + assert ax.yaxis.get_scale() == 'log' + + +def test_scale_deepcopy(): + sc = mscale.LogScale(axis='x', base=10) + sc2 = copy.deepcopy(sc) + assert str(sc.get_transform()) == str(sc2.get_transform()) + assert sc._transform is not sc2._transform + + +class TestAsinhScale: + def test_transforms(self): + a0 = 17.0 + a = np.linspace(-50, 50, 100) + + forward = AsinhTransform(a0) + inverse = forward.inverted() + invinv = inverse.inverted() + + a_forward = forward.transform_non_affine(a) + a_inverted = inverse.transform_non_affine(a_forward) + assert_allclose(a_inverted, a) + + a_invinv = invinv.transform_non_affine(a) + assert_allclose(a_invinv, a0 * np.arcsinh(a / a0)) + + def test_init(self): + fig, ax = plt.subplots() + + s = AsinhScale(axis=None, linear_width=23.0) + assert s.linear_width == 23 + assert s._base == 10 + assert s._subs == (2, 5) + + tx = s.get_transform() + assert isinstance(tx, AsinhTransform) + assert tx.linear_width == s.linear_width + + def test_base_init(self): + fig, ax = plt.subplots() + + s3 = AsinhScale(axis=None, base=3) + assert s3._base == 3 + assert s3._subs == (2,) + + s7 = AsinhScale(axis=None, base=7, subs=(2, 4)) + assert s7._base == 7 + assert s7._subs == (2, 4) + + def test_fmtloc(self): + class DummyAxis: + def __init__(self): + self.fields = {} + def set(self, **kwargs): + self.fields.update(**kwargs) + def set_major_formatter(self, f): + self.fields['major_formatter'] = f + + ax0 = DummyAxis() + s0 = AsinhScale(axis=ax0, base=0) + s0.set_default_locators_and_formatters(ax0) + assert isinstance(ax0.fields['major_locator'], AsinhLocator) + assert isinstance(ax0.fields['major_formatter'], str) + + ax5 = DummyAxis() + s7 = AsinhScale(axis=ax5, base=5) + s7.set_default_locators_and_formatters(ax5) + assert isinstance(ax5.fields['major_locator'], AsinhLocator) + assert isinstance(ax5.fields['major_formatter'], + LogFormatterSciNotation) + + def test_bad_scale(self): + fig, ax = plt.subplots() + + with pytest.raises(ValueError): + AsinhScale(axis=None, linear_width=0) + with pytest.raises(ValueError): + AsinhScale(axis=None, linear_width=-1) + s0 = AsinhScale(axis=None, ) + s1 = AsinhScale(axis=None, linear_width=3.0) diff --git a/lib/matplotlib/tests/test_simplification.py b/lib/matplotlib/tests/test_simplification.py index 07287618f26c..bc9b46b14db2 100644 --- a/lib/matplotlib/tests/test_simplification.py +++ b/lib/matplotlib/tests/test_simplification.py @@ -1,12 +1,14 @@ import base64 import io +import platform import numpy as np from numpy.testing import assert_array_almost_equal, assert_array_equal import pytest -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import ( + check_figures_equal, image_comparison, remove_ticks_and_titles) import matplotlib.pyplot as plt from matplotlib import patches, transforms @@ -26,7 +28,8 @@ def test_clipping(): ax.set_ylim((-0.20, -0.28)) -@image_comparison(['overflow'], remove_text=True) +@image_comparison(['overflow'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.007) def test_overflow(): x = np.array([1.0, 2.0, 3.0, 2.0e5]) y = np.arange(len(x)) @@ -47,6 +50,29 @@ def test_diamond(): ax.set_ylim(-0.6, 0.6) +def test_clipping_out_of_bounds(): + # Should work on a Path *without* codes. + path = Path([(0, 0), (1, 2), (2, 1)]) + simplified = path.cleaned(clip=(10, 10, 20, 20)) + assert_array_equal(simplified.vertices, [(0, 0)]) + assert simplified.codes == [Path.STOP] + + # Should work on a Path *with* codes, and no curves. + path = Path([(0, 0), (1, 2), (2, 1)], + [Path.MOVETO, Path.LINETO, Path.LINETO]) + simplified = path.cleaned(clip=(10, 10, 20, 20)) + assert_array_equal(simplified.vertices, [(0, 0)]) + assert simplified.codes == [Path.STOP] + + # A Path with curves does not do any clipping yet. + path = Path([(0, 0), (1, 2), (2, 3)], + [Path.MOVETO, Path.CURVE3, Path.CURVE3]) + simplified = path.cleaned() + simplified_clipped = path.cleaned(clip=(10, 10, 20, 20)) + assert_array_equal(simplified.vertices, simplified_clipped.vertices) + assert_array_equal(simplified.codes, simplified_clipped.codes) + + def test_noise(): np.random.seed(0) x = np.random.uniform(size=50000) * 50 @@ -207,7 +233,7 @@ def test_sine_plus_noise(): assert simplified.vertices.size == 25240 -@image_comparison(['simplify_curve'], remove_text=True) +@image_comparison(['simplify_curve'], remove_text=True, tol=0.017) def test_simplify_curve(): pp1 = patches.PathPatch( Path([(0, 0), (1, 0), (1, 1), (np.nan, 1), (0, 0), (2, 0), (2, 2), @@ -222,6 +248,155 @@ def test_simplify_curve(): ax.set_ylim((0, 2)) +@check_figures_equal(extensions=['png', 'pdf', 'svg']) +def test_closed_path_nan_removal(fig_test, fig_ref): + ax_test = fig_test.subplots(2, 2).flatten() + ax_ref = fig_ref.subplots(2, 2).flatten() + + # NaN on the first point also removes the last point, because it's closed. + path = Path( + [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, -3]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + ax_test[0].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, np.nan]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO]) + ax_ref[0].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN on second-last point should not re-close. + path = Path( + [[-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + ax_test[0].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO]) + ax_ref[0].add_patch(patches.PathPatch(path, facecolor='none')) + + # Test multiple loops in a single path (with same paths as above). + path = Path( + [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, -3], + [-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + ax_test[1].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, np.nan], + [-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO]) + ax_ref[1].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN in first point of CURVE3 should not re-close, and hide entire curve. + path = Path( + [[-1, -1], [1, -1], [1, np.nan], [0, 1], [-1, 1], [-1, -1]], + [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO, + Path.CLOSEPOLY]) + ax_test[2].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-1, -1], [1, -1], [1, np.nan], [0, 1], [-1, 1], [-1, -1]], + [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO, + Path.CLOSEPOLY]) + ax_ref[2].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN in second point of CURVE3 should not re-close, and hide entire curve + # plus next line segment. + path = Path( + [[-3, -3], [3, -3], [3, 0], [0, np.nan], [-3, 3], [-3, -3]], + [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO, + Path.LINETO]) + ax_test[2].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-3, -3], [3, -3], [3, 0], [0, np.nan], [-3, 3], [-3, -3]], + [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO, + Path.LINETO]) + ax_ref[2].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN in first point of CURVE4 should not re-close, and hide entire curve. + path = Path( + [[-1, -1], [1, -1], [1, np.nan], [0, 0], [0, 1], [-1, 1], [-1, -1]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.CLOSEPOLY]) + ax_test[3].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-1, -1], [1, -1], [1, np.nan], [0, 0], [0, 1], [-1, 1], [-1, -1]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.CLOSEPOLY]) + ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN in second point of CURVE4 should not re-close, and hide entire curve. + path = Path( + [[-2, -2], [2, -2], [2, 0], [0, np.nan], [0, 2], [-2, 2], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.LINETO]) + ax_test[3].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-2, -2], [2, -2], [2, 0], [0, np.nan], [0, 2], [-2, 2], [-2, -2]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.LINETO]) + ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none')) + + # NaN in third point of CURVE4 should not re-close, and hide entire curve + # plus next line segment. + path = Path( + [[-3, -3], [3, -3], [3, 0], [0, 0], [0, np.nan], [-3, 3], [-3, -3]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.LINETO]) + ax_test[3].add_patch(patches.PathPatch(path, facecolor='none')) + path = Path( + [[-3, -3], [3, -3], [3, 0], [0, 0], [0, np.nan], [-3, 3], [-3, -3]], + [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, + Path.LINETO, Path.LINETO]) + ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none')) + + # Keep everything clean. + for ax in [*ax_test.flat, *ax_ref.flat]: + ax.set(xlim=(-3.5, 3.5), ylim=(-3.5, 3.5)) + remove_ticks_and_titles(fig_test) + remove_ticks_and_titles(fig_ref) + + +@check_figures_equal(extensions=['png', 'pdf', 'svg']) +def test_closed_path_clipping(fig_test, fig_ref): + vertices = [] + for roll in range(8): + offset = 0.1 * roll + 0.1 + + # A U-like pattern. + pattern = [ + [-0.5, 1.5], [-0.5, -0.5], [1.5, -0.5], [1.5, 1.5], # Outer square + # With a notch in the top. + [1 - offset / 2, 1.5], [1 - offset / 2, offset], + [offset / 2, offset], [offset / 2, 1.5], + ] + + # Place the initial/final point anywhere in/out of the clipping area. + pattern = np.roll(pattern, roll, axis=0) + pattern = np.concatenate((pattern, pattern[:1, :])) + + vertices.append(pattern) + + # Multiple subpaths are used here to ensure they aren't broken by closed + # loop clipping. + codes = np.full(len(vertices[0]), Path.LINETO) + codes[0] = Path.MOVETO + codes[-1] = Path.CLOSEPOLY + codes = np.tile(codes, len(vertices)) + vertices = np.concatenate(vertices) + + fig_test.set_size_inches((5, 5)) + path = Path(vertices, codes) + fig_test.add_artist(patches.PathPatch(path, facecolor='none')) + + # For reference, we draw the same thing, but unclosed by using a line to + # the last point only. + fig_ref.set_size_inches((5, 5)) + codes = codes.copy() + codes[codes == Path.CLOSEPOLY] = Path.LINETO + path = Path(vertices, codes) + fig_ref.add_artist(patches.PathPatch(path, facecolor='none')) + + @image_comparison(['hatch_simplify'], remove_text=True) def test_hatch(): fig, ax = plt.subplots() @@ -282,8 +457,8 @@ def test_start_with_moveto(): def test_throw_rendering_complexity_exceeded(): plt.rcParams['path.simplify'] = False - xx = np.arange(200000) - yy = np.random.rand(200000) + xx = np.arange(2_000_000) + yy = np.random.rand(2_000_000) yy[1000] = np.nan fig, ax = plt.subplots() @@ -343,3 +518,54 @@ def test_clipping_full(): simplified = list(p.iter_segments(clip=[0, 0, 100, 100])) assert ([(list(x), y) for x, y in simplified] == [([50, 40], 1)]) + + +def test_simplify_closepoly(): + # The values of the vertices in a CLOSEPOLY should always be ignored, + # in favor of the most recent MOVETO's vertex values + paths = [Path([(1, 1), (2, 1), (2, 2), (np.nan, np.nan)], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]), + Path([(1, 1), (2, 1), (2, 2), (40, 50)], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])] + expected_path = Path([(1, 1), (2, 1), (2, 2), (1, 1), (1, 1), (0, 0)], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.STOP]) + + for path in paths: + simplified_path = path.cleaned(simplify=True) + assert_array_equal(expected_path.vertices, simplified_path.vertices) + assert_array_equal(expected_path.codes, simplified_path.codes) + + # test that a compound path also works + path = Path([(1, 1), (2, 1), (2, 2), (np.nan, np.nan), + (-1, 0), (-2, 0), (-2, 1), (np.nan, np.nan)], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + expected_path = Path([(1, 1), (2, 1), (2, 2), (1, 1), + (-1, 0), (-2, 0), (-2, 1), (-1, 0), (-1, 0), (0, 0)], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.STOP]) + + simplified_path = path.cleaned(simplify=True) + assert_array_equal(expected_path.vertices, simplified_path.vertices) + assert_array_equal(expected_path.codes, simplified_path.codes) + + # test for a path with an invalid MOVETO + # CLOSEPOLY with an invalid MOVETO should be ignored + path = Path([(1, 0), (1, -1), (2, -1), + (np.nan, np.nan), (-1, -1), (-2, 1), (-1, 1), + (2, 2), (0, -1)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.CLOSEPOLY, Path.LINETO]) + expected_path = Path([(1, 0), (1, -1), (2, -1), + (np.nan, np.nan), (-1, -1), (-2, 1), (-1, 1), + (0, -1), (0, -1), (0, 0)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.STOP]) + + simplified_path = path.cleaned(simplify=True) + assert_array_equal(expected_path.vertices, simplified_path.vertices) + assert_array_equal(expected_path.codes, simplified_path.codes) diff --git a/lib/matplotlib/tests/test_skew.py b/lib/matplotlib/tests/test_skew.py index 3b05b8c44fb5..8527e474fa21 100644 --- a/lib/matplotlib/tests/test_skew.py +++ b/lib/matplotlib/tests/test_skew.py @@ -1,9 +1,10 @@ """ -Testing that skewed axes properly work. +Testing that skewed Axes properly work. """ from contextlib import ExitStack import itertools +import platform import matplotlib.pyplot as plt from matplotlib.testing.decorators import image_comparison @@ -69,18 +70,17 @@ def _adjust_location(self): # spines and axes instances as appropriate. class SkewXAxes(Axes): # The projection must specify a name. This will be used be the - # user to select the projection, i.e. ``subplot(111, - # projection='skewx')``. + # user to select the projection, i.e. ``subplot(projection='skewx')``. name = 'skewx' def _init_axis(self): # Taken from Axes and modified to use our modified X-axis self.xaxis = SkewXAxis(self) - self.spines['top'].register_axis(self.xaxis) - self.spines['bottom'].register_axis(self.xaxis) + self.spines.top.register_axis(self.xaxis) + self.spines.bottom.register_axis(self.xaxis) self.yaxis = maxis.YAxis(self) - self.spines['left'].register_axis(self.yaxis) - self.spines['right'].register_axis(self.yaxis) + self.spines.left.register_axis(self.yaxis) + self.spines.right.register_axis(self.yaxis) def _gen_axes_spines(self): spines = {'top': SkewSpine.linear_spine(self, 'top'), @@ -133,7 +133,7 @@ def upper_xlim(self): register_projection(SkewXAxes) -@image_comparison(['skew_axes'], remove_text=True) +@image_comparison(['skew_axes.png'], remove_text=True) def test_set_line_coll_dash_image(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection='skewx') @@ -145,7 +145,8 @@ def test_set_line_coll_dash_image(): ax.axvline(0, color='b') -@image_comparison(['skew_rects'], remove_text=True) +@image_comparison(['skew_rects.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.009) def test_skew_rectangle(): fix, axes = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(8, 8)) @@ -161,7 +162,7 @@ def test_skew_rectangle(): xdeg, ydeg = 45 * xrots, 45 * yrots t = transforms.Affine2D().skew_deg(xdeg, ydeg) - ax.set_title('Skew of {0} in X and {1} in Y'.format(xdeg, ydeg)) + ax.set_title(f'Skew of {xdeg} in X and {ydeg} in Y') ax.add_patch(mpatch.Rectangle([-1, -1], 2, 2, transform=t + ax.transData, alpha=0.5, facecolor='coral')) diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index fd9e9344e5ac..6a81f56fe924 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -1,38 +1,61 @@ """Tests for tinypages build using sphinx extensions.""" import filecmp +import os from pathlib import Path -from subprocess import Popen, PIPE +import shutil import sys +from matplotlib.testing import subprocess_run_for_testing import pytest -pytest.importorskip('sphinx') +pytest.importorskip('sphinx', minversion='4.1.3') -def test_tinypages(tmpdir): - tmp_path = Path(tmpdir) - html_dir = tmp_path / 'html' - doctree_dir = tmp_path / 'doctrees' +tinypages = Path(__file__).parent / 'data/tinypages' + + +def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): # Build the pages with warnings turned into errors + extra_args = [] if extra_args is None else extra_args cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', - '-d', str(doctree_dir), - str(Path(__file__).parent / 'tinypages'), str(html_dir)] - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) - out, err = proc.communicate() + '-d', str(doctree_dir), str(source_dir), str(html_dir), *extra_args] + # On CI, gcov emits warnings (due to agg headers being included with the + # same name in multiple extension modules -- but we don't care about their + # coverage anyways); hide them using GCOV_ERROR_FILE. + proc = subprocess_run_for_testing( + cmd, capture_output=True, text=True, + env={**os.environ, "MPLBACKEND": "", "GCOV_ERROR_FILE": os.devnull} + ) + out = proc.stdout + err = proc.stderr + assert proc.returncode == 0, \ - "sphinx build failed with stdout:\n{}\nstderr:\n{}\n".format(out, err) + f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n" if err: - pytest.fail("sphinx build emitted the following warnings:\n{}" - .format(err)) + pytest.fail(f"sphinx build emitted the following warnings:\n{err}") assert html_dir.is_dir() + +def test_tinypages(tmp_path): + shutil.copytree(tinypages, tmp_path, dirs_exist_ok=True) + html_dir = tmp_path / '_build' / 'html' + img_dir = html_dir / '_images' + doctree_dir = tmp_path / 'doctrees' + + # Build the pages with warnings turned into errors + build_sphinx_html(tmp_path, doctree_dir, html_dir) + def plot_file(num): - return html_dir / f'some_plots-{num}.png' + return img_dir / f'some_plots-{num}.png' + + def plot_directive_file(num): + # This is always next to the doctree dir. + return doctree_dir.parent / 'plot_directive' / f'some_plots-{num}.png' - range_10, range_6, range_4 = [plot_file(i) for i in range(1, 4)] + range_10, range_6, range_4 = (plot_file(i) for i in range(1, 4)) # Plot 5 is range(6) plot assert filecmp.cmp(range_6, plot_file(5)) # Plot 7 is range(4) plot @@ -45,10 +68,148 @@ def plot_file(num): # Plot 13 shows close-figs in action assert filecmp.cmp(range_4, plot_file(13)) # Plot 14 has included source - html_contents = (html_dir / 'some_plots.html').read_bytes() - assert b'# Only a comment' in html_contents + html_contents = (html_dir / 'some_plots.html').read_text(encoding='utf-8') + + assert '# Only a comment' in html_contents # check plot defined in external file. - assert filecmp.cmp(range_4, html_dir / 'range4.png') - assert filecmp.cmp(range_6, html_dir / 'range6.png') + assert filecmp.cmp(range_4, img_dir / 'range4.png') + assert filecmp.cmp(range_6, img_dir / 'range6_range6.png') + # check if figure caption made it into html file + assert 'This is the caption for plot 15.' in html_contents + # check if figure caption using :caption: made it into html file (because this plot + # doesn't use srcset, the caption preserves newlines in the output.) + assert 'Plot 17 uses the caption option,\nwith multi-line input.' in html_contents + # check if figure alt text using :alt: made it into html file + assert 'Plot 17 uses the alt option, with multi-line input.' in html_contents # check if figure caption made it into html file - assert b'This is the caption for plot 15.' in html_contents + assert 'This is the caption for plot 18.' in html_contents + # check if the custom classes made it into the html file + assert 'plot-directive my-class my-other-class' in html_contents + # check that the multi-image caption is applied twice + assert html_contents.count('This caption applies to both plots.') == 2 + # Plot 21 is range(6) plot via an include directive. But because some of + # the previous plots are repeated, the argument to plot_file() is only 17. + assert filecmp.cmp(range_6, plot_file(17)) + # plot 22 is from the range6.py file again, but a different function + assert filecmp.cmp(range_10, img_dir / 'range6_range10.png') + + # Modify the included plot + contents = (tmp_path / 'included_plot_21.rst').read_bytes() + contents = contents.replace(b'plt.plot(range(6))', b'plt.plot(range(4))') + (tmp_path / 'included_plot_21.rst').write_bytes(contents) + # Build the pages again and check that the modified file was updated + modification_times = [plot_directive_file(i).stat().st_mtime + for i in (1, 2, 3, 5)] + build_sphinx_html(tmp_path, doctree_dir, html_dir) + assert filecmp.cmp(range_4, plot_file(17)) + # Check that the plots in the plot_directive folder weren't changed. + # (plot_directive_file(1) won't be modified, but it will be copied to html/ + # upon compilation, so plot_file(1) will be modified) + assert plot_directive_file(1).stat().st_mtime == modification_times[0] + assert plot_directive_file(2).stat().st_mtime == modification_times[1] + assert plot_directive_file(3).stat().st_mtime == modification_times[2] + assert filecmp.cmp(range_10, plot_file(1)) + assert filecmp.cmp(range_6, plot_file(2)) + assert filecmp.cmp(range_4, plot_file(3)) + # Make sure that figures marked with context are re-created (but that the + # contents are the same) + assert plot_directive_file(5).stat().st_mtime > modification_times[3] + assert filecmp.cmp(range_6, plot_file(5)) + + +def test_plot_html_show_source_link(tmp_path): + shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py') + shutil.copytree(tinypages / '_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" +.. plot:: + + plt.plot(range(2)) +""") + # Make sure source scripts are created by default + html_dir1 = tmp_path / '_build' / 'html1' + build_sphinx_html(tmp_path, doctree_dir, html_dir1) + assert len(list(html_dir1.glob("**/index-1.py"))) == 1 + # Make sure source scripts are NOT created when + # plot_html_show_source_link` is False + html_dir2 = tmp_path / '_build' / 'html2' + build_sphinx_html(tmp_path, doctree_dir, html_dir2, + extra_args=['-D', 'plot_html_show_source_link=0']) + assert len(list(html_dir2.glob("**/index-1.py"))) == 0 + + +@pytest.mark.parametrize('plot_html_show_source_link', [0, 1]) +def test_show_source_link_true(tmp_path, plot_html_show_source_link): + # Test that a source link is generated if :show-source-link: is true, + # whether or not plot_html_show_source_link is true. + shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py') + shutil.copytree(tinypages / '_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" +.. plot:: + :show-source-link: true + + plt.plot(range(2)) +""") + html_dir = tmp_path / '_build' / 'html' + build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[ + '-D', f'plot_html_show_source_link={plot_html_show_source_link}']) + assert len(list(html_dir.glob("**/index-1.py"))) == 1 + + +@pytest.mark.parametrize('plot_html_show_source_link', [0, 1]) +def test_show_source_link_false(tmp_path, plot_html_show_source_link): + # Test that a source link is NOT generated if :show-source-link: is false, + # whether or not plot_html_show_source_link is true. + shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py') + shutil.copytree(tinypages / '_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" +.. plot:: + :show-source-link: false + + plt.plot(range(2)) +""") + html_dir = tmp_path / '_build' / 'html' + build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[ + '-D', f'plot_html_show_source_link={plot_html_show_source_link}']) + assert len(list(html_dir.glob("**/index-1.py"))) == 0 + + +def test_srcset_version(tmp_path): + html_dir = tmp_path / '_build' / 'html' + img_dir = html_dir / '_images' + doctree_dir = tmp_path / 'doctrees' + + build_sphinx_html(tinypages, doctree_dir, html_dir, + extra_args=['-D', 'plot_srcset=2x']) + + def plot_file(num, suff=''): + return img_dir / f'some_plots-{num}{suff}.png' + + # check some-plots + for ind in [1, 2, 3, 5, 7, 11, 13, 15, 17]: + assert plot_file(ind).exists() + assert plot_file(ind, suff='.2x').exists() + + assert (img_dir / 'nestedpage-index-1.png').exists() + assert (img_dir / 'nestedpage-index-1.2x.png').exists() + assert (img_dir / 'nestedpage-index-2.png').exists() + assert (img_dir / 'nestedpage-index-2.2x.png').exists() + assert (img_dir / 'nestedpage2-index-1.png').exists() + assert (img_dir / 'nestedpage2-index-1.2x.png').exists() + assert (img_dir / 'nestedpage2-index-2.png').exists() + assert (img_dir / 'nestedpage2-index-2.2x.png').exists() + + # Check html for srcset + + assert ('srcset="_images/some_plots-1.png, _images/some_plots-1.2x.png 2.00x"' + in (html_dir / 'some_plots.html').read_text(encoding='utf-8')) + + st = ('srcset="../_images/nestedpage-index-1.png, ' + '../_images/nestedpage-index-1.2x.png 2.00x"') + assert st in (html_dir / 'nestedpage/index.html').read_text(encoding='utf-8') + + st = ('srcset="../_images/nestedpage2-index-2.png, ' + '../_images/nestedpage2-index-2.2x.png 2.00x"') + assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8') diff --git a/lib/matplotlib/tests/test_spines.py b/lib/matplotlib/tests/test_spines.py index 4be97e2b58d5..353aede00298 100644 --- a/lib/matplotlib/tests/test_spines.py +++ b/lib/matplotlib/tests/test_spines.py @@ -1,10 +1,61 @@ import numpy as np +import pytest import matplotlib.pyplot as plt +from matplotlib.spines import Spines from matplotlib.testing.decorators import check_figures_equal, image_comparison -@image_comparison(['spines_axes_positions']) +def test_spine_class(): + """Test Spines and SpinesProxy in isolation.""" + class SpineMock: + def __init__(self): + self.val = None + + def set(self, **kwargs): + vars(self).update(kwargs) + + def set_val(self, val): + self.val = val + + spines_dict = { + 'left': SpineMock(), + 'right': SpineMock(), + 'top': SpineMock(), + 'bottom': SpineMock(), + } + spines = Spines(**spines_dict) + + assert spines['left'] is spines_dict['left'] + assert spines.left is spines_dict['left'] + + spines[['left', 'right']].set_val('x') + assert spines.left.val == 'x' + assert spines.right.val == 'x' + assert spines.top.val is None + assert spines.bottom.val is None + + spines[:].set_val('y') + assert all(spine.val == 'y' for spine in spines.values()) + + spines[:].set(foo='bar') + assert all(spine.foo == 'bar' for spine in spines.values()) + + with pytest.raises(AttributeError, match='foo'): + spines.foo + with pytest.raises(KeyError, match='foo'): + spines['foo'] + with pytest.raises(KeyError, match='foo, bar'): + spines[['left', 'foo', 'right', 'bar']] + with pytest.raises(ValueError, match='single list'): + spines['left', 'right'] + with pytest.raises(ValueError, match='Spines does not support slicing'): + spines['left':'right'] + with pytest.raises(ValueError, match='Spines does not support slicing'): + spines['top':] + + +@image_comparison(['spines_axes_positions.png']) def test_spines_axes_positions(): # SF bug 2852168 fig = plt.figure() @@ -13,27 +64,26 @@ def test_spines_axes_positions(): ax = fig.add_subplot(1, 1, 1) ax.set_title('centered spines') ax.plot(x, y) - ax.spines['right'].set_position(('axes', 0.1)) + ax.spines.right.set_position(('axes', 0.1)) ax.yaxis.set_ticks_position('right') - ax.spines['top'].set_position(('axes', 0.25)) + ax.spines.top.set_position(('axes', 0.25)) ax.xaxis.set_ticks_position('top') - ax.spines['left'].set_color('none') - ax.spines['bottom'].set_color('none') + ax.spines.left.set_color('none') + ax.spines.bottom.set_color('none') -@image_comparison(['spines_data_positions']) +@image_comparison(['spines_data_positions.png']) def test_spines_data_positions(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - ax.spines['left'].set_position(('data', -1.5)) - ax.spines['top'].set_position(('data', 0.5)) - ax.spines['right'].set_position(('data', -0.5)) - ax.spines['bottom'].set_position('zero') + fig, ax = plt.subplots() + ax.spines.left.set_position(('data', -1.5)) + ax.spines.top.set_position(('data', 0.5)) + ax.spines.right.set_position(('data', -0.5)) + ax.spines.bottom.set_position('zero') ax.set_xlim([-2, 2]) ax.set_ylim([-2, 2]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_spine_nonlinear_data_positions(fig_test, fig_ref): plt.style.use("default") @@ -43,52 +93,64 @@ def test_spine_nonlinear_data_positions(fig_test, fig_ref): # linewidth to distinguish them. The calls to tick_params removes labels # (for image comparison purposes) and harmonizes tick positions with the # reference). - ax.spines["left"].set_position(("data", 1)) - ax.spines["left"].set_linewidth(2) - ax.spines["right"].set_position(("data", .1)) + ax.spines.left.set_position(("data", 1)) + ax.spines.left.set_linewidth(2) + ax.spines.right.set_position(("data", .1)) ax.tick_params(axis="y", labelleft=False, direction="in") ax = fig_ref.add_subplot() ax.set(xscale="log", xlim=(.1, 1)) - ax.spines["right"].set_linewidth(2) + ax.spines.right.set_linewidth(2) ax.tick_params(axis="y", labelleft=False, left=False, right=True) -@image_comparison(['spines_capstyle']) +@image_comparison(['spines_capstyle.png']) def test_spines_capstyle(): # issue 2542 plt.rc('axes', linewidth=20) - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.set_xticks([]) ax.set_yticks([]) def test_label_without_ticks(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() plt.subplots_adjust(left=0.3, bottom=0.3) ax.plot(np.arange(10)) ax.yaxis.set_ticks_position('left') - ax.spines['left'].set_position(('outward', 30)) - ax.spines['right'].set_visible(False) + ax.spines.left.set_position(('outward', 30)) + ax.spines.right.set_visible(False) ax.set_ylabel('y label') ax.xaxis.set_ticks_position('bottom') - ax.spines['bottom'].set_position(('outward', 30)) - ax.spines['top'].set_visible(False) + ax.spines.bottom.set_position(('outward', 30)) + ax.spines.top.set_visible(False) ax.set_xlabel('x label') ax.xaxis.set_ticks([]) ax.yaxis.set_ticks([]) plt.draw() - spine = ax.spines['left'] + spine = ax.spines.left spinebbox = spine.get_transform().transform_path( spine.get_path()).get_extents() assert ax.yaxis.label.get_position()[0] < spinebbox.xmin, \ "Y-Axis label not left of the spine" - spine = ax.spines['bottom'] + spine = ax.spines.bottom spinebbox = spine.get_transform().transform_path( spine.get_path()).get_extents() assert ax.xaxis.label.get_position()[1] < spinebbox.ymin, \ "X-Axis label not below the spine" + + +@image_comparison(['black_axes.png']) +def test_spines_black_axes(): + # GitHub #18804 + plt.rcParams["savefig.pad_inches"] = 0 + plt.rcParams["savefig.bbox"] = 'tight' + fig = plt.figure(0, figsize=(4, 4)) + ax = fig.add_axes((0, 0, 1, 1)) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_facecolor((0, 0, 0)) diff --git a/lib/matplotlib/tests/test_streamplot.py b/lib/matplotlib/tests/test_streamplot.py index 35848df89d36..697ee527f253 100644 --- a/lib/matplotlib/tests/test_streamplot.py +++ b/lib/matplotlib/tests/test_streamplot.py @@ -1,25 +1,20 @@ -import sys - import numpy as np from numpy.testing import assert_array_almost_equal +import pytest import matplotlib.pyplot as plt from matplotlib.testing.decorators import image_comparison import matplotlib.transforms as mtransforms -on_win = (sys.platform == 'win32') -on_mac = (sys.platform == 'darwin') - - def velocity_field(): - Y, X = np.mgrid[-3:3:100j, -3:3:100j] + Y, X = np.mgrid[-3:3:100j, -3:3:200j] U = -1 - X**2 + Y V = 1 + X - Y**2 return X, Y, U, V def swirl_velocity_field(): - x = np.linspace(-3., 3., 100) + x = np.linspace(-3., 3., 200) y = np.linspace(-3., 3., 100) X, Y = np.meshgrid(x, y) a = 0.1 @@ -28,61 +23,52 @@ def swirl_velocity_field(): return x, y, U, V -@image_comparison(['streamplot_startpoints'], remove_text=True, style='mpl20') +@image_comparison(['streamplot_startpoints.png'], remove_text=True, style='mpl20', + tol=0.003) def test_startpoints(): + # Test varying startpoints. Also tests a non-default num_arrows argument. X, Y, U, V = velocity_field() - start_x = np.linspace(X.min(), X.max(), 10) - start_y = np.linspace(Y.min(), Y.max(), 10) - start_points = np.column_stack([start_x, start_y]) - plt.streamplot(X, Y, U, V, start_points=start_points) + start_x, start_y = np.meshgrid(np.linspace(X.min(), X.max(), 5), + np.linspace(Y.min(), Y.max(), 5)) + start_points = np.column_stack([start_x.ravel(), start_y.ravel()]) + plt.streamplot(X, Y, U, V, start_points=start_points, num_arrows=4) plt.plot(start_x, start_y, 'ok') -@image_comparison(['streamplot_colormap'], - tol=.04, remove_text=True, style='mpl20') +@image_comparison(['streamplot_colormap.png'], remove_text=True, style='mpl20', + tol=0.022) def test_colormap(): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - X, Y, U, V = velocity_field() plt.streamplot(X, Y, U, V, color=U, density=0.6, linewidth=2, - cmap=plt.cm.autumn) + cmap="autumn") plt.colorbar() -@image_comparison(['streamplot_linewidth'], remove_text=True, style='mpl20') +@image_comparison(['streamplot_linewidth.png'], remove_text=True, style='mpl20', + tol=0.03) def test_linewidth(): X, Y, U, V = velocity_field() speed = np.hypot(U, V) lw = 5 * speed / speed.max() - # Compatibility for old test image - df = 25 / 30 ax = plt.figure().subplots() - ax.set(xlim=(-3.0, 2.9999999999999947), - ylim=(-3.0000000000000004, 2.9999999999999947)) - ax.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k', - linewidth=lw) + ax.streamplot(X, Y, U, V, density=[0.5, 1], color='k', linewidth=lw, num_arrows=2) -@image_comparison(['streamplot_masks_and_nans'], - remove_text=True, style='mpl20', tol=0.04 if on_win else 0) +@image_comparison(['streamplot_masks_and_nans.png'], + remove_text=True, style='mpl20') def test_masks_and_nans(): X, Y, U, V = velocity_field() mask = np.zeros(U.shape, dtype=bool) - mask[40:60, 40:60] = 1 - U[:20, :20] = np.nan + mask[40:60, 80:120] = 1 + U[:20, :40] = np.nan U = np.ma.array(U, mask=mask) - # Compatibility for old test image ax = plt.figure().subplots() - ax.set(xlim=(-3.0, 2.9999999999999947), - ylim=(-3.0000000000000004, 2.9999999999999947)) with np.errstate(invalid='ignore'): - ax.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues) + ax.streamplot(X, Y, U, V, color=U, cmap="Blues") @image_comparison(['streamplot_maxlength.png'], - remove_text=True, style='mpl20', - tol=0.002 if on_mac else 0) + remove_text=True, style='mpl20', tol=0.302) def test_maxlength(): x, y, U, V = swirl_velocity_field() ax = plt.figure().subplots() @@ -93,8 +79,20 @@ def test_maxlength(): ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413)) +@image_comparison(['streamplot_maxlength_no_broken.png'], + remove_text=True, style='mpl20', tol=0.302) +def test_maxlength_no_broken(): + x, y, U, V = swirl_velocity_field() + ax = plt.figure().subplots() + ax.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]], + linewidth=2, density=2, broken_streamlines=False) + assert ax.get_xlim()[-1] == ax.get_ylim()[-1] == 3 + # Compatibility for old test image + ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413)) + + @image_comparison(['streamplot_direction.png'], - remove_text=True, style='mpl20') + remove_text=True, style='mpl20', tol=0.073) def test_direction(): x, y, U, V = swirl_velocity_field() plt.streamplot(x, y, U, V, integration_direction='backward', @@ -102,6 +100,66 @@ def test_direction(): linewidth=2, density=2) +@image_comparison(['streamplot_integration.png'], style='mpl20', tol=0.05) +def test_integration_options(): + # Linear potential flow over a lifting cylinder + n = 50 + x, y = np.meshgrid(np.linspace(-2, 2, n), np.linspace(-3, 3, n)) + th = np.arctan2(y, x) + r = np.sqrt(x**2 + y**2) + vr = -np.cos(th) / r**2 + vt = -np.sin(th) / r**2 - 1 / r + vx = vr * np.cos(th) - vt * np.sin(th) + 1.0 + vy = vr * np.sin(th) + vt * np.cos(th) + + # Seed points + n_seed = 50 + seed_pts = np.column_stack((np.full(n_seed, -1.75), np.linspace(-2, 2, n_seed))) + + fig, axs = plt.subplots(3, 1, figsize=(6, 14)) + th_circ = np.linspace(0, 2 * np.pi, 100) + for ax, max_val in zip(axs, [0.05, 1, 5]): + ax_ins = ax.inset_axes([0.0, 0.7, 0.3, 0.35]) + for ax_curr, is_inset in zip([ax, ax_ins], [False, True]): + ax_curr.streamplot( + x, + y, + vx, + vy, + start_points=seed_pts, + broken_streamlines=False, + arrowsize=1e-10, + linewidth=2 if is_inset else 0.6, + color="k", + integration_max_step_scale=max_val, + integration_max_error_scale=max_val, + ) + + # Draw the cylinder + ax_curr.fill( + np.cos(th_circ), + np.sin(th_circ), + color="w", + ec="k", + lw=6 if is_inset else 2, + ) + + # Set axis properties + ax_curr.set_aspect("equal") + + # Set axis limits and show zoomed region + ax_ins.set_xlim(-1.2, -0.7) + ax_ins.set_ylim(-0.8, -0.4) + ax_ins.set_yticks(()) + ax_ins.set_xticks(()) + + ax.set_ylim(-1.5, 1.5) + ax.axis("off") + ax.indicate_inset_zoom(ax_ins, ec="k") + + fig.tight_layout() + + def test_streamplot_limits(): ax = plt.axes() x = np.linspace(-5, 10, 20) @@ -114,3 +172,71 @@ def test_streamplot_limits(): # datalim. assert_array_almost_equal(ax.dataLim.bounds, (20, 30, 15, 6), decimal=1) + + +def test_streamplot_grid(): + u = np.ones((2, 2)) + v = np.zeros((2, 2)) + + # Test for same rows and columns + x = np.array([[10, 20], [10, 30]]) + y = np.array([[10, 10], [20, 20]]) + + with pytest.raises(ValueError, match="The rows of 'x' must be equal"): + plt.streamplot(x, y, u, v) + + x = np.array([[10, 20], [10, 20]]) + y = np.array([[10, 10], [20, 30]]) + + with pytest.raises(ValueError, match="The columns of 'y' must be equal"): + plt.streamplot(x, y, u, v) + + x = np.array([[10, 20], [10, 20]]) + y = np.array([[10, 10], [20, 20]]) + plt.streamplot(x, y, u, v) + + # Test for maximum dimensions + x = np.array([0, 10]) + y = np.array([[[0, 10]]]) + + with pytest.raises(ValueError, match="'y' can have at maximum " + "2 dimensions"): + plt.streamplot(x, y, u, v) + + # Test for equal spacing + u = np.ones((3, 3)) + v = np.zeros((3, 3)) + x = np.array([0, 10, 20]) + y = np.array([0, 10, 30]) + + with pytest.raises(ValueError, match="'y' values must be equally spaced"): + plt.streamplot(x, y, u, v) + + # Test for strictly increasing + x = np.array([0, 20, 40]) + y = np.array([0, 20, 10]) + + +def test_streamplot_integration_params(): + x = np.array([[10, 20], [10, 20]]) + y = np.array([[10, 10], [20, 20]]) + u = np.ones((2, 2)) + v = np.zeros((2, 2)) + + err_str = "The value of integration_max_step_scale must be > 0, got -0.5" + with pytest.raises(ValueError, match=err_str): + plt.streamplot(x, y, u, v, integration_max_step_scale=-0.5) + + err_str = "The value of integration_max_error_scale must be > 0, got 0.0" + with pytest.raises(ValueError, match=err_str): + plt.streamplot(x, y, u, v, integration_max_error_scale=0.0) + + +def test_streamplot_inputs(): # test no exception occurs. + # fully-masked + plt.streamplot(np.arange(3), np.arange(3), + np.full((3, 3), np.nan), np.full((3, 3), np.nan), + color=np.random.rand(3, 3)) + # array-likes + plt.streamplot(range(3), range(3), + np.random.rand(3, 3), np.random.rand(3, 3)) diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index 538c89e838c5..be038965e33d 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -1,10 +1,9 @@ -from collections import OrderedDict from contextlib import contextmanager -import gc from pathlib import Path from tempfile import TemporaryDirectory import sys +import numpy as np import pytest import matplotlib as mpl @@ -22,12 +21,13 @@ def temp_style(style_name, settings=None): """Context manager to create a style sheet in a temporary directory.""" if not settings: settings = DUMMY_SETTINGS - temp_file = '%s.%s' % (style_name, STYLE_EXTENSION) + temp_file = f'{style_name}.{STYLE_EXTENSION}' try: with TemporaryDirectory() as tmpdir: # Write style settings to file in the tmpdir. Path(tmpdir, temp_file).write_text( - "\n".join("{}: {}".format(k, v) for k, v in settings.items())) + "\n".join(f"{k}: {v}" for k, v in settings.items()), + encoding="utf-8") # Add tmpdir to style path and reload so we can access this style. USER_LIBRARY_PATHS.append(tmpdir) style.reload_library() @@ -58,9 +58,9 @@ def test_use(): assert mpl.rcParams[PARAM] == VALUE -def test_use_url(tmpdir): - path = Path(tmpdir, 'file') - path.write_text('axes.facecolor: adeade') +def test_use_url(tmp_path): + path = tmp_path / 'file' + path.write_text('axes.facecolor: adeade', encoding='utf-8') with temp_style('test', DUMMY_SETTINGS): url = ('file:' + ('///' if sys.platform == 'win32' else '') @@ -69,11 +69,10 @@ def test_use_url(tmpdir): assert mpl.rcParams['axes.facecolor'] == "#adeade" -def test_single_path(tmpdir): +def test_single_path(tmp_path): mpl.rcParams[PARAM] = 'gray' - temp_file = f'text.{STYLE_EXTENSION}' - path = Path(tmpdir, temp_file) - path.write_text(f'{PARAM} : {VALUE}') + path = tmp_path / f'text.{STYLE_EXTENSION}' + path.write_text(f'{PARAM} : {VALUE}', encoding='utf-8') with style.context(path): assert mpl.rcParams[PARAM] == VALUE assert mpl.rcParams[PARAM] == 'gray' @@ -138,10 +137,9 @@ def test_context_with_union_of_dict_and_namedstyle(): def test_context_with_badparam(): original_value = 'gray' other_value = 'blue' - d = OrderedDict([(PARAM, original_value), ('badparam', None)]) with style.context({PARAM: other_value}): assert mpl.rcParams[PARAM] == other_value - x = style.context([d]) + x = style.context({PARAM: original_value, 'badparam': None}) with pytest.raises(KeyError): with x: pass @@ -167,7 +165,7 @@ def test_xkcd_no_cm(): assert mpl.rcParams["path.sketch"] is None plt.xkcd() assert mpl.rcParams["path.sketch"] == (1, 100, 2) - gc.collect() + np.testing.break_cycles() assert mpl.rcParams["path.sketch"] == (1, 100, 2) @@ -176,3 +174,24 @@ def test_xkcd_cm(): with plt.xkcd(): assert mpl.rcParams["path.sketch"] == (1, 100, 2) assert mpl.rcParams["path.sketch"] is None + + +def test_up_to_date_blacklist(): + assert mpl.style.core.STYLE_BLACKLIST <= {*mpl.rcsetup._validators} + + +def test_style_from_module(tmp_path, monkeypatch): + monkeypatch.syspath_prepend(tmp_path) + monkeypatch.chdir(tmp_path) + pkg_path = tmp_path / "mpl_test_style_pkg" + pkg_path.mkdir() + (pkg_path / "test_style.mplstyle").write_text( + "lines.linewidth: 42", encoding="utf-8") + pkg_path.with_suffix(".mplstyle").write_text( + "lines.linewidth: 84", encoding="utf-8") + mpl.style.use("mpl_test_style_pkg.test_style") + assert mpl.rcParams["lines.linewidth"] == 42 + mpl.style.use("mpl_test_style_pkg.mplstyle") + assert mpl.rcParams["lines.linewidth"] == 84 + mpl.style.use("./mpl_test_style_pkg.mplstyle") + assert mpl.rcParams["lines.linewidth"] == 84 diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index 72f72968d72b..a899110ac77a 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -1,10 +1,12 @@ import itertools +import platform import numpy as np import pytest +from matplotlib.axes import Axes, SubplotBase import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import check_figures_equal, image_comparison def check_shared(axs, x_shared, y_shared): @@ -18,21 +20,40 @@ def check_shared(axs, x_shared, y_shared): enumerate(zip("xy", [x_shared, y_shared]))): if i2 <= i1: continue - assert \ - (getattr(axs[0], "_shared_{}_axes".format(name)).joined(ax1, ax2) - == shared[i1, i2]), \ + assert axs[0]._shared_axes[name].joined(ax1, ax2) == shared[i1, i2], \ "axes %i and %i incorrectly %ssharing %s axis" % ( i1, i2, "not " if shared[i1, i2] else "", name) -def check_visible(axs, x_visible, y_visible): +def check_ticklabel_visible(axs, x_visible, y_visible): + """Check that the x and y ticklabel visibility is as specified.""" for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)): - for l in ax.get_xticklabels() + [ax.get_xaxis().offsetText]: + for l in ax.get_xticklabels() + [ax.xaxis.offsetText]: assert l.get_visible() == vx, \ f"Visibility of x axis #{i} is incorrectly {vx}" - for l in ax.get_yticklabels() + [ax.get_yaxis().offsetText]: + for l in ax.get_yticklabels() + [ax.yaxis.offsetText]: assert l.get_visible() == vy, \ f"Visibility of y axis #{i} is incorrectly {vy}" + # axis label "visibility" is toggled by label_outer by resetting the + # label to empty, but it can also be empty to start with. + if not vx: + assert ax.get_xlabel() == "" + if not vy: + assert ax.get_ylabel() == "" + + +def check_tick1_visible(axs, x_visible, y_visible): + """ + Check that the x and y tick visibility is as specified. + + Note: This only checks the tick1line, i.e. bottom / left ticks. + """ + for ax, visible, in zip(axs, x_visible): + for tick in ax.xaxis.get_major_ticks(): + assert tick.tick1line.get_visible() == visible + for ax, y_visible, in zip(axs, y_visible): + for tick in ax.yaxis.get_major_ticks(): + assert tick.tick1line.get_visible() == visible def test_shared(): @@ -79,21 +100,30 @@ def test_shared(): plt.close(f) # test all option combinations - ops = [False, True, 'all', 'none', 'row', 'col'] + ops = [False, True, 'all', 'none', 'row', 'col', 0, 1] for xo in ops: for yo in ops: f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=xo, sharey=yo) axs = [a1, a2, a3, a4] check_shared(axs, share[xo], share[yo]) - check_visible(axs, visible['x'][xo], visible['y'][yo]) + check_ticklabel_visible(axs, visible['x'][xo], visible['y'][yo]) plt.close(f) - # test label_outer - f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=True, sharey=True) - axs = [a1, a2, a3, a4] - for ax in axs: - ax.label_outer() - check_visible(axs, [False, False, True, True], [True, False, True, False]) + +@pytest.mark.parametrize('remove_ticks', [True, False]) +def test_label_outer(remove_ticks): + f, axs = plt.subplots(2, 2, sharex=True, sharey=True) + for ax in axs.flat: + ax.set(xlabel="foo", ylabel="bar") + ax.label_outer(remove_inner_ticks=remove_ticks) + check_ticklabel_visible( + axs.flat, [False, False, True, True], [True, False, True, False]) + if remove_ticks: + check_tick1_visible( + axs.flat, [False, False, True, True], [True, False, True, False]) + else: + check_tick1_visible( + axs.flat, [True, True, True, True], [True, True, True, True]) def test_label_outer_span(): @@ -112,22 +142,28 @@ def test_label_outer_span(): a4 = fig.add_subplot(gs[2, 1]) for ax in fig.axes: ax.label_outer() - check_visible( + check_ticklabel_visible( fig.axes, [False, True, False, True], [True, True, False, False]) +def test_label_outer_non_gridspec(): + ax = plt.axes((0, 0, 1, 1)) + ax.label_outer() # Does nothing. + check_ticklabel_visible([ax], [True], [True]) + + def test_shared_and_moved(): # test if sharey is on, but then tick_left is called that labels don't # re-appear. Seaborn does this just to be sure yaxis is on left... f, (a1, a2) = plt.subplots(1, 2, sharey=True) - check_visible([a2], [True], [False]) + check_ticklabel_visible([a2], [True], [False]) a2.yaxis.tick_left() - check_visible([a2], [True], [False]) + check_ticklabel_visible([a2], [True], [False]) f, (a1, a2) = plt.subplots(2, 1, sharex=True) - check_visible([a1], [False], [True]) + check_ticklabel_visible([a1], [False], [True]) a2.xaxis.tick_bottom() - check_visible([a1], [False], [True]) + check_ticklabel_visible([a1], [False], [True]) def test_exceptions(): @@ -136,20 +172,10 @@ def test_exceptions(): plt.subplots(2, 2, sharex='blah') with pytest.raises(ValueError): plt.subplots(2, 2, sharey='blah') - # We filter warnings in this test which are genuine since - # the point of this test is to ensure that this raises. - with pytest.warns(UserWarning, match='.*sharex argument to subplots'), \ - pytest.raises(ValueError): - plt.subplots(2, 2, -1) - with pytest.warns(UserWarning, match='.*sharex argument to subplots'), \ - pytest.raises(ValueError): - plt.subplots(2, 2, 0) - with pytest.warns(UserWarning, match='.*sharex argument to subplots'), \ - pytest.raises(ValueError): - plt.subplots(2, 2, 5) - - -@image_comparison(['subplots_offset_text'], remove_text=False) + + +@image_comparison(['subplots_offset_text.png'], + tol=0 if platform.machine() == 'x86_64' else 0.028) def test_subplots_offsettext(): x = np.arange(0, 1e10, 1e9) y = np.arange(0, 100, 10)+1e4 @@ -160,6 +186,45 @@ def test_subplots_offsettext(): axs[1, 1].plot(y, x) +@pytest.mark.parametrize("top", [True, False]) +@pytest.mark.parametrize("bottom", [True, False]) +@pytest.mark.parametrize("left", [True, False]) +@pytest.mark.parametrize("right", [True, False]) +def test_subplots_hide_ticklabels(top, bottom, left, right): + # Ideally, we would also test offset-text visibility (and remove + # test_subplots_offsettext), but currently, setting rcParams fails to move + # the offset texts as well. + with plt.rc_context({"xtick.labeltop": top, "xtick.labelbottom": bottom, + "ytick.labelleft": left, "ytick.labelright": right}): + axs = plt.figure().subplots(3, 3, sharex=True, sharey=True) + for (i, j), ax in np.ndenumerate(axs): + xtop = ax.xaxis._major_tick_kw["label2On"] + xbottom = ax.xaxis._major_tick_kw["label1On"] + yleft = ax.yaxis._major_tick_kw["label1On"] + yright = ax.yaxis._major_tick_kw["label2On"] + assert xtop == (top and i == 0) + assert xbottom == (bottom and i == 2) + assert yleft == (left and j == 0) + assert yright == (right and j == 2) + + +@pytest.mark.parametrize("xlabel_position", ["bottom", "top"]) +@pytest.mark.parametrize("ylabel_position", ["left", "right"]) +def test_subplots_hide_axislabels(xlabel_position, ylabel_position): + axs = plt.figure().subplots(3, 3, sharex=True, sharey=True) + for (i, j), ax in np.ndenumerate(axs): + ax.set(xlabel="foo", ylabel="bar") + ax.xaxis.set_label_position(xlabel_position) + ax.yaxis.set_label_position(ylabel_position) + ax.label_outer() + assert bool(ax.get_xlabel()) == ( + xlabel_position == "bottom" and i == 2 + or xlabel_position == "top" and i == 0) + assert bool(ax.get_ylabel()) == ( + ylabel_position == "left" and j == 0 + or ylabel_position == "right" and j == 2) + + def test_get_gridspec(): # ahem, pretty trivial, but... fig, ax = plt.subplots() @@ -173,3 +238,50 @@ def test_dont_mutate_kwargs(): gridspec_kw=gridspec_kw) assert subplot_kw == {'sharex': 'all'} assert gridspec_kw == {'width_ratios': [1, 2]} + + +@pytest.mark.parametrize("width_ratios", [None, [1, 3, 2]]) +@pytest.mark.parametrize("height_ratios", [None, [1, 2]]) +@check_figures_equal() +def test_width_and_height_ratios(fig_test, fig_ref, + height_ratios, width_ratios): + fig_test.subplots(2, 3, height_ratios=height_ratios, + width_ratios=width_ratios) + fig_ref.subplots(2, 3, gridspec_kw={ + 'height_ratios': height_ratios, + 'width_ratios': width_ratios}) + + +@pytest.mark.parametrize("width_ratios", [None, [1, 3, 2]]) +@pytest.mark.parametrize("height_ratios", [None, [1, 2]]) +@check_figures_equal() +def test_width_and_height_ratios_mosaic(fig_test, fig_ref, + height_ratios, width_ratios): + mosaic_spec = [['A', 'B', 'B'], ['A', 'C', 'D']] + fig_test.subplot_mosaic(mosaic_spec, height_ratios=height_ratios, + width_ratios=width_ratios) + fig_ref.subplot_mosaic(mosaic_spec, gridspec_kw={ + 'height_ratios': height_ratios, + 'width_ratios': width_ratios}) + + +@pytest.mark.parametrize('method,args', [ + ('subplots', (2, 3)), + ('subplot_mosaic', ('abc;def', )) + ] +) +def test_ratio_overlapping_kws(method, args): + with pytest.raises(ValueError, match='height_ratios'): + getattr(plt, method)(*args, height_ratios=[1, 2], + gridspec_kw={'height_ratios': [1, 2]}) + with pytest.raises(ValueError, match='width_ratios'): + getattr(plt, method)(*args, width_ratios=[1, 2, 3], + gridspec_kw={'width_ratios': [1, 2, 3]}) + + +def test_old_subplot_compat(): + fig = plt.figure() + assert isinstance(fig.add_subplot(), SubplotBase) + assert not isinstance(fig.add_axes(rect=[0, 0, 1, 1]), SubplotBase) + with pytest.raises(TypeError): + Axes(fig, [0, 0, 1, 1], rect=[0, 0, 1, 1]) diff --git a/lib/matplotlib/tests/test_table.py b/lib/matplotlib/tests/test_table.py index 5a64f9b4fda5..43b8702737a6 100644 --- a/lib/matplotlib/tests/test_table.py +++ b/lib/matplotlib/tests/test_table.py @@ -1,9 +1,14 @@ -import matplotlib.pyplot as plt +import datetime +from unittest.mock import Mock + import numpy as np -from matplotlib.testing.decorators import image_comparison -from matplotlib.table import CustomCell, Table +import matplotlib.pyplot as plt from matplotlib.path import Path +from matplotlib.table import CustomCell, Table +from matplotlib.testing.decorators import image_comparison, check_figures_equal +from matplotlib.transforms import Bbox +import matplotlib.units as munits def test_non_square(): @@ -50,7 +55,7 @@ def test_label_colours(): dim = 3 c = np.linspace(0, 1, dim) - colours = plt.cm.RdYlGn(c) + colours = plt.colormaps["RdYlGn"](c) cellText = [['1'] * dim] * dim fig = plt.figure() @@ -82,13 +87,13 @@ def test_label_colours(): loc='best') -@image_comparison(['table_cell_manipulation.png'], remove_text=True) -def test_diff_cell_table(): +@image_comparison(['table_cell_manipulation.png'], style='mpl20') +def test_diff_cell_table(text_placeholders): cells = ('horizontal', 'vertical', 'open', 'closed', 'T', 'R', 'B', 'L') cellText = [['1'] * len(cells)] * 2 colWidths = [0.1] * len(cells) - _, axs = plt.subplots(nrows=len(cells), figsize=(4, len(cells)+1)) + _, axs = plt.subplots(nrows=len(cells), figsize=(4, len(cells)+1), layout='tight') for ax, cell in zip(axs, cells): ax.table( colWidths=colWidths, @@ -97,7 +102,6 @@ def test_diff_cell_table(): edges=cell, ) ax.axis('off') - plt.tight_layout() def test_customcell(): @@ -121,10 +125,9 @@ def test_customcell(): @image_comparison(['table_auto_column.png']) def test_auto_column(): - fig = plt.figure() + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1) # iterable list input - ax1 = fig.add_subplot(4, 1, 1) ax1.axis('off') tb1 = ax1.table( cellText=[['Fit Text', 2], @@ -137,7 +140,6 @@ def test_auto_column(): tb1.auto_set_column_width([-1, 0, 1]) # iterable tuple input - ax2 = fig.add_subplot(4, 1, 2) ax2.axis('off') tb2 = ax2.table( cellText=[['Fit Text', 2], @@ -149,8 +151,7 @@ def test_auto_column(): tb2.set_fontsize(12) tb2.auto_set_column_width((-1, 0, 1)) - #3 single inputs - ax3 = fig.add_subplot(4, 1, 3) + # 3 single inputs ax3.axis('off') tb3 = ax3.table( cellText=[['Fit Text', 2], @@ -164,8 +165,8 @@ def test_auto_column(): tb3.auto_set_column_width(0) tb3.auto_set_column_width(1) - #4 non integer iterable input - ax4 = fig.add_subplot(4, 1, 4) + # 4 this used to test non-integer iterable input, which did nothing, but only + # remains to avoid re-generating the test image. ax4.axis('off') tb4 = ax4.table( cellText=[['Fit Text', 2], @@ -175,7 +176,6 @@ def test_auto_column(): loc="center") tb4.auto_set_font_size(False) tb4.set_fontsize(12) - tb4.auto_set_column_width("-101") def test_table_cells(): @@ -190,7 +190,94 @@ def test_table_cells(): table[2, 1] = cell2 assert table[2, 1] is cell2 - # make sure gettitem support has not broken + # make sure getitem support has not broken # properties and setp table.properties() plt.setp(table) + + +@check_figures_equal() +def test_table_bbox(fig_test, fig_ref): + data = [[2, 3], + [4, 5]] + + col_labels = ('Foo', 'Bar') + row_labels = ('Ada', 'Bob') + + cell_text = [[f"{x}" for x in row] for row in data] + + ax_list = fig_test.subplots() + ax_list.table(cellText=cell_text, + rowLabels=row_labels, + colLabels=col_labels, + loc='center', + bbox=[0.1, 0.2, 0.8, 0.6] + ) + + ax_bbox = fig_ref.subplots() + ax_bbox.table(cellText=cell_text, + rowLabels=row_labels, + colLabels=col_labels, + loc='center', + bbox=Bbox.from_extents(0.1, 0.2, 0.9, 0.8) + ) + + +@check_figures_equal() +def test_table_unit(fig_test, fig_ref): + # test that table doesn't participate in unit machinery, instead uses repr/str + + class FakeUnit: + def __init__(self, thing): + pass + def __repr__(self): + return "Hello" + + fake_convertor = munits.ConversionInterface() + # v, u, a = value, unit, axis + fake_convertor.convert = Mock(side_effect=lambda v, u, a: 0) + # not used, here for completeness + fake_convertor.default_units = Mock(side_effect=lambda v, a: None) + fake_convertor.axisinfo = Mock(side_effect=lambda u, a: munits.AxisInfo()) + + munits.registry[FakeUnit] = fake_convertor + + data = [[FakeUnit("yellow"), FakeUnit(42)], + [FakeUnit(datetime.datetime(1968, 8, 1)), FakeUnit(True)]] + + fig_test.subplots().table(data) + fig_ref.subplots().table([["Hello", "Hello"], ["Hello", "Hello"]]) + fig_test.canvas.draw() + fake_convertor.convert.assert_not_called() + + munits.registry.pop(FakeUnit) + assert not munits.registry.get_converter(FakeUnit) + + +def test_table_dataframe(pd): + # Test if Pandas Data Frame can be passed in cellText + + data = { + 'Letter': ['A', 'B', 'C'], + 'Number': [100, 200, 300] + } + + df = pd.DataFrame(data) + fig, ax = plt.subplots() + table = ax.table(df, loc='center') + + for r, (index, row) in enumerate(df.iterrows()): + for c, col in enumerate(df.columns if r == 0 else row.values): + assert table[r if r == 0 else r+1, c].get_text().get_text() == str(col) + + +def test_table_fontsize(): + # Test that the passed fontsize propagates to cells + tableData = [['a', 1], ['b', 2]] + fig, ax = plt.subplots() + test_fontsize = 20 + t = ax.table(cellText=tableData, loc='top', fontsize=test_fontsize) + cell_fontsize = t[(0, 0)].get_fontsize() + assert cell_fontsize == test_fontsize, f"Actual:{test_fontsize},got:{cell_fontsize}" + cell_fontsize = t[(1, 1)].get_fontsize() + assert cell_fontsize == test_fontsize, f"Actual:{test_fontsize},got:{cell_fontsize}" diff --git a/lib/matplotlib/tests/test_testing.py b/lib/matplotlib/tests/test_testing.py index f779d74c1007..c438c54d26fa 100644 --- a/lib/matplotlib/tests/test_testing.py +++ b/lib/matplotlib/tests/test_testing.py @@ -1,5 +1,8 @@ import warnings + import pytest + +import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal @@ -11,7 +14,7 @@ def test_warn_to_fail(): @pytest.mark.parametrize("a", [1]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() @pytest.mark.parametrize("b", [1]) def test_parametrize_with_check_figure_equal(a, fig_ref, b, fig_test): assert a == b @@ -22,3 +25,17 @@ def test_wrap_failure(): @check_figures_equal() def should_fail(test, ref): pass + + +@pytest.mark.xfail(raises=RuntimeError, strict=True, + reason='Test for check_figures_equal test creating ' + 'new figures') +@check_figures_equal() +def test_check_figures_equal_extra_fig(fig_test, fig_ref): + plt.figure() + + +@check_figures_equal() +def test_check_figures_equal_closed_fig(fig_test, fig_ref): + fig = plt.figure() + plt.close(fig) diff --git a/lib/matplotlib/tests/test_texmanager.py b/lib/matplotlib/tests/test_texmanager.py index 170a8362e287..64dcbf46456d 100644 --- a/lib/matplotlib/tests/test_texmanager.py +++ b/lib/matplotlib/tests/test_texmanager.py @@ -1,18 +1,75 @@ +import os +from pathlib import Path +import re +import sys + +import pytest + import matplotlib.pyplot as plt +from matplotlib.testing import subprocess_run_for_testing +from matplotlib.testing._markers import needs_usetex from matplotlib.texmanager import TexManager def test_fontconfig_preamble(): - """ - Test that the preamble is included in _fontconfig - """ + """Test that the preamble is included in the source.""" plt.rcParams['text.usetex'] = True - tm1 = TexManager() - font_config1 = tm1.get_font_config() - + src1 = TexManager()._get_tex_source("", fontsize=12) plt.rcParams['text.latex.preamble'] = '\\usepackage{txfonts}' - tm2 = TexManager() - font_config2 = tm2.get_font_config() + src2 = TexManager()._get_tex_source("", fontsize=12) + + assert src1 != src2 + + +@pytest.mark.parametrize( + "rc, preamble, family", [ + ({"font.family": "sans-serif", "font.sans-serif": "helvetica"}, + r"\usepackage{helvet}", r"\sffamily"), + ({"font.family": "serif", "font.serif": "palatino"}, + r"\usepackage{mathpazo}", r"\rmfamily"), + ({"font.family": "cursive", "font.cursive": "zapf chancery"}, + r"\usepackage{chancery}", r"\rmfamily"), + ({"font.family": "monospace", "font.monospace": "courier"}, + r"\usepackage{courier}", r"\ttfamily"), + ({"font.family": "helvetica"}, r"\usepackage{helvet}", r"\sffamily"), + ({"font.family": "palatino"}, r"\usepackage{mathpazo}", r"\rmfamily"), + ({"font.family": "zapf chancery"}, + r"\usepackage{chancery}", r"\rmfamily"), + ({"font.family": "courier"}, r"\usepackage{courier}", r"\ttfamily") + ]) +def test_font_selection(rc, preamble, family): + plt.rcParams.update(rc) + tm = TexManager() + src = Path(tm.make_tex("hello, world", fontsize=12)).read_text() + assert preamble in src + assert [*re.findall(r"\\\w+family", src)] == [family] + + +@needs_usetex +def test_unicode_characters(): + # Smoke test to see that Unicode characters does not cause issues + # See #23019 + plt.rcParams['text.usetex'] = True + fig, ax = plt.subplots() + ax.set_ylabel('\\textit{Velocity (\N{DEGREE SIGN}/sec)}') + ax.set_xlabel('\N{VULGAR FRACTION ONE QUARTER}Öøæ') + fig.canvas.draw() + + # But not all characters. + # Should raise RuntimeError, not UnicodeDecodeError + with pytest.raises(RuntimeError): + ax.set_title('\N{SNOWMAN}') + fig.canvas.draw() + - assert font_config1 != font_config2 +@needs_usetex +def test_openin_any_paranoid(): + completed = subprocess_run_for_testing( + [sys.executable, "-c", + 'import matplotlib.pyplot as plt;' + 'plt.rcParams.update({"text.usetex": True});' + 'plt.title("paranoid");' + 'plt.show(block=False);'], + env={**os.environ, 'openin_any': 'p'}, check=True, capture_output=True) + assert completed.stderr == "" diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index c79cf4c1422a..407d7a96be4d 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -4,19 +4,24 @@ import numpy as np from numpy.testing import assert_almost_equal +from packaging.version import parse as parse_version +import pyparsing import pytest import matplotlib as mpl from matplotlib.backend_bases import MouseEvent +from matplotlib.backends.backend_agg import RendererAgg +from matplotlib.figure import Figure +from matplotlib.font_manager import FontProperties import matplotlib.patches as mpatches import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec import matplotlib.transforms as mtransforms from matplotlib.testing.decorators import check_figures_equal, image_comparison +from matplotlib.testing._markers import needs_usetex +from matplotlib.text import Text, Annotation, OffsetFrom - -needs_usetex = pytest.mark.skipif( - not mpl.checkdep_usetex(True), - reason="This test needs a TeX installation") +pyparsing_version = parse_version(pyparsing.__version__) @image_comparison(['font_styles']) @@ -34,21 +39,25 @@ def find_matplotlib_font(**kw): UserWarning, module='matplotlib.font_manager') - plt.figure() - ax = plt.subplot(1, 1, 1) + fig, ax = plt.subplots() - normalFont = find_matplotlib_font( + normal_font = find_matplotlib_font( family="sans-serif", style="normal", variant="normal", size=14) - ax.annotate( + a = ax.annotate( "Normal Font", (0.1, 0.1), xycoords='axes fraction', - fontproperties=normalFont) - - boldFont = find_matplotlib_font( + fontproperties=normal_font) + assert a.get_fontname() == 'DejaVu Sans' + assert a.get_fontstyle() == 'normal' + assert a.get_fontvariant() == 'normal' + assert a.get_weight() == 'normal' + assert a.get_stretch() == 'normal' + + bold_font = find_matplotlib_font( family="Foo", style="normal", variant="normal", @@ -59,9 +68,9 @@ def find_matplotlib_font(**kw): "Bold Font", (0.1, 0.2), xycoords='axes fraction', - fontproperties=boldFont) + fontproperties=bold_font) - boldItemFont = find_matplotlib_font( + bold_italic_font = find_matplotlib_font( family="sans serif", style="italic", variant="normal", @@ -72,9 +81,9 @@ def find_matplotlib_font(**kw): "Bold Italic Font", (0.1, 0.3), xycoords='axes fraction', - fontproperties=boldItemFont) + fontproperties=bold_italic_font) - lightFont = find_matplotlib_font( + light_font = find_matplotlib_font( family="sans-serif", style="normal", variant="normal", @@ -85,9 +94,9 @@ def find_matplotlib_font(**kw): "Light Font", (0.1, 0.4), xycoords='axes fraction', - fontproperties=lightFont) + fontproperties=light_font) - condensedFont = find_matplotlib_font( + condensed_font = find_matplotlib_font( family="sans-serif", style="normal", variant="normal", @@ -98,7 +107,7 @@ def find_matplotlib_font(**kw): "Condensed Font", (0.1, 0.5), xycoords='axes fraction', - fontproperties=condensedFont) + fontproperties=condensed_font) ax.set_xticks([]) ax.set_yticks([]) @@ -180,26 +189,23 @@ def draw_box(ax, tt): ax.text(1.2, 0.1, 'Bot align, rot20', color='C2') -@image_comparison(['antialiased.png']) +@image_comparison(['antialiased.png'], style='mpl20') def test_antialiasing(): - mpl.rcParams['text.antialiased'] = True + mpl.rcParams['text.antialiased'] = False # Passed arguments should override. fig = plt.figure(figsize=(5.25, 0.75)) - fig.text(0.5, 0.75, "antialiased", horizontalalignment='center', - verticalalignment='center') - fig.text(0.5, 0.25, r"$\sqrt{x}$", horizontalalignment='center', - verticalalignment='center') - # NOTE: We don't need to restore the rcParams here, because the - # test cleanup will do it for us. In fact, if we do it here, it - # will turn antialiasing back off before the images are actually - # rendered. + fig.text(0.3, 0.75, "antialiased", horizontalalignment='center', + verticalalignment='center', antialiased=True) + fig.text(0.3, 0.25, r"$\sqrt{x}$", horizontalalignment='center', + verticalalignment='center', antialiased=True) + mpl.rcParams['text.antialiased'] = True # Passed arguments should override. + fig.text(0.7, 0.75, "not antialiased", horizontalalignment='center', + verticalalignment='center', antialiased=False) + fig.text(0.7, 0.25, r"$\sqrt{x}$", horizontalalignment='center', + verticalalignment='center', antialiased=False) -def test_afm_kerning(): - fn = mpl.font_manager.findfont("Helvetica", fontext="afm") - with open(fn, 'rb') as fh: - afm = mpl.afm.AFM(fh) - assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718) + mpl.rcParams['text.antialiased'] = False # Should not affect existing text. @image_comparison(['text_contains.png']) @@ -239,12 +245,27 @@ def test_annotation_contains(): fig, ax = plt.subplots() ann = ax.annotate( "hello", xy=(.4, .4), xytext=(.6, .6), arrowprops={"arrowstyle": "->"}) - fig.canvas.draw() # Needed for the same reason as in test_contains. + fig.canvas.draw() # Needed for the same reason as in test_contains. event = MouseEvent( "button_press_event", fig.canvas, *ax.transData.transform((.5, .6))) assert ann.contains(event) == (False, {}) +@pytest.mark.parametrize('err, xycoords, match', ( + (TypeError, print, "xycoords callable must return a BboxBase or Transform, not a"), + (TypeError, [0, 0], r"'xycoords' must be an instance of str, tuple"), + (ValueError, "foo", "'foo' is not a valid coordinate"), + (ValueError, "foo bar", "'foo bar' is not a valid coordinate"), + (ValueError, "offset foo", "xycoords cannot be an offset coordinate"), + (ValueError, "axes foo", "'foo' is not a recognized unit"), +)) +def test_annotate_errors(err, xycoords, match): + fig, ax = plt.subplots() + with pytest.raises(err, match=match): + ax.annotate('xy', (0, 0), xytext=(0.5, 0.5), xycoords=xycoords) + fig.canvas.draw() + + @image_comparison(['titles']) def test_titles(): # left and right side titles @@ -274,8 +295,8 @@ def test_alignment(): ax.plot([0, 1], [0.5, 0.5]) ax.plot([0, 1], [1.0, 1.0]) - ax.set_xlim([0, 1]) - ax.set_ylim([0, 1.5]) + ax.set_xlim(0, 1) + ax.set_ylim(0, 1.5) ax.set_xticks([]) ax.set_yticks([]) @@ -322,6 +343,32 @@ def test_set_position(): assert a + shift_val == b +def test_char_index_at(): + fig = plt.figure() + text = fig.text(0.1, 0.9, "") + + text.set_text("i") + bbox = text.get_window_extent() + size_i = bbox.x1 - bbox.x0 + + text.set_text("m") + bbox = text.get_window_extent() + size_m = bbox.x1 - bbox.x0 + + text.set_text("iiiimmmm") + bbox = text.get_window_extent() + origin = bbox.x0 + + assert text._char_index_at(origin - size_i) == 0 # left of first char + assert text._char_index_at(origin) == 0 + assert text._char_index_at(origin + 0.499*size_i) == 0 + assert text._char_index_at(origin + 0.501*size_i) == 1 + assert text._char_index_at(origin + size_i*3) == 3 + assert text._char_index_at(origin + size_i*4 + size_m*3) == 7 + assert text._char_index_at(origin + size_i*4 + size_m*4) == 8 + assert text._char_index_at(origin + size_i*4 + size_m*10) == 8 + + @pytest.mark.parametrize('text', ['', 'O'], ids=['empty', 'non-empty']) def test_non_default_dpi(text): fig, ax = plt.subplots() @@ -339,33 +386,32 @@ def test_non_default_dpi(text): def test_get_rotation_string(): - assert mpl.text.get_rotation('horizontal') == 0. - assert mpl.text.get_rotation('vertical') == 90. - assert mpl.text.get_rotation('15.') == 15. + assert Text(rotation='horizontal').get_rotation() == 0. + assert Text(rotation='vertical').get_rotation() == 90. def test_get_rotation_float(): for i in [15., 16.70, 77.4]: - assert mpl.text.get_rotation(i) == i + assert Text(rotation=i).get_rotation() == i def test_get_rotation_int(): for i in [67, 16, 41]: - assert mpl.text.get_rotation(i) == float(i) + assert Text(rotation=i).get_rotation() == float(i) def test_get_rotation_raises(): with pytest.raises(ValueError): - mpl.text.get_rotation('hozirontal') + Text(rotation='hozirontal') def test_get_rotation_none(): - assert mpl.text.get_rotation(None) == 0.0 + assert Text(rotation=None).get_rotation() == 0.0 def test_get_rotation_mod360(): for i, j in zip([360., 377., 720+177.2], [0., 17., 177.2]): - assert_almost_equal(mpl.text.get_rotation(i), j) + assert_almost_equal(Text(rotation=i).get_rotation(), j) @pytest.mark.parametrize("ha", ["center", "right", "left"]) @@ -494,7 +540,7 @@ def test_font_scaling(): ax.set_ylim(-10, 600) for i, fs in enumerate(range(4, 43, 2)): - ax.text(0.1, i*30, "{fs} pt font size".format(fs=fs), fontsize=fs) + ax.text(0.1, i*30, f"{fs} pt font size", fontsize=fs) @pytest.mark.parametrize('spacing1, spacing2', [(0.4, 2), (2, 0.4), (2, 2)]) @@ -503,8 +549,8 @@ def test_two_2line_texts(spacing1, spacing2): fig = plt.figure() renderer = fig.canvas.get_renderer() - text1 = plt.text(0.25, 0.5, text_string, linespacing=spacing1) - text2 = plt.text(0.25, 0.5, text_string, linespacing=spacing2) + text1 = fig.text(0.25, 0.5, text_string, linespacing=spacing1) + text2 = fig.text(0.25, 0.5, text_string, linespacing=spacing2) fig.canvas.draw() box1 = text1.get_window_extent(renderer=renderer) @@ -518,6 +564,11 @@ def test_two_2line_texts(spacing1, spacing2): assert box1.height != box2.height +def test_validate_linespacing(): + with pytest.raises(TypeError): + plt.text(.25, .5, "foo", linespacing="abc") + + def test_nonfinite_pos(): fig, ax = plt.subplots() ax.text(0, np.nan, 'nan') @@ -599,8 +650,7 @@ def test_text_as_text_opacity(): def test_text_repr(): # smoketest to make sure text repr doesn't error for category plt.plot(['A', 'B'], [1, 2]) - txt = plt.text(['A'], 0.5, 'Boo') - print(txt) + repr(plt.text(['A'], 0.5, 'Boo')) def test_annotation_update(): @@ -614,7 +664,7 @@ def test_annotation_update(): rtol=1e-6) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_annotation_units(fig_test, fig_ref): ax = fig_test.add_subplot() ax.plot(datetime.now(), 1, "o") # Implicitly set axes extents. @@ -636,18 +686,28 @@ def test_large_subscript_title(): ax = axs[0] ax.set_title(r'$\sum_{i} x_i$') ax.set_title('New way', loc='left') - ax.set_xticklabels('') + ax.set_xticklabels([]) ax = axs[1] - tt = ax.set_title(r'$\sum_{i} x_i$', y=1.01) + ax.set_title(r'$\sum_{i} x_i$', y=1.01) ax.set_title('Old Way', loc='left') - ax.set_xticklabels('') - - -def test_wrap(): - fig = plt.figure(figsize=(6, 4)) + ax.set_xticklabels([]) + + +@pytest.mark.parametrize( + "x, rotation, halign", + [(0.7, 0, 'left'), + (0.5, 95, 'left'), + (0.3, 0, 'right'), + (0.3, 185, 'left')]) +def test_wrap(x, rotation, halign): + fig = plt.figure(figsize=(18, 18)) + gs = GridSpec(nrows=3, ncols=3, figure=fig) + subfig = fig.add_subfigure(gs[1, 1]) + # we only use the central subfigure, which does not align with any + # figure boundary, to ensure only subfigure boundaries are relevant s = 'This is a very long text that should be wrapped multiple times.' - text = fig.text(0.7, 0.5, s, wrap=True) + text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign) fig.canvas.draw() assert text._get_wrapped_text() == ('This is a very long\n' 'text that should be\n' @@ -655,6 +715,31 @@ def test_wrap(): 'times.') +def test_mathwrap(): + fig = plt.figure(figsize=(6, 4)) + s = r'This is a very $\overline{\mathrm{long}}$ line of Mathtext.' + text = fig.text(0, 0.5, s, size=40, wrap=True) + fig.canvas.draw() + assert text._get_wrapped_text() == ('This is a very $\\overline{\\mathrm{long}}$\n' + 'line of Mathtext.') + + +def test_get_window_extent_wrapped(): + # Test that a long title that wraps to two lines has the same vertical + # extent as an explicit two line title. + + fig1 = plt.figure(figsize=(3, 3)) + fig1.suptitle("suptitle that is clearly too long in this case", wrap=True) + window_extent_test = fig1._suptitle.get_window_extent() + + fig2 = plt.figure(figsize=(3, 3)) + fig2.suptitle("suptitle that is clearly\ntoo long in this case") + window_extent_ref = fig2._suptitle.get_window_extent() + + assert window_extent_test.y0 == window_extent_ref.y0 + assert window_extent_test.y1 == window_extent_ref.y1 + + def test_long_word_wrap(): fig = plt.figure(figsize=(6, 4)) text = fig.text(9.5, 8, 'Alonglineoftexttowrap', wrap=True) @@ -669,7 +754,7 @@ def test_wrap_no_wrap(): assert text._get_wrapped_text() == 'non wrapped text' -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_buffer_size(fig_test, fig_ref): # On old versions of the Agg renderer, large non-ascii single-character # strings (here, "€") would be rendered clipped because the rendering @@ -699,3 +784,402 @@ def test_transform_rotates_text(): transform_rotates_text=True) result = text.get_rotation() assert_almost_equal(result, 30) + + +def test_update_mutate_input(): + inp = dict(fontproperties=FontProperties(weight="bold"), + bbox=None) + cache = dict(inp) + t = Text() + t.update(inp) + assert inp['fontproperties'] == cache['fontproperties'] + assert inp['bbox'] == cache['bbox'] + + +@pytest.mark.parametrize('rotation', ['invalid string', [90]]) +def test_invalid_rotation_values(rotation): + with pytest.raises( + ValueError, + match=("rotation must be 'vertical', 'horizontal' or a number")): + Text(0, 0, 'foo', rotation=rotation) + + +def test_invalid_color(): + with pytest.raises(ValueError): + plt.figtext(.5, .5, "foo", c="foobar") + + +@image_comparison(['text_pdf_kerning.pdf'], style='mpl20') +def test_pdf_kerning(): + plt.figure() + plt.figtext(0.1, 0.5, "ATATATATATATATATATA", size=30) + + +def test_unsupported_script(recwarn): + fig = plt.figure() + t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") + fig.canvas.draw() + assert all(isinstance(warn.message, UserWarning) for warn in recwarn) + assert ( + [warn.message.args for warn in recwarn] == + [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) " + + f"{t.get_fontname()}.",), + (r"Matplotlib currently does not support Bengali natively.",)]) + + +# See gh-26152 for more information on this xfail +@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0), + reason="Error messages are incorrect with pyparsing 3.1.0") +def test_parse_math(): + fig, ax = plt.subplots() + ax.text(0, 0, r"$ \wrong{math} $", parse_math=False) + fig.canvas.draw() + + ax.text(0, 0, r"$ \wrong{math} $", parse_math=True) + with pytest.raises(ValueError, match='Unknown symbol'): + fig.canvas.draw() + + +# See gh-26152 for more information on this xfail +@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0), + reason="Error messages are incorrect with pyparsing 3.1.0") +def test_parse_math_rcparams(): + # Default is True + fig, ax = plt.subplots() + ax.text(0, 0, r"$ \wrong{math} $") + with pytest.raises(ValueError, match='Unknown symbol'): + fig.canvas.draw() + + # Setting rcParams to False + with mpl.rc_context({'text.parse_math': False}): + fig, ax = plt.subplots() + ax.text(0, 0, r"$ \wrong{math} $") + fig.canvas.draw() + + +@image_comparison(['text_pdf_font42_kerning.pdf'], style='mpl20') +def test_pdf_font42_kerning(): + plt.rcParams['pdf.fonttype'] = 42 + plt.figure() + plt.figtext(0.1, 0.5, "ATAVATAVATAVATAVATA", size=30) + + +@image_comparison(['text_pdf_chars_beyond_bmp.pdf'], style='mpl20') +def test_pdf_chars_beyond_bmp(): + plt.rcParams['pdf.fonttype'] = 42 + plt.rcParams['mathtext.fontset'] = 'stixsans' + plt.figure() + plt.figtext(0.1, 0.5, "Mass $m$ \U00010308", size=30) + + +@needs_usetex +def test_metrics_cache(): + mpl.text._get_text_metrics_with_cache_impl.cache_clear() + + fig = plt.figure() + fig.text(.3, .5, "foo\nbar") + fig.text(.3, .5, "foo\nbar", usetex=True) + fig.text(.5, .5, "foo\nbar", usetex=True) + fig.canvas.draw() + renderer = fig._get_renderer() + ys = {} # mapping of strings to where they were drawn in y with draw_tex. + + def call(*args, **kwargs): + renderer, x, y, s, *_ = args + ys.setdefault(s, set()).add(y) + + renderer.draw_tex = call + fig.canvas.draw() + assert [*ys] == ["foo", "bar"] + # Check that both TeX strings were drawn with the same y-position for both + # single-line substrings. Previously, there used to be an incorrect cache + # collision with the non-TeX string (drawn first here) whose metrics would + # get incorrectly reused by the first TeX string. + assert len(ys["foo"]) == len(ys["bar"]) == 1 + + info = mpl.text._get_text_metrics_with_cache_impl.cache_info() + # Every string gets a miss for the first layouting (extents), then a hit + # when drawing, but "foo\nbar" gets two hits as it's drawn twice. + assert info.hits > info.misses + + +def test_annotate_offset_fontsize(): + # Test that offset_fontsize parameter works and uses accurate values + fig, ax = plt.subplots() + text_coords = ['offset points', 'offset fontsize'] + # 10 points should be equal to 1 fontsize unit at fontsize=10 + xy_text = [(10, 10), (1, 1)] + anns = [ax.annotate('test', xy=(0.5, 0.5), + xytext=xy_text[i], + fontsize='10', + xycoords='data', + textcoords=text_coords[i]) for i in range(2)] + points_coords, fontsize_coords = (ann.get_window_extent() for ann in anns) + fig.canvas.draw() + assert str(points_coords) == str(fontsize_coords) + + +def test_get_set_antialiased(): + txt = Text(.5, .5, "foo\nbar") + assert txt._antialiased == mpl.rcParams['text.antialiased'] + assert txt.get_antialiased() == mpl.rcParams['text.antialiased'] + + txt.set_antialiased(True) + assert txt._antialiased is True + assert txt.get_antialiased() == txt._antialiased + + txt.set_antialiased(False) + assert txt._antialiased is False + assert txt.get_antialiased() == txt._antialiased + + +def test_annotation_antialiased(): + annot = Annotation("foo\nbar", (.5, .5), antialiased=True) + assert annot._antialiased is True + assert annot.get_antialiased() == annot._antialiased + + annot2 = Annotation("foo\nbar", (.5, .5), antialiased=False) + assert annot2._antialiased is False + assert annot2.get_antialiased() == annot2._antialiased + + annot3 = Annotation("foo\nbar", (.5, .5), antialiased=False) + annot3.set_antialiased(True) + assert annot3.get_antialiased() is True + assert annot3._antialiased is True + + annot4 = Annotation("foo\nbar", (.5, .5)) + assert annot4._antialiased == mpl.rcParams['text.antialiased'] + + +@check_figures_equal() +def test_annotate_and_offsetfrom_copy_input(fig_test, fig_ref): + # Both approaches place the text (10, 0) pixels away from the center of the line. + ax = fig_test.add_subplot() + l, = ax.plot([0, 2], [0, 2]) + of_xy = np.array([.5, .5]) + ax.annotate("foo", textcoords=OffsetFrom(l, of_xy), xytext=(10, 0), + xy=(0, 0)) # xy is unused. + of_xy[:] = 1 + ax = fig_ref.add_subplot() + l, = ax.plot([0, 2], [0, 2]) + an_xy = np.array([.5, .5]) + ax.annotate("foo", xy=an_xy, xycoords=l, xytext=(10, 0), textcoords="offset points") + an_xy[:] = 2 + + +@check_figures_equal(extensions=['png', 'pdf', 'svg']) +def test_text_antialiased_off_default_vs_manual(fig_test, fig_ref): + fig_test.text(0.5, 0.5, '6 inches x 2 inches', + antialiased=False) + + mpl.rcParams['text.antialiased'] = False + fig_ref.text(0.5, 0.5, '6 inches x 2 inches') + + +@check_figures_equal(extensions=['png', 'pdf', 'svg']) +def test_text_antialiased_on_default_vs_manual(fig_test, fig_ref): + fig_test.text(0.5, 0.5, '6 inches x 2 inches', antialiased=True) + + mpl.rcParams['text.antialiased'] = True + fig_ref.text(0.5, 0.5, '6 inches x 2 inches') + + +def test_text_annotation_get_window_extent(): + figure = Figure(dpi=100) + renderer = RendererAgg(200, 200, 100) + + # Only text annotation + annotation = Annotation('test', xy=(0, 0), xycoords='figure pixels') + annotation.set_figure(figure) + + text = Text(text='test', x=0, y=0) + text.set_figure(figure) + + bbox = annotation.get_window_extent(renderer=renderer) + + text_bbox = text.get_window_extent(renderer=renderer) + assert bbox.width == text_bbox.width + assert bbox.height == text_bbox.height + + _, _, d = renderer.get_text_width_height_descent( + 'text', annotation._fontproperties, ismath=False) + _, _, lp_d = renderer.get_text_width_height_descent( + 'lp', annotation._fontproperties, ismath=False) + below_line = max(d, lp_d) + + # These numbers are specific to the current implementation of Text + points = bbox.get_points() + assert points[0, 0] == 0.0 + assert points[1, 0] == text_bbox.width + assert points[0, 1] == -below_line + assert points[1, 1] == text_bbox.height - below_line + + +def test_text_with_arrow_annotation_get_window_extent(): + headwidth = 21 + fig, ax = plt.subplots(dpi=100) + txt = ax.text(s='test', x=0, y=0) + ann = ax.annotate( + 'test', + xy=(0.0, 50.0), + xytext=(50.0, 50.0), xycoords='figure pixels', + arrowprops={ + 'facecolor': 'black', 'width': 2, + 'headwidth': headwidth, 'shrink': 0.0}) + + plt.draw() + renderer = fig.canvas.renderer + # bounding box of text + text_bbox = txt.get_window_extent(renderer=renderer) + # bounding box of annotation (text + arrow) + bbox = ann.get_window_extent(renderer=renderer) + # bounding box of arrow + arrow_bbox = ann.arrow_patch.get_window_extent(renderer) + # bounding box of annotation text + ann_txt_bbox = Text.get_window_extent(ann) + + # make sure annotation width is 50 px wider than + # just the text + assert bbox.width == text_bbox.width + 50.0 + # make sure the annotation text bounding box is same size + # as the bounding box of the same string as a Text object + assert ann_txt_bbox.height == text_bbox.height + assert ann_txt_bbox.width == text_bbox.width + # compute the expected bounding box of arrow + text + expected_bbox = mtransforms.Bbox.union([ann_txt_bbox, arrow_bbox]) + assert_almost_equal(bbox.height, expected_bbox.height) + + +def test_arrow_annotation_get_window_extent(): + dpi = 100 + dots_per_point = dpi / 72 + figure = Figure(dpi=dpi) + figure.set_figwidth(2.0) + figure.set_figheight(2.0) + renderer = RendererAgg(200, 200, 100) + + # Text annotation with arrow; arrow dimensions are in points + annotation = Annotation( + '', xy=(0.0, 50.0), xytext=(50.0, 50.0), xycoords='figure pixels', + arrowprops={ + 'facecolor': 'black', 'width': 8, 'headwidth': 10, 'shrink': 0.0}) + annotation.set_figure(figure) + annotation.draw(renderer) + + bbox = annotation.get_window_extent() + points = bbox.get_points() + + assert bbox.width == 50.0 + assert_almost_equal(bbox.height, 10.0 * dots_per_point) + assert points[0, 0] == 0.0 + assert points[0, 1] == 50.0 - 5 * dots_per_point + + +def test_empty_annotation_get_window_extent(): + figure = Figure(dpi=100) + figure.set_figwidth(2.0) + figure.set_figheight(2.0) + renderer = RendererAgg(200, 200, 100) + + # Text annotation with arrow + annotation = Annotation( + '', xy=(0.0, 50.0), xytext=(0.0, 50.0), xycoords='figure pixels') + annotation.set_figure(figure) + annotation.draw(renderer) + + bbox = annotation.get_window_extent() + points = bbox.get_points() + + assert points[0, 0] == 0.0 + assert points[1, 0] == 0.0 + assert points[1, 1] == 50.0 + assert points[0, 1] == 50.0 + + +@image_comparison(baseline_images=['basictext_wrap'], + extensions=['png']) +def test_basic_wrap(): + fig = plt.figure() + plt.axis([0, 10, 0, 10]) + t = "This is a really long string that I'd rather have wrapped so that" \ + " it doesn't go outside of the figure, but if it's long enough it" \ + " will go off the top or bottom!" + plt.text(4, 1, t, ha='left', rotation=15, wrap=True) + plt.text(6, 5, t, ha='left', rotation=15, wrap=True) + plt.text(5, 5, t, ha='right', rotation=-15, wrap=True) + plt.text(5, 10, t, fontsize=18, style='oblique', ha='center', + va='top', wrap=True) + plt.text(3, 4, t, family='serif', style='italic', ha='right', wrap=True) + plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True) + + +@image_comparison(baseline_images=['fonttext_wrap'], + extensions=['png']) +def test_font_wrap(): + fig = plt.figure() + plt.axis([0, 10, 0, 10]) + t = "This is a really long string that I'd rather have wrapped so that" \ + " it doesn't go outside of the figure, but if it's long enough it" \ + " will go off the top or bottom!" + plt.text(4, -1, t, fontsize=18, family='serif', ha='left', rotation=15, + wrap=True) + plt.text(6, 5, t, family='sans serif', ha='left', rotation=15, wrap=True) + plt.text(5, 10, t, weight='heavy', ha='center', va='top', wrap=True) + plt.text(3, 4, t, family='monospace', ha='right', wrap=True) + plt.text(-1, 0, t, fontsize=14, style='italic', ha='left', rotation=-15, + wrap=True) + + +def test_ha_for_angle(): + text_instance = Text() + angles = np.arange(0, 360.1, 0.1) + for angle in angles: + alignment = text_instance._ha_for_angle(angle) + assert alignment in ['center', 'left', 'right'] + + +def test_va_for_angle(): + text_instance = Text() + angles = np.arange(0, 360.1, 0.1) + for angle in angles: + alignment = text_instance._va_for_angle(angle) + assert alignment in ['center', 'top', 'baseline'] + + +@image_comparison(baseline_images=['xtick_rotation_mode'], + remove_text=False, extensions=['png'], style='mpl20') +def test_xtick_rotation_mode(): + fig, ax = plt.subplots(figsize=(12, 1)) + ax.set_yticks([]) + ax2 = ax.twiny() + + ax.set_xticks(range(37), ['foo'] * 37, rotation_mode="xtick") + ax2.set_xticks(range(37), ['foo'] * 37, rotation_mode="xtick") + + angles = np.linspace(0, 360, 37) + + for tick, angle in zip(ax.get_xticklabels(), angles): + tick.set_rotation(angle) + for tick, angle in zip(ax2.get_xticklabels(), angles): + tick.set_rotation(angle) + + plt.subplots_adjust(left=0.01, right=0.99, top=.6, bottom=.4) + + +@image_comparison(baseline_images=['ytick_rotation_mode'], + remove_text=False, extensions=['png'], style='mpl20') +def test_ytick_rotation_mode(): + fig, ax = plt.subplots(figsize=(1, 12)) + ax.set_xticks([]) + ax2 = ax.twinx() + + ax.set_yticks(range(37), ['foo'] * 37, rotation_mode="ytick") + ax2.set_yticks(range(37), ['foo'] * 37, rotation_mode='ytick') + + angles = np.linspace(0, 360, 37) + for tick, angle in zip(ax.get_yticklabels(), angles): + tick.set_rotation(angle) + for tick, angle in zip(ax2.get_yticklabels(), angles): + tick.set_rotation(angle) + + plt.subplots_adjust(left=0.4, right=0.6, top=.99, bottom=.01) diff --git a/lib/matplotlib/tests/test_textpath.py b/lib/matplotlib/tests/test_textpath.py new file mode 100644 index 000000000000..e421d2623cad --- /dev/null +++ b/lib/matplotlib/tests/test_textpath.py @@ -0,0 +1,10 @@ +import copy + +from matplotlib.textpath import TextPath + + +def test_copy(): + tp = TextPath((0, 0), ".") + assert copy.deepcopy(tp).vertices is not tp.vertices + assert (copy.deepcopy(tp).vertices == tp.vertices).all() + assert copy.copy(tp).vertices is tp.vertices diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 9c75678e14d3..0f54230663aa 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1,13 +1,15 @@ from contextlib import nullcontext -import re import itertools +import locale +import logging +import re +from packaging.version import parse as parse_version import numpy as np from numpy.testing import assert_almost_equal, assert_array_equal import pytest import matplotlib as mpl -from matplotlib import cbook import matplotlib.pyplot as plt import matplotlib.ticker as mticker @@ -37,6 +39,27 @@ def test_integer(self, vmin, vmax, steps, expected): loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps) assert_almost_equal(loc.tick_values(vmin, vmax), expected) + @pytest.mark.parametrize('kwargs, errortype, match', [ + ({'foo': 0}, TypeError, + re.escape("set_params() got an unexpected keyword argument 'foo'")), + ({'steps': [2, 1]}, ValueError, "steps argument must be an increasing"), + ({'steps': 2}, ValueError, "steps argument must be an increasing"), + ({'steps': [2, 11]}, ValueError, "steps argument must be an increasing"), + ]) + def test_errors(self, kwargs, errortype, match): + with pytest.raises(errortype, match=match): + mticker.MaxNLocator(**kwargs) + + @pytest.mark.parametrize('steps, result', [ + ([1, 2, 10], [1, 2, 10]), + ([2, 10], [1, 2, 10]), + ([1, 2], [1, 2, 10]), + ([2], [1, 2, 10]), + ]) + def test_padding(self, steps, result): + loc = mticker.MaxNLocator(steps=steps) + assert (loc._steps == result).all() + class TestLinearLocator: def test_basic(self): @@ -44,6 +67,10 @@ def test_basic(self): test_value = np.array([-0.8, -0.3, 0.2]) assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value) + def test_zero_numticks(self): + loc = mticker.LinearLocator(numticks=0) + loc.tick_values(-0.8, 0.2) == [] + def test_set_params(self): """ Create linear locator with presets={}, numticks=2 and change it to @@ -54,6 +81,15 @@ def test_set_params(self): assert loc.numticks == 8 assert loc.presets == {(0, 1): []} + def test_presets(self): + loc = mticker.LinearLocator(presets={(1, 2): [1, 1.25, 1.75], + (0, 2): [0.5, 1.5]}) + assert loc.tick_values(1, 2) == [1, 1.25, 1.75] + assert loc.tick_values(2, 1) == [1, 1.25, 1.75] + assert loc.tick_values(0, 2) == [0.5, 1.5] + assert loc.tick_values(0.0, 2.0) == [0.5, 1.5] + assert (loc.tick_values(0, 1) == np.linspace(0, 1, 11)).all() + class TestMultipleLocator: def test_basic(self): @@ -62,6 +98,12 @@ def test_basic(self): 9.441, 12.588]) assert_almost_equal(loc.tick_values(-7, 10), test_value) + def test_basic_with_offset(self): + loc = mticker.MultipleLocator(base=3.147, offset=1.2) + test_value = np.array([-8.241, -5.094, -1.947, 1.2, 4.347, 7.494, + 10.641]) + assert_almost_equal(loc.tick_values(-7, 10), test_value) + def test_view_limits(self): """ Test basic behavior of view limits. @@ -79,6 +121,23 @@ def test_view_limits_round_numbers(self): loc = mticker.MultipleLocator(base=3.147) assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294)) + def test_view_limits_round_numbers_with_offset(self): + """ + Test that everything works properly with 'round_numbers' for auto + limit. + """ + with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}): + loc = mticker.MultipleLocator(base=3.147, offset=1.3) + assert_almost_equal(loc.view_limits(-4, 4), (-4.994, 4.447)) + + def test_view_limits_single_bin(self): + """ + Test that 'round_numbers' works properly with a single bin. + """ + with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}): + loc = mticker.MaxNLocator(nbins=1) + assert_almost_equal(loc.view_limits(-2.3, 2.3), (-4, 4)) + def test_set_params(self): """ Create multiple locator with 0.7 base, and change it to something else. @@ -87,6 +146,8 @@ def test_set_params(self): mult = mticker.MultipleLocator(base=0.7) mult.set_params(base=1.7) assert mult._edge.step == 1.7 + mult.set_params(offset=3) + assert mult._offset == 3 class TestAutoMinorLocator: @@ -105,6 +166,25 @@ def test_basic(self): (1, 0) # a single major tick => no minor tick ] + def test_first_and_last_minorticks(self): + """ + Test that first and last minor tick appear as expected. + """ + # This test is related to issue #22331 + fig, ax = plt.subplots() + ax.set_xlim(-1.9, 1.9) + ax.xaxis.set_minor_locator(mticker.AutoMinorLocator()) + test_value = np.array([-1.9, -1.8, -1.7, -1.6, -1.4, -1.3, -1.2, -1.1, + -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1, + 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1, + 1.2, 1.3, 1.4, 1.6, 1.7, 1.8, 1.9]) + assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value) + + ax.set_xlim(-5, 5) + test_value = np.array([-5.0, -4.5, -3.5, -3.0, -2.5, -1.5, -1.0, -0.5, + 0.5, 1.0, 1.5, 2.5, 3.0, 3.5, 4.5, 5.0]) + assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value) + @pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params) def test_low_number_of_majorticks( self, nb_majorticks, expected_nb_minorticks): @@ -191,6 +271,60 @@ def test_additional(self, lim, ref): assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref) + @pytest.mark.parametrize('use_rcparam', [False, True]) + @pytest.mark.parametrize( + 'lim, ref', [ + ((0, 1.39), + [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7, + 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35]), + ((0, 0.139), + [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, + 0.065, 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, + 0.125, 0.13, 0.135]), + ]) + def test_number_of_minor_ticks_auto(self, lim, ref, use_rcparam): + if use_rcparam: + context = {'xtick.minor.ndivs': 'auto', 'ytick.minor.ndivs': 'auto'} + kwargs = {} + else: + context = {} + kwargs = {'n': 'auto'} + + with mpl.rc_context(context): + fig, ax = plt.subplots() + ax.set_xlim(*lim) + ax.set_ylim(*lim) + ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs)) + ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs)) + assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref) + assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref) + + @pytest.mark.parametrize('use_rcparam', [False, True]) + @pytest.mark.parametrize( + 'n, lim, ref', [ + (2, (0, 4), [0.5, 1.5, 2.5, 3.5]), + (4, (0, 2), [0.25, 0.5, 0.75, 1.25, 1.5, 1.75]), + (10, (0, 1), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]), + ]) + def test_number_of_minor_ticks_int(self, n, lim, ref, use_rcparam): + if use_rcparam: + context = {'xtick.minor.ndivs': n, 'ytick.minor.ndivs': n} + kwargs = {} + else: + context = {} + kwargs = {'n': n} + + with mpl.rc_context(context): + fig, ax = plt.subplots() + ax.set_xlim(*lim) + ax.set_ylim(*lim) + ax.xaxis.set_major_locator(mticker.MultipleLocator(1)) + ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs)) + ax.yaxis.set_major_locator(mticker.MultipleLocator(1)) + ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs)) + assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref) + assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref) + class TestLogLocator: def test_basic(self): @@ -198,15 +332,22 @@ def test_basic(self): with pytest.raises(ValueError): loc.tick_values(0, 1000) - test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01, - 1.00000000e+01, 1.00000000e+03, 1.00000000e+05, - 1.00000000e+07, 1.000000000e+09]) + test_value = np.array([1e-5, 1e-3, 1e-1, 1e+1, 1e+3, 1e+5, 1e+7]) assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value) loc = mticker.LogLocator(base=2) - test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.]) + test_value = np.array([.5, 1., 2., 4., 8., 16., 32., 64., 128.]) assert_almost_equal(loc.tick_values(1, 100), test_value) + def test_polar_axes(self): + """ + Polar Axes have a different ticking logic. + """ + fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) + ax.set_yscale('log') + ax.set_ylim(1, 100) + assert_array_equal(ax.get_yticks(), [10, 100, 1000]) + def test_switch_to_autolocator(self): loc = mticker.LogLocator(subs="all") assert_array_equal(loc.tick_values(0.45, 0.55), @@ -219,16 +360,47 @@ def test_switch_to_autolocator(self): def test_set_params(self): """ Create log locator with default value, base=10.0, subs=[1.0], - numdecs=4, numticks=15 and change it to something else. + numticks=15 and change it to something else. See if change was successful. Should not raise exception. """ loc = mticker.LogLocator() - loc.set_params(numticks=7, numdecs=8, subs=[2.0], base=4) + loc.set_params(numticks=7, subs=[2.0], base=4) assert loc.numticks == 7 - assert loc.numdecs == 8 assert loc._base == 4 assert list(loc._subs) == [2.0] + def test_tick_values_correct(self): + ll = mticker.LogLocator(subs=(1, 2, 5)) + test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00, + 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02, + 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04, + 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06, + 1.e+07, 2.e+07, 5.e+07]) + assert_almost_equal(ll.tick_values(1, 1e7), test_value) + + def test_tick_values_not_empty(self): + mpl.rcParams['_internal.classic_mode'] = False + ll = mticker.LogLocator(subs=(1, 2, 5)) + test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00, + 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02, + 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04, + 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06, + 1.e+07, 2.e+07, 5.e+07, 1.e+08, 2.e+08, 5.e+08]) + assert_almost_equal(ll.tick_values(1, 1e8), test_value) + + def test_multiple_shared_axes(self): + rng = np.random.default_rng(19680801) + dummy_data = [rng.normal(size=100), [], []] + fig, axes = plt.subplots(len(dummy_data), sharex=True, sharey=True) + + for ax, data in zip(axes.flatten(), dummy_data): + ax.hist(data, bins=10) + ax.set_yscale('log', nonpositive='clip') + + for ax in axes.flatten(): + assert all(ax.get_yticks() == axes[0].get_yticks()) + assert ax.get_ylim() == axes[0].get_ylim() + class TestNullLocator: def test_set_params(self): @@ -442,20 +614,136 @@ def test_set_params(self): assert sym._subs == [2.0] assert sym.numticks == 8 + @pytest.mark.parametrize( + 'vmin, vmax, expected', + [ + (0, 1, [0, 1]), + (-1, 1, [-1, 0, 1]), + ], + ) + def test_values(self, vmin, vmax, expected): + # https://github.com/matplotlib/matplotlib/issues/25945 + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1) + ticks = sym.tick_values(vmin=vmin, vmax=vmax) + assert_array_equal(ticks, expected) -class TestIndexFormatter: - @pytest.mark.parametrize('x, label', [(-2, ''), - (-1, 'label0'), - (0, 'label0'), - (0.5, 'label1'), - (1, 'label1'), - (1.5, 'label2'), - (2, 'label2'), - (2.5, '')]) - def test_formatting(self, x, label): - with cbook._suppress_matplotlib_deprecation_warning(): - formatter = mticker.IndexFormatter(['label0', 'label1', 'label2']) - assert formatter(x) == label + def test_subs(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1, subs=[2.0, 4.0]) + sym.create_dummy_axis() + sym.axis.set_view_interval(-10, 10) + assert_array_equal(sym(), [-20, -40, -2, -4, 0, 2, 4, 20, 40]) + + def test_extending(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1) + sym.create_dummy_axis() + sym.axis.set_view_interval(8, 9) + assert (sym() == [1.0]).all() + sym.axis.set_view_interval(8, 12) + assert (sym() == [1.0, 10.0]).all() + assert sym.view_limits(10, 10) == (1, 100) + assert sym.view_limits(-10, -10) == (-100, -1) + assert sym.view_limits(0, 0) == (-0.001, 0.001) + + +class TestAsinhLocator: + def test_init(self): + lctr = mticker.AsinhLocator(linear_width=2.718, numticks=19) + assert lctr.linear_width == 2.718 + assert lctr.numticks == 19 + assert lctr.base == 10 + + def test_set_params(self): + lctr = mticker.AsinhLocator(linear_width=5, + numticks=17, symthresh=0.125, + base=4, subs=(2.5, 3.25)) + assert lctr.numticks == 17 + assert lctr.symthresh == 0.125 + assert lctr.base == 4 + assert lctr.subs == (2.5, 3.25) + + lctr.set_params(numticks=23) + assert lctr.numticks == 23 + lctr.set_params(None) + assert lctr.numticks == 23 + + lctr.set_params(symthresh=0.5) + assert lctr.symthresh == 0.5 + lctr.set_params(symthresh=None) + assert lctr.symthresh == 0.5 + + lctr.set_params(base=7) + assert lctr.base == 7 + lctr.set_params(base=None) + assert lctr.base == 7 + + lctr.set_params(subs=(2, 4.125)) + assert lctr.subs == (2, 4.125) + lctr.set_params(subs=None) + assert lctr.subs == (2, 4.125) + lctr.set_params(subs=[]) + assert lctr.subs is None + + def test_linear_values(self): + lctr = mticker.AsinhLocator(linear_width=100, numticks=11, base=0) + + assert_almost_equal(lctr.tick_values(-1, 1), + np.arange(-1, 1.01, 0.2)) + assert_almost_equal(lctr.tick_values(-0.1, 0.1), + np.arange(-0.1, 0.101, 0.02)) + assert_almost_equal(lctr.tick_values(-0.01, 0.01), + np.arange(-0.01, 0.0101, 0.002)) + + def test_wide_values(self): + lctr = mticker.AsinhLocator(linear_width=0.1, numticks=11, base=0) + + assert_almost_equal(lctr.tick_values(-100, 100), + [-100, -20, -5, -1, -0.2, + 0, 0.2, 1, 5, 20, 100]) + assert_almost_equal(lctr.tick_values(-1000, 1000), + [-1000, -100, -20, -3, -0.4, + 0, 0.4, 3, 20, 100, 1000]) + + def test_near_zero(self): + """Check that manually injected zero will supersede nearby tick""" + lctr = mticker.AsinhLocator(linear_width=100, numticks=3, base=0) + + assert_almost_equal(lctr.tick_values(-1.1, 0.9), [-1.0, 0.0, 0.9]) + + def test_fallback(self): + lctr = mticker.AsinhLocator(1.0, numticks=11) + + assert_almost_equal(lctr.tick_values(101, 102), + np.arange(101, 102.01, 0.1)) + + def test_symmetrizing(self): + lctr = mticker.AsinhLocator(linear_width=1, numticks=3, + symthresh=0.25, base=0) + lctr.create_dummy_axis() + + lctr.axis.set_view_interval(-1, 2) + assert_almost_equal(lctr(), [-1, 0, 2]) + + lctr.axis.set_view_interval(-1, 0.9) + assert_almost_equal(lctr(), [-1, 0, 1]) + + lctr.axis.set_view_interval(-0.85, 1.05) + assert_almost_equal(lctr(), [-1, 0, 1]) + + lctr.axis.set_view_interval(1, 1.1) + assert_almost_equal(lctr(), [1, 1.05, 1.1]) + + def test_base_rounding(self): + lctr10 = mticker.AsinhLocator(linear_width=1, numticks=8, + base=10, subs=(1, 3, 5)) + assert_almost_equal(lctr10.tick_values(-110, 110), + [-500, -300, -100, -50, -30, -10, -5, -3, -1, + -0.5, -0.3, -0.1, 0, 0.1, 0.3, 0.5, + 1, 3, 5, 10, 30, 50, 100, 300, 500]) + + lctr5 = mticker.AsinhLocator(linear_width=1, numticks=20, base=5) + assert_almost_equal(lctr5.tick_values(-1050, 1050), + [-625, -125, -25, -5, -1, -0.2, 0, + 0.2, 1, 5, 25, 125, 625]) class TestScalarFormatter: @@ -494,6 +782,8 @@ class TestScalarFormatter: use_offset_data = [True, False] + useMathText_data = [True, False] + # (sci_type, scilimits, lim, orderOfMag, fewticks) scilimits_data = [ (False, (0, 0), (10.0, 20.0), 0, False), @@ -515,6 +805,13 @@ class TestScalarFormatter: [12.3, "12.300"], ] + format_data = [ + (.1, "1e-1"), + (.11, "1.1e-1"), + (1e8, "1e8"), + (1.1e8, "1.1e8"), + ] + @pytest.mark.parametrize('unicode_minus, result', [(True, "\N{MINUS SIGN}1"), (False, "-1")]) def test_unicode_minus(self, unicode_minus, result): @@ -526,18 +823,18 @@ def test_unicode_minus(self, unicode_minus, result): @pytest.mark.parametrize('left, right, offset', offset_data) def test_offset_value(self, left, right, offset): fig, ax = plt.subplots() - formatter = ax.get_xaxis().get_major_formatter() + formatter = ax.xaxis.get_major_formatter() with (pytest.warns(UserWarning, match='Attempting to set identical') if left == right else nullcontext()): ax.set_xlim(left, right) - ax.get_xaxis()._update_ticks() + ax.xaxis._update_ticks() assert formatter.offset == offset with (pytest.warns(UserWarning, match='Attempting to set identical') if left == right else nullcontext()): ax.set_xlim(right, left) - ax.get_xaxis()._update_ticks() + ax.xaxis._update_ticks() assert formatter.offset == offset @pytest.mark.parametrize('use_offset', use_offset_data) @@ -545,6 +842,50 @@ def test_use_offset(self, use_offset): with mpl.rc_context({'axes.formatter.useoffset': use_offset}): tmp_form = mticker.ScalarFormatter() assert use_offset == tmp_form.get_useOffset() + assert tmp_form.offset == 0 + + @pytest.mark.parametrize('use_math_text', useMathText_data) + def test_useMathText(self, use_math_text): + with mpl.rc_context({'axes.formatter.use_mathtext': use_math_text}): + tmp_form = mticker.ScalarFormatter() + assert use_math_text == tmp_form.get_useMathText() + + def test_set_use_offset_float(self): + tmp_form = mticker.ScalarFormatter() + tmp_form.set_useOffset(0.5) + assert not tmp_form.get_useOffset() + assert tmp_form.offset == 0.5 + + def test_set_use_offset_bool(self): + tmp_form = mticker.ScalarFormatter() + tmp_form.set_useOffset(True) + assert tmp_form.get_useOffset() + assert tmp_form.offset == 0 + + tmp_form.set_useOffset(False) + assert not tmp_form.get_useOffset() + assert tmp_form.offset == 0 + + def test_set_use_offset_int(self): + tmp_form = mticker.ScalarFormatter() + tmp_form.set_useOffset(1) + assert not tmp_form.get_useOffset() + assert tmp_form.offset == 1 + + def test_use_locale(self): + conv = locale.localeconv() + sep = conv['thousands_sep'] + if not sep or conv['grouping'][-1:] in ([], [locale.CHAR_MAX]): + pytest.skip('Locale does not apply grouping') # pragma: no cover + + with mpl.rc_context({'axes.formatter.use_locale': True}): + tmp_form = mticker.ScalarFormatter() + assert tmp_form.get_useLocale() + + tmp_form.create_dummy_axis() + tmp_form.axis.set_data_interval(0, 10) + tmp_form.set_locs([1, 2, 3]) + assert sep in tmp_form(1e9) @pytest.mark.parametrize( 'sci_type, scilimits, lim, orderOfMag, fewticks', scilimits_data) @@ -561,6 +902,12 @@ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks): tmp_form.set_locs(ax.yaxis.get_majorticklocs()) assert orderOfMag == tmp_form.orderOfMagnitude + @pytest.mark.parametrize('value, expected', format_data) + def test_format_data(self, value, expected): + mpl.rcParams['axes.unicode_minus'] = False + sf = mticker.ScalarFormatter() + assert sf.format_data(value) == expected + @pytest.mark.parametrize('data, expected', cursor_data) def test_cursor_precision(self, data, expected): fig, ax = plt.subplots() @@ -573,19 +920,53 @@ def test_cursor_dummy_axis(self, data, expected): # Issue #17624 sf = mticker.ScalarFormatter() sf.create_dummy_axis() - sf.set_bounds(0, 10) + sf.axis.set_view_interval(0, 10) fmt = sf.format_data_short assert fmt(data) == expected - - -class FakeAxis: - """Allow Formatter to be called without having a "full" plot set up.""" - def __init__(self, vmin=1, vmax=10): - self.vmin = vmin - self.vmax = vmax - - def get_view_interval(self): - return self.vmin, self.vmax + assert sf.axis.get_tick_space() == 9 + assert sf.axis.get_minpos() == 0 + + def test_mathtext_ticks(self): + mpl.rcParams.update({ + 'font.family': 'serif', + 'font.serif': 'cmr10', + 'axes.formatter.use_mathtext': False + }) + + if parse_version(pytest.__version__).major < 8: + with pytest.warns(UserWarning, match='cmr10 font should ideally'): + fig, ax = plt.subplots() + ax.set_xticks([-1, 0, 1]) + fig.canvas.draw() + else: + with (pytest.warns(UserWarning, match="Glyph 8722"), + pytest.warns(UserWarning, match='cmr10 font should ideally')): + fig, ax = plt.subplots() + ax.set_xticks([-1, 0, 1]) + fig.canvas.draw() + + def test_cmr10_substitutions(self, caplog): + mpl.rcParams.update({ + 'font.family': 'cmr10', + 'mathtext.fontset': 'cm', + 'axes.formatter.use_mathtext': True, + }) + + # Test that it does not log a warning about missing glyphs. + with caplog.at_level(logging.WARNING, logger='matplotlib.mathtext'): + fig, ax = plt.subplots() + ax.plot([-0.03, 0.05], [40, 0.05]) + ax.set_yscale('log') + yticks = [0.02, 0.3, 4, 50] + formatter = mticker.LogFormatterSciNotation() + ax.set_yticks(yticks, map(formatter, yticks)) + fig.canvas.draw() + assert not caplog.text + + def test_empty_locs(self): + sf = mticker.ScalarFormatter() + sf.set_locs([]) + assert sf(0.5) == '' class TestLogFormatterExponent: @@ -609,15 +990,18 @@ def test_basic(self, labelOnlyBase, base, exponent, locs, positions, expected): formatter = mticker.LogFormatterExponent(base=base, labelOnlyBase=labelOnlyBase) - formatter.axis = FakeAxis(1, base**exponent) + formatter.create_dummy_axis() + formatter.axis.set_view_interval(1, base**exponent) vals = base**locs labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)] + expected = [label.replace('-', '\N{Minus Sign}') for label in expected] assert labels == expected def test_blank(self): # Should be a blank string for non-integer powers if labelOnlyBase=True formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True) - formatter.axis = FakeAxis() + formatter.create_dummy_axis() + formatter.axis.set_view_interval(1, 10) assert formatter(10**0.1) == '' @@ -660,11 +1044,10 @@ class TestLogFormatterSciNotation: (10, 500000, '$\\mathdefault{5\\times10^{5}}$'), ] - @pytest.mark.style('default') + @mpl.style.context('default') @pytest.mark.parametrize('base, value, expected', test_data) def test_basic(self, base, value, expected): formatter = mticker.LogFormatterSciNotation(base=base) - formatter.sublabel = {1, 2, 5, 1.2} with mpl.rc_context({'text.usetex': False}): assert formatter(value) == expected @@ -811,6 +1194,20 @@ def test_pprint(self, value, domain, expected): label = fmt._pprint_val(value, domain) assert label == expected + @pytest.mark.parametrize('value, long, short', [ + (0.0, "0", "0"), + (0, "0", "0"), + (-1.0, "-10^0", "-1"), + (2e-10, "2x10^-10", "2e-10"), + (1e10, "10^10", "1e+10"), + ]) + def test_format_data(self, value, long, short): + fig, ax = plt.subplots() + ax.set_xscale('log') + fmt = ax.xaxis.get_major_formatter() + assert fmt.format_data(value) == long + assert fmt.format_data_short(value) == short + def _sub_labels(self, axis, subs=()): """Test whether locator marks subs to be labeled.""" fmt = axis.get_minor_formatter() @@ -821,7 +1218,7 @@ def _sub_labels(self, axis, subs=()): label_test = [fmt(x) != '' for x in minor_tlocs] assert label_test == label_expected - @pytest.mark.style('default') + @mpl.style.context('default') def test_sublabel(self): # test label locator fig, ax = plt.subplots() @@ -851,11 +1248,16 @@ def test_sublabel(self): ax.set_xlim(1, 80) self._sub_labels(ax.xaxis, subs=[]) - # axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6 + # axis range slightly more than 1 decade, but spanning a single major + # tick, label subs 2, 3, 4, 6 + ax.set_xlim(.8, 9) + self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6]) + + # axis range at 0.4 to 1 decade, label subs 2, 3, 4, 6 ax.set_xlim(1, 8) self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6]) - # axis range at 0 to 0.4 decades, label all + # axis range at 0 to 0.4 decade, label all ax.set_xlim(0.5, 0.9) self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int)) @@ -863,9 +1265,18 @@ def test_sublabel(self): def test_LogFormatter_call(self, val): # test _num_to_string method used in __call__ temp_lf = mticker.LogFormatter() - temp_lf.axis = FakeAxis() + temp_lf.create_dummy_axis() + temp_lf.axis.set_view_interval(1, 10) assert temp_lf(val) == str(val) + @pytest.mark.parametrize('val', [1e-323, 2e-323, 10e-323, 11e-323]) + def test_LogFormatter_call_tiny(self, val): + # test coeff computation in __call__ + temp_lf = mticker.LogFormatter() + temp_lf.create_dummy_axis() + temp_lf.axis.set_view_interval(1, 10) + temp_lf(val) + class TestLogitFormatter: @staticmethod @@ -1054,14 +1465,21 @@ def test_basic(self): class TestStrMethodFormatter: test_data = [ - ('{x:05d}', (2,), '00002'), - ('{x:03d}-{pos:02d}', (2, 1), '002-01'), + ('{x:05d}', (2,), False, '00002'), + ('{x:05d}', (2,), True, '00002'), + ('{x:05d}', (-2,), False, '-0002'), + ('{x:05d}', (-2,), True, '\N{MINUS SIGN}0002'), + ('{x:03d}-{pos:02d}', (2, 1), False, '002-01'), + ('{x:03d}-{pos:02d}', (2, 1), True, '002-01'), + ('{x:03d}-{pos:02d}', (-2, 1), False, '-02-01'), + ('{x:03d}-{pos:02d}', (-2, 1), True, '\N{MINUS SIGN}02-01'), ] - @pytest.mark.parametrize('format, input, expected', test_data) - def test_basic(self, format, input, expected): - fmt = mticker.StrMethodFormatter(format) - assert fmt(*input) == expected + @pytest.mark.parametrize('format, input, unicode_minus, expected', test_data) + def test_basic(self, format, input, unicode_minus, expected): + with mpl.rc_context({"axes.unicode_minus": unicode_minus}): + fmt = mticker.StrMethodFormatter(format) + assert fmt(*input) == expected class TestEngFormatter: @@ -1101,8 +1519,8 @@ class TestEngFormatter: (True, 1001, ('1.001 k', '1 k', '1.00 k')), (True, 100001, ('100.001 k', '100 k', '100.00 k')), (True, 987654.321, ('987.654 k', '988 k', '987.65 k')), - # OoR value (> 1000 Y) - (True, 1.23e27, ('1230 Y', '1230 Y', '1230.00 Y')) + # OoR value (> 1000 Q) + (True, 1.23e33, ('1230 Q', '1230 Q', '1230.00 Q')) ] @pytest.mark.parametrize('unicode_minus, input, expected', raw_format_data) @@ -1148,7 +1566,7 @@ def test_params(self, unicode_minus, input, expected): assert _formatter(input) == _exp_output # Test several non default separators: no separator, a narrow - # no-break space (unicode character) and an extravagant string. + # no-break space (Unicode character) and an extravagant string. for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"): # Case 2: unit=UNIT and sep=_sep. # Replace the default space separator from the reference case @@ -1191,6 +1609,73 @@ def test_engformatter_usetex_useMathText(): assert x_tick_label_text == ['$0$', '$500$', '$1$ k'] +@pytest.mark.parametrize( + 'data_offset, noise, oom_center_desired, oom_noise_desired', [ + (271_490_000_000.0, 10, 9, 0), + (27_149_000_000_000.0, 10_000_000, 12, 6), + (27.149, 0.01, 0, -3), + (2_714.9, 0.01, 3, -3), + (271_490.0, 0.001, 3, -3), + (271.49, 0.001, 0, -3), + # The following sets of parameters demonstrates that when + # oom(data_offset)-1 and oom(noise)-2 equal a standard 3*N oom, we get + # that oom_noise_desired < oom(noise) + (27_149_000_000.0, 100, 9, +3), + (27.149, 1e-07, 0, -6), + (271.49, 0.0001, 0, -3), + (27.149, 0.0001, 0, -3), + # Tests where oom(data_offset) <= oom(noise), those are probably + # covered by the part where formatter.offset != 0 + (27_149.0, 10_000, 0, 3), + (27.149, 10_000, 0, 3), + (27.149, 1_000, 0, 3), + (27.149, 100, 0, 0), + (27.149, 10, 0, 0), + ] +) +def test_engformatter_offset_oom( + data_offset, + noise, + oom_center_desired, + oom_noise_desired +): + UNIT = "eV" + fig, ax = plt.subplots() + ydata = data_offset + np.arange(-5, 7, dtype=float)*noise + ax.plot(ydata) + formatter = mticker.EngFormatter(useOffset=True, unit=UNIT) + # So that offset strings will always have the same size + formatter.ENG_PREFIXES[0] = "_" + ax.yaxis.set_major_formatter(formatter) + fig.canvas.draw() + offset_got = formatter.get_offset() + ticks_got = [labl.get_text() for labl in ax.get_yticklabels()] + # Predicting whether offset should be 0 or not is essentially testing + # ScalarFormatter._compute_offset . This function is pretty complex and it + # would be nice to test it, but this is out of scope for this test which + # only makes sure that offset text and the ticks gets the correct unit + # prefixes and the ticks. + if formatter.offset: + prefix_noise_got = offset_got[2] + prefix_noise_desired = formatter.ENG_PREFIXES[oom_noise_desired] + prefix_center_got = offset_got[-1-len(UNIT)] + prefix_center_desired = formatter.ENG_PREFIXES[oom_center_desired] + assert prefix_noise_desired == prefix_noise_got + assert prefix_center_desired == prefix_center_got + # Make sure the ticks didn't get the UNIT + for tick in ticks_got: + assert UNIT not in tick + else: + assert oom_center_desired == 0 + assert offset_got == "" + # Make sure the ticks contain now the prefixes + for tick in ticks_got: + # 0 is zero on all orders of magnitudes, no matter what is + # oom_noise_desired + prefix_idx = 0 if tick[0] == "0" else oom_noise_desired + assert tick.endswith(formatter.ENG_PREFIXES[prefix_idx] + UNIT) + + class TestPercentFormatter: percent_data = [ # Check explicitly set decimals over different intervals and values @@ -1260,6 +1745,43 @@ def test_latex(self, is_latex, usetex, expected): assert fmt.format_pct(50, 100) == expected +def _impl_locale_comma(): + try: + locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8') + except locale.Error: + print('SKIP: Locale de_DE.UTF-8 is not supported on this machine') + return + ticks = mticker.ScalarFormatter(useMathText=True, useLocale=True) + fmt = '$\\mathdefault{%1.1f}$' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == '$\\mathdefault{0{,}5}$' + # Do not change , in the format string + fmt = ',$\\mathdefault{,%1.1f},$' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == ',$\\mathdefault{,0{,}5},$' + # Make sure no brackets are added if not using math text + ticks = mticker.ScalarFormatter(useMathText=False, useLocale=True) + fmt = '%1.1f' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == '0,5' + + +def test_locale_comma(): + # On some systems/pytest versions, `pytest.skip` in an exception handler + # does not skip, but is treated as an exception, so directly running this + # test can incorrectly fail instead of skip. + # Instead, run this test in a subprocess, which avoids the problem, and the + # need to fix the locale after. + proc = mpl.testing.subprocess_run_helper(_impl_locale_comma, timeout=60, + extra_env={'MPLBACKEND': 'Agg'}) + skip_msg = next((line[len('SKIP:'):].strip() + for line in proc.stdout.splitlines() + if line.startswith('SKIP:')), + '') + if skip_msg: + pytest.skip(skip_msg) + + def test_majformatter_type(): fig, ax = plt.subplots() with pytest.raises(TypeError): @@ -1302,6 +1824,46 @@ def minorticksubplot(xminor, yminor, i): minorticksubplot(True, True, 4) +def test_minorticks_toggle(): + """ + Test toggling minor ticks + + Test `.Axis.minorticks_on()` and `.Axis.minorticks_off()`. Testing is + limited to a subset of built-in scales - `'linear'`, `'log'`, `'asinh'` + and `'logit'`. `symlog` scale does not seem to have a working minor + locator and is omitted. In future, this test should cover all scales in + `matplotlib.scale.get_scale_names()`. + """ + fig = plt.figure() + def minortickstoggle(xminor, yminor, scale, i): + ax = fig.add_subplot(2, 2, i) + ax.set_xscale(scale) + ax.set_yscale(scale) + if not xminor and not yminor: + ax.minorticks_off() + if xminor and not yminor: + ax.xaxis.minorticks_on() + ax.yaxis.minorticks_off() + if not xminor and yminor: + ax.xaxis.minorticks_off() + ax.yaxis.minorticks_on() + if xminor and yminor: + ax.minorticks_on() + + assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor + assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor + + scales = ['linear', 'log', 'asinh', 'logit'] + for scale in scales: + minortickstoggle(False, False, scale, 1) + minortickstoggle(True, False, scale, 2) + minortickstoggle(False, True, scale, 3) + minortickstoggle(True, True, scale, 4) + fig.clear() + + plt.close(fig) + + @pytest.mark.parametrize('remove_overlapping_locs, expected_num', ((True, 6), (None, 6), # this tests the default @@ -1345,4 +1907,91 @@ def test_remove_overlap(remove_overlapping_locs, expected_num): def test_bad_locator_subs(sub): ll = mticker.LogLocator() with pytest.raises(ValueError): - ll.subs(sub) + ll.set_params(subs=sub) + + +@pytest.mark.parametrize("numticks, lims, ticks", [ + (1, (.5, 5), [.1, 1, 10]), + (2, (.5, 5), [.1, 1, 10]), + (3, (.5, 5), [.1, 1, 10]), + (9, (.5, 5), [.1, 1, 10]), + (1, (.5, 50), [.1, 10, 1_000]), + (2, (.5, 50), [.1, 1, 10, 100]), + (3, (.5, 50), [.1, 1, 10, 100]), + (9, (.5, 50), [.1, 1, 10, 100]), + (1, (.5, 500), [.1, 10, 1_000]), + (2, (.5, 500), [.01, 1, 100, 10_000]), + (3, (.5, 500), [.1, 1, 10, 100, 1_000]), + (9, (.5, 500), [.1, 1, 10, 100, 1_000]), + (1, (.5, 5000), [.1, 100, 100_000]), + (2, (.5, 5000), [.001, 1, 1_000, 1_000_000]), + (3, (.5, 5000), [.001, 1, 1_000, 1_000_000]), + (9, (.5, 5000), [.1, 1, 10, 100, 1_000, 10_000]), +]) +@mpl.style.context('default') +def test_small_range_loglocator(numticks, lims, ticks): + ll = mticker.LogLocator(numticks=numticks) + assert_array_equal(ll.tick_values(*lims), ticks) + + +@mpl.style.context('default') +def test_loglocator_properties(): + # Test that LogLocator returns ticks satisfying basic desirable properties + # for a wide range of inputs. + max_numticks = 8 + pow_end = 20 + for numticks, (lo, hi) in itertools.product( + range(1, max_numticks + 1), itertools.combinations(range(pow_end), 2)): + ll = mticker.LogLocator(numticks=numticks) + decades = np.log10(ll.tick_values(10**lo, 10**hi)).round().astype(int) + # There are no more ticks than the requested number, plus exactly one + # tick below and one tick above the limits. + assert len(decades) <= numticks + 2 + assert decades[0] < lo <= decades[1] + assert decades[-2] <= hi < decades[-1] + stride, = {*np.diff(decades)} # Extract the (constant) stride. + # Either the ticks are on integer multiples of the stride... + if not (decades % stride == 0).all(): + # ... or (for this given stride) no offset would be acceptable, + # i.e. they would either result in fewer ticks than the selected + # solution, or more than the requested number of ticks. + for offset in range(0, stride): + alt_decades = range(lo + offset, hi + 1, stride) + assert len(alt_decades) < len(decades) or len(alt_decades) > numticks + + +def test_NullFormatter(): + formatter = mticker.NullFormatter() + assert formatter(1.0) == '' + assert formatter.format_data(1.0) == '' + assert formatter.format_data_short(1.0) == '' + + +@pytest.mark.parametrize('formatter', ( + mticker.FuncFormatter(lambda a: f'val: {a}'), + mticker.FixedFormatter(('foo', 'bar')))) +def test_set_offset_string(formatter): + assert formatter.get_offset() == '' + formatter.set_offset_string('mpl') + assert formatter.get_offset() == 'mpl' + + +def test_minorticks_on_multi_fig(): + """ + Turning on minor gridlines in a multi-Axes Figure + that contains more than one boxplot and shares the x-axis + should not raise an exception. + """ + fig, ax = plt.subplots() + + ax.boxplot(np.arange(10), positions=[0]) + ax.boxplot(np.arange(10), positions=[0]) + ax.boxplot(np.arange(10), positions=[1]) + + ax.grid(which="major") + ax.grid(which="minor") + ax.minorticks_on() + fig.draw_without_rendering() + + assert ax.get_xgridlines() + assert isinstance(ax.xaxis.get_minor_locator(), mpl.ticker.AutoMinorLocator) diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index cc928005c15b..f6b6d8f644cc 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -11,6 +11,11 @@ from matplotlib.patches import Rectangle +pytestmark = [ + pytest.mark.usefixtures('text_placeholders') +] + + def example_plot(ax, fontsize=12): ax.plot([1, 2]) ax.locator_params(nbins=3) @@ -19,7 +24,7 @@ def example_plot(ax, fontsize=12): ax.set_title('Title', fontsize=fontsize) -@image_comparison(['tight_layout1'], tol=1.9) +@image_comparison(['tight_layout1'], style='mpl20') def test_tight_layout1(): """Test tight_layout for a single subplot.""" fig, ax = plt.subplots() @@ -27,7 +32,7 @@ def test_tight_layout1(): plt.tight_layout() -@image_comparison(['tight_layout2']) +@image_comparison(['tight_layout2'], style='mpl20') def test_tight_layout2(): """Test tight_layout for multiple subplots.""" fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2) @@ -38,7 +43,7 @@ def test_tight_layout2(): plt.tight_layout() -@image_comparison(['tight_layout3']) +@image_comparison(['tight_layout3'], style='mpl20') def test_tight_layout3(): """Test tight_layout for multiple subplots.""" ax1 = plt.subplot(221) @@ -50,8 +55,7 @@ def test_tight_layout3(): plt.tight_layout() -@image_comparison(['tight_layout4'], freetype_version=('2.5.5', '2.6.1'), - tol=0.015) +@image_comparison(['tight_layout4'], style='mpl20') def test_tight_layout4(): """Test tight_layout for subplot2grid.""" ax1 = plt.subplot2grid((3, 3), (0, 0)) @@ -65,16 +69,16 @@ def test_tight_layout4(): plt.tight_layout() -@image_comparison(['tight_layout5']) +@image_comparison(['tight_layout5'], style='mpl20') def test_tight_layout5(): """Test tight_layout for image.""" - ax = plt.subplot(111) + ax = plt.subplot() arr = np.arange(100).reshape((10, 10)) ax.imshow(arr, interpolation="none") plt.tight_layout() -@image_comparison(['tight_layout6']) +@image_comparison(['tight_layout6'], style='mpl20') def test_tight_layout6(): """Test tight_layout for gridspec.""" @@ -116,7 +120,7 @@ def test_tight_layout6(): h_pad=0.45) -@image_comparison(['tight_layout7'], tol=1.9) +@image_comparison(['tight_layout7'], style='mpl20') def test_tight_layout7(): # tight layout with left and right titles fontsize = 24 @@ -130,16 +134,17 @@ def test_tight_layout7(): plt.tight_layout() -@image_comparison(['tight_layout8']) +@image_comparison(['tight_layout8'], style='mpl20', tol=0.005) def test_tight_layout8(): """Test automatic use of tight_layout.""" fig = plt.figure() - fig.set_tight_layout({'pad': .1}) - ax = fig.add_subplot(111) + fig.set_layout_engine(layout='tight', pad=0.1) + ax = fig.add_subplot() example_plot(ax, fontsize=24) + fig.draw_without_rendering() -@image_comparison(['tight_layout9']) +@image_comparison(['tight_layout9'], style='mpl20') def test_tight_layout9(): # Test tight_layout for non-visible subplots # GH 8244 @@ -172,13 +177,15 @@ def test_outward_ticks(): plt.tight_layout() # These values were obtained after visual checking that they correspond # to a tight layouting that did take the ticks into account. - ans = [[[0.091, 0.607], [0.433, 0.933]], - [[0.579, 0.607], [0.922, 0.933]], - [[0.091, 0.140], [0.433, 0.466]], - [[0.579, 0.140], [0.922, 0.466]]] + expected = [ + [[0.092, 0.605], [0.433, 0.933]], + [[0.581, 0.605], [0.922, 0.933]], + [[0.092, 0.138], [0.433, 0.466]], + [[0.581, 0.138], [0.922, 0.466]], + ] for nn, ax in enumerate(fig.axes): assert_array_equal(np.round(ax.get_position().get_points(), 3), - ans[nn]) + expected[nn]) def add_offsetboxes(ax, size=10, margin=.1, color='black'): @@ -187,8 +194,8 @@ def add_offsetboxes(ax, size=10, margin=.1, color='black'): """ m, mp = margin, 1+margin anchor_points = [(-m, -m), (-m, .5), (-m, mp), - (mp, .5), (.5, mp), (mp, mp), - (.5, -m), (mp, -m), (.5, -m)] + (.5, mp), (mp, mp), (mp, .5), + (mp, -m), (.5, -m)] for point in anchor_points: da = DrawingArea(size, size) background = Rectangle((0, 0), width=size, @@ -208,51 +215,82 @@ def add_offsetboxes(ax, size=10, margin=.1, color='black'): bbox_transform=ax.transAxes, borderpad=0.) ax.add_artist(anchored_box) - return anchored_box -@image_comparison(['tight_layout_offsetboxes1', 'tight_layout_offsetboxes2']) def test_tight_layout_offsetboxes(): - # 1. + # 0. # - Create 4 subplots # - Plot a diagonal line on them + # - Use tight_layout + # + # 1. + # - Same 4 subplots # - Surround each plot with 7 boxes # - Use tight_layout - # - See that the squares are included in the tight_layout - # and that the squares in the middle do not overlap + # - See that the squares are included in the tight_layout and that the squares do + # not overlap # # 2. - # - Make the squares around the right side axes invisible - # - See that the invisible squares do not affect the - # tight_layout + # - Make the squares around the Axes invisible + # - See that the invisible squares do not affect the tight_layout rows = cols = 2 colors = ['red', 'blue', 'green', 'yellow'] x = y = [0, 1] - def _subplots(): - _, axs = plt.subplots(rows, cols) - axs = axs.flat - for ax, color in zip(axs, colors): + def _subplots(with_boxes): + fig, axs = plt.subplots(rows, cols) + for ax, color in zip(axs.flat, colors): ax.plot(x, y, color=color) - add_offsetboxes(ax, 20, color=color) - return axs + if with_boxes: + add_offsetboxes(ax, 20, color=color) + return fig, axs + + # 0. + fig0, axs0 = _subplots(False) + fig0.tight_layout() # 1. - axs = _subplots() - plt.tight_layout() + fig1, axs1 = _subplots(True) + fig1.tight_layout() + + # The AnchoredOffsetbox should be added to the bounding of the Axes, causing them to + # be smaller than the plain figure. + for ax0, ax1 in zip(axs0.flat, axs1.flat): + bbox0 = ax0.get_position() + bbox1 = ax1.get_position() + assert bbox1.x0 > bbox0.x0 + assert bbox1.x1 < bbox0.x1 + assert bbox1.y0 > bbox0.y0 + assert bbox1.y1 < bbox0.y1 + + # No AnchoredOffsetbox should overlap with another. + bboxes = [] + for ax1 in axs1.flat: + for child in ax1.get_children(): + if not isinstance(child, AnchoredOffsetbox): + continue + bbox = child.get_window_extent() + for other_bbox in bboxes: + assert not bbox.overlaps(other_bbox) + bboxes.append(bbox) # 2. - axs = _subplots() - for ax in (axs[cols-1::rows]): + fig2, axs2 = _subplots(True) + for ax in axs2.flat: for child in ax.get_children(): if isinstance(child, AnchoredOffsetbox): child.set_visible(False) - - plt.tight_layout() + fig2.tight_layout() + # The invisible AnchoredOffsetbox should not count for tight layout, so it should + # look the same as when they were never added. + for ax0, ax2 in zip(axs0.flat, axs2.flat): + bbox0 = ax0.get_position() + bbox2 = ax2.get_position() + assert_array_equal(bbox2.get_points(), bbox0.get_points()) def test_empty_layout(): - """Test that tight layout doesn't cause an error when there are no axes.""" + """Test that tight layout doesn't cause an error when there are no Axes.""" fig = plt.gcf() fig.tight_layout() @@ -288,13 +326,16 @@ def test_badsubplotgrid(): def test_collapsed(): - # test that if a call to tight_layout will collapses the axes that - # it does not get applied: + # test that if the amount of space required to make all the axes + # decorations fit would mean that the actual Axes would end up with size + # zero (i.e. margins add up to more than the available width) that a call + # to tight_layout will not get applied: fig, ax = plt.subplots(tight_layout=True) ax.set_xlim([0, 1]) ax.set_ylim([0, 1]) - ax.annotate('BIG LONG STRING', xy=(1.25, 2), xytext=(10.5, 1.75),) + ax.annotate('BIG LONG STRING', xy=(1.25, 2), xytext=(10.5, 1.75), + annotation_clip=False) p1 = ax.get_position() with pytest.warns(UserWarning): plt.tight_layout() @@ -326,3 +367,64 @@ def __init__(self, *args, **kwargs): monkeypatch.setattr(mpl.backend_bases.RendererBase, "__init__", __init__) fig, ax = plt.subplots() fig.tight_layout() + + +def test_manual_colorbar(): + # This should warn, but not raise + fig, axes = plt.subplots(1, 2) + pts = axes[1].scatter([0, 1], [0, 1], c=[1, 5]) + ax_rect = axes[1].get_position() + cax = fig.add_axes( + [ax_rect.x1 + 0.005, ax_rect.y0, 0.015, ax_rect.height] + ) + fig.colorbar(pts, cax=cax) + with pytest.warns(UserWarning, match="This figure includes Axes"): + fig.tight_layout() + + +def test_clipped_to_axes(): + # Ensure that _fully_clipped_to_axes() returns True under default + # conditions for all projection types. Axes.get_tightbbox() + # uses this to skip artists in layout calculations. + arr = np.arange(100).reshape((10, 10)) + fig = plt.figure(figsize=(6, 2)) + ax1 = fig.add_subplot(131, projection='rectilinear') + ax2 = fig.add_subplot(132, projection='mollweide') + ax3 = fig.add_subplot(133, projection='polar') + for ax in (ax1, ax2, ax3): + # Default conditions (clipped by ax.bbox or ax.patch) + ax.grid(False) + h, = ax.plot(arr[:, 0]) + m = ax.pcolor(arr) + assert h._fully_clipped_to_axes() + assert m._fully_clipped_to_axes() + # Non-default conditions (not clipped by ax.patch) + rect = Rectangle((0, 0), 0.5, 0.5, transform=ax.transAxes) + h.set_clip_path(rect) + m.set_clip_path(rect.get_path(), rect.get_transform()) + assert not h._fully_clipped_to_axes() + assert not m._fully_clipped_to_axes() + + +def test_tight_pads(): + fig, ax = plt.subplots() + with pytest.warns(PendingDeprecationWarning, + match='will be deprecated'): + fig.set_tight_layout({'pad': 0.15}) + fig.draw_without_rendering() + + +def test_tight_kwargs(): + fig, ax = plt.subplots(tight_layout={'pad': 0.15}) + fig.draw_without_rendering() + + +def test_tight_toggle(): + fig, ax = plt.subplots() + with pytest.warns(PendingDeprecationWarning): + fig.set_tight_layout(True) + assert fig.get_tight_layout() + fig.set_tight_layout(False) + assert not fig.get_tight_layout() + fig.set_tight_layout(True) + assert fig.get_tight_layout() diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index a7ddbf770d7e..b4db34db5a91 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -1,3 +1,5 @@ +import copy + import numpy as np from numpy.testing import (assert_allclose, assert_almost_equal, assert_array_equal, assert_array_almost_equal) @@ -7,8 +9,362 @@ import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.transforms as mtransforms +from matplotlib.transforms import Affine2D, Bbox, TransformedBbox, _ScaledRotation from matplotlib.path import Path -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal +from unittest.mock import MagicMock + + +class TestAffine2D: + single_point = [1.0, 1.0] + multiple_points = [[0.0, 2.0], [3.0, 3.0], [4.0, 0.0]] + pivot = single_point + + def test_init(self): + Affine2D([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], int)) + Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], float)) + + def test_values(self): + np.random.seed(19680801) + values = np.random.random(6) + assert_array_equal(Affine2D.from_values(*values).to_values(), values) + + def test_modify_inplace(self): + # Some polar transforms require modifying the matrix in place. + trans = Affine2D() + mtx = trans.get_matrix() + mtx[0, 0] = 42 + assert_array_equal(trans.get_matrix(), [[42, 0, 0], [0, 1, 0], [0, 0, 1]]) + + def test_clear(self): + a = Affine2D(np.random.rand(3, 3) + 5) # Anything non-identity. + a.clear() + assert_array_equal(a.get_matrix(), [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + def test_rotate(self): + r_pi_2 = Affine2D().rotate(np.pi / 2) + r90 = Affine2D().rotate_deg(90) + assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix()) + assert_array_almost_equal(r90.transform(self.single_point), [-1, 1]) + assert_array_almost_equal(r90.transform(self.multiple_points), + [[-2, 0], [-3, 3], [0, 4]]) + + r_pi = Affine2D().rotate(np.pi) + r180 = Affine2D().rotate_deg(180) + assert_array_equal(r_pi.get_matrix(), r180.get_matrix()) + assert_array_almost_equal(r180.transform(self.single_point), [-1, -1]) + assert_array_almost_equal(r180.transform(self.multiple_points), + [[0, -2], [-3, -3], [-4, 0]]) + + r_pi_3_2 = Affine2D().rotate(3 * np.pi / 2) + r270 = Affine2D().rotate_deg(270) + assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix()) + assert_array_almost_equal(r270.transform(self.single_point), [1, -1]) + assert_array_almost_equal(r270.transform(self.multiple_points), + [[2, 0], [3, -3], [0, -4]]) + + assert_array_equal((r90 + r90).get_matrix(), r180.get_matrix()) + assert_array_equal((r90 + r180).get_matrix(), r270.get_matrix()) + + def test_rotate_around(self): + r_pi_2 = Affine2D().rotate_around(*self.pivot, np.pi / 2) + r90 = Affine2D().rotate_deg_around(*self.pivot, 90) + assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix()) + assert_array_almost_equal(r90.transform(self.single_point), [1, 1]) + assert_array_almost_equal(r90.transform(self.multiple_points), + [[0, 0], [-1, 3], [2, 4]]) + + r_pi = Affine2D().rotate_around(*self.pivot, np.pi) + r180 = Affine2D().rotate_deg_around(*self.pivot, 180) + assert_array_equal(r_pi.get_matrix(), r180.get_matrix()) + assert_array_almost_equal(r180.transform(self.single_point), [1, 1]) + assert_array_almost_equal(r180.transform(self.multiple_points), + [[2, 0], [-1, -1], [-2, 2]]) + + r_pi_3_2 = Affine2D().rotate_around(*self.pivot, 3 * np.pi / 2) + r270 = Affine2D().rotate_deg_around(*self.pivot, 270) + assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix()) + assert_array_almost_equal(r270.transform(self.single_point), [1, 1]) + assert_array_almost_equal(r270.transform(self.multiple_points), + [[2, 2], [3, -1], [0, -2]]) + + assert_array_almost_equal((r90 + r90).get_matrix(), r180.get_matrix()) + assert_array_almost_equal((r90 + r180).get_matrix(), r270.get_matrix()) + + def test_scale(self): + sx = Affine2D().scale(3, 1) + sy = Affine2D().scale(1, -2) + trans = Affine2D().scale(3, -2) + assert_array_equal((sx + sy).get_matrix(), trans.get_matrix()) + assert_array_equal(trans.transform(self.single_point), [3, -2]) + assert_array_equal(trans.transform(self.multiple_points), + [[0, -4], [9, -6], [12, 0]]) + + def test_skew(self): + trans_rad = Affine2D().skew(np.pi / 8, np.pi / 12) + trans_deg = Affine2D().skew_deg(22.5, 15) + assert_array_equal(trans_rad.get_matrix(), trans_deg.get_matrix()) + # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output. + trans = Affine2D().skew_deg(26.5650512, 14.0362435) + assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[1, 2], [4.5, 3.75], [4, 1]]) + + def test_translate(self): + tx = Affine2D().translate(23, 0) + ty = Affine2D().translate(0, 42) + trans = Affine2D().translate(23, 42) + assert_array_equal((tx + ty).get_matrix(), trans.get_matrix()) + assert_array_equal(trans.transform(self.single_point), [24, 43]) + assert_array_equal(trans.transform(self.multiple_points), + [[23, 44], [26, 45], [27, 42]]) + + def test_rotate_plus_other(self): + trans = Affine2D().rotate_deg(90).rotate_deg_around(*self.pivot, 180) + trans_added = (Affine2D().rotate_deg(90) + + Affine2D().rotate_deg_around(*self.pivot, 180)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [3, 1]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[4, 2], [5, -1], [2, -2]]) + + trans = Affine2D().rotate_deg(90).scale(3, -2) + trans_added = Affine2D().rotate_deg(90) + Affine2D().scale(3, -2) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-3, -2]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-6, -0], [-9, -6], [0, -8]]) + + trans = (Affine2D().rotate_deg(90) + .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25) + trans_added = (Affine2D().rotate_deg(90) + + Affine2D().skew_deg(26.5650512, 14.0362435)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-0.5, 0.75]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-2, -0.5], [-1.5, 2.25], [2, 4]]) + + trans = Affine2D().rotate_deg(90).translate(23, 42) + trans_added = Affine2D().rotate_deg(90) + Affine2D().translate(23, 42) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [22, 43]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[21, 42], [20, 45], [23, 46]]) + + def test_rotate_around_plus_other(self): + trans = Affine2D().rotate_deg_around(*self.pivot, 90).rotate_deg(180) + trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) + + Affine2D().rotate_deg(180)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-1, -1]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[0, 0], [1, -3], [-2, -4]]) + + trans = Affine2D().rotate_deg_around(*self.pivot, 90).scale(3, -2) + trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) + + Affine2D().scale(3, -2)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [3, -2]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[0, 0], [-3, -6], [6, -8]]) + + trans = (Affine2D().rotate_deg_around(*self.pivot, 90) + .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25) + trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) + + Affine2D().skew_deg(26.5650512, 14.0362435)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[0, 0], [0.5, 2.75], [4, 4.5]]) + + trans = Affine2D().rotate_deg_around(*self.pivot, 90).translate(23, 42) + trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) + + Affine2D().translate(23, 42)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [24, 43]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[23, 42], [22, 45], [25, 46]]) + + def test_scale_plus_other(self): + trans = Affine2D().scale(3, -2).rotate_deg(90) + trans_added = Affine2D().scale(3, -2) + Affine2D().rotate_deg(90) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_equal(trans.transform(self.single_point), [2, 3]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[4, 0], [6, 9], [0, 12]]) + + trans = Affine2D().scale(3, -2).rotate_deg_around(*self.pivot, 90) + trans_added = (Affine2D().scale(3, -2) + + Affine2D().rotate_deg_around(*self.pivot, 90)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_equal(trans.transform(self.single_point), [4, 3]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[6, 0], [8, 9], [2, 12]]) + + trans = (Affine2D().scale(3, -2) + .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25) + trans_added = (Affine2D().scale(3, -2) + + Affine2D().skew_deg(26.5650512, 14.0362435)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [2, -1.25]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-2, -4], [6, -3.75], [12, 3]]) + + trans = Affine2D().scale(3, -2).translate(23, 42) + trans_added = Affine2D().scale(3, -2) + Affine2D().translate(23, 42) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_equal(trans.transform(self.single_point), [26, 40]) + assert_array_equal(trans.transform(self.multiple_points), + [[23, 38], [32, 36], [35, 42]]) + + def test_skew_plus_other(self): + # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output. + trans = Affine2D().skew_deg(26.5650512, 14.0362435).rotate_deg(90) + trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) + + Affine2D().rotate_deg(90)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-1.25, 1.5]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-2, 1], [-3.75, 4.5], [-1, 4]]) + + trans = (Affine2D().skew_deg(26.5650512, 14.0362435) + .rotate_deg_around(*self.pivot, 90)) + trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) + + Affine2D().rotate_deg_around(*self.pivot, 90)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [0.75, 1.5]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[0, 1], [-1.75, 4.5], [1, 4]]) + + trans = Affine2D().skew_deg(26.5650512, 14.0362435).scale(3, -2) + trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) + + Affine2D().scale(3, -2)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [4.5, -2.5]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[3, -4], [13.5, -7.5], [12, -2]]) + + trans = Affine2D().skew_deg(26.5650512, 14.0362435).translate(23, 42) + trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) + + Affine2D().translate(23, 42)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [24.5, 43.25]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[24, 44], [27.5, 45.75], [27, 43]]) + + def test_translate_plus_other(self): + trans = Affine2D().translate(23, 42).rotate_deg(90) + trans_added = Affine2D().translate(23, 42) + Affine2D().rotate_deg(90) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-43, 24]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-44, 23], [-45, 26], [-42, 27]]) + + trans = Affine2D().translate(23, 42).rotate_deg_around(*self.pivot, 90) + trans_added = (Affine2D().translate(23, 42) + + Affine2D().rotate_deg_around(*self.pivot, 90)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [-41, 24]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[-42, 23], [-43, 26], [-40, 27]]) + + trans = Affine2D().translate(23, 42).scale(3, -2) + trans_added = Affine2D().translate(23, 42) + Affine2D().scale(3, -2) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [72, -86]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[69, -88], [78, -90], [81, -84]]) + + trans = (Affine2D().translate(23, 42) + .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25) + trans_added = (Affine2D().translate(23, 42) + + Affine2D().skew_deg(26.5650512, 14.0362435)) + assert_array_equal(trans.get_matrix(), trans_added.get_matrix()) + assert_array_almost_equal(trans.transform(self.single_point), [45.5, 49]) + assert_array_almost_equal(trans.transform(self.multiple_points), + [[45, 49.75], [48.5, 51.5], [48, 48.75]]) + + def test_invalid_transform(self): + t = mtransforms.Affine2D() + # There are two different exceptions, since the wrong number of + # dimensions is caught when constructing an array_view, and that + # raises a ValueError, and a wrong shape with a possible number + # of dimensions is caught by our CALL_CPP macro, which always + # raises the less precise RuntimeError. + with pytest.raises(ValueError): + t.transform(1) + with pytest.raises(ValueError): + t.transform([[[1]]]) + with pytest.raises(RuntimeError): + t.transform([]) + with pytest.raises(RuntimeError): + t.transform([1]) + with pytest.raises(ValueError): + t.transform([[1]]) + with pytest.raises(ValueError): + t.transform([[1, 2, 3]]) + + def test_copy(self): + a = mtransforms.Affine2D() + b = mtransforms.Affine2D() + s = a + b + # Updating a dependee should invalidate a copy of the dependent. + s.get_matrix() # resolve it. + s1 = copy.copy(s) + assert not s._invalid and not s1._invalid + a.translate(1, 2) + assert s._invalid and s1._invalid + assert (s1.get_matrix() == a.get_matrix()).all() + # Updating a copy of a dependee shouldn't invalidate a dependent. + s.get_matrix() # resolve it. + b1 = copy.copy(b) + b1.translate(3, 4) + assert not s._invalid + assert_array_equal(s.get_matrix(), a.get_matrix()) + + def test_deepcopy(self): + a = mtransforms.Affine2D() + b = mtransforms.Affine2D() + s = a + b + # Updating a dependee shouldn't invalidate a deepcopy of the dependent. + s.get_matrix() # resolve it. + s1 = copy.deepcopy(s) + assert not s._invalid and not s1._invalid + a.translate(1, 2) + assert s._invalid and not s1._invalid + assert_array_equal(s1.get_matrix(), mtransforms.Affine2D().get_matrix()) + # Updating a deepcopy of a dependee shouldn't invalidate a dependent. + s.get_matrix() # resolve it. + b1 = copy.deepcopy(b) + b1.translate(3, 4) + assert not s._invalid + assert_array_equal(s.get_matrix(), a.get_matrix()) + + +class TestAffineDeltaTransform: + def test_invalidate(self): + before = np.array([[1.0, 4.0, 0.0], + [5.0, 1.0, 0.0], + [0.0, 0.0, 1.0]]) + after = np.array([[1.0, 3.0, 0.0], + [5.0, 1.0, 0.0], + [0.0, 0.0, 1.0]]) + + # Translation and skew present + base = mtransforms.Affine2D.from_values(1, 5, 4, 1, 2, 3) + t = mtransforms.AffineDeltaTransform(base) + assert_array_equal(t.get_matrix(), before) + + # Mess with the internal structure of `base` without invalidating + # This should not affect this transform because it's a passthrough: + # it's always invalid + base.get_matrix()[0, 1:] = 3 + assert_array_equal(t.get_matrix(), after) + + # Invalidate the base + base.invalidate() + assert_array_equal(t.get_matrix(), after) def test_non_affine_caching(): @@ -67,16 +423,13 @@ def _as_mpl_transform(self, axes): mtransforms.Affine2D().scale(10).get_matrix()) -@image_comparison(['pre_transform_data'], - tol=0.08, remove_text=True, style='mpl20') +@image_comparison(['pre_transform_data'], remove_text=True, style='mpl20', + tol=0.05) def test_pre_transform_plotting(): # a catch-all for as many as possible plot layouts which handle # pre-transforming the data NOTE: The axis range is important in this # plot. It should be x10 what the data suggests it should be - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - ax = plt.axes() times10 = mtransforms.Affine2D().scale(10) @@ -95,9 +448,8 @@ def test_pre_transform_plotting(): u = 2*np.sin(x) + np.cos(y[:, np.newaxis]) v = np.sin(x) - np.cos(y[:, np.newaxis]) - df = 25. / 30. # Compatibility factor for old test image ax.streamplot(x, y, u, v, transform=times10 + ax.transData, - density=(df, df), linewidth=u**2 + v**2) + linewidth=np.hypot(u, v)) # reduce the vector data down a bit for barb and quiver plotting x, y = x[::3], y[::3] @@ -143,6 +495,25 @@ def test_pcolormesh_pre_transform_limits(): assert_almost_equal(expected, ax.dataLim.get_points()) +def test_pcolormesh_gouraud_nans(): + np.random.seed(19680801) + + values = np.linspace(0, 180, 3) + radii = np.linspace(100, 1000, 10) + z, y = np.meshgrid(values, radii) + x = np.radians(np.random.rand(*z.shape) * 100) + + fig = plt.figure() + ax = fig.add_subplot(111, projection="polar") + # Setting the limit to cause clipping of the r values causes NaN to be + # introduced; these should not crash but be ignored as in other path + # operations. + ax.set_rlim(101, 1000) + ax.pcolormesh(x, y, z, shading="gouraud") + + fig.canvas.draw() + + def test_Affine2D_from_values(): points = np.array([[0, 0], [10, 20], @@ -193,8 +564,7 @@ def test_affine_inverted_invalidated(): def test_clipping_of_log(): # issue 804 - path = Path([(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20), (0.2, -99)], - closed=True) + path = Path._create_closed([(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20)]) # something like this happens in plotting logarithmic histograms trans = mtransforms.BlendedGenericTransform( mtransforms.Affine2D(), scale.LogTransform(10, 'clip')) @@ -203,7 +573,7 @@ def test_clipping_of_log(): clip=(0, 0, 100, 100), simplify=False) tpoints, tcodes = zip(*result) - assert_allclose(tcodes, path.codes) + assert_allclose(tcodes, path.codes[:-1]) # No longer closed. class NonAffineForTest(mtransforms.Transform): @@ -323,6 +693,13 @@ def test_contains_branch(self): assert not self.stack1.contains_branch(self.tn1 + self.ta2) + blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2) + x, y = blend.contains_branch_seperately(self.stack2_subset) + stack_blend = self.tn3 + blend + sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset) + assert x is sx is False + assert y is sy is True + def test_affine_simplification(self): # tests that a transform stack only calls as much is absolutely # necessary "non-affine" allowing the best possible optimization with @@ -425,7 +802,7 @@ def test_pathc_extents_non_affine(self): ax = plt.axes() offset = mtransforms.Affine2D().translate(10, 10) na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10)) - pth = Path(np.array([[0, 0], [0, 10], [10, 10], [10, 0]])) + pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]]) patch = mpatches.PathPatch(pth, transform=offset + na_offset + ax.transData) ax.add_patch(patch) @@ -435,7 +812,7 @@ def test_pathc_extents_non_affine(self): def test_pathc_extents_affine(self): ax = plt.axes() offset = mtransforms.Affine2D().translate(10, 10) - pth = Path(np.array([[0, 0], [0, 10], [10, 10], [10, 0]])) + pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]]) patch = mpatches.PathPatch(pth, transform=offset + ax.transData) ax.add_patch(patch) expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 10 @@ -458,6 +835,12 @@ def assert_bbox_eq(bbox1, bbox2): assert_array_equal(bbox1.bounds, bbox2.bounds) +def test_bbox_frozen_copies_minpos(): + bbox = mtransforms.Bbox.from_extents(0.0, 0.0, 1.0, 1.0, minpos=1.0) + frozen = bbox.frozen() + assert_array_equal(frozen.minpos, bbox.minpos) + + def test_bbox_intersection(): bbox_from_ext = mtransforms.Bbox.from_extents inter = mtransforms.Bbox.intersection @@ -504,18 +887,11 @@ def test_str_transform(): IdentityTransform(), IdentityTransform())), CompositeAffine2D( - Affine2D( - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]]), - Affine2D( - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]]))), + Affine2D().scale(1.0), + Affine2D().scale(1.0))), PolarTransform( - PolarAxesSubplot(0.125,0.1;0.775x0.8), - use_rmin=True, - _apply_theta_transforms=False)), + PolarAxes(0.125,0.1;0.775x0.8), + use_rmin=True)), CompositeGenericTransform( CompositeGenericTransform( PolarAffine( @@ -533,14 +909,8 @@ def test_str_transform(): TransformedBbox( Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0), CompositeAffine2D( - Affine2D( - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]]), - Affine2D( - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]]))), + Affine2D().scale(1.0), + Affine2D().scale(1.0))), LockableBbox( Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0), [[-- --] @@ -551,10 +921,7 @@ def test_str_transform(): BboxTransformTo( TransformedBbox( Bbox(x0=0.0, y0=0.0, x1=8.0, y1=6.0), - Affine2D( - [[80. 0. 0.] - [ 0. 80. 0.] - [ 0. 0. 1.]])))))))""" + Affine2D().scale(80.0)))))))""" def test_transform_single_point(): @@ -604,27 +971,6 @@ def test_nonsingular(): assert_array_equal(out, zero_expansion) -def test_invalid_arguments(): - t = mtransforms.Affine2D() - # There are two different exceptions, since the wrong number of - # dimensions is caught when constructing an array_view, and that - # raises a ValueError, and a wrong shape with a possible number - # of dimensions is caught by our CALL_CPP macro, which always - # raises the less precise RuntimeError. - with pytest.raises(ValueError): - t.transform(1) - with pytest.raises(ValueError): - t.transform([[[1]]]) - with pytest.raises(RuntimeError): - t.transform([]) - with pytest.raises(RuntimeError): - t.transform([1]) - with pytest.raises(RuntimeError): - t.transform([[1]]) - with pytest.raises(RuntimeError): - t.transform([[1, 2, 3]]) - - def test_transformed_path(): points = [(0, 0), (1, 0), (1, 1), (0, 1)] path = Path(points, closed=True) @@ -640,12 +986,6 @@ def test_transformed_path(): [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], atol=1e-15) - # Changing the path does not change the result (it's cached). - path.points = [(0, 0)] * 4 - assert_allclose(trans_path.get_fully_transformed_path().vertices, - [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], - atol=1e-15) - def test_transformed_patch_path(): trans = mtransforms.Affine2D() @@ -696,3 +1036,89 @@ def test_lockable_bbox(locked_element): assert getattr(locked, 'locked_' + locked_element) == 3 for elem in other_elements: assert getattr(locked, elem) == getattr(orig, elem) + + +def test_transformwrapper(): + t = mtransforms.TransformWrapper(mtransforms.Affine2D()) + with pytest.raises(ValueError, match=( + r"The input and output dims of the new child \(1, 1\) " + r"do not match those of current child \(2, 2\)")): + t.set(scale.LogTransform(10)) + + +@check_figures_equal() +def test_scale_swapping(fig_test, fig_ref): + np.random.seed(19680801) + samples = np.random.normal(size=10) + x = np.linspace(-5, 5, 10) + + for fig, log_state in zip([fig_test, fig_ref], [True, False]): + ax = fig.subplots() + ax.hist(samples, log=log_state, density=True) + ax.plot(x, np.exp(-(x**2) / 2) / np.sqrt(2 * np.pi)) + fig.canvas.draw() + ax.set_yscale('linear') + + +def test_offset_copy_errors(): + with pytest.raises(ValueError, + match="'fontsize' is not a valid value for units;" + " supported values are 'dots', 'points', 'inches'"): + mtransforms.offset_copy(None, units='fontsize') + + with pytest.raises(ValueError, + match='For units of inches or points a fig kwarg is needed'): + mtransforms.offset_copy(None, units='inches') + + +def test_transformedbbox_contains(): + bb = TransformedBbox(Bbox.unit(), Affine2D().rotate_deg(30)) + assert bb.contains(.8, .5) + assert bb.contains(-.4, .85) + assert not bb.contains(.9, .5) + bb = TransformedBbox(Bbox.unit(), Affine2D().translate(.25, .5)) + assert bb.contains(1.25, 1.5) + assert not bb.fully_contains(1.25, 1.5) + assert not bb.fully_contains(.1, .1) + + +def test_interval_contains(): + assert mtransforms.interval_contains((0, 1), 0.5) + assert mtransforms.interval_contains((0, 1), 0) + assert mtransforms.interval_contains((0, 1), 1) + assert not mtransforms.interval_contains((0, 1), -1) + assert not mtransforms.interval_contains((0, 1), 2) + assert mtransforms.interval_contains((1, 0), 0.5) + + +def test_interval_contains_open(): + assert mtransforms.interval_contains_open((0, 1), 0.5) + assert not mtransforms.interval_contains_open((0, 1), 0) + assert not mtransforms.interval_contains_open((0, 1), 1) + assert not mtransforms.interval_contains_open((0, 1), -1) + assert not mtransforms.interval_contains_open((0, 1), 2) + assert mtransforms.interval_contains_open((1, 0), 0.5) + + +def test_scaledrotation_initialization(): + """Test that the ScaledRotation object is initialized correctly.""" + theta = 1.0 # Arbitrary theta value for testing + trans_shift = MagicMock() # Mock the trans_shift transformation + scaled_rot = _ScaledRotation(theta, trans_shift) + assert scaled_rot._theta == theta + assert scaled_rot._trans_shift == trans_shift + assert scaled_rot._mtx is None + + +def test_scaledrotation_get_matrix_invalid(): + """Test get_matrix when the matrix is invalid and needs recalculation.""" + theta = np.pi / 2 + trans_shift = MagicMock(transform=MagicMock(return_value=[[theta, 0]])) + scaled_rot = _ScaledRotation(theta, trans_shift) + scaled_rot._invalid = True + matrix = scaled_rot.get_matrix() + trans_shift.transform.assert_called_once_with([[theta, 0]]) + expected_rotation = np.array([[0, -1], + [1, 0]]) + assert matrix is not None + assert_allclose(matrix[:2, :2], expected_rotation, atol=1e-15) diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index cd46b954785c..337443eb1e27 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -5,11 +5,94 @@ import pytest import matplotlib as mpl -import matplotlib.cm as cm import matplotlib.pyplot as plt import matplotlib.tri as mtri from matplotlib.path import Path -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal + + +class TestTriangulationParams: + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + triangles = [[0, 1, 2], [0, 2, 3]] + mask = [False, True] + + @pytest.mark.parametrize('args, kwargs, expected', [ + ([x, y], {}, [x, y, None, None]), + ([x, y, triangles], {}, [x, y, triangles, None]), + ([x, y], dict(triangles=triangles), [x, y, triangles, None]), + ([x, y], dict(mask=mask), [x, y, None, mask]), + ([x, y, triangles], dict(mask=mask), [x, y, triangles, mask]), + ([x, y], dict(triangles=triangles, mask=mask), [x, y, triangles, mask]) + ]) + def test_extract_triangulation_params(self, args, kwargs, expected): + other_args = [1, 2] + other_kwargs = {'a': 3, 'b': '4'} + x_, y_, triangles_, mask_, args_, kwargs_ = \ + mtri.Triangulation._extract_triangulation_params( + args + other_args, {**kwargs, **other_kwargs}) + x, y, triangles, mask = expected + assert x_ is x + assert y_ is y + assert_array_equal(triangles_, triangles) + assert mask_ is mask + assert args_ == other_args + assert kwargs_ == other_kwargs + + +def test_extract_triangulation_positional_mask(): + # mask cannot be passed positionally + mask = [True] + args = [[0, 2, 1], [0, 0, 1], [[0, 1, 2]], mask] + x_, y_, triangles_, mask_, args_, kwargs_ = \ + mtri.Triangulation._extract_triangulation_params(args, {}) + assert mask_ is None + assert args_ == [mask] + # the positional mask must be caught downstream because this must pass + # unknown args through + + +def test_triangulation_init(): + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + with pytest.raises(ValueError, match="x and y must be equal-length"): + mtri.Triangulation(x, [1, 2]) + with pytest.raises( + ValueError, + match=r"triangles must be a \(N, 3\) int array, but found shape " + r"\(3,\)"): + mtri.Triangulation(x, y, [0, 1, 2]) + with pytest.raises( + ValueError, + match=r"triangles must be a \(N, 3\) int array, not 'other'"): + mtri.Triangulation(x, y, 'other') + with pytest.raises(ValueError, match="found value 99"): + mtri.Triangulation(x, y, [[0, 1, 99]]) + with pytest.raises(ValueError, match="found value -1"): + mtri.Triangulation(x, y, [[0, 1, -1]]) + + +def test_triangulation_set_mask(): + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + triangles = [[0, 1, 2], [2, 3, 0]] + triang = mtri.Triangulation(x, y, triangles) + + # Check neighbors, which forces creation of C++ triangulation + assert_array_equal(triang.neighbors, [[-1, -1, 1], [-1, -1, 0]]) + + # Set mask + triang.set_mask([False, True]) + assert_array_equal(triang.mask, [False, True]) + + # Reset mask + triang.set_mask(None) + assert triang.mask is None + + msg = r"mask array must have same length as triangles array" + for mask in ([False, True, False], [False], [True], False, True): + with pytest.raises(ValueError, match=msg): + triang.set_mask(mask) def test_delaunay(): @@ -177,6 +260,58 @@ def test_tripcolor(): plt.title('facecolors') +def test_tripcolor_color(): + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + fig, ax = plt.subplots() + with pytest.raises(TypeError, match=r"tripcolor\(\) missing 1 required "): + ax.tripcolor(x, y) + with pytest.raises(ValueError, match="The length of c must match either"): + ax.tripcolor(x, y, [1, 2, 3]) + with pytest.raises(ValueError, + match="length of facecolors must match .* triangles"): + ax.tripcolor(x, y, facecolors=[1, 2, 3, 4]) + with pytest.raises(ValueError, + match="'gouraud' .* at the points.* not at the faces"): + ax.tripcolor(x, y, facecolors=[1, 2], shading='gouraud') + with pytest.raises(ValueError, + match="'gouraud' .* at the points.* not at the faces"): + ax.tripcolor(x, y, [1, 2], shading='gouraud') # faces + with pytest.raises(TypeError, + match="positional.*'c'.*keyword-only.*'facecolors'"): + ax.tripcolor(x, y, C=[1, 2, 3, 4]) + with pytest.raises(TypeError, match="Unexpected positional parameter"): + ax.tripcolor(x, y, [1, 2], 'unused_positional') + + # smoke test for valid color specifications (via C or facecolors) + ax.tripcolor(x, y, [1, 2, 3, 4]) # edges + ax.tripcolor(x, y, [1, 2, 3, 4], shading='gouraud') # edges + ax.tripcolor(x, y, [1, 2]) # faces + ax.tripcolor(x, y, facecolors=[1, 2]) # faces + + +def test_tripcolor_clim(): + np.random.seed(19680801) + a, b, c = np.random.rand(10), np.random.rand(10), np.random.rand(10) + + ax = plt.figure().add_subplot() + clim = (0.25, 0.75) + norm = ax.tripcolor(a, b, c, clim=clim).norm + assert (norm.vmin, norm.vmax) == clim + + +def test_tripcolor_warnings(): + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + c = [0.4, 0.5] + fig, ax = plt.subplots() + # facecolors takes precedence over c + with pytest.warns(UserWarning, match="Positional parameter c .*no effect"): + ax.tripcolor(x, y, c, facecolors=c) + with pytest.warns(UserWarning, match="Positional parameter c .*no effect"): + ax.tripcolor(x, y, 'interpreted as c', facecolors=c) + + def test_no_modify(): # Test that Triangulation does not modify triangles array passed to it. triangles = np.array([[3, 2, 0], [3, 1, 0]], dtype=np.int32) @@ -501,15 +636,15 @@ def poisson_sparse_matrix(n, m): # Instantiating a sparse Poisson matrix of size 48 x 48: (n, m) = (12, 4) - mat = mtri.triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m)) + mat = mtri._triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m)) mat.compress_csc() mat_dense = mat.to_dense() # Testing a sparse solve for all 48 basis vector for itest in range(n*m): b = np.zeros(n*m, dtype=np.float64) b[itest] = 1. - x, _ = mtri.triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m), - tol=1.e-10) + x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m), + tol=1.e-10) assert_array_almost_equal(np.dot(mat_dense, x), b) # 2) Same matrix with inserting 2 rows - cols with null diag terms @@ -522,16 +657,16 @@ def poisson_sparse_matrix(n, m): rows = np.concatenate([rows, [i_zero, i_zero-1, j_zero, j_zero-1]]) cols = np.concatenate([cols, [i_zero-1, i_zero, j_zero-1, j_zero]]) vals = np.concatenate([vals, [1., 1., 1., 1.]]) - mat = mtri.triinterpolate._Sparse_Matrix_coo(vals, rows, cols, - (n*m + 2, n*m + 2)) + mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols, + (n*m + 2, n*m + 2)) mat.compress_csc() mat_dense = mat.to_dense() # Testing a sparse solve for all 50 basis vec for itest in range(n*m + 2): b = np.zeros(n*m + 2, dtype=np.float64) b[itest] = 1. - x, _ = mtri.triinterpolate._cg(A=mat, b=b, x0=np.ones(n*m + 2), - tol=1.e-10) + x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.ones(n * m + 2), + tol=1.e-10) assert_array_almost_equal(np.dot(mat_dense, x), b) # 3) Now a simple test that summation of duplicate (i.e. with same rows, @@ -542,7 +677,7 @@ def poisson_sparse_matrix(n, m): cols = np.array([0, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2], dtype=np.int32) dim = (3, 3) - mat = mtri.triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim) + mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim) mat.compress_csc() mat_dense = mat.to_dense() assert_array_almost_equal(mat_dense, np.array([ @@ -565,7 +700,7 @@ def test_triinterpcubic_geom_weights(): y_rot = -np.sin(theta)*x + np.cos(theta)*y triang = mtri.Triangulation(x_rot, y_rot, triangles) cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom') - dof_estimator = mtri.triinterpolate._DOF_estimator_geom(cubic_geom) + dof_estimator = mtri._triinterpolate._DOF_estimator_geom(cubic_geom) weights = dof_estimator.compute_geom_weights() # Testing for the 4 possibilities... sum_w[0, :] = np.sum(weights, 1) - 1 @@ -730,7 +865,7 @@ def z(x, y): matest.assert_array_almost_equal(interpz, interp_z0[interp_key]) -@image_comparison(['tri_smooth_contouring.png'], remove_text=True, tol=0.07) +@image_comparison(['tri_smooth_contouring.png'], remove_text=True, tol=0.072) def test_tri_smooth_contouring(): # Image comparison based on example tricontour_smooth_user. n_angles = 20 @@ -811,7 +946,7 @@ def dipole_potential(x, y): plt.triplot(triang, color='0.8') levels = np.arange(0., 1., 0.01) - cmap = cm.get_cmap(name='hot', lut=None) + cmap = mpl.colormaps['hot'] plt.tricontour(tri_refi, z_test_refi, levels=levels, cmap=cmap, linewidths=[2.0, 1.0, 1.0, 1.0]) # Plots direction of the electrical vector field @@ -831,8 +966,7 @@ def test_tritools(): mask = np.array([False, False, True], dtype=bool) triang = mtri.Triangulation(x, y, triangles, mask=mask) analyser = mtri.TriAnalyzer(triang) - assert_array_almost_equal(analyser.scale_factors, - np.array([1., 1./(1.+0.5*np.sqrt(3.))])) + assert_array_almost_equal(analyser.scale_factors, [1, 1/(1+3**.5/2)]) assert_array_almost_equal( analyser.circle_ratios(rescale=False), np.ma.masked_array([0.5, 1./(1.+np.sqrt(2.)), np.nan], mask)) @@ -901,7 +1035,7 @@ def test_trirefine(): x_verif, y_verif = np.meshgrid(x_verif, x_verif) x_verif = x_verif.ravel() y_verif = y_verif.ravel() - ind1d = np.in1d(np.around(x_verif*(2.5+y_verif), 8), + ind1d = np.isin(np.around(x_verif*(2.5+y_verif), 8), np.around(x_refi*(2.5+y_refi), 8)) assert_array_equal(ind1d, True) @@ -1047,71 +1181,78 @@ def test_tricontourf_decreasing_levels(): plt.tricontourf(x, y, z, [1.0, 0.0]) -def test_internal_cpp_api(): +def test_internal_cpp_api() -> None: # Following github issue 8197. - from matplotlib import _tri # noqa: ensure lazy-loaded module *is* loaded. + from matplotlib import _tri # noqa: F401, ensure lazy-loaded module *is* loaded. # C++ Triangulation. with pytest.raises( TypeError, - match=r'function takes exactly 7 arguments \(0 given\)'): - mpl._tri.Triangulation() + match=r'__init__\(\): incompatible constructor arguments.'): + mpl._tri.Triangulation() # type: ignore[call-arg] with pytest.raises( ValueError, match=r'x and y must be 1D arrays of the same length'): - mpl._tri.Triangulation([], [1], [[]], None, None, None, False) + mpl._tri.Triangulation(np.array([]), np.array([1]), np.array([[]]), (), (), (), + False) - x = [0, 1, 1] - y = [0, 0, 1] + x = np.array([0, 1, 1], dtype=np.float64) + y = np.array([0, 0, 1], dtype=np.float64) with pytest.raises( ValueError, match=r'triangles must be a 2D array of shape \(\?,3\)'): - mpl._tri.Triangulation(x, y, [[0, 1]], None, None, None, False) + mpl._tri.Triangulation(x, y, np.array([[0, 1]]), (), (), (), False) - tris = [[0, 1, 2]] + tris = np.array([[0, 1, 2]], dtype=np.int_) with pytest.raises( ValueError, match=r'mask must be a 1D array with the same length as the ' r'triangles array'): - mpl._tri.Triangulation(x, y, tris, [0, 1], None, None, False) + mpl._tri.Triangulation(x, y, tris, np.array([0, 1]), (), (), False) with pytest.raises( ValueError, match=r'edges must be a 2D array with shape \(\?,2\)'): - mpl._tri.Triangulation(x, y, tris, None, [[1]], None, False) + mpl._tri.Triangulation(x, y, tris, (), np.array([[1]]), (), False) with pytest.raises( ValueError, match=r'neighbors must be a 2D array with the same shape as the ' r'triangles array'): - mpl._tri.Triangulation(x, y, tris, None, None, [[-1]], False) + mpl._tri.Triangulation(x, y, tris, (), (), np.array([[-1]]), False) - triang = mpl._tri.Triangulation(x, y, tris, None, None, None, False) + triang = mpl._tri.Triangulation(x, y, tris, (), (), (), False) with pytest.raises( ValueError, - match=r'z array must have same length as triangulation x and y ' - r'array'): + match=r'z must be a 1D array with the same length as the ' + r'triangulation x and y arrays'): triang.calculate_plane_coefficients([]) - with pytest.raises( - ValueError, - match=r'mask must be a 1D array with the same length as the ' - r'triangles array'): - triang.set_mask([0, 1]) + for mask in ([0, 1], None): + with pytest.raises( + ValueError, + match=r'mask must be a 1D array with the same length as the ' + r'triangles array'): + triang.set_mask(mask) # type: ignore[arg-type] + + triang.set_mask(np.array([True])) + assert_array_equal(triang.get_edges(), np.empty((0, 2))) + + triang.set_mask(()) # Equivalent to Python Triangulation mask=None + assert_array_equal(triang.get_edges(), [[1, 0], [2, 0], [2, 1]]) # C++ TriContourGenerator. with pytest.raises( TypeError, - match=r'function takes exactly 2 arguments \(0 given\)'): - mpl._tri.TriContourGenerator() + match=r'__init__\(\): incompatible constructor arguments.'): + mpl._tri.TriContourGenerator() # type: ignore[call-arg] with pytest.raises( ValueError, - match=r'z must be a 1D array with the same length as the x and y ' - r'arrays'): - mpl._tri.TriContourGenerator(triang, [1]) + match=r'z must be a 1D array with the same length as the x and y arrays'): + mpl._tri.TriContourGenerator(triang, np.array([1])) - z = [0, 1, 2] + z = np.array([0, 1, 2]) tcg = mpl._tri.TriContourGenerator(triang, z) with pytest.raises( @@ -1120,14 +1261,15 @@ def test_internal_cpp_api(): # C++ TrapezoidMapTriFinder. with pytest.raises( - TypeError, match=r'function takes exactly 1 argument \(0 given\)'): - mpl._tri.TrapezoidMapTriFinder() + TypeError, + match=r'__init__\(\): incompatible constructor arguments.'): + mpl._tri.TrapezoidMapTriFinder() # type: ignore[call-arg] trifinder = mpl._tri.TrapezoidMapTriFinder(triang) with pytest.raises( ValueError, match=r'x and y must be array-like with same shape'): - trifinder.find_many([0], [0, 1]) + trifinder.find_many(np.array([0]), np.array([0, 1])) def test_qhull_large_offset(): @@ -1163,3 +1305,99 @@ def test_tricontour_non_finite_z(): with pytest.raises(ValueError, match='z must not contain masked points ' 'within the triangulation'): plt.tricontourf(triang, np.ma.array([0, 1, 2, 3], mask=[1, 0, 0, 0])) + + +def test_tricontourset_reuse(): + # If TriContourSet returned from one tricontour(f) call is passed as first + # argument to another the underlying C++ contour generator will be reused. + x = [0.0, 0.5, 1.0] + y = [0.0, 1.0, 0.0] + z = [1.0, 2.0, 3.0] + fig, ax = plt.subplots() + tcs1 = ax.tricontourf(x, y, z) + tcs2 = ax.tricontour(x, y, z) + assert tcs2._contour_generator != tcs1._contour_generator + tcs3 = ax.tricontour(tcs1, z) + assert tcs3._contour_generator == tcs1._contour_generator + + +@check_figures_equal() +def test_triplot_with_ls(fig_test, fig_ref): + x = [0, 2, 1] + y = [0, 0, 1] + data = [[0, 1, 2]] + fig_test.subplots().triplot(x, y, data, ls='--') + fig_ref.subplots().triplot(x, y, data, linestyle='--') + + +def test_triplot_label(): + x = [0, 2, 1] + y = [0, 0, 1] + data = [[0, 1, 2]] + fig, ax = plt.subplots() + lines, markers = ax.triplot(x, y, data, label='label') + handles, labels = ax.get_legend_handles_labels() + assert labels == ['label'] + assert len(handles) == 1 + assert handles[0] is lines + + +def test_tricontour_path(): + x = [0, 4, 4, 0, 2] + y = [0, 0, 4, 4, 2] + triang = mtri.Triangulation(x, y) + _, ax = plt.subplots() + + # Line strip from boundary to boundary + cs = ax.tricontour(triang, [1, 0, 0, 0, 0], levels=[0.5]) + paths = cs.get_paths() + assert len(paths) == 1 + expected_vertices = [[2, 0], [1, 1], [0, 2]] + assert_array_almost_equal(paths[0].vertices, expected_vertices) + assert_array_equal(paths[0].codes, [1, 2, 2]) + assert_array_almost_equal( + paths[0].to_polygons(closed_only=False), [expected_vertices]) + + # Closed line loop inside domain + cs = ax.tricontour(triang, [0, 0, 0, 0, 1], levels=[0.5]) + paths = cs.get_paths() + assert len(paths) == 1 + expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]] + assert_array_almost_equal(paths[0].vertices, expected_vertices) + assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79]) + assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices]) + + +def test_tricontourf_path(): + x = [0, 4, 4, 0, 2] + y = [0, 0, 4, 4, 2] + triang = mtri.Triangulation(x, y) + _, ax = plt.subplots() + + # Polygon inside domain + cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[0.5, 1.5]) + paths = cs.get_paths() + assert len(paths) == 1 + expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]] + assert_array_almost_equal(paths[0].vertices, expected_vertices) + assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79]) + assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices]) + + # Polygon following boundary and inside domain + cs = ax.tricontourf(triang, [1, 0, 0, 0, 0], levels=[0.5, 1.5]) + paths = cs.get_paths() + assert len(paths) == 1 + expected_vertices = [[2, 0], [1, 1], [0, 2], [0, 0], [2, 0]] + assert_array_almost_equal(paths[0].vertices, expected_vertices) + assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79]) + assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices]) + + # Polygon is outer boundary with hole + cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[-0.5, 0.5]) + paths = cs.get_paths() + assert len(paths) == 1 + expected_vertices = [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0], + [1, 1], [1, 3], [3, 3], [3, 1], [1, 1]] + assert_array_almost_equal(paths[0].vertices, expected_vertices) + assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79, 1, 2, 2, 2, 79]) + assert_array_almost_equal(paths[0].to_polygons(), np.split(expected_vertices, [5])) diff --git a/lib/matplotlib/tests/test_ttconv.py b/lib/matplotlib/tests/test_ttconv.py deleted file mode 100644 index 1d839e7094b0..000000000000 --- a/lib/matplotlib/tests/test_ttconv.py +++ /dev/null @@ -1,17 +0,0 @@ -from pathlib import Path - -import matplotlib -from matplotlib.testing.decorators import image_comparison -import matplotlib.pyplot as plt - - -@image_comparison(["truetype-conversion.pdf"]) -# mpltest.ttf does not have "l"/"p" glyphs so we get a warning when trying to -# get the font extents. -def test_truetype_conversion(recwarn): - matplotlib.rcParams['pdf.fonttype'] = 3 - fig, ax = plt.subplots() - ax.text(0, 0, "ABCDE", - font=Path(__file__).with_name("mpltest.ttf"), fontsize=80) - ax.set_xticks([]) - ax.set_yticks([]) diff --git a/lib/matplotlib/tests/test_type1font.py b/lib/matplotlib/tests/test_type1font.py index 8800e184b3b7..b2f93ef28a26 100644 --- a/lib/matplotlib/tests/test_type1font.py +++ b/lib/matplotlib/tests/test_type1font.py @@ -1,10 +1,11 @@ -import matplotlib.type1font as t1f +import matplotlib._type1font as t1f import os.path import difflib +import pytest def test_Type1Font(): - filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb') + filename = os.path.join(os.path.dirname(__file__), 'data', 'cmr10.pfb') font = t1f.Type1Font(filename) slanted = font.transform({'slant': 1}) condensed = font.transform({'extend': 0.5}) @@ -13,8 +14,35 @@ def test_Type1Font(): assert font.parts[0] == rawdata[0x0006:0x10c5] assert font.parts[1] == rawdata[0x10cb:0x897f] assert font.parts[2] == rawdata[0x8985:0x8ba6] - assert font.parts[1:] == slanted.parts[1:] - assert font.parts[1:] == condensed.parts[1:] + assert font.decrypted.startswith(b'dup\n/Private 18 dict dup begin') + assert font.decrypted.endswith(b'mark currentfile closefile\n') + assert slanted.decrypted.startswith(b'dup\n/Private 18 dict dup begin') + assert slanted.decrypted.endswith(b'mark currentfile closefile\n') + assert b'UniqueID 5000793' in font.parts[0] + assert b'UniqueID 5000793' in font.decrypted + assert font._pos['UniqueID'] == [(797, 818), (4483, 4504)] + + len0 = len(font.parts[0]) + for key in font._pos.keys(): + for pos0, pos1 in font._pos[key]: + if pos0 < len0: + data = font.parts[0][pos0:pos1] + else: + data = font.decrypted[pos0-len0:pos1-len0] + assert data.startswith(f'/{key}'.encode('ascii')) + assert {'FontType', 'FontMatrix', 'PaintType', 'ItalicAngle', 'RD' + } < set(font._pos.keys()) + + assert b'UniqueID 5000793' not in slanted.parts[0] + assert b'UniqueID 5000793' not in slanted.decrypted + assert 'UniqueID' not in slanted._pos + assert font.prop['Weight'] == 'Medium' + assert not font.prop['isFixedPitch'] + assert font.prop['ItalicAngle'] == 0 + assert slanted.prop['ItalicAngle'] == -45 + assert font.prop['Encoding'][5] == 'Pi' + assert isinstance(font.prop['CharStrings']['Pi'], bytes) + assert font._abbr['ND'] == 'ND' differ = difflib.Differ() diff = list(differ.compare( @@ -22,14 +50,13 @@ def test_Type1Font(): slanted.parts[0].decode('latin-1').splitlines())) for line in ( # Removes UniqueID - '- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup', - '+ FontDirectory/CMR10 known{/CMR10 findfont dup', + '- /UniqueID 5000793 def', # Changes the font name - '- /FontName /CMR10 def', - '+ /FontName /CMR10_Slant_1000 def', + '- /FontName /CMR10 def', + '+ /FontName/CMR10_Slant_1000 def', # Alters FontMatrix '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def', - '+ /FontMatrix [0.001 0 0.001 0.001 0 0]readonly def', + '+ /FontMatrix [0.001 0 0.001 0.001 0 0] readonly def', # Alters ItalicAngle '- /ItalicAngle 0 def', '+ /ItalicAngle -45.0 def'): @@ -40,30 +67,94 @@ def test_Type1Font(): condensed.parts[0].decode('latin-1').splitlines())) for line in ( # Removes UniqueID - '- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup', - '+ FontDirectory/CMR10 known{/CMR10 findfont dup', + '- /UniqueID 5000793 def', # Changes the font name - '- /FontName /CMR10 def', - '+ /FontName /CMR10_Extend_500 def', + '- /FontName /CMR10 def', + '+ /FontName/CMR10_Extend_500 def', # Alters FontMatrix '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def', - '+ /FontMatrix [0.0005 0 0 0.001 0 0]readonly def'): + '+ /FontMatrix [0.0005 0 0 0.001 0 0] readonly def'): assert line in diff, 'diff to condensed font must contain %s' % line +def test_Type1Font_2(): + filename = os.path.join(os.path.dirname(__file__), 'data', + 'Courier10PitchBT-Bold.pfb') + font = t1f.Type1Font(filename) + assert font.prop['Weight'] == 'Bold' + assert font.prop['isFixedPitch'] + assert font.prop['Encoding'][65] == 'A' # the font uses StandardEncoding + (pos0, pos1), = font._pos['Encoding'] + assert font.parts[0][pos0:pos1] == b'/Encoding StandardEncoding' + assert font._abbr['ND'] == '|-' + + +def test_tokenize(): + data = (b'1234/abc false -9.81 Foo <<[0 1 2]<0 1ef a\t>>>\n' + b'(string with(nested\t\\) par)ens\\\\)') + # 1 2 x 2 xx1 + # 1 and 2 are matching parens, x means escaped character + n, w, num, kw, d = 'name', 'whitespace', 'number', 'keyword', 'delimiter' + b, s = 'boolean', 'string' + correct = [ + (num, 1234), (n, 'abc'), (w, ' '), (b, False), (w, ' '), (num, -9.81), + (w, ' '), (kw, 'Foo'), (w, ' '), (d, '<<'), (d, '['), (num, 0), + (w, ' '), (num, 1), (w, ' '), (num, 2), (d, ']'), (s, b'\x01\xef\xa0'), + (d, '>>'), (w, '\n'), (s, 'string with(nested\t) par)ens\\') + ] + correct_no_ws = [x for x in correct if x[0] != w] + + def convert(tokens): + return [(t.kind, t.value()) for t in tokens] + + assert convert(t1f._tokenize(data, False)) == correct + assert convert(t1f._tokenize(data, True)) == correct_no_ws + + def bin_after(n): + tokens = t1f._tokenize(data, True) + result = [] + for _ in range(n): + result.append(next(tokens)) + result.append(tokens.send(10)) + return convert(result) + + for n in range(1, len(correct_no_ws)): + result = bin_after(n) + assert result[:-1] == correct_no_ws[:n] + assert result[-1][0] == 'binary' + assert isinstance(result[-1][1], bytes) + + +def test_tokenize_errors(): + with pytest.raises(ValueError): + list(t1f._tokenize(b'1234 (this (string) is unterminated\\)', True)) + with pytest.raises(ValueError): + list(t1f._tokenize(b'/Foo<01234', True)) + with pytest.raises(ValueError): + list(t1f._tokenize(b'/Foo<01234abcg>/Bar', True)) + + def test_overprecision(): # We used to output too many digits in FontMatrix entries and # ItalicAngle, which could make Type-1 parsers unhappy. - filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb') + filename = os.path.join(os.path.dirname(__file__), 'data', 'cmr10.pfb') font = t1f.Type1Font(filename) slanted = font.transform({'slant': .167}) lines = slanted.parts[0].decode('ascii').splitlines() - matrix, = [line[line.index('[')+1:line.index(']')] - for line in lines if '/FontMatrix' in line] - angle, = [word + matrix, = (line[line.index('[')+1:line.index(']')] + for line in lines if '/FontMatrix' in line) + angle, = (word for line in lines if '/ItalicAngle' in line - for word in line.split() if word[0] in '-0123456789'] + for word in line.split() if word[0] in '-0123456789') # the following used to include 0.00016700000000000002 assert matrix == '0.001 0 0.000167 0.001 0 0' # and here we had -9.48090361795083 assert angle == '-9.4809' + + +def test_encrypt_decrypt_roundtrip(): + data = b'this is my plaintext \0\1\2\3' + encrypted = t1f.Type1Font._encrypt(data, 'eexec') + decrypted = t1f.Type1Font._decrypt(encrypted, 'eexec') + assert encrypted != decrypted + assert data == decrypted diff --git a/lib/matplotlib/tests/test_units.py b/lib/matplotlib/tests/test_units.py index 3f40a99a2f5a..d2350667e94f 100644 --- a/lib/matplotlib/tests/test_units.py +++ b/lib/matplotlib/tests/test_units.py @@ -4,8 +4,10 @@ import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal, image_comparison +import matplotlib.patches as mpatches import matplotlib.units as munits -from matplotlib.category import UnitData +from matplotlib.category import StrCategoryConverter, UnitData +from matplotlib.dates import DateConverter import numpy as np import pytest @@ -26,6 +28,9 @@ def to(self, new_units): else: return Quantity(self.magnitude, self.units) + def __copy__(self): + return Quantity(self.magnitude, self.units) + def __getattr__(self, attr): return getattr(self.magnitude, attr) @@ -67,15 +72,16 @@ def default_units(value, axis): return None qc.convert = MagicMock(side_effect=convert) - qc.axisinfo = MagicMock(side_effect=lambda u, a: munits.AxisInfo(label=u)) + qc.axisinfo = MagicMock(side_effect=lambda u, a: + munits.AxisInfo(label=u, default_limits=(0, 100))) qc.default_units = MagicMock(side_effect=default_units) return qc # Tests that the conversion machinery works properly for classes that # work as a facade over numpy arrays (like pint) -@image_comparison(['plot_pint.png'], remove_text=False, style='mpl20', - tol=0 if platform.machine() == 'x86_64' else 0.01) +@image_comparison(['plot_pint.png'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.03) def test_numpy_facade(quantity_converter): # use former defaults to match existing baseline image plt.rcParams['axes.formatter.limits'] = -7, 7 @@ -102,7 +108,7 @@ def test_numpy_facade(quantity_converter): # Tests gh-8908 @image_comparison(['plot_masked_units.png'], remove_text=True, style='mpl20', - tol=0 if platform.machine() == 'x86_64' else 0.01) + tol=0 if platform.machine() == 'x86_64' else 0.02) def test_plot_masked_units(): data = np.linspace(-5, 5) data_masked = np.ma.array(data, mask=(data > -2) & (data < 2)) @@ -130,7 +136,7 @@ def test_jpl_bar_units(): day = units.Duration("ET", 24.0 * 60.0 * 60.0) x = [0 * units.km, 1 * units.km, 2 * units.km] w = [1 * day, 2 * day, 3 * day] - b = units.Epoch("ET", dt=datetime(2009, 4, 25)) + b = units.Epoch("ET", dt=datetime(2009, 4, 26)) fig, ax = plt.subplots() ax.bar(x, w, bottom=b) ax.set_ylim([b - 1 * day, b + w[-1] + (1.001) * day]) @@ -145,13 +151,24 @@ def test_jpl_barh_units(): day = units.Duration("ET", 24.0 * 60.0 * 60.0) x = [0 * units.km, 1 * units.km, 2 * units.km] w = [1 * day, 2 * day, 3 * day] - b = units.Epoch("ET", dt=datetime(2009, 4, 25)) + b = units.Epoch("ET", dt=datetime(2009, 4, 26)) fig, ax = plt.subplots() ax.barh(x, w, left=b) ax.set_xlim([b - 1 * day, b + w[-1] + (1.001) * day]) +def test_jpl_datetime_units_consistent(): + import matplotlib.testing.jpl_units as units + units.register() + + dt = datetime(2009, 4, 26) + jpl = units.Epoch("ET", dt=dt) + dt_conv = munits.registry.get_converter(dt).convert(dt, None, None) + jpl_conv = munits.registry.get_converter(jpl).convert(jpl, None, None) + assert dt_conv == jpl_conv + + def test_empty_arrays(): # Check that plotting an empty array with a dtype works plt.scatter(np.array([], dtype='datetime64[ns]'), np.array([])) @@ -166,7 +183,15 @@ def test_scatter_element0_masked(): fig.canvas.draw() -@check_figures_equal(extensions=["png"]) +def test_errorbar_mixed_units(): + x = np.arange(10) + y = [datetime(2020, 5, i * 2 + 1) for i in x] + fig, ax = plt.subplots() + ax.errorbar(x, y, timedelta(days=0.5)) + fig.canvas.draw() + + +@check_figures_equal() def test_subclass(fig_test, fig_ref): class subdate(datetime): pass @@ -211,3 +236,118 @@ def test_shared_axis_categorical(): ax2.plot(d2.keys(), d2.values()) ax1.xaxis.set_units(UnitData(["c", "d"])) assert "c" in ax2.xaxis.get_units()._mapping.keys() + + +def test_explicit_converter(): + d1 = {"a": 1, "b": 2} + str_cat_converter = StrCategoryConverter() + str_cat_converter_2 = StrCategoryConverter() + date_converter = DateConverter() + + # Explicit is set + fig1, ax1 = plt.subplots() + ax1.xaxis.set_converter(str_cat_converter) + assert ax1.xaxis.get_converter() == str_cat_converter + # Explicit not overridden by implicit + ax1.plot(d1.keys(), d1.values()) + assert ax1.xaxis.get_converter() == str_cat_converter + # No error when called twice with equivalent input + ax1.xaxis.set_converter(str_cat_converter) + # Error when explicit called twice + with pytest.raises(RuntimeError): + ax1.xaxis.set_converter(str_cat_converter_2) + + fig2, ax2 = plt.subplots() + ax2.plot(d1.keys(), d1.values()) + + # No error when equivalent type is used + ax2.xaxis.set_converter(str_cat_converter) + + fig3, ax3 = plt.subplots() + ax3.plot(d1.keys(), d1.values()) + + # Warn when implicit overridden + with pytest.warns(): + ax3.xaxis.set_converter(date_converter) + + +def test_empty_default_limits(quantity_converter): + munits.registry[Quantity] = quantity_converter + fig, ax1 = plt.subplots() + ax1.xaxis.update_units(Quantity([10], "miles")) + fig.draw_without_rendering() + assert ax1.get_xlim() == (0, 100) + ax1.yaxis.update_units(Quantity([10], "miles")) + fig.draw_without_rendering() + assert ax1.get_ylim() == (0, 100) + + fig, ax = plt.subplots() + ax.axhline(30) + ax.plot(Quantity(np.arange(0, 3), "miles"), + Quantity(np.arange(0, 6, 2), "feet")) + fig.draw_without_rendering() + assert ax.get_xlim() == (0, 2) + assert ax.get_ylim() == (0, 30) + + fig, ax = plt.subplots() + ax.axvline(30) + ax.plot(Quantity(np.arange(0, 3), "miles"), + Quantity(np.arange(0, 6, 2), "feet")) + fig.draw_without_rendering() + assert ax.get_xlim() == (0, 30) + assert ax.get_ylim() == (0, 4) + + fig, ax = plt.subplots() + ax.xaxis.update_units(Quantity([10], "miles")) + ax.axhline(30) + fig.draw_without_rendering() + assert ax.get_xlim() == (0, 100) + assert ax.get_ylim() == (28.5, 31.5) + + fig, ax = plt.subplots() + ax.yaxis.update_units(Quantity([10], "miles")) + ax.axvline(30) + fig.draw_without_rendering() + assert ax.get_ylim() == (0, 100) + assert ax.get_xlim() == (28.5, 31.5) + + +# test array-like objects... +class Kernel: + def __init__(self, array): + self._array = np.asanyarray(array) + + def __array__(self, dtype=None, copy=None): + if dtype is not None and dtype != self._array.dtype: + if copy is not None and not copy: + raise ValueError( + f"Converting array from {self._array.dtype} to " + f"{dtype} requires a copy" + ) + + arr = np.asarray(self._array, dtype=dtype) + return (arr if not copy else np.copy(arr)) + + @property + def shape(self): + return self._array.shape + + +def test_plot_kernel(): + # just a smoketest that fail + kernel = Kernel([1, 2, 3, 4, 5]) + plt.plot(kernel) + + +def test_connection_patch_units(pd): + # tests that this doesn't raise an error + fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(10, 5)) + x = pd.Timestamp('2017-01-01T12') + ax1.axvline(x) + y = "test test" + ax2.axhline(y) + arr = mpatches.ConnectionPatch((x, 0), (0, y), + coordsA='data', coordsB='data', + axesA=ax1, axesB=ax2) + fig.add_artist(arr) + fig.draw_without_rendering() diff --git a/lib/matplotlib/tests/test_usetex.py b/lib/matplotlib/tests/test_usetex.py index 5e4d1ff7edba..95eb69325622 100644 --- a/lib/matplotlib/tests/test_usetex.py +++ b/lib/matplotlib/tests/test_usetex.py @@ -1,13 +1,19 @@ +import re +from tempfile import TemporaryFile + import numpy as np +from packaging.version import parse as parse_version import pytest import matplotlib as mpl +from matplotlib import dviread +from matplotlib.testing import _has_tex_package from matplotlib.testing.decorators import check_figures_equal, image_comparison +from matplotlib.testing._markers import needs_usetex import matplotlib.pyplot as plt -if not mpl.checkdep_usetex(True): - pytestmark = pytest.mark.skip('Missing TeX of Ghostscript or dvipng') +pytestmark = needs_usetex @image_comparison( @@ -16,8 +22,7 @@ style="mpl20") def test_usetex(): mpl.rcParams['text.usetex'] = True - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() kwargs = {"verticalalignment": "baseline", "size": 24, "bbox": dict(pad=0, edgecolor="k", facecolor="none")} ax.text(0.2, 0.7, @@ -38,13 +43,13 @@ def test_usetex(): ax.set_axis_off() -@check_figures_equal() +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_empty(fig_test, fig_ref): mpl.rcParams['text.usetex'] = True fig_test.text(.5, .5, "% a comment") -@check_figures_equal() +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_unicode_minus(fig_test, fig_ref): mpl.rcParams['text.usetex'] = True fig_test.text(.5, .5, "$-$") @@ -61,6 +66,21 @@ def test_mathdefault(): fig.canvas.draw() +@image_comparison(['eqnarray.png']) +def test_multiline_eqnarray(): + text = ( + r'\begin{eqnarray*}' + r'foo\\' + r'bar\\' + r'baz\\' + r'\end{eqnarray*}' + ) + + fig = plt.figure(figsize=(1, 1)) + fig.text(0.5, 0.5, text, usetex=True, + horizontalalignment='center', verticalalignment='center') + + @pytest.mark.parametrize("fontsize", [8, 10, 12]) def test_minus_no_descent(fontsize): # Test special-casing of minus descent in DviFont._height_depth_of, by @@ -71,7 +91,7 @@ def test_minus_no_descent(fontsize): heights = {} fig = plt.figure() for vals in [(1,), (-1,), (-1, 1)]: - fig.clf() + fig.clear() for x in vals: fig.text(.5, .5, f"${x}$", usetex=True) fig.canvas.draw() @@ -81,8 +101,158 @@ def test_minus_no_descent(fontsize): assert len({*heights.values()}) == 1 -def test_textcomp_full(): - plt.rcParams["text.latex.preamble"] = r"\usepackage[full]{textcomp}" +@pytest.mark.parametrize('pkg', ['xcolor', 'chemformula']) +def test_usetex_packages(pkg): + if not _has_tex_package(pkg): + pytest.skip(f'{pkg} is not available') + mpl.rcParams['text.usetex'] = True + + fig = plt.figure() + text = fig.text(0.5, 0.5, "Some text 0123456789") + fig.canvas.draw() + + mpl.rcParams['text.latex.preamble'] = ( + r'\PassOptionsToPackage{dvipsnames}{xcolor}\usepackage{%s}' % pkg) + fig = plt.figure() + text2 = fig.text(0.5, 0.5, "Some text 0123456789") + fig.canvas.draw() + np.testing.assert_array_equal(text2.get_window_extent(), + text.get_window_extent()) + + +@pytest.mark.parametrize( + "preamble", + [r"\usepackage[full]{textcomp}", r"\usepackage{underscore}"], +) +def test_latex_pkg_already_loaded(preamble): + plt.rcParams["text.latex.preamble"] = preamble fig = plt.figure() fig.text(.5, .5, "hello, world", usetex=True) fig.canvas.draw() + + +def test_usetex_with_underscore(): + plt.rcParams["text.usetex"] = True + df = {"a_b": range(5)[::-1], "c": range(5)} + fig, ax = plt.subplots() + ax.plot("c", "a_b", data=df) + ax.legend() + ax.text(0, 0, "foo_bar", usetex=True) + plt.draw() + + +@pytest.mark.flaky(reruns=3) # Tends to hit a TeX cache lock on AppVeyor. +@pytest.mark.parametrize("fmt", ["pdf", "svg"]) +def test_missing_psfont(fmt, monkeypatch): + """An error is raised if a TeX font lacks a Type-1 equivalent""" + monkeypatch.setattr( + dviread.PsfontsMap, '__getitem__', + lambda self, k: dviread.PsFont( + texname=b'texfont', psname=b'Some Font', + effects=None, encoding=None, filename=None)) + mpl.rcParams['text.usetex'] = True + fig, ax = plt.subplots() + ax.text(0.5, 0.5, 'hello') + with TemporaryFile() as tmpfile, pytest.raises(ValueError): + fig.savefig(tmpfile, format=fmt) + + +def test_pdf_type1_font_subsetting(): + """Test that fonts in PDF output are properly subset.""" + pikepdf = pytest.importorskip("pikepdf") + + mpl.rcParams["text.usetex"] = True + mpl.rcParams["text.latex.preamble"] = r"\usepackage{amssymb}" + fig, ax = plt.subplots() + ax.text(0.2, 0.7, r"$\int_{-\infty}^{\aleph}\sqrt{\alpha\beta\gamma}\mathrm{d}x$") + ax.text(0.2, 0.5, r"$\mathfrak{x}\circledcirc\mathfrak{y}\in\mathbb{R}$") + + with TemporaryFile() as tmpfile: + fig.savefig(tmpfile, format="pdf") + tmpfile.seek(0) + pdf = pikepdf.Pdf.open(tmpfile) + + length = {} + page = pdf.pages[0] + for font_name, font in page.Resources.Font.items(): + assert font.Subtype == "/Type1", ( + f"Font {font_name}={font} is not a Type 1 font" + ) + + # Subsetted font names have a 6-character tag followed by a '+' + base_font = str(font["/BaseFont"]).removeprefix("/") + assert re.match(r"^[A-Z]{6}\+", base_font), ( + f"Font {font_name}={base_font} lacks a subset indicator tag" + ) + assert "/FontFile" in font.FontDescriptor, ( + f"Type 1 font {font_name}={base_font} is not embedded" + ) + _, original_name = base_font.split("+", 1) + length[original_name] = len(bytes(font["/FontDescriptor"]["/FontFile"])) + + print("Embedded font stream lengths:", length) + # We should have several fonts, each much smaller than the original. + # I get under 10kB on my system for each font, but allow 15kB in case + # of differences in the font files. + assert { + 'CMEX10', + 'CMMI12', + 'CMR12', + 'CMSY10', + 'CMSY8', + 'EUFM10', + 'MSAM10', + 'MSBM10', + }.issubset(length), "Missing expected fonts in the PDF" + for font_name, length in length.items(): + assert length < 15_000, ( + f"Font {font_name}={length} is larger than expected" + ) + + # For comparison, lengths without subsetting on my system: + # 'CMEX10': 29686 + # 'CMMI12': 36176 + # 'CMR12': 32157 + # 'CMSY10': 32004 + # 'CMSY8': 32061 + # 'EUFM10': 20546 + # 'MSAM10': 31199 + # 'MSBM10': 34129 + + +try: + _old_gs_version = mpl._get_executable_info('gs').version < parse_version('9.55') +except mpl.ExecutableNotFoundError: + _old_gs_version = True + + +@image_comparison(baseline_images=['rotation'], extensions=['eps', 'pdf', 'png', 'svg'], + style='mpl20', tol=3.91 if _old_gs_version else 0) +def test_rotation(): + mpl.rcParams['text.usetex'] = True + + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1]) + ax.set(xlim=[-0.5, 5], xticks=[], ylim=[-0.5, 3], yticks=[], frame_on=False) + + text = {val: val[0] for val in ['top', 'center', 'bottom', 'left', 'right']} + text['baseline'] = 'B' + text['center_baseline'] = 'C' + + for i, va in enumerate(['top', 'center', 'bottom', 'baseline', 'center_baseline']): + for j, ha in enumerate(['left', 'center', 'right']): + for k, angle in enumerate([0, 90, 180, 270]): + k //= 2 + x = i + k / 2 + y = j + k / 2 + ax.plot(x, y, '+', c=f'C{k}', markersize=20, markeredgewidth=0.5) + # 'My' checks full height letters plus descenders. + ax.text(x, y, f"$\\mathrm{{My {text[ha]}{text[va]} {angle}}}$", + rotation=angle, horizontalalignment=ha, verticalalignment=va) + + +def test_unicode_sizing(): + tp = mpl.textpath.TextToPath() + scale1 = tp.get_glyphs_tex(mpl.font_manager.FontProperties(), "W")[0][0][3] + scale2 = tp.get_glyphs_tex(mpl.font_manager.FontProperties(), r"\textwon")[0][0][3] + assert scale1 == scale2 diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 22844fb68234..808863fd6a94 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1,24 +1,76 @@ +import functools +import io +import operator +from unittest import mock + +from matplotlib.backend_bases import MouseEvent +import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison -from matplotlib.testing.widgets import do_event, get_ax +from matplotlib.testing.decorators import check_figures_equal, image_comparison +from matplotlib.testing.widgets import (click_and_drag, do_event, get_ax, + mock_event, noop) +import numpy as np from numpy.testing import assert_allclose import pytest -def check_rectangle(**kwargs): - ax = get_ax() - - def onselect(epress, erelease): - ax._got_onselect = True - assert epress.xdata == 100 - assert epress.ydata == 100 - assert erelease.xdata == 199 - assert erelease.ydata == 199 - - tool = widgets.RectangleSelector(ax, onselect, **kwargs) +@pytest.fixture +def ax(): + return get_ax() + + +def test_save_blitted_widget_as_pdf(): + from matplotlib.widgets import CheckButtons, RadioButtons + from matplotlib.cbook import _get_running_interactive_framework + if _get_running_interactive_framework() not in ['headless', None]: + pytest.xfail("Callback exceptions are not raised otherwise.") + + fig, ax = plt.subplots( + nrows=2, ncols=2, figsize=(5, 2), width_ratios=[1, 2] + ) + default_rb = RadioButtons(ax[0, 0], ['Apples', 'Oranges']) + styled_rb = RadioButtons( + ax[0, 1], ['Apples', 'Oranges'], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + radio_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']} + ) + + default_cb = CheckButtons(ax[1, 0], ['Apples', 'Oranges'], + actives=[True, True]) + styled_cb = CheckButtons( + ax[1, 1], ['Apples', 'Oranges'], + actives=[True, True], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + frame_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}, + check_props={'color': ['darkred', 'darkorange']} + ) + + ax[0, 0].set_title('Default') + ax[0, 1].set_title('Stylized') + # force an Agg render + fig.canvas.draw() + # force a pdf save + with io.BytesIO() as result_after: + fig.savefig(result_after, format='pdf') + + +@pytest.mark.parametrize('kwargs', [ + dict(), + dict(useblit=True, button=1), + dict(minspanx=10, minspany=10, spancoords='pixels'), + dict(props=dict(fill=True)), +]) +def test_rectangle_selector(ax, kwargs): + onselect = mock.Mock(spec=noop, return_value=None) + + tool = widgets.RectangleSelector(ax, onselect=onselect, **kwargs) do_event(tool, 'press', xdata=100, ydata=100, button=1) do_event(tool, 'onmove', xdata=199, ydata=199, button=1) @@ -31,195 +83,1127 @@ def onselect(epress, erelease): [100, 199, 199, 100, 100]], err_msg=tool.geometry) - assert ax._got_onselect - - -def test_rectangle_selector(): - check_rectangle() - check_rectangle(drawtype='line', useblit=False) - check_rectangle(useblit=True, button=1) - check_rectangle(drawtype='none', minspanx=10, minspany=10) - check_rectangle(minspanx=10, minspany=10, spancoords='pixels') - check_rectangle(rectprops=dict(fill=True)) + onselect.assert_called_once() + (epress, erelease), kwargs = onselect.call_args + assert epress.xdata == 100 + assert epress.ydata == 100 + assert erelease.xdata == 199 + assert erelease.ydata == 199 + assert kwargs == {} + + +@pytest.mark.parametrize('spancoords', ['data', 'pixels']) +@pytest.mark.parametrize('minspanx, x1', [[0, 10], [1, 10.5], [1, 11]]) +@pytest.mark.parametrize('minspany, y1', [[0, 10], [1, 10.5], [1, 11]]) +def test_rectangle_minspan(ax, spancoords, minspanx, x1, minspany, y1): + + onselect = mock.Mock(spec=noop, return_value=None) + + x0, y0 = (10, 10) + if spancoords == 'pixels': + minspanx, minspany = (ax.transData.transform((x1, y1)) - + ax.transData.transform((x0, y0))) + + tool = widgets.RectangleSelector(ax, onselect=onselect, interactive=True, + spancoords=spancoords, + minspanx=minspanx, minspany=minspany) + # Too small to create a selector + click_and_drag(tool, start=(x0, x1), end=(y0, y1)) + assert not tool._selection_completed + onselect.assert_not_called() + + click_and_drag(tool, start=(20, 20), end=(30, 30)) + assert tool._selection_completed + onselect.assert_called_once() + + # Too small to create a selector. Should clear existing selector, and + # trigger onselect because there was a preexisting selector + onselect.reset_mock() + click_and_drag(tool, start=(x0, y0), end=(x1, y1)) + assert not tool._selection_completed + onselect.assert_called_once() + (epress, erelease), kwargs = onselect.call_args + assert epress.xdata == x0 + assert epress.ydata == y0 + assert erelease.xdata == x1 + assert erelease.ydata == y1 + assert kwargs == {} + + +@pytest.mark.parametrize('drag_from_anywhere, new_center', + [[True, (60, 75)], + [False, (30, 20)]]) +def test_rectangle_drag(ax, drag_from_anywhere, new_center): + tool = widgets.RectangleSelector(ax, interactive=True, + drag_from_anywhere=drag_from_anywhere) + # Create rectangle + click_and_drag(tool, start=(0, 10), end=(100, 120)) + assert tool.center == (50, 65) + # Drag inside rectangle, but away from centre handle + # + # If drag_from_anywhere == True, this will move the rectangle by (10, 10), + # giving it a new center of (60, 75) + # + # If drag_from_anywhere == False, this will create a new rectangle with + # center (30, 20) + click_and_drag(tool, start=(25, 15), end=(35, 25)) + assert tool.center == new_center + # Check that in both cases, dragging outside the rectangle draws a new + # rectangle + click_and_drag(tool, start=(175, 185), end=(185, 195)) + assert tool.center == (180, 190) + + +def test_rectangle_selector_set_props_handle_props(ax): + tool = widgets.RectangleSelector(ax, interactive=True, + props=dict(facecolor='b', alpha=0.2), + handle_props=dict(alpha=0.5)) + # Create rectangle + click_and_drag(tool, start=(0, 10), end=(100, 120)) + + artist = tool._selection_artist + assert artist.get_facecolor() == mcolors.to_rgba('b', alpha=0.2) + tool.set_props(facecolor='r', alpha=0.3) + assert artist.get_facecolor() == mcolors.to_rgba('r', alpha=0.3) + + for artist in tool._handles_artists: + assert artist.get_markeredgecolor() == 'black' + assert artist.get_alpha() == 0.5 + tool.set_handle_props(markeredgecolor='r', alpha=0.3) + for artist in tool._handles_artists: + assert artist.get_markeredgecolor() == 'r' + assert artist.get_alpha() == 0.3 + + +def test_rectangle_resize(ax): + tool = widgets.RectangleSelector(ax, interactive=True) + # Create rectangle + click_and_drag(tool, start=(0, 10), end=(100, 120)) + assert tool.extents == (0.0, 100.0, 10.0, 120.0) + + # resize NE handle + extents = tool.extents + xdata, ydata = extents[1], extents[3] + xdata_new, ydata_new = xdata + 10, ydata + 5 + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert tool.extents == (extents[0], xdata_new, extents[2], ydata_new) + + # resize E handle + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdata_new, ydata_new = xdata + 10, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert tool.extents == (extents[0], xdata_new, extents[2], extents[3]) + + # resize W handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdata_new, ydata_new = xdata + 15, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert tool.extents == (xdata_new, extents[1], extents[2], extents[3]) + + # resize SW handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + xdata_new, ydata_new = xdata + 20, ydata + 25 + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert tool.extents == (xdata_new, extents[1], ydata_new, extents[3]) + + +def test_rectangle_add_state(ax): + tool = widgets.RectangleSelector(ax, interactive=True) + # Create rectangle + click_and_drag(tool, start=(70, 65), end=(125, 130)) + with pytest.raises(ValueError): + tool.add_state('unsupported_state') -def test_ellipse(): + with pytest.raises(ValueError): + tool.add_state('clear') + tool.add_state('move') + tool.add_state('square') + tool.add_state('center') + + +@pytest.mark.parametrize('add_state', [True, False]) +def test_rectangle_resize_center(ax, add_state): + tool = widgets.RectangleSelector(ax, interactive=True) + # Create rectangle + click_and_drag(tool, start=(70, 65), end=(125, 130)) + assert tool.extents == (70.0, 125.0, 65.0, 130.0) + + if add_state: + tool.add_state('center') + use_key = None + else: + use_key = 'control' + + # resize NE handle + extents = tool.extents + xdata, ydata = extents[1], extents[3] + xdiff, ydiff = 10, 5 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0] - xdiff, xdata_new, + extents[2] - ydiff, ydata_new) + + # resize E handle + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 10 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0] - xdiff, xdata_new, + extents[2], extents[3]) + + # resize E handle negative diff + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -20 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0] - xdiff, xdata_new, + extents[2], extents[3]) + + # resize W handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 15 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (xdata_new, extents[1] - xdiff, + extents[2], extents[3]) + + # resize W handle negative diff + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -25 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (xdata_new, extents[1] - xdiff, + extents[2], extents[3]) + + # resize SW handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + xdiff, ydiff = 20, 25 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (xdata_new, extents[1] - xdiff, + ydata_new, extents[3] - ydiff) + + +@pytest.mark.parametrize('add_state', [True, False]) +def test_rectangle_resize_square(ax, add_state): + tool = widgets.RectangleSelector(ax, interactive=True) + # Create rectangle + click_and_drag(tool, start=(70, 65), end=(120, 115)) + assert tool.extents == (70.0, 120.0, 65.0, 115.0) + + if add_state: + tool.add_state('square') + use_key = None + else: + use_key = 'shift' + + # resize NE handle + extents = tool.extents + xdata, ydata = extents[1], extents[3] + xdiff, ydiff = 10, 5 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0], xdata_new, + extents[2], extents[3] + xdiff) + + # resize E handle + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 10 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0], xdata_new, + extents[2], extents[3] + xdiff) + + # resize E handle negative diff + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -20 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0], xdata_new, + extents[2], extents[3] + xdiff) + + # resize W handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 15 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (xdata_new, extents[1], + extents[2], extents[3] - xdiff) + + # resize W handle negative diff + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -25 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (xdata_new, extents[1], + extents[2], extents[3] - xdiff) + + # resize SW handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + xdiff, ydiff = 20, 25 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new), + key=use_key) + assert tool.extents == (extents[0] + ydiff, extents[1], + ydata_new, extents[3]) + + +def test_rectangle_resize_square_center(ax): + tool = widgets.RectangleSelector(ax, interactive=True) + # Create rectangle + click_and_drag(tool, start=(70, 65), end=(120, 115)) + tool.add_state('square') + tool.add_state('center') + assert_allclose(tool.extents, (70.0, 120.0, 65.0, 115.0)) + + # resize NE handle + extents = tool.extents + xdata, ydata = extents[1], extents[3] + xdiff, ydiff = 10, 5 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new, + extents[2] - xdiff, extents[3] + xdiff)) + + # resize E handle + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 10 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new, + extents[2] - xdiff, extents[3] + xdiff)) + + # resize E handle negative diff + extents = tool.extents + xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -20 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new, + extents[2] - xdiff, extents[3] + xdiff)) + + # resize W handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = 5 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (xdata_new, extents[1] - xdiff, + extents[2] + xdiff, extents[3] - xdiff)) + + # resize W handle negative diff + extents = tool.extents + xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2 + xdiff = -25 + xdata_new, ydata_new = xdata + xdiff, ydata + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (xdata_new, extents[1] - xdiff, + extents[2] + xdiff, extents[3] - xdiff)) + + # resize SW handle + extents = tool.extents + xdata, ydata = extents[0], extents[2] + xdiff, ydiff = 20, 25 + xdata_new, ydata_new = xdata + xdiff, ydata + ydiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, (extents[0] + ydiff, extents[1] - ydiff, + ydata_new, extents[3] - ydiff)) + + +@pytest.mark.parametrize('selector_class', + [widgets.RectangleSelector, widgets.EllipseSelector]) +def test_rectangle_rotate(ax, selector_class): + tool = selector_class(ax, interactive=True) + # Draw rectangle + click_and_drag(tool, start=(100, 100), end=(130, 140)) + assert tool.extents == (100, 130, 100, 140) + assert len(tool._state) == 0 + + # Rotate anticlockwise using top-right corner + do_event(tool, 'on_key_press', key='r') + assert tool._state == {'rotate'} + assert len(tool._state) == 1 + click_and_drag(tool, start=(130, 140), end=(120, 145)) + do_event(tool, 'on_key_press', key='r') + assert len(tool._state) == 0 + # Extents shouldn't change (as shape of rectangle hasn't changed) + assert tool.extents == (100, 130, 100, 140) + assert_allclose(tool.rotation, 25.56, atol=0.01) + tool.rotation = 45 + assert tool.rotation == 45 + # Corners should move + assert_allclose(tool.corners, + np.array([[118.53, 139.75, 111.46, 90.25], + [95.25, 116.46, 144.75, 123.54]]), atol=0.01) + + # Scale using top-right corner + click_and_drag(tool, start=(110, 145), end=(110, 160)) + assert_allclose(tool.extents, (100, 139.75, 100, 151.82), atol=0.01) + + if selector_class == widgets.RectangleSelector: + with pytest.raises(ValueError): + tool._selection_artist.rotation_point = 'unvalid_value' + + +def test_rectangle_add_remove_set(ax): + tool = widgets.RectangleSelector(ax, interactive=True) + # Draw rectangle + click_and_drag(tool, start=(100, 100), end=(130, 140)) + assert tool.extents == (100, 130, 100, 140) + assert len(tool._state) == 0 + for state in ['rotate', 'square', 'center']: + tool.add_state(state) + assert len(tool._state) == 1 + tool.remove_state(state) + assert len(tool._state) == 0 + + +@pytest.mark.parametrize('use_data_coordinates', [False, True]) +def test_rectangle_resize_square_center_aspect(ax, use_data_coordinates): + ax.set_aspect(0.8) + + tool = widgets.RectangleSelector(ax, interactive=True, + use_data_coordinates=use_data_coordinates) + # Create rectangle + click_and_drag(tool, start=(70, 65), end=(120, 115)) + assert tool.extents == (70.0, 120.0, 65.0, 115.0) + tool.add_state('square') + tool.add_state('center') + + if use_data_coordinates: + # resize E handle + extents = tool.extents + xdata, ydata, width = extents[1], extents[3], extents[1] - extents[0] + xdiff, ycenter = 10, extents[2] + (extents[3] - extents[2]) / 2 + xdata_new, ydata_new = xdata + xdiff, ydata + ychange = width / 2 + xdiff + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new, + ycenter - ychange, ycenter + ychange]) + else: + # resize E handle + extents = tool.extents + xdata, ydata = extents[1], extents[3] + xdiff = 10 + xdata_new, ydata_new = xdata + xdiff, ydata + ychange = xdiff * 1 / tool._aspect_ratio_correction + click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new)) + assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new, + 46.25, 133.75]) + + +def test_ellipse(ax): """For ellipse, test out the key modifiers""" - ax = get_ax() - - def onselect(epress, erelease): - pass - - tool = widgets.EllipseSelector(ax, onselect=onselect, - maxdist=10, interactive=True) + tool = widgets.EllipseSelector(ax, grab_range=10, interactive=True) tool.extents = (100, 150, 100, 150) # drag the rectangle - do_event(tool, 'press', xdata=10, ydata=10, button=1, - key=' ') - - do_event(tool, 'onmove', xdata=30, ydata=30, button=1) - do_event(tool, 'release', xdata=30, ydata=30, button=1) + click_and_drag(tool, start=(125, 125), end=(145, 145)) assert tool.extents == (120, 170, 120, 170) # create from center - do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1, - key='control') - do_event(tool, 'press', xdata=100, ydata=100, button=1) - do_event(tool, 'onmove', xdata=125, ydata=125, button=1) - do_event(tool, 'release', xdata=125, ydata=125, button=1) - do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1, - key='control') + click_and_drag(tool, start=(100, 100), end=(125, 125), key='control') assert tool.extents == (75, 125, 75, 125) # create a square - do_event(tool, 'on_key_press', xdata=10, ydata=10, button=1, - key='shift') - do_event(tool, 'press', xdata=10, ydata=10, button=1) - do_event(tool, 'onmove', xdata=35, ydata=30, button=1) - do_event(tool, 'release', xdata=35, ydata=30, button=1) - do_event(tool, 'on_key_release', xdata=10, ydata=10, button=1, - key='shift') + click_and_drag(tool, start=(10, 10), end=(35, 30), key='shift') extents = [int(e) for e in tool.extents] - assert extents == [10, 35, 10, 34] + assert extents == [10, 35, 10, 35] # create a square from center - do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1, - key='ctrl+shift') - do_event(tool, 'press', xdata=100, ydata=100, button=1) - do_event(tool, 'onmove', xdata=125, ydata=130, button=1) - do_event(tool, 'release', xdata=125, ydata=130, button=1) - do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1, - key='ctrl+shift') + click_and_drag(tool, start=(100, 100), end=(125, 130), key='ctrl+shift') extents = [int(e) for e in tool.extents] - assert extents == [70, 129, 70, 130] + assert extents == [70, 130, 70, 130] assert tool.geometry.shape == (2, 73) assert_allclose(tool.geometry[:, 0], [70., 100]) -def test_rectangle_handles(): - ax = get_ax() - - def onselect(epress, erelease): - pass - - tool = widgets.RectangleSelector(ax, onselect=onselect, - maxdist=10, interactive=True) +def test_rectangle_handles(ax): + tool = widgets.RectangleSelector(ax, grab_range=10, interactive=True, + handle_props={'markerfacecolor': 'r', + 'markeredgecolor': 'b'}) tool.extents = (100, 150, 100, 150) - assert tool.corners == ( - (100, 150, 150, 100), (100, 100, 150, 150)) + assert_allclose(tool.corners, ((100, 150, 150, 100), (100, 100, 150, 150))) assert tool.extents == (100, 150, 100, 150) - assert tool.edge_centers == ( - (100, 125.0, 150, 125.0), (125.0, 100, 125.0, 150)) + assert_allclose(tool.edge_centers, + ((100, 125.0, 150, 125.0), (125.0, 100, 125.0, 150))) assert tool.extents == (100, 150, 100, 150) # grab a corner and move it - do_event(tool, 'press', xdata=100, ydata=100) - do_event(tool, 'onmove', xdata=120, ydata=120) - do_event(tool, 'release', xdata=120, ydata=120) + click_and_drag(tool, start=(100, 100), end=(120, 120)) assert tool.extents == (120, 150, 120, 150) # grab the center and move it - do_event(tool, 'press', xdata=132, ydata=132) - do_event(tool, 'onmove', xdata=120, ydata=120) - do_event(tool, 'release', xdata=120, ydata=120) + click_and_drag(tool, start=(132, 132), end=(120, 120)) assert tool.extents == (108, 138, 108, 138) # create a new rectangle - do_event(tool, 'press', xdata=10, ydata=10) - do_event(tool, 'onmove', xdata=100, ydata=100) - do_event(tool, 'release', xdata=100, ydata=100) + click_and_drag(tool, start=(10, 10), end=(100, 100)) assert tool.extents == (10, 100, 10, 100) + # Check that marker_props worked. + assert mcolors.same_color( + tool._corner_handles.artists[0].get_markerfacecolor(), 'r') + assert mcolors.same_color( + tool._corner_handles.artists[0].get_markeredgecolor(), 'b') -def check_span(*args, **kwargs): - ax = get_ax() - def onselect(vmin, vmax): - ax._got_onselect = True - assert vmin == 100 - assert vmax == 150 +@pytest.mark.parametrize('interactive', [True, False]) +def test_rectangle_selector_onselect(ax, interactive): + # check when press and release events take place at the same position + onselect = mock.Mock(spec=noop, return_value=None) + + tool = widgets.RectangleSelector(ax, onselect=onselect, interactive=interactive) + # move outside of axis + click_and_drag(tool, start=(100, 110), end=(150, 120)) + + onselect.assert_called_once() + assert tool.extents == (100.0, 150.0, 110.0, 120.0) - def onmove(vmin, vmax): - assert vmin == 100 - assert vmax == 125 - ax._got_on_move = True + onselect.reset_mock() + click_and_drag(tool, start=(10, 100), end=(10, 100)) + onselect.assert_called_once() - if 'onmove_callback' in kwargs: + +@pytest.mark.parametrize('ignore_event_outside', [True, False]) +def test_rectangle_selector_ignore_outside(ax, ignore_event_outside): + onselect = mock.Mock(spec=noop, return_value=None) + + tool = widgets.RectangleSelector(ax, onselect=onselect, + ignore_event_outside=ignore_event_outside) + click_and_drag(tool, start=(100, 110), end=(150, 120)) + onselect.assert_called_once() + assert tool.extents == (100.0, 150.0, 110.0, 120.0) + + onselect.reset_mock() + # Trigger event outside of span + click_and_drag(tool, start=(150, 150), end=(160, 160)) + if ignore_event_outside: + # event have been ignored and span haven't changed. + onselect.assert_not_called() + assert tool.extents == (100.0, 150.0, 110.0, 120.0) + else: + # A new shape is created + onselect.assert_called_once() + assert tool.extents == (150.0, 160.0, 150.0, 160.0) + + +@pytest.mark.parametrize('orientation, onmove_callback, kwargs', [ + ('horizontal', False, dict(minspan=10, useblit=True)), + ('vertical', True, dict(button=1)), + ('horizontal', False, dict(props=dict(fill=True))), + ('horizontal', False, dict(interactive=True)), +]) +def test_span_selector(ax, orientation, onmove_callback, kwargs): + onselect = mock.Mock(spec=noop, return_value=None) + onmove = mock.Mock(spec=noop, return_value=None) + if onmove_callback: kwargs['onmove_callback'] = onmove - tool = widgets.SpanSelector(ax, onselect, *args, **kwargs) + # While at it, also test that span selectors work in the presence of twin axes on + # top of the axes that contain the selector. Note that we need to unforce the axes + # aspect here, otherwise the twin axes forces the original axes' limits (to respect + # aspect=1) which makes some of the values below go out of bounds. + ax.set_aspect("auto") + tax = ax.twinx() + + tool = widgets.SpanSelector(ax, onselect, orientation, **kwargs) do_event(tool, 'press', xdata=100, ydata=100, button=1) - do_event(tool, 'onmove', xdata=125, ydata=125, button=1) - do_event(tool, 'release', xdata=150, ydata=150, button=1) + # move outside of axis + do_event(tool, 'onmove', xdata=199, ydata=199, button=1) + do_event(tool, 'release', xdata=250, ydata=250, button=1) + + onselect.assert_called_once_with(100, 199) + if onmove_callback: + onmove.assert_called_once_with(100, 199) + + +@pytest.mark.parametrize('interactive', [True, False]) +def test_span_selector_onselect(ax, interactive): + onselect = mock.Mock(spec=noop, return_value=None) + + tool = widgets.SpanSelector(ax, onselect, 'horizontal', + interactive=interactive) + # move outside of axis + click_and_drag(tool, start=(100, 100), end=(150, 100)) + onselect.assert_called_once() + assert tool.extents == (100, 150) + + onselect.reset_mock() + click_and_drag(tool, start=(10, 100), end=(10, 100)) + onselect.assert_called_once() + + +@pytest.mark.parametrize('ignore_event_outside', [True, False]) +def test_span_selector_ignore_outside(ax, ignore_event_outside): + onselect = mock.Mock(spec=noop, return_value=None) + onmove = mock.Mock(spec=noop, return_value=None) + + tool = widgets.SpanSelector(ax, onselect, 'horizontal', + onmove_callback=onmove, + ignore_event_outside=ignore_event_outside) + click_and_drag(tool, start=(100, 100), end=(125, 125)) + onselect.assert_called_once() + onmove.assert_called_once() + assert tool.extents == (100, 125) + + onselect.reset_mock() + onmove.reset_mock() + # Trigger event outside of span + click_and_drag(tool, start=(150, 150), end=(160, 160)) + if ignore_event_outside: + # event have been ignored and span haven't changed. + onselect.assert_not_called() + onmove.assert_not_called() + assert tool.extents == (100, 125) + else: + # A new shape is created + onselect.assert_called_once() + onmove.assert_called_once() + assert tool.extents == (150, 160) + + +@pytest.mark.parametrize('drag_from_anywhere', [True, False]) +def test_span_selector_drag(ax, drag_from_anywhere): + # Create span + tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal', + interactive=True, + drag_from_anywhere=drag_from_anywhere) + click_and_drag(tool, start=(10, 10), end=(100, 120)) + assert tool.extents == (10, 100) + # Drag inside span + # + # If drag_from_anywhere == True, this will move the span by 10, + # giving new value extents = 20, 110 + # + # If drag_from_anywhere == False, this will create a new span with + # value extents = 25, 35 + click_and_drag(tool, start=(25, 15), end=(35, 25)) + if drag_from_anywhere: + assert tool.extents == (20, 110) + else: + assert tool.extents == (25, 35) + + # Check that in both cases, dragging outside the span draws a new span + click_and_drag(tool, start=(175, 185), end=(185, 195)) + assert tool.extents == (175, 185) + + +def test_span_selector_direction(ax): + tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal', + interactive=True) + assert tool.direction == 'horizontal' + assert tool._edge_handles.direction == 'horizontal' - assert ax._got_onselect + with pytest.raises(ValueError): + tool = widgets.SpanSelector(ax, onselect=noop, + direction='invalid_direction') - if 'onmove_callback' in kwargs: - assert ax._got_on_move + tool.direction = 'vertical' + assert tool.direction == 'vertical' + assert tool._edge_handles.direction == 'vertical' + with pytest.raises(ValueError): + tool.direction = 'invalid_string' + + +def test_span_selector_set_props_handle_props(ax): + tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal', + interactive=True, + props=dict(facecolor='b', alpha=0.2), + handle_props=dict(alpha=0.5)) + # Create rectangle + click_and_drag(tool, start=(0, 10), end=(100, 120)) + + artist = tool._selection_artist + assert artist.get_facecolor() == mcolors.to_rgba('b', alpha=0.2) + tool.set_props(facecolor='r', alpha=0.3) + assert artist.get_facecolor() == mcolors.to_rgba('r', alpha=0.3) + + for artist in tool._handles_artists: + assert artist.get_color() == 'b' + assert artist.get_alpha() == 0.5 + tool.set_handle_props(color='r', alpha=0.3) + for artist in tool._handles_artists: + assert artist.get_color() == 'r' + assert artist.get_alpha() == 0.3 + + +@pytest.mark.parametrize('selector', ['span', 'rectangle']) +def test_selector_clear(ax, selector): + kwargs = dict(ax=ax, interactive=True) + if selector == 'span': + Selector = widgets.SpanSelector + kwargs['direction'] = 'horizontal' + kwargs['onselect'] = noop + else: + Selector = widgets.RectangleSelector + + tool = Selector(**kwargs) + click_and_drag(tool, start=(10, 10), end=(100, 120)) + + # press-release event outside the selector to clear the selector + click_and_drag(tool, start=(130, 130), end=(130, 130)) + assert not tool._selection_completed + + kwargs['ignore_event_outside'] = True + tool = Selector(**kwargs) + assert tool.ignore_event_outside + click_and_drag(tool, start=(10, 10), end=(100, 120)) + + # press-release event outside the selector ignored + click_and_drag(tool, start=(130, 130), end=(130, 130)) + assert tool._selection_completed + + do_event(tool, 'on_key_press', key='escape') + assert not tool._selection_completed + + +@pytest.mark.parametrize('selector', ['span', 'rectangle']) +def test_selector_clear_method(ax, selector): + if selector == 'span': + tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal', + interactive=True, + ignore_event_outside=True) + else: + tool = widgets.RectangleSelector(ax, interactive=True) + click_and_drag(tool, start=(10, 10), end=(100, 120)) + assert tool._selection_completed + assert tool.get_visible() + if selector == 'span': + assert tool.extents == (10, 100) + + tool.clear() + assert not tool._selection_completed + assert not tool.get_visible() + + # Do another cycle of events to make sure we can + click_and_drag(tool, start=(10, 10), end=(50, 120)) + assert tool._selection_completed + assert tool.get_visible() + if selector == 'span': + assert tool.extents == (10, 50) + + +def test_span_selector_add_state(ax): + tool = widgets.SpanSelector(ax, noop, 'horizontal', + interactive=True) -def test_span_selector(): - check_span('horizontal', minspan=10, useblit=True) - check_span('vertical', onmove_callback=True, button=1) - check_span('horizontal', rectprops=dict(fill=True)) + with pytest.raises(ValueError): + tool.add_state('unsupported_state') + with pytest.raises(ValueError): + tool.add_state('center') + with pytest.raises(ValueError): + tool.add_state('square') + tool.add_state('move') -def check_lasso_selector(**kwargs): - ax = get_ax() - def onselect(verts): +def test_tool_line_handle(ax): + positions = [20, 30, 50] + tool_line_handle = widgets.ToolLineHandles(ax, positions, 'horizontal', + useblit=False) + + for artist in tool_line_handle.artists: + assert not artist.get_animated() + assert not artist.get_visible() + + tool_line_handle.set_visible(True) + tool_line_handle.set_animated(True) + + for artist in tool_line_handle.artists: + assert artist.get_animated() + assert artist.get_visible() + + assert tool_line_handle.positions == positions + + +@pytest.mark.parametrize('direction', ("horizontal", "vertical")) +def test_span_selector_bound(direction): + fig, ax = plt.subplots(1, 1) + ax.plot([10, 20], [10, 30]) + fig.canvas.draw() + x_bound = ax.get_xbound() + y_bound = ax.get_ybound() + + tool = widgets.SpanSelector(ax, print, direction, interactive=True) + assert ax.get_xbound() == x_bound + assert ax.get_ybound() == y_bound + + bound = x_bound if direction == 'horizontal' else y_bound + assert tool._edge_handles.positions == list(bound) + + press_data = (10.5, 11.5) + move_data = (11, 13) # Updating selector is done in onmove + release_data = move_data + click_and_drag(tool, start=press_data, end=move_data) + + assert ax.get_xbound() == x_bound + assert ax.get_ybound() == y_bound + + index = 0 if direction == 'horizontal' else 1 + handle_positions = [press_data[index], release_data[index]] + assert tool._edge_handles.positions == handle_positions + + +@pytest.mark.backend('QtAgg', skip_on_importerror=True) +def test_span_selector_animated_artists_callback(): + """Check that the animated artists changed in callbacks are updated.""" + x = np.linspace(0, 2 * np.pi, 100) + values = np.sin(x) + + fig, ax = plt.subplots() + ln, = ax.plot(x, values, animated=True) + ln2, = ax.plot([], animated=True) + + # spin the event loop to let the backend process any pending operations + # before drawing artists + # See blitting tutorial + plt.pause(0.1) + ax.draw_artist(ln) + fig.canvas.blit(fig.bbox) + + def mean(vmin, vmax): + # Return mean of values in x between *vmin* and *vmax* + indmin, indmax = np.searchsorted(x, (vmin, vmax)) + v = values[indmin:indmax].mean() + ln2.set_data(x, np.full_like(x, v)) + + span = widgets.SpanSelector(ax, mean, direction='horizontal', + onmove_callback=mean, + interactive=True, + drag_from_anywhere=True, + useblit=True) + + # Add span selector and check that the line is draw after it was updated + # by the callback + press_data = [1, 2] + move_data = [2, 2] + do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1) + do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1) + assert span._get_animated_artists() == (ln, ln2) + assert ln.stale is False + assert ln2.stale + assert_allclose(ln2.get_ydata(), 0.9547335049088455) + span.update() + assert ln2.stale is False + + # Change span selector and check that the line is drawn/updated after its + # value was updated by the callback + press_data = [4, 0] + move_data = [5, 2] + release_data = [5, 2] + do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1) + do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1) + assert ln.stale is False + assert ln2.stale + assert_allclose(ln2.get_ydata(), -0.9424150707548072) + do_event(span, 'release', xdata=release_data[0], + ydata=release_data[1], button=1) + assert ln2.stale is False + + +def test_snapping_values_span_selector(ax): + def onselect(*args): + pass + + tool = widgets.SpanSelector(ax, onselect, direction='horizontal',) + snap_function = tool._snap + + snap_values = np.linspace(0, 5, 11) + values = np.array([-0.1, 0.1, 0.2, 0.5, 0.6, 0.7, 0.9, 4.76, 5.0, 5.5]) + expect = np.array([00.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 5.00, 5.0, 5.0]) + values = snap_function(values, snap_values) + assert_allclose(values, expect) + + +def test_span_selector_snap(ax): + def onselect(vmin, vmax): ax._got_onselect = True - assert verts == [(100, 100), (125, 125), (150, 150)] - tool = widgets.LassoSelector(ax, onselect, **kwargs) + snap_values = np.arange(50) * 4 + + tool = widgets.SpanSelector(ax, onselect, direction='horizontal', + snap_values=snap_values) + tool.extents = (17, 35) + assert tool.extents == (16, 36) + + tool.snap_values = None + assert tool.snap_values is None + tool.extents = (17, 35) + assert tool.extents == (17, 35) + + +def test_span_selector_extents(ax): + tool = widgets.SpanSelector( + ax, lambda a, b: None, "horizontal", ignore_event_outside=True + ) + tool.extents = (5, 10) + + assert tool.extents == (5, 10) + assert tool._selection_completed + + # Since `ignore_event_outside=True`, this event should be ignored + press_data = (12, 14) + release_data = (20, 14) + click_and_drag(tool, start=press_data, end=release_data) + + assert tool.extents == (5, 10) + + +@pytest.mark.parametrize('kwargs', [ + dict(), + dict(useblit=False, props=dict(color='red')), + dict(useblit=True, button=1), +]) +def test_lasso_selector(ax, kwargs): + onselect = mock.Mock(spec=noop, return_value=None) + + tool = widgets.LassoSelector(ax, onselect=onselect, **kwargs) do_event(tool, 'press', xdata=100, ydata=100, button=1) do_event(tool, 'onmove', xdata=125, ydata=125, button=1) do_event(tool, 'release', xdata=150, ydata=150, button=1) - assert ax._got_onselect + onselect.assert_called_once_with([(100, 100), (125, 125), (150, 150)]) -def test_lasso_selector(): - check_lasso_selector() - check_lasso_selector(useblit=False, lineprops=dict(color='red')) - check_lasso_selector(useblit=True, button=1) +def test_lasso_selector_set_props(ax): + onselect = mock.Mock(spec=noop, return_value=None) + tool = widgets.LassoSelector(ax, onselect=onselect, + props=dict(color='b', alpha=0.2)) -def test_CheckButtons(): - ax = get_ax() - check = widgets.CheckButtons(ax, ('a', 'b', 'c'), (True, False, True)) + artist = tool._selection_artist + assert mcolors.same_color(artist.get_color(), 'b') + assert artist.get_alpha() == 0.2 + tool.set_props(color='r', alpha=0.3) + assert mcolors.same_color(artist.get_color(), 'r') + assert artist.get_alpha() == 0.3 + + +def test_lasso_set_props(ax): + onselect = mock.Mock(spec=noop, return_value=None) + tool = widgets.Lasso(ax, (100, 100), onselect) + line = tool.line + assert mcolors.same_color(line.get_color(), 'black') + assert line.get_linestyle() == '-' + assert line.get_lw() == 2 + tool = widgets.Lasso(ax, (100, 100), onselect, props=dict( + linestyle='-', color='darkblue', alpha=0.2, lw=1)) + + line = tool.line + assert mcolors.same_color(line.get_color(), 'darkblue') + assert line.get_alpha() == 0.2 + assert line.get_lw() == 1 + assert line.get_linestyle() == '-' + line.set_color('r') + line.set_alpha(0.3) + assert mcolors.same_color(line.get_color(), 'r') + assert line.get_alpha() == 0.3 + + +def test_CheckButtons(ax): + labels = ('a', 'b', 'c') + check = widgets.CheckButtons(ax, labels, (True, False, True)) assert check.get_status() == [True, False, True] check.set_active(0) assert check.get_status() == [False, False, True] + assert check.get_checked_labels() == ['c'] + check.clear() + assert check.get_status() == [False, False, False] + assert check.get_checked_labels() == [] + + for invalid_index in [-1, len(labels), len(labels)+5]: + with pytest.raises(ValueError): + check.set_active(index=invalid_index) + + for invalid_value in ['invalid', -1]: + with pytest.raises(TypeError): + check.set_active(1, state=invalid_value) cid = check.on_clicked(lambda: None) check.disconnect(cid) -@image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) -def test_check_radio_buttons_image(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 +@pytest.mark.parametrize("toolbar", ["none", "toolbar2", "toolmanager"]) +def test_TextBox(ax, toolbar): + # Avoid "toolmanager is provisional" warning. + plt.rcParams._set("toolbar", toolbar) + + submit_event = mock.Mock(spec=noop, return_value=None) + text_change_event = mock.Mock(spec=noop, return_value=None) + tool = widgets.TextBox(ax, '') + tool.on_submit(submit_event) + tool.on_text_change(text_change_event) + + assert tool.text == '' + + do_event(tool, '_click') + + tool.set_val('x**2') + + assert tool.text == 'x**2' + assert text_change_event.call_count == 1 + + tool.begin_typing() + tool.stop_typing() + + assert submit_event.call_count == 2 - get_ax() - plt.subplots_adjust(left=0.3) - rax1 = plt.axes([0.05, 0.7, 0.15, 0.15]) - rax2 = plt.axes([0.05, 0.2, 0.15, 0.15]) - widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) - widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), - (False, True, True)) + do_event(tool, '_click', xdata=.5, ydata=.5) # Ensure the click is in the axes. + do_event(tool, '_keypress', key='+') + do_event(tool, '_keypress', key='5') + assert text_change_event.call_count == 3 -@image_comparison(['check_bunch_of_radio_buttons.png'], - style='mpl20', remove_text=True) -def test_check_bunch_of_radio_buttons(): - rax = plt.axes([0.05, 0.1, 0.15, 0.7]) - widgets.RadioButtons(rax, ('B1', 'B2', 'B3', 'B4', 'B5', 'B6', - 'B7', 'B8', 'B9', 'B10', 'B11', 'B12', - 'B13', 'B14', 'B15')) + +def test_RadioButtons(ax): + radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3')) + radio.set_active(1) + assert radio.value_selected == 'Radio 2' + assert radio.index_selected == 1 + radio.clear() + assert radio.value_selected == 'Radio 1' + assert radio.index_selected == 0 + + +@image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) +def test_check_radio_buttons_image(): + ax = get_ax() + fig = ax.get_figure(root=False) + fig.subplots_adjust(left=0.3) + + rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15)) + rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) + + rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15)) + cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), + (False, True, True)) + + rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15)) + rb3 = widgets.RadioButtons( + rax3, ('Radio 1', 'Radio 2', 'Radio 3'), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + radio_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}) + + rax4 = fig.add_axes((0.05, 0.1, 0.2, 0.15)) + cb4 = widgets.CheckButtons( + rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + frame_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}, + check_props={'color': ['red', 'green', 'blue']}) + + +@check_figures_equal() +def test_radio_buttons(fig_test, fig_ref): + widgets.RadioButtons(fig_test.subplots(), ["tea", "coffee"]) + ax = fig_ref.add_subplot(xticks=[], yticks=[]) + ax.scatter([.15, .15], [2/3, 1/3], transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["C0", "none"]) + ax.text(.25, 2/3, "tea", transform=ax.transAxes, va="center") + ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") + + +@check_figures_equal() +def test_radio_buttons_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + radio_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + label_props=label_props, radio_props=radio_props) + + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee']) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) + + +def test_radio_button_active_conflict(ax): + with pytest.warns(UserWarning, + match=r'Both the \*activecolor\* parameter'): + rb = widgets.RadioButtons(ax, ['tea', 'coffee'], activecolor='red', + radio_props={'facecolor': 'green'}) + # *radio_props*' facecolor wins over *activecolor* + assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) + + +@check_figures_equal() +def test_radio_buttons_activecolor_change(fig_test, fig_ref): + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + activecolor='green') + + # Test property setter. + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee'], + activecolor='red') + cb.activecolor = 'green' + + +@check_figures_equal() +def test_check_buttons(fig_test, fig_ref): + widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) + ax = fig_ref.add_subplot(xticks=[], yticks=[]) + ax.scatter([.15, .15], [2/3, 1/3], marker='s', transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["none", "none"]) + ax.scatter([.15, .15], [2/3, 1/3], marker='x', transform=ax.transAxes, + s=(plt.rcParams["font.size"] / 2) ** 2, c=["k", "k"]) + ax.text(.25, 2/3, "tea", transform=ax.transAxes, va="center") + ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") + + +@check_figures_equal() +def test_check_button_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + frame_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + check_props = {'facecolor': 'red', 'linewidth': 2} + + widgets.CheckButtons(fig_ref.subplots(), ['tea', 'coffee'], [True, True], + label_props=label_props, frame_props=frame_props, + check_props=check_props) + + cb = widgets.CheckButtons(fig_test.subplots(), ['tea', 'coffee'], + [True, True]) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_frame_props({**frame_props, 's': (24 / 2)**2}) + # FIXME: Axes.scatter promotes facecolor to edgecolor on unfilled markers, + # but Collection.update doesn't do that (it forgot the marker already). + # This means we cannot pass facecolor to both setters directly. + check_props['edgecolor'] = check_props.pop('facecolor') + cb.set_check_props({**check_props, 's': (24 / 2)**2}) def test_slider_slidermin_slidermax_invalid(): @@ -258,6 +1242,17 @@ def test_slider_valmin_valmax(): assert slider.val == slider.valmax +def test_slider_valstep_snapping(): + fig, ax = plt.subplots() + slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, + valinit=11.4, valstep=1) + assert slider.val == 11 + + slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, + valinit=11.4, valstep=[0, 1, 5.5, 19.7]) + assert slider.val == 5.5 + + def test_slider_horizontal_vertical(): fig, ax = plt.subplots() slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24, @@ -266,7 +1261,7 @@ def test_slider_horizontal_vertical(): assert slider.val == 10 # check the dimension of the slider patch in axes units box = slider.poly.get_extents().transformed(ax.transAxes.inverted()) - assert_allclose(box.bounds, [0, 0, 10/24, 1]) + assert_allclose(box.bounds, [0, .25, 10/24, .5]) fig, ax = plt.subplots() slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24, @@ -275,10 +1270,81 @@ def test_slider_horizontal_vertical(): assert slider.val == 10 # check the dimension of the slider patch in axes units box = slider.poly.get_extents().transformed(ax.transAxes.inverted()) - assert_allclose(box.bounds, [0, 0, 1, 10/24]) + assert_allclose(box.bounds, [.25, 0, .5, 10/24]) -def check_polygon_selector(event_sequence, expected_result, selections_count): +def test_slider_reset(): + fig, ax = plt.subplots() + slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=1, valinit=.5) + slider.set_val(0.75) + slider.reset() + assert slider.val == 0.5 + + +@pytest.mark.parametrize("orientation", ["horizontal", "vertical"]) +def test_range_slider(orientation): + if orientation == "vertical": + idx = [1, 0, 3, 2] + else: + idx = [0, 1, 2, 3] + + fig, ax = plt.subplots() + + slider = widgets.RangeSlider( + ax=ax, label="", valmin=0.0, valmax=1.0, orientation=orientation, + valinit=[0.1, 0.34] + ) + box = slider.poly.get_extents().transformed(ax.transAxes.inverted()) + assert_allclose(box.get_points().flatten()[idx], [0.1, 0.25, 0.34, 0.75]) + + # Check initial value is set correctly + assert_allclose(slider.val, (0.1, 0.34)) + + def handle_positions(slider): + if orientation == "vertical": + return [h.get_ydata()[0] for h in slider._handles] + else: + return [h.get_xdata()[0] for h in slider._handles] + + slider.set_val((0.4, 0.6)) + assert_allclose(slider.val, (0.4, 0.6)) + assert_allclose(handle_positions(slider), (0.4, 0.6)) + + box = slider.poly.get_extents().transformed(ax.transAxes.inverted()) + assert_allclose(box.get_points().flatten()[idx], [0.4, .25, 0.6, .75]) + + slider.set_val((0.2, 0.1)) + assert_allclose(slider.val, (0.1, 0.2)) + assert_allclose(handle_positions(slider), (0.1, 0.2)) + + slider.set_val((-1, 10)) + assert_allclose(slider.val, (0, 1)) + assert_allclose(handle_positions(slider), (0, 1)) + + slider.reset() + assert_allclose(slider.val, (0.1, 0.34)) + assert_allclose(handle_positions(slider), (0.1, 0.34)) + + +@pytest.mark.parametrize("orientation", ["horizontal", "vertical"]) +def test_range_slider_same_init_values(orientation): + if orientation == "vertical": + idx = [1, 0, 3, 2] + else: + idx = [0, 1, 2, 3] + + fig, ax = plt.subplots() + + slider = widgets.RangeSlider( + ax=ax, label="", valmin=0.0, valmax=1.0, orientation=orientation, + valinit=[0, 0] + ) + box = slider.poly.get_extents().transformed(ax.transAxes.inverted()) + assert_allclose(box.get_points().flatten()[idx], [0, 0.25, 0, 0.75]) + + +def check_polygon_selector(event_sequence, expected_result, selections_count, + **kwargs): """ Helper function to test Polygon Selector. @@ -295,22 +1361,20 @@ def check_polygon_selector(event_sequence, expected_result, selections_count): selections_count : int Wait for the tool to call its `onselect` function `selections_count` times, before comparing the result to the `expected_result` + **kwargs + Keyword arguments are passed to PolygonSelector. """ ax = get_ax() - ax._selections_count = 0 + onselect = mock.Mock(spec=noop, return_value=None) - def onselect(vertices): - ax._selections_count += 1 - ax._current_result = vertices - - tool = widgets.PolygonSelector(ax, onselect) + tool = widgets.PolygonSelector(ax, onselect=onselect, **kwargs) for (etype, event_args) in event_sequence: do_event(tool, etype, **event_args) - assert ax._selections_count == selections_count - assert ax._current_result == expected_result + assert onselect.call_count == selections_count + assert onselect.call_args == ((expected_result, ), {}) def polygon_place_vertex(xdata, ydata): @@ -319,97 +1383,377 @@ def polygon_place_vertex(xdata, ydata): ('release', dict(xdata=xdata, ydata=ydata))] -def test_polygon_selector(): +def polygon_remove_vertex(xdata, ydata): + return [('onmove', dict(xdata=xdata, ydata=ydata)), + ('press', dict(xdata=xdata, ydata=ydata, button=3)), + ('release', dict(xdata=xdata, ydata=ydata, button=3))] + + +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +def test_polygon_selector(draw_bounding_box): + check_selector = functools.partial( + check_polygon_selector, draw_bounding_box=draw_bounding_box) + # Simple polygon expected_result = [(50, 50), (150, 50), (50, 150)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 50)) - check_polygon_selector(event_sequence, expected_result, 1) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ] + check_selector(event_sequence, expected_result, 1) # Move first vertex before completing the polygon. expected_result = [(75, 50), (150, 50), (50, 150)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + [('on_key_press', dict(key='control')), - ('onmove', dict(xdata=50, ydata=50)), - ('press', dict(xdata=50, ydata=50)), - ('onmove', dict(xdata=75, ydata=50)), - ('release', dict(xdata=75, ydata=50)), - ('on_key_release', dict(key='control'))] - + polygon_place_vertex(50, 150) - + polygon_place_vertex(75, 50)) - check_polygon_selector(event_sequence, expected_result, 1) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + ('on_key_press', dict(key='control')), + ('onmove', dict(xdata=50, ydata=50)), + ('press', dict(xdata=50, ydata=50)), + ('onmove', dict(xdata=75, ydata=50)), + ('release', dict(xdata=75, ydata=50)), + ('on_key_release', dict(key='control')), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(75, 50), + ] + check_selector(event_sequence, expected_result, 1) # Move first two vertices at once before completing the polygon. expected_result = [(50, 75), (150, 75), (50, 150)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + [('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=100, ydata=125)), - ('release', dict(xdata=100, ydata=125)), - ('on_key_release', dict(key='shift'))] - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 75)) - check_polygon_selector(event_sequence, expected_result, 1) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + ('on_key_press', dict(key='shift')), + ('onmove', dict(xdata=100, ydata=100)), + ('press', dict(xdata=100, ydata=100)), + ('onmove', dict(xdata=100, ydata=125)), + ('release', dict(xdata=100, ydata=125)), + ('on_key_release', dict(key='shift')), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 75), + ] + check_selector(event_sequence, expected_result, 1) # Move first vertex after completing the polygon. expected_result = [(75, 50), (150, 50), (50, 150)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 50) - + [('onmove', dict(xdata=50, ydata=50)), - ('press', dict(xdata=50, ydata=50)), - ('onmove', dict(xdata=75, ydata=50)), - ('release', dict(xdata=75, ydata=50))]) - check_polygon_selector(event_sequence, expected_result, 2) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ('onmove', dict(xdata=50, ydata=50)), + ('press', dict(xdata=50, ydata=50)), + ('onmove', dict(xdata=75, ydata=50)), + ('release', dict(xdata=75, ydata=50)), + ] + check_selector(event_sequence, expected_result, 2) # Move all vertices after completing the polygon. expected_result = [(75, 75), (175, 75), (75, 175)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 50) - + [('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='shift'))]) - check_polygon_selector(event_sequence, expected_result, 2) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ('on_key_press', dict(key='shift')), + ('onmove', dict(xdata=100, ydata=100)), + ('press', dict(xdata=100, ydata=100)), + ('onmove', dict(xdata=125, ydata=125)), + ('release', dict(xdata=125, ydata=125)), + ('on_key_release', dict(key='shift')), + ] + check_selector(event_sequence, expected_result, 2) # Try to move a vertex and move all before placing any vertices. expected_result = [(50, 50), (150, 50), (50, 150)] - event_sequence = ([('on_key_press', dict(key='control')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='control')), - ('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='shift'))] - + polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 50)) - check_polygon_selector(event_sequence, expected_result, 1) + event_sequence = [ + ('on_key_press', dict(key='control')), + ('onmove', dict(xdata=100, ydata=100)), + ('press', dict(xdata=100, ydata=100)), + ('onmove', dict(xdata=125, ydata=125)), + ('release', dict(xdata=125, ydata=125)), + ('on_key_release', dict(key='control')), + ('on_key_press', dict(key='shift')), + ('onmove', dict(xdata=100, ydata=100)), + ('press', dict(xdata=100, ydata=100)), + ('onmove', dict(xdata=125, ydata=125)), + ('release', dict(xdata=125, ydata=125)), + ('on_key_release', dict(key='shift')), + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ] + check_selector(event_sequence, expected_result, 1) # Try to place vertex out-of-bounds, then reset, and start a new polygon. expected_result = [(50, 50), (150, 50), (50, 150)] - event_sequence = (polygon_place_vertex(50, 50) - + polygon_place_vertex(250, 50) - + [('on_key_press', dict(key='escape')), - ('on_key_release', dict(key='escape'))] - + polygon_place_vertex(50, 50) - + polygon_place_vertex(150, 50) - + polygon_place_vertex(50, 150) - + polygon_place_vertex(50, 50)) - check_polygon_selector(event_sequence, expected_result, 1) + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(250, 50), + ('on_key_press', dict(key='escape')), + ('on_key_release', dict(key='escape')), + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ] + check_selector(event_sequence, expected_result, 1) + + +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +def test_polygon_selector_set_props_handle_props(ax, draw_bounding_box): + tool = widgets.PolygonSelector(ax, + props=dict(color='b', alpha=0.2), + handle_props=dict(alpha=0.5), + draw_bounding_box=draw_bounding_box) + + event_sequence = [ + *polygon_place_vertex(50, 50), + *polygon_place_vertex(150, 50), + *polygon_place_vertex(50, 150), + *polygon_place_vertex(50, 50), + ] + + for (etype, event_args) in event_sequence: + do_event(tool, etype, **event_args) + + artist = tool._selection_artist + assert artist.get_color() == 'b' + assert artist.get_alpha() == 0.2 + tool.set_props(color='r', alpha=0.3) + assert artist.get_color() == 'r' + assert artist.get_alpha() == 0.3 + + for artist in tool._handles_artists: + assert artist.get_color() == 'b' + assert artist.get_alpha() == 0.5 + tool.set_handle_props(color='r', alpha=0.3) + for artist in tool._handles_artists: + assert artist.get_color() == 'r' + assert artist.get_alpha() == 0.3 + + +@check_figures_equal() +def test_rect_visibility(fig_test, fig_ref): + # Check that requesting an invisible selector makes it invisible + ax_test = fig_test.subplots() + _ = fig_ref.subplots() + + tool = widgets.RectangleSelector(ax_test, props={'visible': False}) + tool.extents = (0.2, 0.8, 0.3, 0.7) + + +# Change the order that the extra point is inserted in +@pytest.mark.parametrize('idx', [1, 2, 3]) +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +def test_polygon_selector_remove(idx, draw_bounding_box): + verts = [(50, 50), (150, 50), (50, 150)] + event_sequence = [polygon_place_vertex(*verts[0]), + polygon_place_vertex(*verts[1]), + polygon_place_vertex(*verts[2]), + # Finish the polygon + polygon_place_vertex(*verts[0])] + # Add an extra point + event_sequence.insert(idx, polygon_place_vertex(200, 200)) + # Remove the extra point + event_sequence.append(polygon_remove_vertex(200, 200)) + # Flatten list of lists + event_sequence = functools.reduce(operator.iadd, event_sequence, []) + check_polygon_selector(event_sequence, verts, 2, + draw_bounding_box=draw_bounding_box) + + +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +def test_polygon_selector_remove_first_point(draw_bounding_box): + verts = [(50, 50), (150, 50), (50, 150)] + event_sequence = [ + *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(*verts[2]), + *polygon_place_vertex(*verts[0]), + *polygon_remove_vertex(*verts[0]), + ] + check_polygon_selector(event_sequence, verts[1:], 2, + draw_bounding_box=draw_bounding_box) + + +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +def test_polygon_selector_redraw(ax, draw_bounding_box): + verts = [(50, 50), (150, 50), (50, 150)] + event_sequence = [ + *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(*verts[2]), + *polygon_place_vertex(*verts[0]), + # Polygon completed, now remove first two verts. + *polygon_remove_vertex(*verts[1]), + *polygon_remove_vertex(*verts[2]), + # At this point the tool should be reset so we can add more vertices. + *polygon_place_vertex(*verts[1]), + ] + + tool = widgets.PolygonSelector(ax, draw_bounding_box=draw_bounding_box) + for (etype, event_args) in event_sequence: + do_event(tool, etype, **event_args) + # After removing two verts, only one remains, and the + # selector should be automatically reset + assert tool.verts == verts[0:2] + + +@pytest.mark.parametrize('draw_bounding_box', [False, True]) +@check_figures_equal() +def test_polygon_selector_verts_setter(fig_test, fig_ref, draw_bounding_box): + verts = [(0.1, 0.4), (0.5, 0.9), (0.3, 0.2)] + ax_test = fig_test.add_subplot() + + tool_test = widgets.PolygonSelector(ax_test, draw_bounding_box=draw_bounding_box) + tool_test.verts = verts + assert tool_test.verts == verts + + ax_ref = fig_ref.add_subplot() + tool_ref = widgets.PolygonSelector(ax_ref, draw_bounding_box=draw_bounding_box) + event_sequence = [ + *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(*verts[2]), + *polygon_place_vertex(*verts[0]), + ] + for (etype, event_args) in event_sequence: + do_event(tool_ref, etype, **event_args) + + +def test_polygon_selector_box(ax): + # Create a diamond (adjusting axes lims s.t. the diamond lies within axes limits). + ax.set(xlim=(-10, 50), ylim=(-10, 50)) + verts = [(20, 0), (0, 20), (20, 40), (40, 20)] + event_sequence = [ + *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(*verts[2]), + *polygon_place_vertex(*verts[3]), + *polygon_place_vertex(*verts[0]), + ] + + # Create selector + tool = widgets.PolygonSelector(ax, draw_bounding_box=True) + for (etype, event_args) in event_sequence: + do_event(tool, etype, **event_args) + + # In order to trigger the correct callbacks, trigger events on the canvas + # instead of the individual tools + t = ax.transData + canvas = ax.get_figure(root=True).canvas + + # Scale to half size using the top right corner of the bounding box + MouseEvent( + "button_press_event", canvas, *t.transform((40, 40)), 1)._process() + MouseEvent( + "motion_notify_event", canvas, *t.transform((20, 20)))._process() + MouseEvent( + "button_release_event", canvas, *t.transform((20, 20)), 1)._process() + np.testing.assert_allclose( + tool.verts, [(10, 0), (0, 10), (10, 20), (20, 10)]) + + # Move using the center of the bounding box + MouseEvent( + "button_press_event", canvas, *t.transform((10, 10)), 1)._process() + MouseEvent( + "motion_notify_event", canvas, *t.transform((30, 30)))._process() + MouseEvent( + "button_release_event", canvas, *t.transform((30, 30)), 1)._process() + np.testing.assert_allclose( + tool.verts, [(30, 20), (20, 30), (30, 40), (40, 30)]) + + # Remove a point from the polygon and check that the box extents update + np.testing.assert_allclose( + tool._box.extents, (20.0, 40.0, 20.0, 40.0)) + + MouseEvent( + "button_press_event", canvas, *t.transform((30, 20)), 3)._process() + MouseEvent( + "button_release_event", canvas, *t.transform((30, 20)), 3)._process() + np.testing.assert_allclose( + tool.verts, [(20, 30), (30, 40), (40, 30)]) + np.testing.assert_allclose( + tool._box.extents, (20.0, 40.0, 30.0, 40.0)) + + +def test_polygon_selector_clear_method(ax): + onselect = mock.Mock(spec=noop, return_value=None) + tool = widgets.PolygonSelector(ax, onselect) + + for result in ([(50, 50), (150, 50), (50, 150), (50, 50)], + [(50, 50), (100, 50), (50, 150), (50, 50)]): + for x, y in result: + for etype, event_args in polygon_place_vertex(x, y): + do_event(tool, etype, **event_args) + + artist = tool._selection_artist + + assert tool._selection_completed + assert tool.get_visible() + assert artist.get_visible() + np.testing.assert_equal(artist.get_xydata(), result) + assert onselect.call_args == ((result[:-1],), {}) + + tool.clear() + assert not tool._selection_completed + np.testing.assert_equal(artist.get_xydata(), [(0, 0)]) + + +@pytest.mark.parametrize("horizOn", [False, True]) +@pytest.mark.parametrize("vertOn", [False, True]) +def test_MultiCursor(horizOn, vertOn): + fig = plt.figure() + (ax1, ax3) = fig.subplots(2, sharex=True) + ax2 = plt.figure().subplots() + + # useblit=false to avoid having to draw the figure to cache the renderer + multi = widgets.MultiCursor( + None, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn + ) + + # Only two of the axes should have a line drawn on them. + assert len(multi.vlines) == 2 + assert len(multi.hlines) == 2 + + # mock a motion_notify_event + # Can't use `do_event` as that helper requires the widget + # to have a single .ax attribute. + event = mock_event(ax1, xdata=.5, ydata=.25) + multi.onmove(event) + # force a draw + draw event to exercise clear + fig.canvas.draw() + + # the lines in the first two ax should both move + for l in multi.vlines: + assert l.get_xdata() == (.5, .5) + for l in multi.hlines: + assert l.get_ydata() == (.25, .25) + # The relevant lines get turned on after move. + assert len([line for line in multi.vlines if line.get_visible()]) == ( + 2 if vertOn else 0) + assert len([line for line in multi.hlines if line.get_visible()]) == ( + 2 if horizOn else 0) + + # After toggling settings, the opposite lines should be visible after move. + multi.horizOn = not multi.horizOn + multi.vertOn = not multi.vertOn + event = mock_event(ax1, xdata=.5, ydata=.25) + multi.onmove(event) + assert len([line for line in multi.vlines if line.get_visible()]) == ( + 0 if vertOn else 2) + assert len([line for line in multi.hlines if line.get_visible()]) == ( + 0 if horizOn else 2) + + # test a move event in an Axes not part of the MultiCursor + # the lines in ax1 and ax2 should not have moved. + event = mock_event(ax3, xdata=.75, ydata=.75) + multi.onmove(event) + for l in multi.vlines: + assert l.get_xdata() == (.5, .5) + for l in multi.hlines: + assert l.get_ydata() == (.25, .25) diff --git a/lib/matplotlib/tests/tinypages/conf.py b/lib/matplotlib/tests/tinypages/conf.py deleted file mode 100644 index 970a3c5a4d45..000000000000 --- a/lib/matplotlib/tests/tinypages/conf.py +++ /dev/null @@ -1,262 +0,0 @@ -# tinypages documentation build configuration file, created by -# sphinx-quickstart on Tue Mar 18 11:58:34 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -from os.path import join as pjoin, abspath -import sphinx -from distutils.version import LooseVersion - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, abspath(pjoin('..', '..'))) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['matplotlib.sphinxext.plot_directive'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'tinypages' -copyright = '2014, Matplotlib developers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.1' -# The full version, including alpha/beta/rc tags. -release = '0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -if LooseVersion(sphinx.__version__) >= LooseVersion('1.3'): - html_theme = 'classic' -else: - html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'tinypagesdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'tinypages.tex', 'tinypages Documentation', - 'Matplotlib developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'tinypages', 'tinypages Documentation', - ['Matplotlib developers'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'tinypages', 'tinypages Documentation', - 'Matplotlib developers', 'tinypages', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/lib/matplotlib/tests/tinypages/index.rst b/lib/matplotlib/tests/tinypages/index.rst deleted file mode 100644 index 3905483a8a57..000000000000 --- a/lib/matplotlib/tests/tinypages/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. tinypages documentation master file, created by - sphinx-quickstart on Tue Mar 18 11:58:34 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to tinypages's documentation! -===================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - some_plots - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/lib/matplotlib/tests/tinypages/range6.py b/lib/matplotlib/tests/tinypages/range6.py deleted file mode 100644 index fa5d035e4ab2..000000000000 --- a/lib/matplotlib/tests/tinypages/range6.py +++ /dev/null @@ -1,13 +0,0 @@ -from matplotlib import pyplot as plt - - -def range4(): - """Never called if plot_directive works as expected.""" - raise NotImplementedError - - -def range6(): - """The function that should be executed.""" - plt.figure() - plt.plot(range(6)) - plt.show() diff --git a/lib/matplotlib/tests/tinypages/some_plots.rst b/lib/matplotlib/tests/tinypages/some_plots.rst deleted file mode 100644 index 615908b0107f..000000000000 --- a/lib/matplotlib/tests/tinypages/some_plots.rst +++ /dev/null @@ -1,129 +0,0 @@ -########## -Some plots -########## - -Plot 1 does not use context: - -.. plot:: - - plt.plot(range(10)) - a = 10 - -Plot 2 doesn't use context either; has length 6: - -.. plot:: - - plt.plot(range(6)) - -Plot 3 has length 4: - -.. plot:: - - plt.plot(range(4)) - -Plot 4 shows that a new block with context does not see the variable defined -in the no-context block: - -.. plot:: - :context: - - assert 'a' not in globals() - -Plot 5 defines ``a`` in a context block: - -.. plot:: - :context: - - plt.plot(range(6)) - a = 10 - -Plot 6 shows that a block with context sees the new variable. It also uses -``:nofigs:``: - -.. plot:: - :context: - :nofigs: - - assert a == 10 - b = 4 - -Plot 7 uses a variable previously defined in previous ``nofigs`` context. It -also closes any previous figures to create a fresh figure: - -.. plot:: - :context: close-figs - - assert b == 4 - plt.plot(range(b)) - -Plot 8 shows that a non-context block still doesn't have ``a``: - -.. plot:: - :nofigs: - - assert 'a' not in globals() - -Plot 9 has a context block, and does have ``a``: - -.. plot:: - :context: - :nofigs: - - assert a == 10 - -Plot 10 resets context, and ``a`` has gone again: - -.. plot:: - :context: reset - :nofigs: - - assert 'a' not in globals() - c = 10 - -Plot 11 continues the context, we have the new value, but not the old: - -.. plot:: - :context: - - assert c == 10 - assert 'a' not in globals() - plt.plot(range(c)) - -Plot 12 opens a new figure. By default the directive will plot both the first -and the second figure: - -.. plot:: - :context: - - plt.figure() - plt.plot(range(6)) - -Plot 13 shows ``close-figs`` in action. ``close-figs`` closes all figures -previous to this plot directive, so we get always plot the figure we create in -the directive: - -.. plot:: - :context: close-figs - - plt.figure() - plt.plot(range(4)) - -Plot 14 uses ``include-source``: - -.. plot:: - :include-source: - - # Only a comment - -Plot 15 uses an external file with the plot commands and a caption: - -.. plot:: range4.py - - This is the caption for plot 15. - - -Plot 16 uses a specific function in a file with plot commands: - -.. plot:: range6.py range6 - - diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 254a36fb8e34..020a26e31cbe 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -1,427 +1,371 @@ r""" -Support for embedded TeX expressions in Matplotlib via dvipng and dvips for the -raster and PostScript backends. The tex and dvipng/dvips information is cached -in ~/.matplotlib/tex.cache for reuse between sessions. +Support for embedded TeX expressions in Matplotlib. Requirements: -* latex -* \*Agg backends: dvipng>=1.6 -* PS backend: psfrag, dvips, and Ghostscript>=8.60 - -Backends: +* LaTeX. +* \*Agg backends: dvipng>=1.6. +* PS backend: PSfrag, dvips, and Ghostscript>=9.0. +* PDF and SVG backends: if LuaTeX is present, it will be used to speed up some + post-processing steps, but note that it is not used to parse the TeX string + itself (only LaTeX is supported). -* \*Agg -* PS -* PDF - -For raster output, you can get RGBA numpy arrays from TeX expressions -as follows:: +To enable TeX rendering of all text in your Matplotlib figure, set +:rc:`text.usetex` to True. - texmanager = TexManager() - s = ('\TeX\ is Number ' - '$\displaystyle\sum_{n=1}^\infty\frac{-e^{i\pi}}{2^n}$!') - Z = texmanager.get_rgba(s, fontsize=12, dpi=80, rgb=(1, 0, 0)) +TeX and dvipng/dvips processing results are cached +in ~/.matplotlib/tex.cache for reuse between sessions. -To enable tex rendering of all text in your matplotlib figure, set -:rc:`text.usetex` to True. +`TexManager.get_rgba` can also be used to directly obtain raster output as RGBA +NumPy arrays. """ import functools -import glob import hashlib import logging import os from pathlib import Path -import re import subprocess from tempfile import TemporaryDirectory import numpy as np import matplotlib as mpl -from matplotlib import cbook, dviread, rcParams +from matplotlib import cbook, dviread _log = logging.getLogger(__name__) +def _usepackage_if_not_loaded(package, *, option=None): + """ + Output LaTeX code that loads a package (possibly with an option) if it + hasn't been loaded yet. + + LaTeX cannot load twice a package with different options, so this helper + can be used to protect against users loading arbitrary packages/options in + their custom preamble. + """ + option = f"[{option}]" if option is not None else "" + return ( + r"\makeatletter" + r"\@ifpackageloaded{%(package)s}{}{\usepackage%(option)s{%(package)s}}" + r"\makeatother" + ) % {"package": package, "option": option} + + class TexManager: """ Convert strings to dvi files using TeX, caching the results to a directory. + The cache directory is called ``tex.cache`` and is located in the directory + returned by `.get_cachedir`. + Repeated calls to this constructor always return the same instance. """ - # Caches. - texcache = os.path.join(mpl.get_cachedir(), 'tex.cache') - grey_arrayd = {} - - font_family = 'serif' - font_families = ('serif', 'sans-serif', 'cursive', 'monospace') - - font_info = { - 'new century schoolbook': ('pnc', r'\renewcommand{\rmdefault}{pnc}'), - 'bookman': ('pbk', r'\renewcommand{\rmdefault}{pbk}'), - 'times': ('ptm', r'\usepackage{mathptmx}'), - 'palatino': ('ppl', r'\usepackage{mathpazo}'), - 'zapf chancery': ('pzc', r'\usepackage{chancery}'), - 'cursive': ('pzc', r'\usepackage{chancery}'), - 'charter': ('pch', r'\usepackage{charter}'), - 'serif': ('cmr', ''), - 'sans-serif': ('cmss', ''), - 'helvetica': ('phv', r'\usepackage{helvet}'), - 'avant garde': ('pag', r'\usepackage{avant}'), - 'courier': ('pcr', r'\usepackage{courier}'), - # Loading the type1ec package ensures that cm-super is installed, which - # is necessary for unicode computer modern. (It also allows the use of - # computer modern at arbitrary sizes, but that's just a side effect.) - 'monospace': ('cmtt', r'\usepackage{type1ec}'), - 'computer modern roman': ('cmr', r'\usepackage{type1ec}'), - 'computer modern sans serif': ('cmss', r'\usepackage{type1ec}'), - 'computer modern typewriter': ('cmtt', r'\usepackage{type1ec}')} - - @cbook.deprecated("3.3", alternative="matplotlib.get_cachedir()") - @property - def cachedir(self): - return mpl.get_cachedir() - - @cbook.deprecated("3.3") - @property - def rgba_arrayd(self): - return {} - - @functools.lru_cache() # Always return the same instance. + _texcache = os.path.join(mpl.get_cachedir(), 'tex.cache') + _grey_arrayd = {} + + _font_families = ('serif', 'sans-serif', 'cursive', 'monospace') + # Check for the cm-super package (which registers unicode computer modern + # support just by being installed) without actually loading any package + # (because we already load the incompatible fix-cm). + _check_cmsuper_installed = ( + r'\IfFileExists{type1ec.sty}{}{\PackageError{matplotlib-support}{' + r'Missing cm-super package, required by Matplotlib}{}}' + ) + _font_preambles = { + 'new century schoolbook': r'\renewcommand{\rmdefault}{pnc}', + 'bookman': r'\renewcommand{\rmdefault}{pbk}', + 'times': r'\usepackage{mathptmx}', + 'palatino': r'\usepackage{mathpazo}', + 'zapf chancery': r'\usepackage{chancery}', + 'cursive': r'\usepackage{chancery}', + 'charter': r'\usepackage{charter}', + 'serif': '', + 'sans-serif': '', + 'helvetica': r'\usepackage{helvet}', + 'avant garde': r'\usepackage{avant}', + 'courier': r'\usepackage{courier}', + 'monospace': _check_cmsuper_installed, + 'computer modern roman': _check_cmsuper_installed, + 'computer modern sans serif': _check_cmsuper_installed, + 'computer modern typewriter': _check_cmsuper_installed, + } + _font_types = { + 'new century schoolbook': 'serif', + 'bookman': 'serif', + 'times': 'serif', + 'palatino': 'serif', + 'zapf chancery': 'cursive', + 'charter': 'serif', + 'helvetica': 'sans-serif', + 'avant garde': 'sans-serif', + 'courier': 'monospace', + 'computer modern roman': 'serif', + 'computer modern sans serif': 'sans-serif', + 'computer modern typewriter': 'monospace', + } + + @functools.lru_cache # Always return the same instance. def __new__(cls): - Path(cls.texcache).mkdir(parents=True, exist_ok=True) + Path(cls._texcache).mkdir(parents=True, exist_ok=True) return object.__new__(cls) - _fonts = {} # Only for deprecation period. - - @cbook.deprecated("3.3") - @property - def serif(self): - return self._fonts.get("serif", ('cmr', '')) - - @cbook.deprecated("3.3") - @property - def sans_serif(self): - return self._fonts.get("sans-serif", ('cmss', '')) - - @cbook.deprecated("3.3") - @property - def cursive(self): - return self._fonts.get("cursive", ('pzc', r'\usepackage{chancery}')) - - @cbook.deprecated("3.3") - @property - def monospace(self): - return self._fonts.get("monospace", ('cmtt', '')) - - def get_font_config(self): - ff = rcParams['font.family'] - if len(ff) == 1 and ff[0].lower() in self.font_families: - self.font_family = ff[0].lower() + @classmethod + def _get_font_family_and_reduced(cls): + """Return the font family name and whether the font is reduced.""" + ff = mpl.rcParams['font.family'] + ff_val = ff[0].lower() if len(ff) == 1 else None + if len(ff) == 1 and ff_val in cls._font_families: + return ff_val, False + elif len(ff) == 1 and ff_val in cls._font_preambles: + return cls._font_types[ff_val], True else: _log.info('font.family must be one of (%s) when text.usetex is ' 'True. serif will be used by default.', - ', '.join(self.font_families)) - self.font_family = 'serif' - - fontconfig = [self.font_family] - for font_family in self.font_families: - for font in rcParams['font.' + font_family]: - if font.lower() in self.font_info: - self._fonts[font_family] = self.font_info[font.lower()] - _log.debug('family: %s, font: %s, info: %s', - font_family, font, self.font_info[font.lower()]) - break - else: - _log.debug('%s font is not compatible with usetex.', font) + ', '.join(cls._font_families)) + return 'serif', False + + @classmethod + def _get_font_preamble_and_command(cls): + requested_family, is_reduced_font = cls._get_font_family_and_reduced() + + preambles = {} + for font_family in cls._font_families: + if is_reduced_font and font_family == requested_family: + preambles[font_family] = cls._font_preambles[ + mpl.rcParams['font.family'][0].lower()] else: - _log.info('No LaTeX-compatible font found for the %s font ' - 'family in rcParams. Using default.', font_family) - self._fonts[font_family] = self.font_info[font_family] - fontconfig.append(self._fonts[font_family][0]) - # Add a hash of the latex preamble to fontconfig so that the - # correct png is selected for strings rendered with same font and dpi - # even if the latex preamble changes within the session - preamble_bytes = self.get_custom_preamble().encode('utf-8') - fontconfig.append(hashlib.md5(preamble_bytes).hexdigest()) + rcfonts = mpl.rcParams[f"font.{font_family}"] + for i, font in enumerate(map(str.lower, rcfonts)): + if font in cls._font_preambles: + preambles[font_family] = cls._font_preambles[font] + _log.debug( + 'family: %s, package: %s, font: %s, skipped: %s', + font_family, cls._font_preambles[font], rcfonts[i], + ', '.join(rcfonts[:i]), + ) + break + else: + _log.info('No LaTeX-compatible font found for the %s font' + 'family in rcParams. Using default.', + font_family) + preambles[font_family] = cls._font_preambles[font_family] # The following packages and commands need to be included in the latex # file's preamble: - cmd = [self._fonts['serif'][1], - self._fonts['sans-serif'][1], - self._fonts['monospace'][1]] - if self.font_family == 'cursive': - cmd.append(self._fonts['cursive'][1]) - self._font_preamble = '\n'.join([r'\usepackage{type1cm}', *cmd]) - - return ''.join(fontconfig) - - def get_basefile(self, tex, fontsize, dpi=None): + cmd = {preambles[family] + for family in ['serif', 'sans-serif', 'monospace']} + if requested_family == 'cursive': + cmd.add(preambles['cursive']) + cmd.add(r'\usepackage{type1cm}') + preamble = '\n'.join(sorted(cmd)) + fontcmd = (r'\sffamily' if requested_family == 'sans-serif' else + r'\ttfamily' if requested_family == 'monospace' else + r'\rmfamily') + return preamble, fontcmd + + @classmethod + def get_basefile(cls, tex, fontsize, dpi=None): """ Return a filename based on a hash of the string, fontsize, and dpi. """ - s = ''.join([tex, self.get_font_config(), '%f' % fontsize, - self.get_custom_preamble(), str(dpi or '')]) - return os.path.join( - self.texcache, hashlib.md5(s.encode('utf-8')).hexdigest()) - - def get_font_preamble(self): + src = cls._get_tex_source(tex, fontsize) + str(dpi) + filehash = hashlib.sha256( + src.encode('utf-8'), + usedforsecurity=False + ).hexdigest() + filepath = Path(cls._texcache) + + num_letters, num_levels = 2, 2 + for i in range(0, num_letters*num_levels, num_letters): + filepath = filepath / Path(filehash[i:i+2]) + + filepath.mkdir(parents=True, exist_ok=True) + return os.path.join(filepath, filehash) + + @classmethod + def get_font_preamble(cls): """ Return a string containing font configuration for the tex preamble. """ - return self._font_preamble + font_preamble, command = cls._get_font_preamble_and_command() + return font_preamble - def get_custom_preamble(self): + @classmethod + def get_custom_preamble(cls): """Return a string containing user additions to the tex preamble.""" - return rcParams['text.latex.preamble'] + return mpl.rcParams['text.latex.preamble'] - def _get_preamble(self): + @classmethod + def _get_tex_source(cls, tex, fontsize): + """Return the complete TeX source for processing a TeX string.""" + font_preamble, fontcmd = cls._get_font_preamble_and_command() + baselineskip = 1.25 * fontsize return "\n".join([ + r"\RequirePackage{fix-cm}", r"\documentclass{article}", - # Pass-through \mathdefault, which is used in non-usetex mode to - # use the default text font but was historically suppressed in - # usetex mode. + r"% Pass-through \mathdefault, which is used in non-usetex mode", + r"% to use the default text font but was historically suppressed", + r"% in usetex mode.", r"\newcommand{\mathdefault}[1]{#1}", - self._font_preamble, + font_preamble, r"\usepackage[utf8]{inputenc}", r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}", - # geometry is loaded before the custom preamble as convert_psfrags - # relies on a custom preamble to change the geometry. + r"% geometry is loaded before the custom preamble as ", + r"% convert_psfrags relies on a custom preamble to change the ", + r"% geometry.", r"\usepackage[papersize=72in, margin=1in]{geometry}", - self.get_custom_preamble(), - # textcomp is loaded last (if not already loaded by the custom - # preamble) in order not to clash with custom packages (e.g. - # newtxtext) which load it with different options. - r"\makeatletter" - r"\@ifpackageloaded{textcomp}{}{\usepackage{textcomp}}" - r"\makeatother", + cls.get_custom_preamble(), + r"% Use `underscore` package to take care of underscores in text.", + r"% The [strings] option allows to use underscores in file names.", + _usepackage_if_not_loaded("underscore", option="strings"), + r"% Custom packages (e.g. newtxtext) may already have loaded ", + r"% textcomp with different options.", + _usepackage_if_not_loaded("textcomp"), + r"\pagestyle{empty}", + r"\begin{document}", + r"% The empty hbox ensures that a page is printed even for empty", + r"% inputs, except when using psfrag which gets confused by it.", + r"% matplotlibbaselinemarker is used by dviread to detect the", + r"% last line's baseline.", + rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%", + r"\ifdefined\psfrag\else\hbox{}\fi%", + rf"{{{fontcmd} {tex}}}%", + r"\end{document}", ]) - def make_tex(self, tex, fontsize): + @classmethod + def make_tex(cls, tex, fontsize): """ Generate a tex file to render the tex string at a specific font size. Return the file name. """ - basefile = self.get_basefile(tex, fontsize) - texfile = '%s.tex' % basefile - fontcmd = {'sans-serif': r'{\sffamily %s}', - 'monospace': r'{\ttfamily %s}'}.get(self.font_family, - r'{\rmfamily %s}') - - Path(texfile).write_text( - r""" -%s -\pagestyle{empty} -\begin{document} -%% The empty hbox ensures that a page is printed even for empty inputs, except -%% when using psfrag which gets confused by it. -\fontsize{%f}{%f}%% -\ifdefined\psfrag\else\hbox{}\fi%% -%s -\end{document} -""" % (self._get_preamble(), fontsize, fontsize * 1.25, fontcmd % tex), - encoding='utf-8') - + texfile = cls.get_basefile(tex, fontsize) + ".tex" + Path(texfile).write_text(cls._get_tex_source(tex, fontsize), + encoding='utf-8') return texfile - _re_vbox = re.compile( - r"MatplotlibBox:\(([\d.]+)pt\+([\d.]+)pt\)x([\d.]+)pt") - - @cbook.deprecated("3.3") - def make_tex_preview(self, tex, fontsize): - """ - Generate a tex file to render the tex string at a specific font size. - - It uses the preview.sty to determine the dimension (width, height, - descent) of the output. - - Return the file name. - """ - basefile = self.get_basefile(tex, fontsize) - texfile = '%s.tex' % basefile - fontcmd = {'sans-serif': r'{\sffamily %s}', - 'monospace': r'{\ttfamily %s}'}.get(self.font_family, - r'{\rmfamily %s}') - - # newbox, setbox, immediate, etc. are used to find the box - # extent of the rendered text. - - Path(texfile).write_text( - r""" -%s -\usepackage[active,showbox,tightpage]{preview} - -%% we override the default showbox as it is treated as an error and makes -%% the exit status not zero -\def\showbox#1%% -{\immediate\write16{MatplotlibBox:(\the\ht#1+\the\dp#1)x\the\wd#1}} - -\begin{document} -\begin{preview} -{\fontsize{%f}{%f}%s} -\end{preview} -\end{document} -""" % (self._get_preamble(), fontsize, fontsize * 1.25, fontcmd % tex), - encoding='utf-8') - - return texfile - - def _run_checked_subprocess(self, command, tex, *, cwd=None): + @classmethod + def _run_checked_subprocess(cls, command, tex, *, cwd=None): _log.debug(cbook._pformat_subprocess(command)) try: report = subprocess.check_output( - command, cwd=cwd if cwd is not None else self.texcache, + command, cwd=cwd if cwd is not None else cls._texcache, stderr=subprocess.STDOUT) except FileNotFoundError as exc: raise RuntimeError( - 'Failed to process string with tex because {} could not be ' - 'found'.format(command[0])) from exc + f'Failed to process string with tex because {command[0]} ' + 'could not be found') from exc except subprocess.CalledProcessError as exc: raise RuntimeError( '{prog} was not able to process the following string:\n' '{tex!r}\n\n' - 'Here is the full report generated by {prog}:\n' + 'Here is the full command invocation and its output:\n\n' + '{format_command}\n\n' '{exc}\n\n'.format( prog=command[0], + format_command=cbook._pformat_subprocess(command), tex=tex.encode('unicode_escape'), - exc=exc.output.decode('utf-8'))) from exc + exc=exc.output.decode('utf-8', 'backslashreplace')) + ) from None _log.debug(report) return report - def make_dvi(self, tex, fontsize): + @classmethod + def make_dvi(cls, tex, fontsize): """ Generate a dvi file containing latex's layout of tex string. Return the file name. """ - - if dict.__getitem__(rcParams, 'text.latex.preview'): - return self.make_dvi_preview(tex, fontsize) - - basefile = self.get_basefile(tex, fontsize) + basefile = cls.get_basefile(tex, fontsize) dvifile = '%s.dvi' % basefile if not os.path.exists(dvifile): - texfile = self.make_tex(tex, fontsize) + texfile = Path(cls.make_tex(tex, fontsize)) # Generate the dvi in a temporary directory to avoid race # conditions e.g. if multiple processes try to process the same tex # string at the same time. Having tmpdir be a subdirectory of the # final output dir ensures that they are on the same filesystem, - # and thus replace() works atomically. - with TemporaryDirectory(dir=Path(dvifile).parent) as tmpdir: - self._run_checked_subprocess( + # and thus replace() works atomically. It also allows referring to + # the texfile with a relative path (for pathological MPLCONFIGDIRs, + # the absolute path may contain characters (e.g. ~) that TeX does + # not support; n.b. relative paths cannot traverse parents, or it + # will be blocked when `openin_any = p` in texmf.cnf). + cwd = Path(dvifile).parent + with TemporaryDirectory(dir=cwd) as tmpdir: + tmppath = Path(tmpdir) + cls._run_checked_subprocess( ["latex", "-interaction=nonstopmode", "--halt-on-error", - texfile], tex, cwd=tmpdir) - (Path(tmpdir) / Path(dvifile).name).replace(dvifile) - return dvifile - - @cbook.deprecated("3.3") - def make_dvi_preview(self, tex, fontsize): - """ - Generate a dvi file containing latex's layout of tex string. - - It calls make_tex_preview() method and store the size information - (width, height, descent) in a separate file. - - Return the file name. - """ - basefile = self.get_basefile(tex, fontsize) - dvifile = '%s.dvi' % basefile - baselinefile = '%s.baseline' % basefile - - if not os.path.exists(dvifile) or not os.path.exists(baselinefile): - texfile = self.make_tex_preview(tex, fontsize) - report = self._run_checked_subprocess( - ["latex", "-interaction=nonstopmode", "--halt-on-error", - texfile], tex) - - # find the box extent information in the latex output - # file and store them in ".baseline" file - m = TexManager._re_vbox.search(report.decode("utf-8")) - with open(basefile + '.baseline', "w") as fh: - fh.write(" ".join(m.groups())) - - for fname in glob.glob(basefile + '*'): - if not fname.endswith(('dvi', 'tex', 'baseline')): - try: - os.remove(fname) - except OSError: - pass - + f"--output-directory={tmppath.name}", + f"{texfile.name}"], tex, cwd=cwd) + (tmppath / Path(dvifile).name).replace(dvifile) return dvifile - def make_png(self, tex, fontsize, dpi): + @classmethod + def make_png(cls, tex, fontsize, dpi): """ Generate a png file containing latex's rendering of tex string. Return the file name. """ - basefile = self.get_basefile(tex, fontsize, dpi) + basefile = cls.get_basefile(tex, fontsize, dpi) pngfile = '%s.png' % basefile # see get_rgba for a discussion of the background if not os.path.exists(pngfile): - dvifile = self.make_dvi(tex, fontsize) + dvifile = cls.make_dvi(tex, fontsize) cmd = ["dvipng", "-bg", "Transparent", "-D", str(dpi), "-T", "tight", "-o", pngfile, dvifile] # When testing, disable FreeType rendering for reproducibility; but # dvipng 1.16 has a bug (fixed in f3ff241) that breaks --freetype0 # mode, so for it we keep FreeType enabled; the image will be # slightly off. - if (getattr(mpl, "_called_from_pytest", False) - and mpl._get_executable_info("dvipng").version != "1.16"): + if (getattr(mpl, "_called_from_pytest", False) and + mpl._get_executable_info("dvipng").raw_version != "1.16"): cmd.insert(1, "--freetype0") - self._run_checked_subprocess(cmd, tex) + cls._run_checked_subprocess(cmd, tex) return pngfile - def get_grey(self, tex, fontsize=None, dpi=None): + @classmethod + def get_grey(cls, tex, fontsize=None, dpi=None): """Return the alpha channel.""" - if not fontsize: - fontsize = rcParams['font.size'] - if not dpi: - dpi = rcParams['savefig.dpi'] - key = tex, self.get_font_config(), fontsize, dpi - alpha = self.grey_arrayd.get(key) + fontsize = mpl._val_or_rc(fontsize, 'font.size') + dpi = mpl._val_or_rc(dpi, 'savefig.dpi') + key = cls._get_tex_source(tex, fontsize), dpi + alpha = cls._grey_arrayd.get(key) if alpha is None: - pngfile = self.make_png(tex, fontsize, dpi) - rgba = mpl.image.imread(os.path.join(self.texcache, pngfile)) - self.grey_arrayd[key] = alpha = rgba[:, :, -1] + pngfile = cls.make_png(tex, fontsize, dpi) + rgba = mpl.image.imread(os.path.join(cls._texcache, pngfile)) + cls._grey_arrayd[key] = alpha = rgba[:, :, -1] return alpha - def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)): - """Return latex's rendering of the tex string as an rgba array.""" - alpha = self.get_grey(tex, fontsize, dpi) + @classmethod + def get_rgba(cls, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)): + r""" + Return latex's rendering of the tex string as an RGBA array. + + Examples + -------- + >>> texmanager = TexManager() + >>> s = r"\TeX\ is $\displaystyle\sum_n\frac{-e^{i\pi}}{2^n}$!" + >>> Z = texmanager.get_rgba(s, fontsize=12, dpi=80, rgb=(1, 0, 0)) + """ + alpha = cls.get_grey(tex, fontsize, dpi) rgba = np.empty((*alpha.shape, 4)) rgba[..., :3] = mpl.colors.to_rgb(rgb) rgba[..., -1] = alpha return rgba - def get_text_width_height_descent(self, tex, fontsize, renderer=None): + @classmethod + def get_text_width_height_descent(cls, tex, fontsize, renderer=None): """Return width, height and descent of the text.""" if tex.strip() == '': return 0, 0, 0 - + dvifile = cls.make_dvi(tex, fontsize) dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1 - - if dict.__getitem__(rcParams, 'text.latex.preview'): - # use preview.sty - basefile = self.get_basefile(tex, fontsize) - baselinefile = '%s.baseline' % basefile - - if not os.path.exists(baselinefile): - dvifile = self.make_dvi_preview(tex, fontsize) - - with open(baselinefile) as fh: - l = fh.read().split() - height, depth, width = [float(l1) * dpi_fraction for l1 in l] - return width, height + depth, depth - - else: - # use dviread. - dvifile = self.make_dvi(tex, fontsize) - with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi: - page, = dvi - # A total height (including the descent) needs to be returned. - return page.width, page.height + page.descent, page.descent + with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi: + page, = dvi + # A total height (including the descent) needs to be returned. + return page.width, page.height + page.descent, page.descent diff --git a/lib/matplotlib/texmanager.pyi b/lib/matplotlib/texmanager.pyi new file mode 100644 index 000000000000..94f0d76fa814 --- /dev/null +++ b/lib/matplotlib/texmanager.pyi @@ -0,0 +1,38 @@ +from .backend_bases import RendererBase + +from matplotlib.typing import ColorType + +import numpy as np + +class TexManager: + texcache: str + @classmethod + def get_basefile( + cls, tex: str, fontsize: float, dpi: float | None = ... + ) -> str: ... + @classmethod + def get_font_preamble(cls) -> str: ... + @classmethod + def get_custom_preamble(cls) -> str: ... + @classmethod + def make_tex(cls, tex: str, fontsize: float) -> str: ... + @classmethod + def make_dvi(cls, tex: str, fontsize: float) -> str: ... + @classmethod + def make_png(cls, tex: str, fontsize: float, dpi: float) -> str: ... + @classmethod + def get_grey( + cls, tex: str, fontsize: float | None = ..., dpi: float | None = ... + ) -> np.ndarray: ... + @classmethod + def get_rgba( + cls, + tex: str, + fontsize: float | None = ..., + dpi: float | None = ..., + rgb: ColorType = ..., + ) -> np.ndarray: ... + @classmethod + def get_text_width_height_descent( + cls, tex: str, fontsize, renderer: RendererBase | None = ... + ) -> tuple[int, int, int]: ... diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index a1d475c1b948..acde4fb179a2 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2,18 +2,20 @@ Classes for including text in a figure. """ -import contextlib +import functools import logging import math +from numbers import Real import weakref import numpy as np -from . import artist, cbook, docstring, rcParams +import matplotlib as mpl +from . import _api, artist, cbook, _docstring from .artist import Artist from .font_manager import FontProperties from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle -from .textpath import TextPath # Unused, but imported by others. +from .textpath import TextPath, TextToPath # noqa # Logically located here from .transforms import ( Affine2D, Bbox, BboxBase, BboxTransformTo, IdentityTransform, Transform) @@ -21,56 +23,15 @@ _log = logging.getLogger(__name__) -@contextlib.contextmanager -def _wrap_text(textobj): - """Temporarily inserts newlines if the wrap option is enabled.""" - if textobj.get_wrap(): - old_text = textobj.get_text() - try: - textobj.set_text(textobj._get_wrapped_text()) - yield textobj - finally: - textobj.set_text(old_text) - else: - yield textobj - - -# Extracted from Text's method to serve as a function -def get_rotation(rotation): - """ - Return *rotation* normalized to an angle between 0 and 360 degrees. - - Parameters - ---------- - rotation : float or {None, 'horizontal', 'vertical'} - Rotation angle in degrees. *None* and 'horizontal' equal 0, - 'vertical' equals 90. - - Returns - ------- - float - """ - try: - return float(rotation) % 360 - except (ValueError, TypeError) as err: - if cbook._str_equal(rotation, 'horizontal') or rotation is None: - return 0. - elif cbook._str_equal(rotation, 'vertical'): - return 90. - else: - raise ValueError("rotation is {!r}; expected either 'horizontal', " - "'vertical', numeric value, or None" - .format(rotation)) from err - - def _get_textbox(text, renderer): """ - Calculate the bounding box of the text. Unlike - :meth:`matplotlib.text.Text.get_extents` method, The bbox size of - the text before the rotation is calculated. + Calculate the bounding box of the text. + + The bbox position takes text rotation into account, but the width and + height are those of the unrotated box (unlike `.Text.get_window_extent`). """ # TODO : This function may move into the Text class as a method. As a - # matter of fact, The information from the _get_textbox function + # matter of fact, the information from the _get_textbox function # should be available during the Text._get_layout() call, which is # called within the _get_textbox. So, it would better to move this # function as a method with some refactoring of _get_layout method. @@ -101,31 +62,47 @@ def _get_textbox(text, renderer): return x_box, y_box, w_box, h_box -@cbook._define_aliases({ +def _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi): + """Call ``renderer.get_text_width_height_descent``, caching the results.""" + # Cached based on a copy of fontprop so that later in-place mutations of + # the passed-in argument do not mess up the cache. + return _get_text_metrics_with_cache_impl( + weakref.ref(renderer), text, fontprop.copy(), ismath, dpi) + + +@functools.lru_cache(4096) +def _get_text_metrics_with_cache_impl( + renderer_ref, text, fontprop, ismath, dpi): + # dpi is unused, but participates in cache invalidation (via the renderer). + return renderer_ref().get_text_width_height_descent(text, fontprop, ismath) + + +@_docstring.interpd +@_api.define_aliases({ "color": ["c"], - "fontfamily": ["family"], "fontproperties": ["font", "font_properties"], - "horizontalalignment": ["ha"], - "multialignment": ["ma"], + "fontfamily": ["family"], "fontname": ["name"], "fontsize": ["size"], "fontstretch": ["stretch"], "fontstyle": ["style"], "fontvariant": ["variant"], - "verticalalignment": ["va"], "fontweight": ["weight"], + "horizontalalignment": ["ha"], + "verticalalignment": ["va"], + "multialignment": ["ma"], }) class Text(Artist): """Handle storing and drawing of text in window or data coordinates.""" zorder = 3 - _cached = cbook.maxdict(50) + _charsize_cache = dict() def __repr__(self): - return "Text(%s, %s, %s)" % (self._x, self._y, repr(self._text)) + return f"Text({self._x}, {self._y}, {self._text!r})" def __init__(self, - x=0, y=0, text='', + x=0, y=0, text='', *, color=None, # defaults to rc params verticalalignment='baseline', horizontalalignment='left', @@ -137,48 +114,97 @@ def __init__(self, usetex=None, # defaults to rcParams['text.usetex'] wrap=False, transform_rotates_text=False, + parse_math=None, # defaults to rcParams['text.parse_math'] + antialiased=None, # defaults to rcParams['text.antialiased'] **kwargs ): """ Create a `.Text` instance at *x*, *y* with string *text*. + The text is aligned relative to the anchor point (*x*, *y*) according + to ``horizontalalignment`` (default: 'left') and ``verticalalignment`` + (default: 'baseline'). See also + :doc:`/gallery/text_labels_and_annotations/text_alignment`. + + While Text accepts the 'label' keyword argument, by default it is not + added to the handles of a legend. + Valid keyword arguments are: - %(Text)s + %(Text:kwdoc)s """ super().__init__() self._x, self._y = x, y self._text = '' + self._reset_visual_defaults( + text=text, + color=color, + fontproperties=fontproperties, + usetex=usetex, + parse_math=parse_math, + wrap=wrap, + verticalalignment=verticalalignment, + horizontalalignment=horizontalalignment, + multialignment=multialignment, + rotation=rotation, + transform_rotates_text=transform_rotates_text, + linespacing=linespacing, + rotation_mode=rotation_mode, + antialiased=antialiased + ) + self.update(kwargs) + + def _reset_visual_defaults( + self, + text='', + color=None, + fontproperties=None, + usetex=None, + parse_math=None, + wrap=False, + verticalalignment='baseline', + horizontalalignment='left', + multialignment=None, + rotation=None, + transform_rotates_text=False, + linespacing=None, + rotation_mode=None, + antialiased=None + ): self.set_text(text) - self.set_color(color if color is not None else rcParams["text.color"]) + self.set_color(mpl._val_or_rc(color, "text.color")) self.set_fontproperties(fontproperties) self.set_usetex(usetex) + self.set_parse_math(mpl._val_or_rc(parse_math, 'text.parse_math')) self.set_wrap(wrap) self.set_verticalalignment(verticalalignment) self.set_horizontalalignment(horizontalalignment) self._multialignment = multialignment - self._rotation = rotation + self.set_rotation(rotation) self._transform_rotates_text = transform_rotates_text self._bbox_patch = None # a FancyBboxPatch instance self._renderer = None if linespacing is None: - linespacing = 1.2 # Maybe use rcParam later. - self._linespacing = linespacing + linespacing = 1.2 # Maybe use rcParam later. + self.set_linespacing(linespacing) self.set_rotation_mode(rotation_mode) - self.update(kwargs) + self.set_antialiased(mpl._val_or_rc(antialiased, 'text.antialiased')) def update(self, kwargs): # docstring inherited + ret = [] + kwargs = cbook.normalize_kwargs(kwargs, Text) sentinel = object() # bbox can be None, so use another sentinel. # Update fontproperties first, as it has lowest priority. fontproperties = kwargs.pop("fontproperties", sentinel) if fontproperties is not sentinel: - self.set_fontproperties(fontproperties) + ret.append(self.set_fontproperties(fontproperties)) # Update bbox last, as it depends on font properties. bbox = kwargs.pop("bbox", sentinel) - super().update(kwargs) + ret.extend(super().update(kwargs)) if bbox is not sentinel: - self.set_bbox(bbox) + ret.append(self.set_bbox(bbox)) + return ret def __getstate__(self): d = super().__getstate__() @@ -191,20 +217,15 @@ def contains(self, mouseevent): Return whether the mouse event occurred inside the axis-aligned bounding-box of the text. """ - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info - - if not self.get_visible() or self._renderer is None: + if (self._different_canvas(mouseevent) or not self.get_visible() + or self._renderer is None): return False, {} - # Explicitly use Text.get_window_extent(self) and not # self.get_window_extent() so that Annotation.contains does not # accidentally cover the entire annotation bounding box. bbox = Text.get_window_extent(self) inside = (bbox.x0 <= mouseevent.x <= bbox.x1 and bbox.y0 <= mouseevent.y <= bbox.y1) - cattr = {} # if the text has a surrounding patch, also check containment for it, # and merge the results with the results for the text. @@ -212,7 +233,6 @@ def contains(self, mouseevent): patch_inside, patch_cattr = self._bbox_patch.contains(mouseevent) inside = inside or patch_inside cattr["bbox_patch"] = patch_cattr - return inside, cattr def _get_xy_display(self): @@ -228,16 +248,45 @@ def _get_multialignment(self): else: return self._horizontalalignment + def _char_index_at(self, x): + """ + Calculate the index closest to the coordinate x in display space. + + The position of text[index] is assumed to be the sum of the widths + of all preceding characters text[:index]. + + This works only on single line texts. + """ + if not self._text: + return 0 + + text = self._text + + fontproperties = str(self._fontproperties) + if fontproperties not in Text._charsize_cache: + Text._charsize_cache[fontproperties] = dict() + + charsize_cache = Text._charsize_cache[fontproperties] + for char in set(text): + if char not in charsize_cache: + self.set_text(char) + bb = self.get_window_extent() + charsize_cache[char] = bb.x1 - bb.x0 + + self.set_text(text) + bb = self.get_window_extent() + + size_accum = np.cumsum([0] + [charsize_cache[x] for x in text]) + std_x = x - bb.x0 + return (np.abs(size_accum - std_x)).argmin() + def get_rotation(self): """Return the text angle in degrees between 0 and 360.""" if self.get_transform_rotates_text(): - angle = get_rotation(self._rotation) - x, y = self.get_unitless_position() - angles = [angle, ] - pts = [[x, y]] - return self.get_transform().transform_angles(angles, pts).item(0) + return self.get_transform().transform_angles( + [self._rotation], [self.get_unitless_position()]).item(0) else: - return get_rotation(self._rotation) # string_or_number -> number + return self._rotation def get_transform_rotates_text(self): """ @@ -251,12 +300,19 @@ def set_rotation_mode(self, m): Parameters ---------- - m : {None, 'default', 'anchor'} - If ``None`` or ``"default"``, the text will be first rotated, then - aligned according to their horizontal and vertical alignments. If - ``"anchor"``, then alignment occurs before rotation. - """ - cbook._check_in_list(["anchor", "default", None], rotation_mode=m) + m : {None, 'default', 'anchor', 'xtick', 'ytick'} + If ``"default"``, the text will be first rotated, then aligned according + to their horizontal and vertical alignments. If ``"anchor"``, then + alignment occurs before rotation. "xtick" and "ytick" adjust the + horizontal/vertical alignment so that the text is visually pointing + towards its anchor point. This is primarily used for rotated tick + labels and positions them nicely towards their ticks. Passing + ``None`` will set the rotation mode to ``"default"``. + """ + if m is None: + m = "default" + else: + _api.check_in_list(("anchor", "default", "xtick", "ytick"), rotation_mode=m) self._rotation_mode = m self.stale = True @@ -264,6 +320,27 @@ def get_rotation_mode(self): """Return the text rotation mode.""" return self._rotation_mode + def set_antialiased(self, antialiased): + """ + Set whether to use antialiased rendering. + + Parameters + ---------- + antialiased : bool + + Notes + ----- + Antialiasing will be determined by :rc:`text.antialiased` + and the parameter *antialiased* will have no effect if the text contains + math expressions. + """ + self._antialiased = antialiased + self.stale = True + + def get_antialiased(self): + """Return whether antialiased rendering is used.""" + return self._antialiased + def update_from(self, other): # docstring inherited super().update_from(other) @@ -277,6 +354,7 @@ def update_from(self, other): self._transform_rotates_text = other._transform_rotates_text self._picker = other._picker self._linespacing = other._linespacing + self._antialiased = other._antialiased self.stale = True def _get_layout(self, renderer): @@ -285,12 +363,8 @@ def _get_layout(self, renderer): multiple-alignment information. Note that it returns an extent of a rotated text when necessary. """ - key = self.get_prop_tup(renderer=renderer) - if key in self._cached: - return self._cached[key] - thisx, thisy = 0.0, 0.0 - lines = self.get_text().split("\n") # Ensures lines is not empty. + lines = self._get_wrapped_text().split("\n") # Ensures lines is not empty. ws = [] hs = [] @@ -298,16 +372,18 @@ def _get_layout(self, renderer): ys = [] # Full vertical extent of font, including ascenders and descenders: - _, lp_h, lp_d = renderer.get_text_width_height_descent( - "lp", self._fontproperties, - ismath="TeX" if self.get_usetex() else False) + _, lp_h, lp_d = _get_text_metrics_with_cache( + renderer, "lp", self._fontproperties, + ismath="TeX" if self.get_usetex() else False, + dpi=self.get_figure(root=True).dpi) min_dy = (lp_h - lp_d) * self._linespacing for i, line in enumerate(lines): clean_line, ismath = self._preprocess_math(line) if clean_line: - w, h, d = renderer.get_text_width_height_descent( - clean_line, self._fontproperties, ismath=ismath) + w, h, d = _get_text_metrics_with_cache( + renderer, clean_line, self._fontproperties, + ismath=ismath, dpi=self.get_figure(root=True).dpi) else: w = h = d = 0 @@ -344,7 +420,6 @@ def _get_layout(self, renderer): xmax = width ymax = 0 ymin = ys[-1] - descent # baseline of last line minus its descent - height = ymax - ymin # get the rotation matrix M = Affine2D().rotate_deg(self.get_rotation()) @@ -381,6 +456,11 @@ def _get_layout(self, renderer): rotation_mode = self.get_rotation_mode() if rotation_mode != "anchor": + angle = self.get_rotation() + if rotation_mode == 'xtick': + halign = self._ha_for_angle(angle) + elif rotation_mode == 'ytick': + valign = self._va_for_angle(angle) # compute the text location in display coords and the offsets # necessary to align the bbox with that location if halign == 'center': @@ -432,20 +512,25 @@ def _get_layout(self, renderer): # now rotate the positions around the first (x, y) position xys = M.transform(offset_layout) - (offsetx, offsety) - ret = bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent - self._cached[key] = ret - return ret + return bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent def set_bbox(self, rectprops): """ - Draw a bounding box around self. + Draw a box behind/around the text. + + This can be used to set a background and/or a frame around the text. + It's realized through a `.FancyBboxPatch` behind the text (see also + `.Text.get_bbox_patch`). The bbox patch is None by default and only + created when needed. Parameters ---------- - rectprops : dict with properties for `.patches.FancyBboxPatch` + rectprops : dict with properties for `.FancyBboxPatch` or None The default boxstyle is 'square'. The mutation scale of the `.patches.FancyBboxPatch` is set to the fontsize. + Pass ``None`` to remove the bbox patch completely. + Examples -------- :: @@ -480,6 +565,8 @@ def get_bbox_patch(self): """ Return the bbox Patch, or None if the `.patches.FancyBboxPatch` is not made. + + For more details see `.Text.set_bbox`. """ return self._bbox_patch @@ -490,17 +577,12 @@ def update_bbox_position_size(self, renderer): This method should be used when the position and size of the bbox needs to be updated before actually drawing the bbox. """ - if self._bbox_patch: - - trans = self.get_transform() - # don't use self.get_unitless_position here, which refers to text # position in Text: posx = float(self.convert_xunits(self._x)) posy = float(self.convert_yunits(self._y)) - - posx, posy = trans.transform((posx, posy)) + posx, posy = self.get_transform().transform((posx, posy)) x_box, y_box, w_box, h_box = _get_textbox(self, renderer) self._bbox_patch.set_bounds(0., 0., w_box, h_box) @@ -511,27 +593,11 @@ def update_bbox_position_size(self, renderer): fontsize_in_pixel = renderer.points_to_pixels(self.get_size()) self._bbox_patch.set_mutation_scale(fontsize_in_pixel) - def _draw_bbox(self, renderer, posx, posy): - """ - Update the location and size of the bbox (`.patches.FancyBboxPatch`), - and draw. - """ - - x_box, y_box, w_box, h_box = _get_textbox(self, renderer) - self._bbox_patch.set_bounds(0., 0., w_box, h_box) - theta = np.deg2rad(self.get_rotation()) - tr = Affine2D().rotate(theta) - tr = tr.translate(posx + x_box, posy + y_box) - self._bbox_patch.set_transform(tr) - fontsize_in_pixel = renderer.points_to_pixels(self.get_size()) - self._bbox_patch.set_mutation_scale(fontsize_in_pixel) - self._bbox_patch.draw(renderer) - def _update_clip_properties(self): - clipprops = dict(clip_box=self.clipbox, - clip_path=self._clippath, - clip_on=self._clipon) if self._bbox_patch: + clipprops = dict(clip_box=self.clipbox, + clip_path=self._clippath, + clip_on=self._clipon) self._bbox_patch.update(clipprops) def set_clip_box(self, clipbox): @@ -557,9 +623,20 @@ def set_wrap(self, wrap): """ Set whether the text can be wrapped. + Wrapping makes sure the text is confined to the (sub)figure box. It + does not take into account any other artists. + Parameters ---------- wrap : bool + + Notes + ----- + Wrapping does not work together with + ``savefig(..., bbox_inches='tight')`` (which is also used internally + by ``%matplotlib inline`` in IPython/Jupyter). The 'tight' setting + rescales the canvas to accommodate all content and happens before + wrapping. """ self._wrap = wrap @@ -596,16 +673,16 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box): """ if rotation > 270: quad = rotation - 270 - h1 = y0 / math.cos(math.radians(quad)) + h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad)) h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad)) elif rotation > 180: quad = rotation - 180 - h1 = x0 / math.cos(math.radians(quad)) - h2 = y0 / math.cos(math.radians(90 - quad)) + h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad)) + h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad)) elif rotation > 90: quad = rotation - 90 h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad)) - h2 = x0 / math.cos(math.radians(90 - quad)) + h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad)) else: h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation)) h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation)) @@ -616,17 +693,21 @@ def _get_rendered_text_width(self, text): """ Return the width of a given text string, in pixels. """ - w, h, d = self._renderer.get_text_width_height_descent( - text, - self.get_fontproperties(), - False) + + w, h, d = _get_text_metrics_with_cache( + self._renderer, text, self.get_fontproperties(), + cbook.is_math_text(text), + self.get_figure(root=True).dpi) return math.ceil(w) def _get_wrapped_text(self): """ - Return a copy of the text with new lines added, so that - the text is wrapped relative to the parent figure. + Return a copy of the text string with new lines added so that the text + is wrapped relative to the parent figure (if `get_wrap` is True). """ + if not self.get_wrap(): + return self.get_text() + # Not fit to handle breaking up latex syntax correctly, so # ignore latex for now. if self.get_usetex(): @@ -683,55 +764,65 @@ def draw(self, renderer): renderer.open_group('text', self.get_gid()) - with _wrap_text(self) as textobj: - bbox, info, descent = textobj._get_layout(renderer) - trans = textobj.get_transform() + with self._cm_set(text=self._get_wrapped_text()): + bbox, info, descent = self._get_layout(renderer) + trans = self.get_transform() - # don't use textobj.get_position here, which refers to text + # don't use self.get_position here, which refers to text # position in Text: - posx = float(textobj.convert_xunits(textobj._x)) - posy = float(textobj.convert_yunits(textobj._y)) + x, y = self._x, self._y + if np.ma.is_masked(x): + x = np.nan + if np.ma.is_masked(y): + y = np.nan + posx = float(self.convert_xunits(x)) + posy = float(self.convert_yunits(y)) posx, posy = trans.transform((posx, posy)) + if np.isnan(posx) or np.isnan(posy): + return # don't throw a warning here if not np.isfinite(posx) or not np.isfinite(posy): _log.warning("posx and posy should be finite values") return canvasw, canvash = renderer.get_canvas_width_height() - # draw the FancyBboxPatch - if textobj._bbox_patch: - textobj._draw_bbox(renderer, posx, posy) + # Update the location and size of the bbox + # (`.patches.FancyBboxPatch`), and draw it. + if self._bbox_patch: + self.update_bbox_position_size(renderer) + self._bbox_patch.draw(renderer) gc = renderer.new_gc() - gc.set_foreground(textobj.get_color()) - gc.set_alpha(textobj.get_alpha()) - gc.set_url(textobj._url) - textobj._set_gc_clip(gc) + gc.set_foreground(self.get_color()) + gc.set_alpha(self.get_alpha()) + gc.set_url(self._url) + gc.set_antialiased(self._antialiased) + self._set_gc_clip(gc) - angle = textobj.get_rotation() + angle = self.get_rotation() for line, wh, x, y in info: - mtext = textobj if len(info) == 1 else None + mtext = self if len(info) == 1 else None x = x + posx y = y + posy if renderer.flipy(): y = canvash - y - clean_line, ismath = textobj._preprocess_math(line) + clean_line, ismath = self._preprocess_math(line) - if textobj.get_path_effects(): + if self.get_path_effects(): from matplotlib.patheffects import PathEffectRenderer textrenderer = PathEffectRenderer( - textobj.get_path_effects(), renderer) + self.get_path_effects(), renderer) else: textrenderer = renderer - if textobj.get_usetex(): + if self.get_usetex(): textrenderer.draw_tex(gc, x, y, clean_line, - textobj._fontproperties, angle, + self._fontproperties, angle, mtext=mtext) else: textrenderer.draw_text(gc, x, y, clean_line, - textobj._fontproperties, angle, + self._fontproperties, angle, ismath=ismath, mtext=mtext) gc.restore() @@ -837,25 +928,6 @@ def get_position(self): # specified with 'set_x' and 'set_y'. return self._x, self._y - def get_prop_tup(self, renderer=None): - """ - Return a hashable tuple of properties. - - Not intended to be human readable, but useful for backends who - want to cache derived information about text (e.g., layouts) and - need to know if the text has changed. - """ - x, y = self.get_unitless_position() - renderer = renderer or self._renderer - return (x, y, self.get_text(), self._color, - self._verticalalignment, self._horizontalalignment, - hash(self._fontproperties), - self._rotation, self._rotation_mode, - self._transform_rotates_text, - self.figure.dpi, weakref.ref(renderer), - self._linespacing - ) - def get_text(self): """Return the text string.""" return self._text @@ -863,7 +935,7 @@ def get_text(self): def get_verticalalignment(self): """ Return the vertical alignment as a string. Will be one of - 'top', 'center', 'bottom' or 'baseline'. + 'top', 'center', 'bottom', 'baseline' or 'center_baseline'. """ return self._verticalalignment @@ -880,32 +952,36 @@ def get_window_extent(self, renderer=None, dpi=None): A renderer is needed to compute the bounding box. If the artist has already been drawn, the renderer is cached; thus, it is only necessary to pass this argument when calling `get_window_extent` - before the first `draw`. In practice, it is usually easier to - trigger a draw first (e.g. by saving the figure). + before the first draw. In practice, it is usually easier to + trigger a draw first, e.g. by calling + `~.Figure.draw_without_rendering` or ``plt.show()``. dpi : float, optional The dpi value for computing the bbox, defaults to - ``self.figure.dpi`` (*not* the renderer dpi); should be set e.g. if - to match regions with a figure saved with a custom dpi value. + ``self.get_figure(root=True).dpi`` (*not* the renderer dpi); should be set + e.g. if to match regions with a figure saved with a custom dpi value. """ - #return _unit_box if not self.get_visible(): return Bbox.unit() + + fig = self.get_figure(root=True) if dpi is None: - dpi = self.figure.dpi + dpi = fig.dpi if self.get_text() == '': - with cbook._setattr_cm(self.figure, dpi=dpi): + with cbook._setattr_cm(fig, dpi=dpi): tx, ty = self._get_xy_display() return Bbox.from_bounds(tx, ty, 0, 0) if renderer is not None: self._renderer = renderer if self._renderer is None: - self._renderer = self.figure._cachedRenderer + self._renderer = fig._get_renderer() if self._renderer is None: - raise RuntimeError('Cannot get window extent w/o renderer') + raise RuntimeError( + "Cannot get window extent of text w/o renderer. You likely " + "want to call 'figure.draw_without_rendering()' first.") - with cbook._setattr_cm(self.figure, dpi=dpi): + with cbook._setattr_cm(fig, dpi=dpi): bbox, info, descent = self._get_layout(self._renderer) x, y = self.get_unitless_position() x, y = self.get_transform().transform((x, y)) @@ -914,11 +990,14 @@ def get_window_extent(self, renderer=None, dpi=None): def set_backgroundcolor(self, color): """ - Set the background color of the text by updating the bbox. + Set the background color of the text. + + This is realized through the bbox (see `.set_bbox`), + creating the bbox patch if needed. Parameters ---------- - color : color + color : :mpltype:`color` See Also -------- @@ -938,25 +1017,26 @@ def set_color(self, color): Parameters ---------- - color : color + color : :mpltype:`color` """ - # Make sure it is hashable, or get_prop_tup will fail. - try: - hash(color) - except TypeError: - color = tuple(color) + # "auto" is only supported by axisartist, but we can just let it error + # out at draw time for simplicity. + if not cbook._str_equal(color, "auto"): + mpl.colors._check_color_like(color=color) self._color = color self.stale = True def set_horizontalalignment(self, align): """ - Set the horizontal alignment to one of + Set the horizontal alignment relative to the anchor point. + + See also :doc:`/gallery/text_labels_and_annotations/text_alignment`. Parameters ---------- - align : {'center', 'right', 'left'} + align : {'left', 'center', 'right'} """ - cbook._check_in_list(['center', 'right', 'left'], align=align) + _api.check_in_list(['center', 'right', 'left'], align=align) self._horizontalalignment = align self.stale = True @@ -972,7 +1052,7 @@ def set_multialignment(self, align): ---------- align : {'left', 'right', 'center'} """ - cbook._check_in_list(['center', 'right', 'left'], align=align) + _api.check_in_list(['center', 'right', 'left'], align=align) self._multialignment = align self.stale = True @@ -986,12 +1066,13 @@ def set_linespacing(self, spacing): ---------- spacing : float (multiple of font size) """ + _api.check_isinstance(Real, spacing=spacing) self._linespacing = spacing self.stale = True def set_fontfamily(self, fontname): """ - Set the font family. May be either a single string, or a list of + Set the font family. Can be either a single string, or a list of strings in decreasing priority. Each string may be either a real font name or a generic font class name. If the latter, the specific font names will be looked up in the corresponding rcParams. @@ -1051,7 +1132,7 @@ def set_fontsize(self, fontsize): ---------- fontsize : float or {'xx-small', 'x-small', 'small', 'medium', \ 'large', 'x-large', 'xx-large'} - If float, the fontsize in points. The string values denote sizes + If a float, the fontsize in points. The string values denote sizes relative to the default font size. See Also @@ -1086,7 +1167,7 @@ def set_math_fontfamily(self, fontfamily): The name of the font family. Available font families are defined in the - :ref:`matplotlibrc.template file + :ref:`default matplotlibrc file `. See Also @@ -1172,7 +1253,15 @@ def set_rotation(self, s): The rotation angle in degrees in mathematically positive direction (counterclockwise). 'horizontal' equals 0, 'vertical' equals 90. """ - self._rotation = s + if isinstance(s, Real): + self._rotation = float(s) % 360 + elif cbook._str_equal(s, 'horizontal') or s is None: + self._rotation = 0. + elif cbook._str_equal(s, 'vertical'): + self._rotation = 90. + else: + raise ValueError("rotation must be 'vertical', 'horizontal' or " + f"a number, not {s}") self.stale = True def set_transform_rotates_text(self, t): @@ -1188,13 +1277,15 @@ def set_transform_rotates_text(self, t): def set_verticalalignment(self, align): """ - Set the vertical alignment. + Set the vertical alignment relative to the anchor point. + + See also :doc:`/gallery/text_labels_and_annotations/text_alignment`. Parameters ---------- - align : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} + align : {'baseline', 'bottom', 'center', 'center_baseline', 'top'} """ - cbook._check_in_list( + _api.check_in_list( ['top', 'bottom', 'center', 'baseline', 'center_baseline'], align=align) self._verticalalignment = align @@ -1212,10 +1303,9 @@ def set_text(self, s): Any object gets converted to its `str` representation, except for ``None`` which is converted to an empty string. """ - if s is None: - s = '' + s = '' if s is None else str(s) if s != self._text: - self._text = str(s) + self._text = s self.stale = True def _preprocess_math(self, s): @@ -1226,7 +1316,8 @@ def _preprocess_math(self, s): - If *self* is configured to use TeX, return *s* unchanged except that a single space gets escaped, and the flag "TeX". - Otherwise, if *s* is mathtext (has an even number of unescaped dollar - signs), return *s* and the flag True. + signs) and ``parse_math`` is not set to False, return *s* and the + flag True. - Otherwise, return *s* with dollar signs unescaped, and the flag False. """ @@ -1234,6 +1325,8 @@ def _preprocess_math(self, s): if s == " ": s = r"\ " return s, "TeX" + elif not self.get_parse_math(): + return s, False elif cbook.is_math_text(s): return s, True else: @@ -1253,6 +1346,7 @@ def set_fontproperties(self, fp): self._fontproperties = FontProperties._from_any(fp).copy() self.stale = True + @_docstring.kwarg_doc("bool, default: :rc:`text.usetex`") def set_usetex(self, usetex): """ Parameters @@ -1261,19 +1355,32 @@ def set_usetex(self, usetex): Whether to render using TeX, ``None`` means to use :rc:`text.usetex`. """ - if usetex is None: - self._usetex = rcParams['text.usetex'] - else: - self._usetex = bool(usetex) + self._usetex = bool(mpl._val_or_rc(usetex, 'text.usetex')) self.stale = True def get_usetex(self): """Return whether this `Text` object uses TeX for rendering.""" return self._usetex + def set_parse_math(self, parse_math): + """ + Override switch to disable any mathtext parsing for this `Text`. + + Parameters + ---------- + parse_math : bool + If False, this `Text` will never use mathtext. If True, mathtext + will be used if there is an even number of unescaped dollar signs. + """ + self._parse_math = bool(parse_math) + + def get_parse_math(self): + """Return whether mathtext parsing is considered for this `Text`.""" + return self._parse_math + def set_fontname(self, fontname): """ - Alias for `set_family`. + Alias for `set_fontfamily`. One-way alias only: the getter differs. @@ -1287,11 +1394,33 @@ def set_fontname(self, fontname): .font_manager.FontProperties.set_family """ - return self.set_family(fontname) + self.set_fontfamily(fontname) + def _ha_for_angle(self, angle): + """ + Determines horizontal alignment ('ha') for rotation_mode "xtick" based on + the angle of rotation in degrees and the vertical alignment. + """ + anchor_at_bottom = self.get_verticalalignment() == 'bottom' + if (angle <= 10 or 85 <= angle <= 95 or 350 <= angle or + 170 <= angle <= 190 or 265 <= angle <= 275): + return 'center' + elif 10 < angle < 85 or 190 < angle < 265: + return 'left' if anchor_at_bottom else 'right' + return 'right' if anchor_at_bottom else 'left' -docstring.interpd.update(Text=artist.kwdoc(Text)) -docstring.dedent_interpd(Text.__init__) + def _va_for_angle(self, angle): + """ + Determines vertical alignment ('va') for rotation_mode "ytick" based on + the angle of rotation in degrees and the horizontal alignment. + """ + anchor_at_left = self.get_horizontalalignment() == 'left' + if (angle <= 10 or 350 <= angle or 170 <= angle <= 190 + or 80 <= angle <= 100 or 260 <= angle <= 280): + return 'center' + elif 190 < angle < 260 or 10 < angle < 80: + return 'baseline' if anchor_at_left else 'top' + return 'top' if anchor_at_left else 'baseline' class OffsetFrom: @@ -1301,7 +1430,7 @@ def __init__(self, artist, ref_coord, unit="points"): """ Parameters ---------- - artist : `.Artist` or `.BboxBase` or `.Transform` + artist : `~matplotlib.artist.Artist` or `.BboxBase` or `.Transform` The object to compute the offset from. ref_coord : (float, float) @@ -1316,7 +1445,8 @@ def __init__(self, artist, ref_coord, unit="points"): The screen units to use (pixels or points) for the offset input. """ self._artist = artist - self._ref_coord = ref_coord + x, y = ref_coord # Make copy when ref_coord is an array (and check the shape). + self._ref_coord = x, y self.set_unit(unit) def set_unit(self, unit): @@ -1327,20 +1457,13 @@ def set_unit(self, unit): ---------- unit : {'points', 'pixels'} """ - cbook._check_in_list(["points", "pixels"], unit=unit) + _api.check_in_list(["points", "pixels"], unit=unit) self._unit = unit def get_unit(self): """Return the unit for input to the transform used by ``__call__``.""" return self._unit - def _get_scale(self, renderer): - unit = self.get_unit() - if unit == "pixels": - return 1. - else: - return renderer.points_to_pixels(1.) - def __call__(self, renderer): """ Return the offset transform. @@ -1369,12 +1492,9 @@ def __call__(self, renderer): elif isinstance(self._artist, Transform): x, y = self._artist.transform(self._ref_coord) else: - raise RuntimeError("unknown type") - - sc = self._get_scale(renderer) - tr = Affine2D().scale(sc).translate(x, y) - - return tr + _api.check_isinstance((Artist, BboxBase, Transform), artist=self._artist) + scale = 1 if self._unit == "pixels" else renderer.points_to_pixels(1) + return Affine2D().scale(scale).translate(x, y) class _AnnotationBase: @@ -1383,122 +1503,95 @@ def __init__(self, xycoords='data', annotation_clip=None): - self.xy = xy + x, y = xy # Make copy when xy is an array (and check the shape). + self.xy = x, y self.xycoords = xycoords self.set_annotation_clip(annotation_clip) self._draggable = None - def _get_xy(self, renderer, x, y, s): - if isinstance(s, tuple): - s1, s2 = s - else: - s1, s2 = s, s - if s1 == 'data': + def _get_xy(self, renderer, xy, coords): + x, y = xy + xcoord, ycoord = coords if isinstance(coords, tuple) else (coords, coords) + if xcoord == 'data': x = float(self.convert_xunits(x)) - if s2 == 'data': + if ycoord == 'data': y = float(self.convert_yunits(y)) - return self._get_xy_transform(renderer, s).transform((x, y)) + return self._get_xy_transform(renderer, coords).transform((x, y)) - def _get_xy_transform(self, renderer, s): + def _get_xy_transform(self, renderer, coords): - if isinstance(s, tuple): - s1, s2 = s + if isinstance(coords, tuple): + xcoord, ycoord = coords from matplotlib.transforms import blended_transform_factory - tr1 = self._get_xy_transform(renderer, s1) - tr2 = self._get_xy_transform(renderer, s2) - tr = blended_transform_factory(tr1, tr2) - return tr - elif callable(s): - tr = s(renderer) + tr1 = self._get_xy_transform(renderer, xcoord) + tr2 = self._get_xy_transform(renderer, ycoord) + return blended_transform_factory(tr1, tr2) + elif callable(coords): + tr = coords(renderer) if isinstance(tr, BboxBase): return BboxTransformTo(tr) elif isinstance(tr, Transform): return tr else: - raise RuntimeError("unknown return type ...") - elif isinstance(s, Artist): - bbox = s.get_window_extent(renderer) + raise TypeError( + f"xycoords callable must return a BboxBase or Transform, not a " + f"{type(tr).__name__}") + elif isinstance(coords, Artist): + bbox = coords.get_window_extent(renderer) return BboxTransformTo(bbox) - elif isinstance(s, BboxBase): - return BboxTransformTo(s) - elif isinstance(s, Transform): - return s - elif not isinstance(s, str): - raise RuntimeError("unknown coordinate type : %s" % s) - - if s == 'data': + elif isinstance(coords, BboxBase): + return BboxTransformTo(coords) + elif isinstance(coords, Transform): + return coords + elif not isinstance(coords, str): + raise TypeError( + f"'xycoords' must be an instance of str, tuple[str, str], Artist, " + f"Transform, or Callable, not a {type(coords).__name__}") + + if coords == 'data': return self.axes.transData - elif s == 'polar': + elif coords == 'polar': from matplotlib.projections import PolarAxes - tr = PolarAxes.PolarTransform() - trans = tr + self.axes.transData - return trans + return PolarAxes.PolarTransform() + self.axes.transData - s_ = s.split() - if len(s_) != 2: - raise ValueError("%s is not a recognized coordinate" % s) + try: + bbox_name, unit = coords.split() + except ValueError: # i.e. len(coords.split()) != 2. + raise ValueError(f"{coords!r} is not a valid coordinate") from None bbox0, xy0 = None, None - bbox_name, unit = s_ # if unit is offset-like if bbox_name == "figure": - bbox0 = self.figure.bbox + bbox0 = self.get_figure(root=False).figbbox + elif bbox_name == "subfigure": + bbox0 = self.get_figure(root=False).bbox elif bbox_name == "axes": bbox0 = self.axes.bbox - # elif bbox_name == "bbox": - # if bbox is None: - # raise RuntimeError("bbox is specified as a coordinate but " - # "never set") - # bbox0 = self._get_bbox(renderer, bbox) + # reference x, y in display coordinate if bbox0 is not None: xy0 = bbox0.p0 elif bbox_name == "offset": - xy0 = self._get_ref_xy(renderer) - - if xy0 is not None: - # reference x, y in display coordinate - ref_x, ref_y = xy0 - from matplotlib.transforms import Affine2D - if unit == "points": - # dots per points - dpp = self.figure.get_dpi() / 72. - tr = Affine2D().scale(dpp) - elif unit == "pixels": - tr = Affine2D() - elif unit == "fontsize": - fontsize = self.get_size() - dpp = fontsize * self.figure.get_dpi() / 72. - tr = Affine2D().scale(dpp) - elif unit == "fraction": - w, h = bbox0.size - tr = Affine2D().scale(w, h) - else: - raise ValueError("%s is not a recognized coordinate" % s) - - return tr.translate(ref_x, ref_y) - + xy0 = self._get_position_xy(renderer) else: - raise ValueError("%s is not a recognized coordinate" % s) - - def _get_ref_xy(self, renderer): - """ - Return x, y (in display coordinates) that is to be used for a reference - of any offset coordinate. - """ - return self._get_xy(renderer, *self.xy, self.xycoords) + raise ValueError(f"{coords!r} is not a valid coordinate") + + if unit == "points": + tr = Affine2D().scale( + self.get_figure(root=True).dpi / 72) # dpi/72 dots per point + elif unit == "pixels": + tr = Affine2D() + elif unit == "fontsize": + tr = Affine2D().scale( + self.get_size() * self.get_figure(root=True).dpi / 72) + elif unit == "fraction": + tr = Affine2D().scale(*bbox0.size) + else: + raise ValueError(f"{unit!r} is not a recognized unit") - # def _get_bbox(self, renderer): - # if hasattr(bbox, "bounds"): - # return bbox - # elif hasattr(bbox, "get_window_extent"): - # bbox = bbox.get_window_extent() - # return bbox - # else: - # raise ValueError("A bbox instance is expected but got %s" % - # str(bbox)) + return tr.translate(*xy0) def set_annotation_clip(self, b): """ @@ -1507,12 +1600,11 @@ def set_annotation_clip(self, b): Parameters ---------- b : bool or None - - True: the annotation will only be drawn when ``self.xy`` is - inside the axes. - - False: the annotation will always be drawn regardless of its - position. - - None: the ``self.xy`` will be checked only if *xycoords* is - "data". + - True: The annotation will be clipped when ``self.xy`` is + outside the Axes. + - False: The annotation will always be drawn. + - None: The annotation will be clipped when ``self.xy`` is + outside the Axes and ``self.xycoords == "data"``. """ self._annotation_clip = b @@ -1526,14 +1618,15 @@ def get_annotation_clip(self): def _get_position_xy(self, renderer): """Return the pixel position of the annotated point.""" - x, y = self.xy - return self._get_xy(renderer, x, y, self.xycoords) + return self._get_xy(renderer, self.xy, self.xycoords) - def _check_xy(self, renderer): + def _check_xy(self, renderer=None): """Check whether the annotation at *xy_pixel* should be drawn.""" + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() b = self.get_annotation_clip() if b or (b is None and self.xycoords == "data"): - # check if self.xy is inside the axes. + # check if self.xy is inside the Axes. xy_pixel = self._get_position_xy(renderer) return self.axes.contains_point(xy_pixel) return True @@ -1547,6 +1640,9 @@ def draggable(self, state=None, use_blit=False): state : bool or None - True or False: set the draggability. - None: toggle the draggability. + use_blit : bool, default: False + Use blitting for faster image composition. For details see + :ref:`func-animation`. Returns ------- @@ -1588,7 +1684,7 @@ class Annotation(Text, _AnnotationBase): """ def __str__(self): - return "Annotation(%g, %g, %r)" % (self.xy[0], self.xy[1], self._text) + return f"Annotation({self.xy[0]:g}, {self.xy[1]:g}, {self._text!r})" def __init__(self, text, xy, xytext=None, @@ -1609,8 +1705,7 @@ def __init__(self, text, xy, Parameters ---------- text : str - The text of the annotation. *s* is a deprecated synonym for this - parameter. + The text of the annotation. xy : (float, float) The point *(x, y)* to annotate. The coordinate system is determined @@ -1620,27 +1715,35 @@ def __init__(self, text, xy, The position *(x, y)* to place the text at. The coordinate system is determined by *textcoords*. - xycoords : str or `.Artist` or `.Transform` or callable or \ -(float, float), default: 'data' + xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \ +callable, default: 'data' The coordinate system that *xy* is given in. The following types of values are supported: - One of the following strings: - ================= ============================================= - Value Description - ================= ============================================= - 'figure points' Points from the lower left of the figure - 'figure pixels' Pixels from the lower left of the figure - 'figure fraction' Fraction of figure from lower left - 'axes points' Points from lower left corner of axes - 'axes pixels' Pixels from lower left corner of axes - 'axes fraction' Fraction of axes from lower left - 'data' Use the coordinate system of the object being - annotated (default) - 'polar' *(theta, r)* if not native 'data' coordinates - ================= ============================================= + ==================== ============================================ + Value Description + ==================== ============================================ + 'figure points' Points from the lower left of the figure + 'figure pixels' Pixels from the lower left of the figure + 'figure fraction' Fraction of figure from lower left + 'subfigure points' Points from the lower left of the subfigure + 'subfigure pixels' Pixels from the lower left of the subfigure + 'subfigure fraction' Fraction of subfigure from lower left + 'axes points' Points from lower left corner of the Axes + 'axes pixels' Pixels from lower left corner of the Axes + 'axes fraction' Fraction of Axes from lower left + 'data' Use the coordinate system of the object + being annotated (default) + 'polar' *(theta, r)* if not native 'data' + coordinates + ==================== ============================================ + + Note that 'subfigure pixels' and 'figure pixels' are the same + for the parent figure, so users who want code that is usable in + a subfigure can use 'subfigure pixels'. - An `.Artist`: *xy* is interpreted as a fraction of the artist's `~matplotlib.transforms.Bbox`. E.g. *(0, 0)* would be the lower @@ -1665,89 +1768,86 @@ def transform(renderer) -> Transform See :ref:`plotting-guide-annotation` for more details. - textcoords : str or `.Artist` or `.Transform` or callable or \ -(float, float), default: value of *xycoords* + textcoords : single or two-tuple of str or `.Artist` or `.Transform` \ +or callable, default: value of *xycoords* The coordinate system that *xytext* is given in. - All *xycoords* values are valid as well as the following - strings: + All *xycoords* values are valid as well as the following strings: - ================= ========================================= + ================= ================================================= Value Description - ================= ========================================= - 'offset points' Offset (in points) from the *xy* value - 'offset pixels' Offset (in pixels) from the *xy* value - ================= ========================================= + ================= ================================================= + 'offset points' Offset, in points, from the *xy* value + 'offset pixels' Offset, in pixels, from the *xy* value + 'offset fontsize' Offset, relative to fontsize, from the *xy* value + ================= ================================================= arrowprops : dict, optional The properties used to draw a `.FancyArrowPatch` arrow between the - positions *xy* and *xytext*. + positions *xy* and *xytext*. Defaults to None, i.e. no arrow is + drawn. + + For historical reasons there are two different ways to specify + arrows, "simple" and "fancy": + + **Simple arrow:** If *arrowprops* does not contain the key 'arrowstyle' the allowed keys are: - ========== ====================================================== - Key Description - ========== ====================================================== - width The width of the arrow in points - headwidth The width of the base of the arrow head in points - headlength The length of the arrow head in points - shrink Fraction of total length to shrink from both ends - ? Any key to :class:`matplotlib.patches.FancyArrowPatch` - ========== ====================================================== - - If *arrowprops* contains the key 'arrowstyle' the - above keys are forbidden. The allowed values of - ``'arrowstyle'`` are: - - ============ ============================================= - Name Attrs - ============ ============================================= - ``'-'`` None - ``'->'`` head_length=0.4,head_width=0.2 - ``'-['`` widthB=1.0,lengthB=0.2,angleB=None - ``'|-|'`` widthA=1.0,widthB=1.0 - ``'-|>'`` head_length=0.4,head_width=0.2 - ``'<-'`` head_length=0.4,head_width=0.2 - ``'<->'`` head_length=0.4,head_width=0.2 - ``'<|-'`` head_length=0.4,head_width=0.2 - ``'<|-|>'`` head_length=0.4,head_width=0.2 - ``'fancy'`` head_length=0.4,head_width=0.4,tail_width=0.4 - ``'simple'`` head_length=0.5,head_width=0.5,tail_width=0.2 - ``'wedge'`` tail_width=0.3,shrink_factor=0.5 - ============ ============================================= - - Valid keys for `~matplotlib.patches.FancyArrowPatch` are: - - =============== ================================================== + ========== ================================================= + Key Description + ========== ================================================= + width The width of the arrow in points + headwidth The width of the base of the arrow head in points + headlength The length of the arrow head in points + shrink Fraction of total length to shrink from both ends + ? Any `.FancyArrowPatch` property + ========== ================================================= + + The arrow is attached to the edge of the text box, the exact + position (corners or centers) depending on where it's pointing to. + + **Fancy arrow:** + + This is used if 'arrowstyle' is provided in the *arrowprops*. + + Valid keys are the following `.FancyArrowPatch` parameters: + + =============== =================================== Key Description - =============== ================================================== - arrowstyle the arrow style - connectionstyle the connection style - relpos default is (0.5, 0.5) - patchA default is bounding box of the text - patchB default is None - shrinkA default is 2 points - shrinkB default is 2 points - mutation_scale default is text size (in points) - mutation_aspect default is 1. - ? any key for :class:`matplotlib.patches.PathPatch` - =============== ================================================== - - Defaults to None, i.e. no arrow is drawn. + =============== =================================== + arrowstyle The arrow style + connectionstyle The connection style + relpos See below; default is (0.5, 0.5) + patchA Default is bounding box of the text + patchB Default is None + shrinkA In points. Default is 2 points + shrinkB In points. Default is 2 points + mutation_scale Default is text size (in points) + mutation_aspect Default is 1 + ? Any `.FancyArrowPatch` property + =============== =================================== + + The exact starting point position of the arrow is defined by + *relpos*. It's a tuple of relative coordinates of the text box, + where (0, 0) is the lower left corner and (1, 1) is the upper + right corner. Values <0 and >1 are supported and specify points + outside the text box. By default (0.5, 0.5), so the starting point + is centered in the text box. annotation_clip : bool or None, default: None - Whether to draw the annotation when the annotation point *xy* is - outside the axes area. + Whether to clip (i.e. not draw) the annotation when the annotation + point *xy* is outside the Axes area. - - If *True*, the annotation will only be drawn when *xy* is - within the axes. + - If *True*, the annotation will be clipped when *xy* is outside + the Axes. - If *False*, the annotation will always be drawn. - - If *None*, the annotation will only be drawn when *xy* is - within the axes and *xycoords* is 'data'. + - If *None*, the annotation will be clipped when *xy* is outside + the Axes and *xycoords* is 'data'. **kwargs - Additional kwargs are passed to `~matplotlib.text.Text`. + Additional kwargs are passed to `.Text`. Returns ------- @@ -1755,7 +1855,7 @@ def transform(renderer) -> Transform See Also -------- - :ref:`plotting-guide-annotation` + :ref:`annotations` """ _AnnotationBase.__init__(self, @@ -1766,9 +1866,9 @@ def transform(renderer) -> Transform if (xytext is None and textcoords is not None and textcoords != xycoords): - cbook._warn_external("You have used the `textcoords` kwarg, but " - "not the `xytext` kwarg. This can lead to " - "surprising results.") + _api.warn_external("You have used the `textcoords` kwarg, but " + "not the `xytext` kwarg. This can lead to " + "surprising results.") # clean up textcoords and assign default if textcoords is None: @@ -1787,8 +1887,7 @@ def transform(renderer) -> Transform self._arrow_relpos = arrowprops.pop("relpos", (0.5, 0.5)) else: # modified YAArrow API to be used with FancyArrowPatch - for key in [ - 'width', 'headwidth', 'headlength', 'shrink', 'frac']: + for key in ['width', 'headwidth', 'headlength', 'shrink']: arrowprops.pop(key, None) self.arrow_patch = FancyArrowPatch((0, 0), (1, 1), **arrowprops) else: @@ -1797,13 +1896,12 @@ def transform(renderer) -> Transform # Must come last, as some kwargs may be propagated to arrow_patch. Text.__init__(self, x, y, text, **kwargs) - def contains(self, event): - inside, info = self._default_contains(event) - if inside is not None: - return inside, info - contains, tinfo = Text.contains(self, event) + def contains(self, mouseevent): + if self._different_canvas(mouseevent): + return False, {} + contains, tinfo = Text.contains(self, mouseevent) if self.arrow_patch is not None: - in_patch, _ = self.arrow_patch.contains(event) + in_patch, _ = self.arrow_patch.contains(mouseevent) contains = contains or in_patch return contains, tinfo @@ -1863,32 +1961,26 @@ def update_positions(self, renderer): """ Update the pixel positions of the annotation text and the arrow patch. """ - x1, y1 = self._get_position_xy(renderer) # Annotated position. - # generate transformation, + # generate transformation self.set_transform(self._get_xy_transform(renderer, self.anncoords)) - if self.arrowprops is None: + arrowprops = self.arrowprops + if arrowprops is None: return bbox = Text.get_window_extent(self, renderer) - d = self.arrowprops.copy() - ms = d.pop("mutation_scale", self.get_size()) + arrow_end = x1, y1 = self._get_position_xy(renderer) # Annotated pos. + + ms = arrowprops.get("mutation_scale", self.get_size()) self.arrow_patch.set_mutation_scale(ms) - if "arrowstyle" not in d: + if "arrowstyle" not in arrowprops: # Approximately simulate the YAArrow. - # Pop its kwargs: - shrink = d.pop('shrink', 0.0) - width = d.pop('width', 4) - headwidth = d.pop('headwidth', 12) - # Ignore frac--it is useless. - frac = d.pop('frac', None) - if frac is not None: - cbook._warn_external( - "'frac' option in 'arrowprops' is no longer supported;" - " use 'headlength' to set the head length in points.") - headlength = d.pop('headlength', 12) + shrink = arrowprops.get('shrink', 0.0) + width = arrowprops.get('width', 4) + headwidth = arrowprops.get('headwidth', 12) + headlength = arrowprops.get('headlength', 12) # NB: ms is in pts stylekw = dict(head_length=headlength / ms, @@ -1910,29 +2002,25 @@ def update_positions(self, renderer): # adjust the starting point of the arrow relative to the textbox. # TODO : Rotation needs to be accounted. - relposx, relposy = self._arrow_relpos - x0 = bbox.x0 + bbox.width * relposx - y0 = bbox.y0 + bbox.height * relposy - - # The arrow will be drawn from (x0, y0) to (x1, y1). It will be first + arrow_begin = bbox.p0 + bbox.size * self._arrow_relpos + # The arrow is drawn from arrow_begin to arrow_end. It will be first # clipped by patchA and patchB. Then it will be shrunk by shrinkA and - # shrinkB (in points). If patch A is not set, self.bbox_patch is used. - self.arrow_patch.set_positions((x0, y0), (x1, y1)) - - if "patchA" in d: - self.arrow_patch.set_patchA(d.pop("patchA")) + # shrinkB (in points). If patchA is not set, self.bbox_patch is used. + self.arrow_patch.set_positions(arrow_begin, arrow_end) + + if "patchA" in arrowprops: + patchA = arrowprops["patchA"] + elif self._bbox_patch: + patchA = self._bbox_patch + elif self.get_text() == "": + patchA = None else: - if self._bbox_patch: - self.arrow_patch.set_patchA(self._bbox_patch) - else: - if self.get_text() == "": - self.arrow_patch.set_patchA(None) - return - pad = renderer.points_to_pixels(4) - r = Rectangle(xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2), - width=bbox.width + pad, height=bbox.height + pad, - transform=IdentityTransform(), clip_on=False) - self.arrow_patch.set_patchA(r) + pad = renderer.points_to_pixels(4) + patchA = Rectangle( + xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2), + width=bbox.width + pad, height=bbox.height + pad, + transform=IdentityTransform(), clip_on=False) + self.arrow_patch.set_patchA(patchA) @artist.allow_rasterization def draw(self, renderer): @@ -1941,39 +2029,31 @@ def draw(self, renderer): self._renderer = renderer if not self.get_visible() or not self._check_xy(renderer): return + # Update text positions before `Text.draw` would, so that the + # FancyArrowPatch is correctly positioned. self.update_positions(renderer) self.update_bbox_position_size(renderer) - if self.arrow_patch is not None: # FancyArrowPatch - if self.arrow_patch.figure is None and self.figure is not None: - self.arrow_patch.figure = self.figure + if self.arrow_patch is not None: # FancyArrowPatch + if (self.arrow_patch.get_figure(root=False) is None and + (fig := self.get_figure(root=False)) is not None): + self.arrow_patch.set_figure(fig) self.arrow_patch.draw(renderer) # Draw text, including FancyBboxPatch, after FancyArrowPatch. # Otherwise, a wedge arrowstyle can land partly on top of the Bbox. Text.draw(self, renderer) def get_window_extent(self, renderer=None): - """ - Return the `.Bbox` bounding the text and arrow, in display units. - - Parameters - ---------- - renderer : Renderer, optional - A renderer is needed to compute the bounding box. If the artist - has already been drawn, the renderer is cached; thus, it is only - necessary to pass this argument when calling `get_window_extent` - before the first `draw`. In practice, it is usually easier to - trigger a draw first (e.g. by saving the figure). - """ + # docstring inherited # This block is the same as in Text.get_window_extent, but we need to # set the renderer before calling update_positions(). - if not self.get_visible(): + if not self.get_visible() or not self._check_xy(renderer): return Bbox.unit() if renderer is not None: self._renderer = renderer if self._renderer is None: - self._renderer = self.figure._cachedRenderer + self._renderer = self.get_figure(root=True)._get_renderer() if self._renderer is None: - raise RuntimeError('Cannot get window extent w/o renderer') + raise RuntimeError('Cannot get window extent without renderer') self.update_positions(self._renderer) @@ -1985,5 +2065,11 @@ def get_window_extent(self, renderer=None): return Bbox.union(bboxes) + def get_tightbbox(self, renderer=None): + # docstring inherited + if not self._check_xy(renderer): + return Bbox.null() + return super().get_tightbbox(renderer) + -docstring.interpd.update(Annotation=Annotation.__init__.__doc__) +_docstring.interpd.register(Annotation=Annotation.__init__.__doc__) diff --git a/lib/matplotlib/text.pyi b/lib/matplotlib/text.pyi new file mode 100644 index 000000000000..41c7b761ae32 --- /dev/null +++ b/lib/matplotlib/text.pyi @@ -0,0 +1,183 @@ +from .artist import Artist +from .backend_bases import RendererBase +from .font_manager import FontProperties +from .offsetbox import DraggableAnnotation +from .path import Path +from .patches import FancyArrowPatch, FancyBboxPatch +from .textpath import ( # noqa: F401, reexported API + TextPath as TextPath, + TextToPath as TextToPath, +) +from .transforms import ( + Bbox, + BboxBase, + Transform, +) + +from collections.abc import Iterable +from typing import Any, Literal +from .typing import ColorType, CoordsType + +class Text(Artist): + zorder: float + def __init__( + self, + x: float = ..., + y: float = ..., + text: Any = ..., + *, + color: ColorType | None = ..., + verticalalignment: Literal[ + "bottom", "baseline", "center", "center_baseline", "top" + ] = ..., + horizontalalignment: Literal["left", "center", "right"] = ..., + multialignment: Literal["left", "center", "right"] | None = ..., + fontproperties: str | Path | FontProperties | None = ..., + rotation: float | Literal["vertical", "horizontal"] | None = ..., + linespacing: float | None = ..., + rotation_mode: Literal["default", "anchor"] | None = ..., + usetex: bool | None = ..., + wrap: bool = ..., + transform_rotates_text: bool = ..., + parse_math: bool | None = ..., + antialiased: bool | None = ..., + **kwargs + ) -> None: ... + def update(self, kwargs: dict[str, Any]) -> list[Any]: ... + def get_rotation(self) -> float: ... + def get_transform_rotates_text(self) -> bool: ... + def set_rotation_mode(self, m: None | Literal["default", "anchor", "xtick", "ytick"]) -> None: ... + def get_rotation_mode(self) -> Literal["default", "anchor", "xtick", "ytick"]: ... + def set_bbox(self, rectprops: dict[str, Any] | None) -> None: ... + def get_bbox_patch(self) -> None | FancyBboxPatch: ... + def update_bbox_position_size(self, renderer: RendererBase) -> None: ... + def get_wrap(self) -> bool: ... + def set_wrap(self, wrap: bool) -> None: ... + def get_color(self) -> ColorType: ... + def get_fontproperties(self) -> FontProperties: ... + def get_fontfamily(self) -> list[str]: ... + def get_fontname(self) -> str: ... + def get_fontstyle(self) -> Literal["normal", "italic", "oblique"]: ... + def get_fontsize(self) -> float | str: ... + def get_fontvariant(self) -> Literal["normal", "small-caps"]: ... + def get_fontweight(self) -> int | str: ... + def get_stretch(self) -> int | str: ... + def get_horizontalalignment(self) -> Literal["left", "center", "right"]: ... + def get_unitless_position(self) -> tuple[float, float]: ... + def get_position(self) -> tuple[float, float]: ... + def get_text(self) -> str: ... + def get_verticalalignment( + self, + ) -> Literal["bottom", "baseline", "center", "center_baseline", "top"]: ... + def get_window_extent( + self, renderer: RendererBase | None = ..., dpi: float | None = ... + ) -> Bbox: ... + def set_backgroundcolor(self, color: ColorType) -> None: ... + def set_color(self, color: ColorType) -> None: ... + def set_horizontalalignment( + self, align: Literal["left", "center", "right"] + ) -> None: ... + def set_multialignment(self, align: Literal["left", "center", "right"]) -> None: ... + def set_linespacing(self, spacing: float) -> None: ... + def set_fontfamily(self, fontname: str | Iterable[str]) -> None: ... + def set_fontvariant(self, variant: Literal["normal", "small-caps"]) -> None: ... + def set_fontstyle( + self, fontstyle: Literal["normal", "italic", "oblique"] + ) -> None: ... + def set_fontsize(self, fontsize: float | str) -> None: ... + def get_math_fontfamily(self) -> str: ... + def set_math_fontfamily(self, fontfamily: str) -> None: ... + def set_fontweight(self, weight: int | str) -> None: ... + def set_fontstretch(self, stretch: int | str) -> None: ... + def set_position(self, xy: tuple[float, float]) -> None: ... + def set_x(self, x: float) -> None: ... + def set_y(self, y: float) -> None: ... + def set_rotation(self, s: float) -> None: ... + def set_transform_rotates_text(self, t: bool) -> None: ... + def set_verticalalignment( + self, align: Literal["bottom", "baseline", "center", "center_baseline", "top"] + ) -> None: ... + def set_text(self, s: Any) -> None: ... + def set_fontproperties(self, fp: FontProperties | str | Path | None) -> None: ... + def set_usetex(self, usetex: bool | None) -> None: ... + def get_usetex(self) -> bool: ... + def set_parse_math(self, parse_math: bool) -> None: ... + def get_parse_math(self) -> bool: ... + def set_fontname(self, fontname: str | Iterable[str]) -> None: ... + def get_antialiased(self) -> bool: ... + def set_antialiased(self, antialiased: bool) -> None: ... + def _ha_for_angle(self, angle: Any) -> Literal['center', 'right', 'left'] | None: ... + def _va_for_angle(self, angle: Any) -> Literal['center', 'top', 'baseline'] | None: ... + +class OffsetFrom: + def __init__( + self, + artist: Artist | BboxBase | Transform, + ref_coord: tuple[float, float], + unit: Literal["points", "pixels"] = ..., + ) -> None: ... + def set_unit(self, unit: Literal["points", "pixels"]) -> None: ... + def get_unit(self) -> Literal["points", "pixels"]: ... + def __call__(self, renderer: RendererBase) -> Transform: ... + +class _AnnotationBase: + xy: tuple[float, float] + xycoords: CoordsType + def __init__( + self, + xy, + xycoords: CoordsType = ..., + annotation_clip: bool | None = ..., + ) -> None: ... + def set_annotation_clip(self, b: bool | None) -> None: ... + def get_annotation_clip(self) -> bool | None: ... + def draggable( + self, state: bool | None = ..., use_blit: bool = ... + ) -> DraggableAnnotation | None: ... + +class Annotation(Text, _AnnotationBase): + arrowprops: dict[str, Any] | None + arrow_patch: FancyArrowPatch | None + def __init__( + self, + text: str, + xy: tuple[float, float], + xytext: tuple[float, float] | None = ..., + xycoords: CoordsType = ..., + textcoords: CoordsType | None = ..., + arrowprops: dict[str, Any] | None = ..., + annotation_clip: bool | None = ..., + **kwargs + ) -> None: ... + @property + def xycoords( + self, + ) -> CoordsType: ... + @xycoords.setter + def xycoords( + self, + xycoords: CoordsType, + ) -> None: ... + @property + def xyann(self) -> tuple[float, float]: ... + @xyann.setter + def xyann(self, xytext: tuple[float, float]) -> None: ... + def get_anncoords( + self, + ) -> CoordsType: ... + def set_anncoords( + self, + coords: CoordsType, + ) -> None: ... + @property + def anncoords( + self, + ) -> CoordsType: ... + @anncoords.setter + def anncoords( + self, + coords: CoordsType, + ) -> None: ... + def update_positions(self, renderer: RendererBase) -> None: ... + # Drops `dpi` parameter from superclass + def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... # type: ignore[override] diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 8f0f2d7b434b..b57597ded363 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -1,15 +1,17 @@ from collections import OrderedDict -import functools import logging import urllib.parse import numpy as np -from matplotlib import _text_layout, dviread, font_manager, rcParams -from matplotlib.font_manager import FontProperties, get_font -from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_TARGET_LIGHT +from matplotlib import _text_helpers, dviread +from matplotlib.font_manager import ( + FontProperties, get_font, fontManager as _fontManager +) +from matplotlib.ft2font import LoadFlags from matplotlib.mathtext import MathTextParser from matplotlib.path import Path +from matplotlib.texmanager import TexManager from matplotlib.transforms import Affine2D _log = logging.getLogger(__name__) @@ -29,13 +31,13 @@ def _get_font(self, prop): """ Find the `FT2Font` matching font properties *prop*, with its size set. """ - fname = font_manager.findfont(prop) - font = get_font(fname) + filenames = _fontManager._find_fonts_by_props(prop) + font = get_font(filenames) font.set_size(self.FONT_SCALE, self.DPI) return font def _get_hinting_flag(self): - return LOAD_NO_HINTING + return LoadFlags.NO_HINTING def _get_char_id(self, font, ccode): """ @@ -44,14 +46,11 @@ def _get_char_id(self, font, ccode): return urllib.parse.quote(f"{font.postscript_name}-{ccode:x}") def get_text_width_height_descent(self, s, prop, ismath): + fontsize = prop.get_size_in_points() + if ismath == "TeX": - texmanager = self.get_texmanager() - fontsize = prop.get_size_in_points() - w, h, d = texmanager.get_text_width_height_descent(s, fontsize, - renderer=None) - return w, h, d + return TexManager().get_text_width_height_descent(s, fontsize) - fontsize = prop.get_size_in_points() scale = fontsize / self.FONT_SCALE if ismath: @@ -62,7 +61,7 @@ def get_text_width_height_descent(self, s, prop, ismath): return width * scale, height * scale, descent * scale font = self._get_font(prop) - font.set_text(s, 0.0, flags=LOAD_NO_HINTING) + font.set_text(s, 0.0, flags=LoadFlags.NO_HINTING) w, h = font.get_width_height() w /= 64.0 # convert from subpixels h /= 64.0 @@ -79,19 +78,15 @@ def get_text_path(self, prop, s, ismath=False): ---------- prop : `~matplotlib.font_manager.FontProperties` The font properties for the text. - s : str The text to be converted. - ismath : {False, True, "TeX"} If True, use mathtext parser. If "TeX", use tex for rendering. Returns ------- verts : list - A list of numpy arrays containing the x and y coordinates of the - vertices. - + A list of arrays containing the (x, y) coordinates of the vertices. codes : list A list of path codes. @@ -101,10 +96,10 @@ def get_text_path(self, prop, s, ismath=False): from those:: from matplotlib.path import Path - from matplotlib.textpath import TextToPath + from matplotlib.text import TextToPath from matplotlib.font_manager import FontProperties - fp = FontProperties(family="Humor Sans", style="italic") + fp = FontProperties(family="Comic Neue", style="italic") verts, codes = TextToPath().get_text_path(fp, "ABC") path = Path(verts, codes, closed=False) @@ -119,18 +114,19 @@ def get_text_path(self, prop, s, ismath=False): glyph_info, glyph_map, rects = self.get_glyphs_mathtext(prop, s) verts, codes = [], [] - for glyph_id, xposition, yposition, scale in glyph_info: verts1, codes1 = glyph_map[glyph_id] - if len(verts1): - verts1 = np.array(verts1) * scale + [xposition, yposition] - verts.extend(verts1) - codes.extend(codes1) - + verts.extend(verts1 * scale + [xposition, yposition]) + codes.extend(codes1) for verts1, codes1 in rects: verts.extend(verts1) codes.extend(codes1) + # Make sure an empty string or one with nothing to print + # (e.g. only spaces & newlines) will be valid/empty path + if not verts: + verts = np.empty((0, 2)) + return verts, codes def get_glyphs_with_font(self, font, s, glyph_map=None, @@ -149,12 +145,12 @@ def get_glyphs_with_font(self, font, s, glyph_map=None, xpositions = [] glyph_ids = [] - for char, (_, x) in zip(s, _text_layout.layout(s, font)): - char_id = self._get_char_id(font, ord(char)) + for item in _text_helpers.layout(s, font): + char_id = self._get_char_id(item.ft_object, ord(item.char)) glyph_ids.append(char_id) - xpositions.append(x) + xpositions.append(item.x) if char_id not in glyph_map: - glyph_map_new[char_id] = font.get_path() + glyph_map_new[char_id] = item.ft_object.get_path() ypositions = [0] * len(xpositions) sizes = [1.] * len(xpositions) @@ -194,7 +190,7 @@ def get_glyphs_mathtext(self, prop, s, glyph_map=None, if char_id not in glyph_map: font.clear() font.set_size(self.FONT_SCALE, self.DPI) - font.load_char(ccode, flags=LOAD_NO_HINTING) + font.load_char(ccode, flags=LoadFlags.NO_HINTING) glyph_map_new[char_id] = font.get_path() xpositions.append(ox) @@ -215,19 +211,12 @@ def get_glyphs_mathtext(self, prop, s, glyph_map=None, return (list(zip(glyph_ids, xpositions, ypositions, sizes)), glyph_map_new, myrects) - def get_texmanager(self): - """Return the cached `~.texmanager.TexManager` instance.""" - if self._texmanager is None: - from matplotlib.texmanager import TexManager - self._texmanager = TexManager() - return self._texmanager - def get_glyphs_tex(self, prop, s, glyph_map=None, return_new_glyphs_only=False): """Convert the string *s* to vertices and codes using usetex mode.""" # Mostly borrowed from pdf backend. - dvifile = self.get_texmanager().make_dvi(s, self.FONT_SCALE) + dvifile = TexManager().make_dvi(s, self.FONT_SCALE) with dviread.Dvi(dvifile, self.DPI) as dvi: page, = dvi @@ -243,25 +232,20 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, # Gather font information and do some setup for combining # characters into strings. - for x1, y1, dvifont, glyph, width in page.text: - font, enc = self._get_ps_font_and_encoding(dvifont.texname) - char_id = self._get_char_id(font, glyph) - + t1_encodings = {} + for text in page.text: + font = get_font(text.font_path) + char_id = self._get_char_id(font, text.glyph) if char_id not in glyph_map: font.clear() font.set_size(self.FONT_SCALE, self.DPI) - # See comments in _get_ps_font_and_encoding. - if enc is not None: - index = font.get_name_index(enc[glyph]) - font.load_glyph(index, flags=LOAD_TARGET_LIGHT) - else: - font.load_char(glyph, flags=LOAD_TARGET_LIGHT) + font.load_glyph(text.index, flags=LoadFlags.TARGET_LIGHT) glyph_map_new[char_id] = font.get_path() glyph_ids.append(char_id) - xpositions.append(x1) - ypositions.append(y1) - sizes.append(dvifont.size / self.FONT_SCALE) + xpositions.append(text.x) + ypositions.append(text.y) + sizes.append(text.font_size / self.FONT_SCALE) myrects = [] @@ -276,50 +260,6 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, return (list(zip(glyph_ids, xpositions, ypositions, sizes)), glyph_map_new, myrects) - @staticmethod - @functools.lru_cache(50) - def _get_ps_font_and_encoding(texname): - tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map')) - psfont = tex_font_map[texname] - if psfont.filename is None: - raise ValueError( - f"No usable font file found for {psfont.psname} ({texname}). " - f"The font may lack a Type-1 version.") - - font = get_font(psfont.filename) - - if psfont.encoding: - # If psfonts.map specifies an encoding, use it: it gives us a - # mapping of glyph indices to Adobe glyph names; use it to convert - # dvi indices to glyph names and use the FreeType-synthesized - # unicode charmap to convert glyph names to glyph indices (with - # FT_Get_Name_Index/get_name_index), and load the glyph using - # FT_Load_Glyph/load_glyph. (That charmap has a coverage at least - # as good as, and possibly better than, the native charmaps.) - enc = dviread._parse_enc(psfont.encoding) - else: - # If psfonts.map specifies no encoding, the indices directly - # map to the font's "native" charmap; so don't use the - # FreeType-synthesized charmap but the native ones (we can't - # directly identify it but it's typically an Adobe charmap), and - # directly load the dvi glyph indices using FT_Load_Char/load_char. - for charmap_code in [ - 1094992451, # ADOBE_CUSTOM. - 1094995778, # ADOBE_STANDARD. - ]: - try: - font.select_charmap(charmap_code) - except (ValueError, RuntimeError): - pass - else: - break - else: - _log.warning("No supported encoding in font (%s).", - psfont.filename) - enc = None - - return font, enc - text_to_path = TextToPath() @@ -333,7 +273,7 @@ def __init__(self, xy, s, size=None, prop=None, _interpolation_steps=1, usetex=False): r""" Create a path from the text. Note that it simply is a path, - not an artist. You need to use the `~.PathPatch` (or other artists) + not an artist. You need to use the `.PathPatch` (or other artists) to draw this path onto the canvas. Parameters @@ -348,10 +288,10 @@ def __init__(self, xy, s, size=None, prop=None, Font size in points. Defaults to the size specified via the font properties *prop*. - prop : `matplotlib.font_manager.FontProperties`, optional + prop : `~matplotlib.font_manager.FontProperties`, optional Font property. If not provided, will use a default - ``FontProperties`` with parameters from the - :ref:`rcParams `. + `.FontProperties` with parameters from the + :ref:`rcParams`. _interpolation_steps : int, optional (Currently ignored) @@ -364,7 +304,7 @@ def __init__(self, xy, s, size=None, prop=None, The following creates a path from the string "ABC" with Helvetica font face; and another path from the latex fraction 1/2:: - from matplotlib.textpath import TextPath + from matplotlib.text import TextPath from matplotlib.font_manager import FontProperties fp = FontProperties(family="Helvetica", style="italic") @@ -385,11 +325,11 @@ def __init__(self, xy, s, size=None, prop=None, self._cached_vertices = None s, ismath = Text(usetex=usetex)._preprocess_math(s) - self._vertices, self._codes = text_to_path.get_text_path( - prop, s, ismath=ismath) + super().__init__( + *text_to_path.get_text_path(prop, s, ismath=ismath), + _interpolation_steps=_interpolation_steps, + readonly=True) self._should_simplify = False - self._simplify_threshold = rcParams['path.simplify_threshold'] - self._interpolation_steps = _interpolation_steps def set_size(self, size): """Set the text size.""" @@ -420,11 +360,12 @@ def _revalidate_path(self): Update the path if necessary. The path for the text is initially create with the font size of - `~.FONT_SCALE`, and this path is rescaled to other size when necessary. + `.FONT_SCALE`, and this path is rescaled to other size when necessary. """ if self._invalid or self._cached_vertices is None: tr = (Affine2D() .scale(self._size / text_to_path.FONT_SCALE) .translate(*self._xy)) self._cached_vertices = tr.transform(self._vertices) + self._cached_vertices.flags.writeable = False self._invalid = False diff --git a/lib/matplotlib/textpath.pyi b/lib/matplotlib/textpath.pyi new file mode 100644 index 000000000000..34d4e92ac47e --- /dev/null +++ b/lib/matplotlib/textpath.pyi @@ -0,0 +1,74 @@ +from matplotlib.font_manager import FontProperties +from matplotlib.ft2font import FT2Font +from matplotlib.mathtext import MathTextParser, VectorParse +from matplotlib.path import Path + +import numpy as np + +from typing import Literal + +class TextToPath: + FONT_SCALE: float + DPI: float + mathtext_parser: MathTextParser[VectorParse] + def __init__(self) -> None: ... + def get_text_width_height_descent( + self, s: str, prop: FontProperties, ismath: bool | Literal["TeX"] + ) -> tuple[float, float, float]: ... + def get_text_path( + self, prop: FontProperties, s: str, ismath: bool | Literal["TeX"] = ... + ) -> list[np.ndarray]: ... + def get_glyphs_with_font( + self, + font: FT2Font, + s: str, + glyph_map: dict[str, tuple[np.ndarray, np.ndarray]] | None = ..., + return_new_glyphs_only: bool = ..., + ) -> tuple[ + list[tuple[str, float, float, float]], + dict[str, tuple[np.ndarray, np.ndarray]], + list[tuple[list[tuple[float, float]], list[int]]], + ]: ... + def get_glyphs_mathtext( + self, + prop: FontProperties, + s: str, + glyph_map: dict[str, tuple[np.ndarray, np.ndarray]] | None = ..., + return_new_glyphs_only: bool = ..., + ) -> tuple[ + list[tuple[str, float, float, float]], + dict[str, tuple[np.ndarray, np.ndarray]], + list[tuple[list[tuple[float, float]], list[int]]], + ]: ... + def get_glyphs_tex( + self, + prop: FontProperties, + s: str, + glyph_map: dict[str, tuple[np.ndarray, np.ndarray]] | None = ..., + return_new_glyphs_only: bool = ..., + ) -> tuple[ + list[tuple[str, float, float, float]], + dict[str, tuple[np.ndarray, np.ndarray]], + list[tuple[list[tuple[float, float]], list[int]]], + ]: ... + +text_to_path: TextToPath + +class TextPath(Path): + def __init__( + self, + xy: tuple[float, float], + s: str, + size: float | None = ..., + prop: FontProperties | None = ..., + _interpolation_steps: int = ..., + usetex: bool = ..., + ) -> None: ... + def set_size(self, size: float | None) -> None: ... + def get_size(self) -> float | None: ... + + # These are read only... there actually are protections in the base class, so probably can be deleted... + @property # type: ignore[misc] + def vertices(self) -> np.ndarray: ... # type: ignore[override] + @property # type: ignore[misc] + def codes(self) -> np.ndarray: ... # type: ignore[override] diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index f3d97d95093c..b5c12e7f4905 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -9,6 +9,9 @@ Although the locators know nothing about major or minor ticks, they are used by the Axis class to support major and minor tick locating and formatting. +.. _tick_locating: +.. _locators: + Tick locating ------------- @@ -18,50 +21,33 @@ `MultipleLocator`. It is initialized with a base, e.g., 10, and it picks axis limits and ticks that are multiples of that base. -The Locator subclasses defined here are - -:class:`AutoLocator` - `MaxNLocator` with simple defaults. This is the default tick locator for - most plotting. - -:class:`MaxNLocator` - Finds up to a max number of intervals with ticks at nice locations. - -:class:`LinearLocator` - Space ticks evenly from min to max. - -:class:`LogLocator` - Space ticks logarithmically from min to max. - -:class:`MultipleLocator` - Ticks and range are a multiple of base; either integer or float. - -:class:`FixedLocator` - Tick locations are fixed. - -:class:`IndexLocator` - Locator for index plots (e.g., where ``x = range(len(y))``). - -:class:`NullLocator` - No ticks. - -:class:`SymmetricalLogLocator` - Locator for use with with the symlog norm; works like `LogLocator` for the - part outside of the threshold and adds 0 if inside the limits. - -:class:`LogitLocator` - Locator for logit scaling. - -:class:`OldAutoLocator` - Choose a `MultipleLocator` and dynamically reassign it for intelligent - ticking during navigation. - -:class:`AutoMinorLocator` - Locator for minor ticks when the axis is linear and the - major ticks are uniformly spaced. Subdivides the major - tick interval into a specified number of minor intervals, - defaulting to 4 or 5 depending on the major interval. - +The Locator subclasses defined here are: + +======================= ======================================================= +`AutoLocator` `MaxNLocator` with simple defaults. This is the default + tick locator for most plotting. +`MaxNLocator` Finds up to a max number of intervals with ticks at + nice locations. +`LinearLocator` Space ticks evenly from min to max. +`LogLocator` Space ticks logarithmically from min to max. +`MultipleLocator` Ticks and range are a multiple of base; either integer + or float. +`FixedLocator` Tick locations are fixed. +`IndexLocator` Locator for index plots (e.g., where + ``x = range(len(y))``). +`NullLocator` No ticks. +`SymmetricalLogLocator` Locator for use with the symlog norm; works like + `LogLocator` for the part outside of the threshold and + adds 0 if inside the limits. +`AsinhLocator` Locator for use with the asinh norm, attempting to + space ticks approximately uniformly. +`LogitLocator` Locator for logit scaling. +`AutoMinorLocator` Locator for minor ticks when the axis is linear and the + major ticks are uniformly spaced. Subdivides the major + tick interval into a specified number of minor + intervals, defaulting to 4 or 5 depending on the major + interval. +======================= ======================================================= There are a number of locators specialized for date locations - see the :mod:`.dates` module. @@ -72,7 +58,7 @@ view limits from the data limits. If you want to override the default locator, use one of the above or a custom -locator and pass it to the x or y axis instance. The relevant methods are:: +locator and pass it to the x- or y-axis instance. The relevant methods are:: ax.xaxis.set_major_locator(xmajor_locator) ax.xaxis.set_minor_locator(xminor_locator) @@ -81,54 +67,45 @@ The default minor locator is `NullLocator`, i.e., no minor ticks on by default. -Tick formatting ---------------- - -Tick formatting is controlled by classes derived from Formatter. The formatter -operates on a single tick value and returns a string to the axis. - -:class:`NullFormatter` - No labels on the ticks. - -:class:`IndexFormatter` - Set the strings from a list of labels. +.. note:: + `Locator` instances should not be used with more than one + `~matplotlib.axis.Axis` or `~matplotlib.axes.Axes`. So instead of:: -:class:`FixedFormatter` - Set the strings manually for the labels. + locator = MultipleLocator(5) + ax.xaxis.set_major_locator(locator) + ax2.xaxis.set_major_locator(locator) -:class:`FuncFormatter` - User defined function sets the labels. + do the following instead:: -:class:`StrMethodFormatter` - Use string `format` method. + ax.xaxis.set_major_locator(MultipleLocator(5)) + ax2.xaxis.set_major_locator(MultipleLocator(5)) -:class:`FormatStrFormatter` - Use an old-style sprintf format string. +.. _formatters: -:class:`ScalarFormatter` - Default formatter for scalars: autopick the format string. - -:class:`LogFormatter` - Formatter for log axes. - -:class:`LogFormatterExponent` - Format values for log axis using ``exponent = log_base(value)``. - -:class:`LogFormatterMathtext` - Format values for log axis using ``exponent = log_base(value)`` - using Math text. - -:class:`LogFormatterSciNotation` - Format values for log axis using scientific notation. - -:class:`LogitFormatter` - Probability formatter. +Tick formatting +--------------- -:class:`EngFormatter` - Format labels in engineering notation. +Tick formatting is controlled by classes derived from Formatter. The formatter +operates on a single tick value and returns a string to the axis. -:class:`PercentFormatter` - Format labels as a percentage. +========================= ===================================================== +`NullFormatter` No labels on the ticks. +`FixedFormatter` Set the strings manually for the labels. +`FuncFormatter` User defined function sets the labels. +`StrMethodFormatter` Use string `format` method. +`FormatStrFormatter` Use an old-style sprintf format string. +`ScalarFormatter` Default formatter for scalars: autopick the format + string. +`LogFormatter` Formatter for log axes. +`LogFormatterExponent` Format values for log axis using + ``exponent = log_base(value)``. +`LogFormatterMathtext` Format values for log axis using + ``exponent = log_base(value)`` using Math text. +`LogFormatterSciNotation` Format values for log axis using scientific notation. +`LogitFormatter` Probability formatter. +`EngFormatter` Format labels in engineering notation. +`PercentFormatter` Format labels as a percentage. +========================= ===================================================== You can derive your own formatter from the Formatter base class by simply overriding the ``__call__`` method. The formatter class has @@ -148,9 +125,9 @@ the input ``str``. For function input, a `.FuncFormatter` with the input function will be generated and used. -See :doc:`/gallery/ticks_and_spines/major_minor_demo` for an -example of setting major and minor ticks. See the :mod:`matplotlib.dates` -module for more information and examples of using date locators and formatters. +See :doc:`/gallery/ticks/major_minor_demo` for an example of setting major +and minor ticks. See the :mod:`matplotlib.dates` module for more information +and examples of using date locators and formatters. """ import itertools @@ -158,11 +135,12 @@ import locale import math from numbers import Integral +import string import numpy as np import matplotlib as mpl -from matplotlib import cbook +from matplotlib import _api, cbook from matplotlib import transforms as mtransforms _log = logging.getLogger(__name__) @@ -171,37 +149,36 @@ 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', 'LogFormatterExponent', 'LogFormatterMathtext', - 'IndexFormatter', 'LogFormatterSciNotation', + 'LogFormatterSciNotation', 'LogitFormatter', 'EngFormatter', 'PercentFormatter', - 'OldScalarFormatter', 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator', 'LinearLocator', 'LogLocator', 'AutoLocator', 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator', - 'SymmetricalLogLocator', 'LogitLocator', 'OldAutoLocator') + 'SymmetricalLogLocator', 'AsinhLocator', 'LogitLocator') class _DummyAxis: __name__ = "dummy" def __init__(self, minpos=0): - self.dataLim = mtransforms.Bbox.unit() - self.viewLim = mtransforms.Bbox.unit() + self._data_interval = (0, 1) + self._view_interval = (0, 1) self._minpos = minpos def get_view_interval(self): - return self.viewLim.intervalx + return self._view_interval def set_view_interval(self, vmin, vmax): - self.viewLim.intervalx = vmin, vmax + self._view_interval = (vmin, vmax) def get_minpos(self): return self._minpos def get_data_interval(self): - return self.dataLim.intervalx + return self._data_interval def set_data_interval(self, vmin, vmax): - self.dataLim.intervalx = vmin, vmax + self._data_interval = (vmin, vmax) def get_tick_space(self): # Just use the long-standing default of nbins==9 @@ -218,16 +195,6 @@ def create_dummy_axis(self, **kwargs): if self.axis is None: self.axis = _DummyAxis(**kwargs) - def set_view_interval(self, vmin, vmax): - self.axis.set_view_interval(vmin, vmax) - - def set_data_interval(self, vmin, vmax): - self.axis.set_data_interval(vmin, vmax) - - def set_bounds(self, vmin, vmax): - self.set_view_interval(vmin, vmax) - self.set_data_interval(vmin, vmax) - class Formatter(TickHelper): """ @@ -280,7 +247,7 @@ def set_locs(self, locs): def fix_minus(s): """ Some classes may want to replace a hyphen for minus with the proper - unicode symbol (U+2212) for typographical correctness. This is a + Unicode symbol (U+2212) for typographical correctness. This is a helper method to perform such a replacement when it is enabled via :rc:`axes.unicode_minus`. """ @@ -293,35 +260,6 @@ def _set_locator(self, locator): pass -@cbook.deprecated("3.3") -class IndexFormatter(Formatter): - """ - Format the position x to the nearest i-th label where ``i = int(x + 0.5)``. - Positions where ``i < 0`` or ``i > len(list)`` have no tick labels. - - Parameters - ---------- - labels : list - List of labels. - """ - def __init__(self, labels): - self.labels = labels - self.n = len(labels) - - def __call__(self, x, pos=None): - """ - Return the format for tick value *x* at position pos. - - The position is ignored and the value is rounded to the nearest - integer, which is used to look up the label. - """ - i = int(x + 0.5) - if i < 0 or i >= self.n: - return '' - else: - return self.labels[i] - - class NullFormatter(Formatter): """Always return the empty string.""" @@ -398,7 +336,12 @@ class FormatStrFormatter(Formatter): The format string should have a single variable format (%) in it. It will be applied to the value (not the position) of the tick. + + Negative numeric values (e.g., -1) will use a dash, not a Unicode minus; + use mathtext to get a Unicode minus by wrapping the format specifier with $ + (e.g. "$%g$"). """ + def __init__(self, fmt): self.fmt = fmt @@ -411,13 +354,33 @@ def __call__(self, x, pos=None): return self.fmt % x +class _UnicodeMinusFormat(string.Formatter): + """ + A specialized string formatter so that `.StrMethodFormatter` respects + :rc:`axes.unicode_minus`. This implementation relies on the fact that the + format string is only ever called with kwargs *x* and *pos*, so it blindly + replaces dashes by unicode minuses without further checking. + """ + + def format_field(self, value, format_spec): + return Formatter.fix_minus(super().format_field(value, format_spec)) + + class StrMethodFormatter(Formatter): """ Use a new-style format string (as used by `str.format`) to format the tick. The field used for the tick value must be labeled *x* and the field used for the tick position must be labeled *pos*. + + The formatter will respect :rc:`axes.unicode_minus` when formatting + negative numeric values. + + It is typically unnecessary to explicitly construct `.StrMethodFormatter` + objects, as `~.Axis.set_major_formatter` directly accepts the format string + itself. """ + def __init__(self, fmt): self.fmt = fmt @@ -428,41 +391,7 @@ def __call__(self, x, pos=None): *x* and *pos* are passed to `str.format` as keyword arguments with those exact names. """ - return self.fmt.format(x=x, pos=pos) - - -@cbook.deprecated("3.3") -class OldScalarFormatter(Formatter): - """ - Tick location is a plain old number. - """ - - def __call__(self, x, pos=None): - """ - Return the format for tick val *x* based on the width of the axis. - - The position *pos* is ignored. - """ - xmin, xmax = self.axis.get_view_interval() - # If the number is not too big and it's an int, format it as an int. - if abs(x) < 1e4 and x == int(x): - return '%d' % x - d = abs(xmax - xmin) - fmt = ('%1.3e' if d < 1e-2 else - '%1.3f' if d <= 1 else - '%1.2f' if d <= 10 else - '%1.1f' if d <= 1e5 else - '%1.1e') - s = fmt % x - tup = s.split('e') - if len(tup) == 2: - mantissa = tup[0].rstrip('0').rstrip('.') - sign = tup[1][0].replace('+', '') - exponent = tup[1][1:].lstrip('0') - s = '%se%s%s' % (mantissa, sign, exponent) - else: - s = s.rstrip('0').rstrip('.') - return s + return _UnicodeMinusFormat().format(self.fmt, x=x, pos=pos) class ScalarFormatter(Formatter): @@ -478,6 +407,11 @@ class ScalarFormatter(Formatter): useLocale : bool, default: :rc:`axes.formatter.use_locale`. Whether to use locale settings for decimal sign and positive sign. See `.set_useLocale`. + usetex : bool, default: :rc:`text.usetex` + To enable/disable the use of TeX's math mode for rendering the + numbers in the formatter. + + .. versionadded:: 3.10 Notes ----- @@ -506,32 +440,37 @@ class ScalarFormatter(Formatter): lim = (1_000_000, 1_000_010) fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={'hspace': 2}) - ax1.set(title='offset_notation', xlim=lim) + ax1.set(title='offset notation', xlim=lim) ax2.set(title='scientific notation', xlim=lim) ax2.xaxis.get_major_formatter().set_useOffset(False) - ax3.set(title='floating point notation', xlim=lim) + ax3.set(title='floating-point notation', xlim=lim) ax3.xaxis.get_major_formatter().set_useOffset(False) ax3.xaxis.get_major_formatter().set_scientific(False) """ - def __init__(self, useOffset=None, useMathText=None, useLocale=None): - if useOffset is None: - useOffset = mpl.rcParams['axes.formatter.useoffset'] - self._offset_threshold = \ - mpl.rcParams['axes.formatter.offset_threshold'] + def __init__(self, useOffset=None, useMathText=None, useLocale=None, *, + usetex=None): + useOffset = mpl._val_or_rc(useOffset, 'axes.formatter.useoffset') + self._offset_threshold = mpl.rcParams['axes.formatter.offset_threshold'] self.set_useOffset(useOffset) - self._usetex = mpl.rcParams['text.usetex'] - if useMathText is None: - useMathText = mpl.rcParams['axes.formatter.use_mathtext'] + self.set_usetex(usetex) self.set_useMathText(useMathText) self.orderOfMagnitude = 0 self.format = '' self._scientific = True self._powerlimits = mpl.rcParams['axes.formatter.limits'] - if useLocale is None: - useLocale = mpl.rcParams['axes.formatter.use_locale'] - self._useLocale = useLocale + self.set_useLocale(useLocale) + + def get_usetex(self): + """Return whether TeX's math mode is enabled for rendering.""" + return self._usetex + + def set_usetex(self, val): + """Set whether to use TeX's math mode for rendering numbers in the formatter.""" + self._usetex = mpl._val_or_rc(val, 'text.usetex') + + usetex = property(fget=get_usetex, fset=set_usetex) def get_useOffset(self): """ @@ -573,7 +512,7 @@ def set_useOffset(self, val): will be formatted as ``0, 2, 4, 6, 8`` plus an offset ``+1e5``, which is written to the edge of the axis. """ - if val in [True, False]: + if isinstance(val, bool): self.offset = 0 self._useOffset = val else: @@ -601,13 +540,23 @@ def set_useLocale(self, val): val : bool or None *None* resets to :rc:`axes.formatter.use_locale`. """ - if val is None: - self._useLocale = mpl.rcParams['axes.formatter.use_locale'] - else: - self._useLocale = val + self._useLocale = mpl._val_or_rc(val, 'axes.formatter.use_locale') useLocale = property(fget=get_useLocale, fset=set_useLocale) + def _format_maybe_minus_and_locale(self, fmt, arg): + """ + Format *arg* with *fmt*, applying Unicode minus and locale if desired. + """ + return self.fix_minus( + # Escape commas introduced by locale.format_string if using math text, + # but not those present from the beginning in fmt. + (",".join(locale.format_string(part, (arg,), True).replace(",", "{,}") + for part in fmt.split(",")) if self._useMathText + else locale.format_string(fmt, (arg,), True)) + if self._useLocale + else fmt % arg) + def get_useMathText(self): """ Return whether to use fancy math formatting. @@ -631,6 +580,23 @@ def set_useMathText(self, val): """ if val is None: self._useMathText = mpl.rcParams['axes.formatter.use_mathtext'] + if self._useMathText is False: + try: + from matplotlib import font_manager + ufont = font_manager.findfont( + font_manager.FontProperties( + family=mpl.rcParams["font.family"] + ), + fallback_to_default=False, + ) + except ValueError: + ufont = None + + if ufont == str(cbook._get_data_path("fonts/ttf/cmr10.ttf")): + _api.warn_external( + "cmr10 font should ideally be used with " + "mathtext, set axes.formatter.use_mathtext to True" + ) else: self._useMathText = val @@ -646,11 +612,7 @@ def __call__(self, x, pos=None): xp = (x - self.offset) / (10. ** self.orderOfMagnitude) if abs(xp) < 1e-8: xp = 0 - if self._useLocale: - s = locale.format_string(self.format, (xp,)) - else: - s = self.format % xp - return self.fix_minus(s) + return self._format_maybe_minus_and_locale(self.format, xp) def set_scientific(self, b): """ @@ -671,7 +633,7 @@ def set_powerlimits(self, lims): lims : (int, int) A tuple *(min_exp, max_exp)* containing the powers of 10 that determine the switchover threshold. For a number representable as - :math:`a \times 10^\mathrm{exp}`` with :math:`1 <= |a| < 10`, + :math:`a \times 10^\mathrm{exp}` with :math:`1 <= |a| < 10`, scientific notation will be used if ``exp <= min_exp`` or ``exp >= max_exp``. @@ -698,7 +660,7 @@ def set_powerlimits(self, lims): def format_data_short(self, value): # docstring inherited - if isinstance(value, np.ma.MaskedArray) and value.mask: + if value is np.ma.masked: return "" if isinstance(value, Integral): fmt = "%d" @@ -719,30 +681,26 @@ def format_data_short(self, value): delta = abs(neighbor_values - value).max() else: # Rough approximation: no more than 1e4 divisions. - delta = np.diff(self.axis.get_view_interval()) / 1e4 - # If e.g. value = 45.67 and delta = 0.02, then we want to round to - # 2 digits after the decimal point (floor(log10(0.02)) = -2); - # 45.67 contributes 2 digits before the decimal point - # (floor(log10(45.67)) + 1 = 2): the total is 4 significant digits. - # A value of 0 contributes 1 "digit" before the decimal point. - sig_digits = max( - 0, - (math.floor(math.log10(abs(value))) + 1 if value else 1) - - math.floor(math.log10(delta))) - fmt = f"%-#.{sig_digits}g" - return ( - self.fix_minus( - locale.format_string(fmt, (value,)) if self._useLocale else - fmt % value)) + a, b = self.axis.get_view_interval() + delta = (b - a) / 1e4 + fmt = f"%-#.{cbook._g_sig_digits(value, delta)}g" + return self._format_maybe_minus_and_locale(fmt, value) def format_data(self, value): # docstring inherited - if self._useLocale: - s = locale.format_string('%1.10e', (value,)) + e = math.floor(math.log10(abs(value))) + s = round(value / 10**e, 10) + significand = self._format_maybe_minus_and_locale( + "%d" if s % 1 == 0 else "%1.10g", s) + if e == 0: + return significand + exponent = self._format_maybe_minus_and_locale("%d", e) + if self._useMathText or self._usetex: + exponent = "10^{%s}" % exponent + return (exponent if s == 1 # reformat 1x10^y as 10^y + else rf"{significand} \times {exponent}") else: - s = '%1.10e' % value - s = self._formatSciNotation(s) - return self.fix_minus(s) + return f"{significand}e{exponent}" def get_offset(self): """ @@ -750,7 +708,6 @@ def get_offset(self): """ if len(self.locs) == 0: return '' - s = '' if self.orderOfMagnitude or self.offset: offsetStr = '' sciNotStr = '' @@ -766,11 +723,11 @@ def get_offset(self): if self._useMathText or self._usetex: if sciNotStr != '': sciNotStr = r'\times\mathdefault{%s}' % sciNotStr - s = r'$%s\mathdefault{%s}$' % (sciNotStr, offsetStr) + s = fr'${sciNotStr}\mathdefault{{{offsetStr}}}$' else: s = ''.join((sciNotStr, offsetStr)) - - return self.fix_minus(s) + return self.fix_minus(s) + return '' def set_locs(self, locs): # docstring inherited @@ -822,7 +779,7 @@ def _compute_offset(self): def _set_order_of_magnitude(self): # if scientific notation is to be used, find the appropriate exponent - # if using an numerical offset, find the exponent after applying the + # if using a numerical offset, find the exponent after applying the # offset. When lower power limit = upper <> 0, use provided exponent. if not self._scientific: self.orderOfMagnitude = 0 @@ -842,10 +799,7 @@ def _set_order_of_magnitude(self): if self.offset: oom = math.floor(math.log10(vmax - vmin)) else: - if locs[0] > locs[-1]: - val = locs[0] - else: - val = locs[-1] + val = locs.max() if val == 0: oom = 0 else: @@ -886,39 +840,10 @@ def _set_format(self): else: break sigfigs += 1 - self.format = '%1.' + str(sigfigs) + 'f' + self.format = f'%1.{sigfigs}f' if self._usetex or self._useMathText: self.format = r'$\mathdefault{%s}$' % self.format - def _formatSciNotation(self, s): - # transform 1e+004 into 1e4, for example - if self._useLocale: - decimal_point = locale.localeconv()['decimal_point'] - positive_sign = locale.localeconv()['positive_sign'] - else: - decimal_point = '.' - positive_sign = '+' - tup = s.split('e') - try: - significand = tup[0].rstrip('0').rstrip(decimal_point) - sign = tup[1][0].replace(positive_sign, '') - exponent = tup[1][1:].lstrip('0') - if self._useMathText or self._usetex: - if significand == '1' and exponent != '': - # reformat 1x10^y as 10^y - significand = '' - if exponent: - exponent = '10^{%s%s}' % (sign, exponent) - if significand and exponent: - return r'%s{\times}%s' % (significand, exponent) - else: - return r'%s%s' % (significand, exponent) - else: - s = ('%se%s%s' % (significand, sign, exponent)).rstrip('e') - return s - except IndexError: - return s - class LogFormatter(Formatter): """ @@ -933,20 +858,23 @@ class LogFormatter(Formatter): labelOnlyBase : bool, default: False If True, label ticks only at integer powers of base. - This is normally True for major ticks and False for - minor ticks. + This is normally True for major ticks and False for minor ticks. minor_thresholds : (subset, all), default: (1, 0.4) If labelOnlyBase is False, these two numbers control the labeling of ticks that are not at integer powers of - base; normally these are the minor ticks. The controlling - parameter is the log of the axis data range. In the typical - case where base is 10 it is the number of decades spanned - by the axis, so we can call it 'numdec'. If ``numdec <= all``, - all minor ticks will be labeled. If ``all < numdec <= subset``, - then only a subset of minor ticks will be labeled, so as to - avoid crowding. If ``numdec > subset`` then no minor ticks will - be labeled. + base; normally these are the minor ticks. + + The first number (*subset*) is the largest number of major ticks for + which minor ticks are labeled; e.g., the default, 1, means that minor + ticks are labeled as long as there is no more than 1 major tick. (It + is assumed that major ticks are at integer powers of *base*.) + + The second number (*all*) is a threshold, in log-units of the axis + limit range, over which only a subset of the minor ticks are labeled, + so as to avoid crowding; e.g., with the default value (0.4) and the + usual ``base=10``, all minor ticks are shown only if the axis limit + range spans less than 0.4 decades. linthresh : None or float, default: None If a symmetric log scale is in use, its ``linthresh`` @@ -970,20 +898,17 @@ class LogFormatter(Formatter): Examples -------- - To label a subset of minor ticks when the view limits span up - to 2 decades, and all of the ticks when zoomed in to 0.5 decades - or less, use ``minor_thresholds=(2, 0.5)``. - - To label all minor ticks when the view limits span up to 1.5 - decades, use ``minor_thresholds=(1.5, 1.5)``. + To label a subset of minor ticks when there are up to 2 major ticks, + and all of the ticks when zoomed in to 0.5 decades or less, use + ``minor_thresholds=(2, 0.5)``. """ def __init__(self, base=10.0, labelOnlyBase=False, minor_thresholds=None, linthresh=None): - self._base = float(base) - self.labelOnlyBase = labelOnlyBase + self.set_base(base) + self.set_label_minor(labelOnlyBase) if minor_thresholds is None: if mpl.rcParams['_internal.classic_mode']: minor_thresholds = (0, 0) @@ -993,16 +918,16 @@ def __init__(self, base=10.0, labelOnlyBase=False, self._sublabels = None self._linthresh = linthresh - def base(self, base): + def set_base(self, base): """ Change the *base* for labeling. .. warning:: Should always match the base used for :class:`LogLocator` """ - self._base = base + self._base = float(base) - def label_minor(self, labelOnlyBase): + def set_label_minor(self, labelOnlyBase): """ Switch minor tick labeling on or off. @@ -1043,22 +968,32 @@ def set_locs(self, locs=None): return b = self._base + if linthresh is not None: # symlog - # Only compute the number of decades in the logarithmic part of the - # axis - numdec = 0 + # Only count ticks and decades in the logarithmic part of the axis. + numdec = numticks = 0 if vmin < -linthresh: rhs = min(vmax, -linthresh) - numdec += math.log(vmin / rhs) / math.log(b) + numticks += ( + math.floor(math.log(abs(rhs), b)) + - math.floor(math.nextafter(math.log(abs(vmin), b), -math.inf))) + numdec += math.log(vmin / rhs, b) if vmax > linthresh: lhs = max(vmin, linthresh) - numdec += math.log(vmax / lhs) / math.log(b) + numticks += ( + math.floor(math.log(vmax, b)) + - math.floor(math.nextafter(math.log(lhs, b), -math.inf))) + numdec += math.log(vmax / lhs, b) else: - vmin = math.log(vmin) / math.log(b) - vmax = math.log(vmax) / math.log(b) - numdec = abs(vmax - vmin) - - if numdec > self.minor_thresholds[0]: + lmin = math.log(vmin, b) + lmax = math.log(vmax, b) + # The nextafter call handles the case where vmin is exactly at a + # decade (e.g. there's one major tick between 1 and 5). + numticks = (math.floor(lmax) + - math.floor(math.nextafter(lmin, -math.inf))) + numdec = abs(lmax - lmin) + + if numticks > self.minor_thresholds[0]: # Label only bases self._sublabels = {1} elif numdec > self.minor_thresholds[1]: @@ -1073,13 +1008,7 @@ def set_locs(self, locs=None): self._sublabels = set(np.arange(1, b + 1)) def _num_to_string(self, x, vmin, vmax): - if x > 10000: - s = '%1.0e' % x - elif x < 1: - s = '%1.0e' % x - else: - s = self._pprint_val(x, vmax - vmin) - return s + return self._pprint_val(x, vmax - vmin) if 1 <= x <= 10000 else f"{x:1.0e}" def __call__(self, x, pos=None): # docstring inherited @@ -1090,9 +1019,9 @@ def __call__(self, x, pos=None): b = self._base # only label the decades fx = math.log(x) / math.log(b) - is_x_decade = is_close_to_int(fx) + is_x_decade = _is_close_to_int(fx) exponent = round(fx) if is_x_decade else np.floor(fx) - coeff = round(x / b ** exponent) + coeff = round(b ** (fx - exponent)) if self.labelOnlyBase and not is_x_decade: return '' @@ -1102,7 +1031,7 @@ def __call__(self, x, pos=None): vmin, vmax = self.axis.get_view_interval() vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) s = self._num_to_string(x, vmin, vmax) - return s + return self.fix_minus(s) def format_data(self, value): with cbook._setattr_cm(self, labelOnlyBase=False): @@ -1110,7 +1039,7 @@ def format_data(self, value): def format_data_short(self, value): # docstring inherited - return '%-12g' % value + return ('%-12g' % value).rstrip() def _pprint_val(self, x, d): # If the number is not too big and it's an int, format it as an int. @@ -1139,15 +1068,14 @@ class LogFormatterExponent(LogFormatter): """ Format values for log axis using ``exponent = log_base(value)``. """ + def _num_to_string(self, x, vmin, vmax): fx = math.log(x) / math.log(self._base) - if abs(fx) > 10000: - s = '%1.0g' % fx - elif abs(fx) < 1: - s = '%1.0g' % fx - else: + if 1 <= abs(fx) <= 10000: fd = math.log(vmax - vmin) / math.log(self._base) s = self._pprint_val(fx, fd) + else: + s = f"{fx:1.0g}" return s @@ -1162,9 +1090,6 @@ def _non_decade_format(self, sign_string, base, fx, usetex): def __call__(self, x, pos=None): # docstring inherited - usetex = mpl.rcParams['text.usetex'] - min_exp = mpl.rcParams['axes.formatter.min_exponent'] - if x == 0: # Symlog return r'$\mathdefault{0}$' @@ -1174,26 +1099,28 @@ def __call__(self, x, pos=None): # only label the decades fx = math.log(x) / math.log(b) - is_x_decade = is_close_to_int(fx) + is_x_decade = _is_close_to_int(fx) exponent = round(fx) if is_x_decade else np.floor(fx) - coeff = round(x / b ** exponent) - if is_x_decade: - fx = round(fx) + coeff = round(b ** (fx - exponent)) if self.labelOnlyBase and not is_x_decade: return '' if self._sublabels is not None and coeff not in self._sublabels: return '' + if is_x_decade: + fx = round(fx) + # use string formatting of the base if it is not an integer if b % 1 == 0.0: base = '%d' % b else: base = '%s' % b - if abs(fx) < min_exp: + if abs(fx) < mpl.rcParams['axes.formatter.min_exponent']: return r'$\mathdefault{%s%g}$' % (sign_string, x) elif not is_x_decade: + usetex = mpl.rcParams['text.usetex'] return self._non_decade_format(sign_string, base, fx, usetex) else: return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx) @@ -1208,8 +1135,8 @@ def _non_decade_format(self, sign_string, base, fx, usetex): """Return string for non-decade locations.""" b = float(base) exponent = math.floor(fx) - coeff = b ** fx / b ** exponent - if is_close_to_int(coeff): + coeff = b ** (fx - exponent) + if _is_close_to_int(coeff): coeff = round(coeff) return r'$\mathdefault{%s%g\times%s^{%d}}$' \ % (sign_string, coeff, base, exponent) @@ -1233,10 +1160,10 @@ def __init__( Parameters ---------- use_overline : bool, default: False - If x > 1/2, with x = 1-v, indicate if x should be displayed as - $\overline{v}$. The default is to display $1-v$. + If x > 1/2, with x = 1 - v, indicate if x should be displayed as + $\overline{v}$. The default is to display $1 - v$. - one_half : str, default: r"\frac{1}{2}" + one_half : str, default: r"\\frac{1}{2}" The string used to represent 1/2. minor : bool, default: False @@ -1266,9 +1193,9 @@ def use_overline(self, use_overline): Parameters ---------- - use_overline : bool, default: False - If x > 1/2, with x = 1-v, indicate if x should be displayed as - $\overline{v}$. The default is to display $1-v$. + use_overline : bool + If x > 1/2, with x = 1 - v, indicate if x should be displayed as + $\overline{v}$. The default is to display $1 - v$. """ self._use_overline = use_overline @@ -1276,7 +1203,7 @@ def set_one_half(self, one_half): r""" Set the way one half is displayed. - one_half : str, default: r"\frac{1}{2}" + one_half : str The string used to represent 1/2. """ self._one_half = one_half @@ -1313,9 +1240,10 @@ def set_locs(self, locs): if not self._minor: return None if all( - is_decade(x, rtol=1e-7) - or is_decade(1 - x, rtol=1e-7) - or (is_close_to_int(2 * x) and int(np.round(2 * x)) == 1) + _is_decade(x, rtol=1e-7) + or _is_decade(1 - x, rtol=1e-7) + or (_is_close_to_int(2 * x) and + int(np.round(2 * x)) == 1) for x in locs ): # minor ticks are subsample from ideal, so no label @@ -1362,7 +1290,7 @@ def _format_value(self, x, locs, sci_notation=True): precision = -np.log10(diff) + exponent precision = ( int(np.round(precision)) - if is_close_to_int(precision) + if _is_close_to_int(precision) else math.ceil(precision) ) if precision < min_precision: @@ -1377,20 +1305,20 @@ def _one_minus(self, s): if self._use_overline: return r"\overline{%s}" % s else: - return "1-{}".format(s) + return f"1-{s}" def __call__(self, x, pos=None): if self._minor and x not in self._labelled: return "" if x <= 0 or x >= 1: return "" - if is_close_to_int(2 * x) and round(2 * x) == 1: + if _is_close_to_int(2 * x) and round(2 * x) == 1: s = self._one_half - elif x < 0.5 and is_decade(x, rtol=1e-7): - exponent = round(np.log10(x)) + elif x < 0.5 and _is_decade(x, rtol=1e-7): + exponent = round(math.log10(x)) s = "10^{%d}" % exponent - elif x > 0.5 and is_decade(1 - x, rtol=1e-7): - exponent = round(np.log10(1 - x)) + elif x > 0.5 and _is_decade(1 - x, rtol=1e-7): + exponent = round(math.log10(1 - x)) s = self._one_minus("10^{%d}" % exponent) elif x < 0.1: s = self._format_value(x, self.locs) @@ -1404,13 +1332,13 @@ def format_data_short(self, value): # docstring inherited # Thresholds chosen to use scientific notation iff exponent <= -2. if value < 0.1: - return "{:e}".format(value) + return f"{value:e}" if value < 0.9: - return "{:f}".format(value) - return "1-{:e}".format(1 - value) + return f"{value:f}" + return f"1-{1 - value:e}" -class EngFormatter(Formatter): +class EngFormatter(ScalarFormatter): """ Format axis values using engineering prefixes to represent powers of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. @@ -1418,6 +1346,8 @@ class EngFormatter(Formatter): # The SI engineering prefixes ENG_PREFIXES = { + -30: "q", + -27: "r", -24: "y", -21: "z", -18: "a", @@ -1434,11 +1364,13 @@ class EngFormatter(Formatter): 15: "P", 18: "E", 21: "Z", - 24: "Y" + 24: "Y", + 27: "R", + 30: "Q" } def __init__(self, unit="", places=None, sep=" ", *, usetex=None, - useMathText=None): + useMathText=None, useOffset=False): r""" Parameters ---------- @@ -1472,76 +1404,124 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None, useMathText : bool, default: :rc:`axes.formatter.use_mathtext` To enable/disable the use mathtext for rendering the numbers in the formatter. + useOffset : bool or float, default: False + Whether to use offset notation with :math:`10^{3*N}` based prefixes. + This features allows showing an offset with standard SI order of + magnitude prefix near the axis. Offset is computed similarly to + how `ScalarFormatter` computes it internally, but here you are + guaranteed to get an offset which will make the tick labels exceed + 3 digits. See also `.set_useOffset`. + + .. versionadded:: 3.10 """ self.unit = unit self.places = places self.sep = sep - self.set_usetex(usetex) - self.set_useMathText(useMathText) - - def get_usetex(self): - return self._usetex - - def set_usetex(self, val): - if val is None: - self._usetex = mpl.rcParams['text.usetex'] - else: - self._usetex = val - - usetex = property(fget=get_usetex, fset=set_usetex) + super().__init__( + useOffset=useOffset, + useMathText=useMathText, + useLocale=False, + usetex=usetex, + ) - def get_useMathText(self): - return self._useMathText + def __call__(self, x, pos=None): + """ + Return the format for tick value *x* at position *pos*. - def set_useMathText(self, val): - if val is None: - self._useMathText = mpl.rcParams['axes.formatter.use_mathtext'] + If there is no currently offset in the data, it returns the best + engineering formatting that fits the given argument, independently. + """ + if len(self.locs) == 0 or self.offset == 0: + return self.fix_minus(self.format_data(x)) else: - self._useMathText = val + xp = (x - self.offset) / (10. ** self.orderOfMagnitude) + if abs(xp) < 1e-8: + xp = 0 + return self._format_maybe_minus_and_locale(self.format, xp) - useMathText = property(fget=get_useMathText, fset=set_useMathText) + def set_locs(self, locs): + # docstring inherited + self.locs = locs + if len(self.locs) > 0: + vmin, vmax = sorted(self.axis.get_view_interval()) + if self._useOffset: + self._compute_offset() + if self.offset != 0: + # We don't want to use the offset computed by + # self._compute_offset because it rounds the offset unaware + # of our engineering prefixes preference, and this can + # cause ticks with 4+ digits to appear. These ticks are + # slightly less readable, so if offset is justified + # (decided by self._compute_offset) we set it to better + # value: + self.offset = round((vmin + vmax)/2, 3) + # Use log1000 to use engineers' oom standards + self.orderOfMagnitude = math.floor(math.log(vmax - vmin, 1000))*3 + self._set_format() - def __call__(self, x, pos=None): - s = "%s%s" % (self.format_eng(x), self.unit) - # Remove the trailing separator when there is neither prefix nor unit - if self.sep and s.endswith(self.sep): - s = s[:-len(self.sep)] - return self.fix_minus(s) + # Simplify a bit ScalarFormatter.get_offset: We always want to use + # self.format_data. Also we want to return a non-empty string only if there + # is an offset, no matter what is self.orderOfMagnitude. If there _is_ an + # offset, self.orderOfMagnitude is consulted. This behavior is verified + # in `test_ticker.py`. + def get_offset(self): + # docstring inherited + if len(self.locs) == 0: + return '' + if self.offset: + offsetStr = '' + if self.offset: + offsetStr = self.format_data(self.offset) + if self.offset > 0: + offsetStr = '+' + offsetStr + sciNotStr = self.format_data(10 ** self.orderOfMagnitude) + if self._useMathText or self._usetex: + if sciNotStr != '': + sciNotStr = r'\times%s' % sciNotStr + s = f'${sciNotStr}{offsetStr}$' + else: + s = sciNotStr + offsetStr + return self.fix_minus(s) + return '' def format_eng(self, num): + """Alias to EngFormatter.format_data""" + return self.format_data(num) + + def format_data(self, value): """ Format a number in engineering notation, appending a letter representing the power of 1000 of the original number. Some examples: - >>> format_eng(0) # for self.places = 0 + >>> format_data(0) # for self.places = 0 '0' - >>> format_eng(1000000) # for self.places = 1 + >>> format_data(1000000) # for self.places = 1 '1.0 M' - >>> format_eng("-1e-6") # for self.places = 2 + >>> format_data(-1e-6) # for self.places = 2 '-1.00 \N{MICRO SIGN}' """ sign = 1 - fmt = "g" if self.places is None else ".{:d}f".format(self.places) + fmt = "g" if self.places is None else f".{self.places:d}f" - if num < 0: + if value < 0: sign = -1 - num = -num + value = -value - if num != 0: - pow10 = int(math.floor(math.log10(num) / 3) * 3) + if value != 0: + pow10 = int(math.floor(math.log10(value) / 3) * 3) else: pow10 = 0 - # Force num to zero, to avoid inconsistencies like + # Force value to zero, to avoid inconsistencies like # format_eng(-0) = "0" and format_eng(0.0) = "0" # but format_eng(-0.0) = "-0.0" - num = 0.0 + value = 0.0 pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES)) - mant = sign * num / (10.0 ** pow10) + mant = sign * value / (10.0 ** pow10) # Taking care of the cases like 999.9..., which may be rounded to 1000 # instead of 1 k. Beware of the corner case of values that are beyond # the range of SI prefixes (i.e. > 'Y'). @@ -1550,15 +1530,15 @@ def format_eng(self, num): mant /= 1000 pow10 += 3 - prefix = self.ENG_PREFIXES[int(pow10)] + unit_prefix = self.ENG_PREFIXES[int(pow10)] + if self.unit or unit_prefix: + suffix = f"{self.sep}{unit_prefix}{self.unit}" + else: + suffix = "" if self._usetex or self._useMathText: - formatted = "${mant:{fmt}}${sep}{prefix}".format( - mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) + return f"${mant:{fmt}}${suffix}" else: - formatted = "{mant:{fmt}}{sep}{prefix}".format( - mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) - - return formatted + return f"{mant:{fmt}}{suffix}" class PercentFormatter(Formatter): @@ -1608,17 +1588,14 @@ def format_pct(self, x, display_range): decimal point is set based on the *display_range* of the axis as follows: - +---------------+----------+------------------------+ - | display_range | decimals | sample | - +---------------+----------+------------------------+ - | >50 | 0 | ``x = 34.5`` => 35% | - +---------------+----------+------------------------+ - | >5 | 1 | ``x = 34.5`` => 34.5% | - +---------------+----------+------------------------+ - | >0.5 | 2 | ``x = 34.5`` => 34.50% | - +---------------+----------+------------------------+ - | ... | ... | ... | - +---------------+----------+------------------------+ + ============= ======== ======================= + display_range decimals sample + ============= ======== ======================= + >50 0 ``x = 34.5`` => 35% + >5 1 ``x = 34.5`` => 34.5% + >0.5 2 ``x = 34.5`` => 34.50% + ... ... ... + ============= ======== ======================= This method will not be very good for tiny axis ranges or extremely large ones. It assumes that the values on the chart @@ -1642,7 +1619,7 @@ def format_pct(self, x, display_range): decimals = 0 else: decimals = self.decimals - s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) + s = f'{x:0.{int(decimals)}f}' return s + self.symbol @@ -1661,7 +1638,7 @@ def symbol(self): symbol = self._symbol if not symbol: symbol = '' - elif mpl.rcParams['text.usetex'] and not self._is_latex: + elif not self._is_latex and mpl.rcParams['text.usetex']: # Source: http://www.personal.ceu.hu/tex/specchar.htm # Backslash must be first for this to work correctly since # it keeps getting added in @@ -1674,21 +1651,9 @@ def symbol(self, symbol): self._symbol = symbol -def _if_refresh_overridden_call_and_emit_deprec(locator): - if not locator.refresh.__func__.__module__.startswith("matplotlib."): - cbook.warn_external( - "3.3", message="Automatic calls to Locator.refresh by the draw " - "machinery are deprecated since %(since)s and will be removed in " - "%(removal)s. You are using a third-party locator that overrides " - "the refresh() method; this locator should instead perform any " - "required processing in __call__().") - with cbook._suppress_matplotlib_deprecation_warning(): - locator.refresh() - - class Locator(TickHelper): """ - Determine the tick locations; + Determine tick locations. Note that the same locator should not be used across multiple `~matplotlib.axis.Axis` because the locator stores references to the Axis @@ -1707,7 +1672,7 @@ def tick_values(self, vmin, vmax): .. note:: To get tick locations with the vmin and vmax values defined - automatically for the associated :attr:`axis` simply call + automatically for the associated ``axis`` simply call the Locator instance:: >>> print(type(loc)) @@ -1723,7 +1688,7 @@ def set_params(self, **kwargs): Do nothing, and raise a warning. Any locator class not supporting the set_params() function will call this. """ - cbook._warn_external( + _api.warn_external( "'set_params()' not defined for locator of type " + str(type(self))) @@ -1756,7 +1721,7 @@ def nonsingular(self, v0, v1): Adjust a range as needed to avoid singularities. This method gets called during autoscaling, with ``(v0, v1)`` set to - the data limits on the axes if the axes contains any data, or + the data limits on the Axes if the Axes contains any data, or ``(-inf, +inf)`` if not. - If ``v0 == v1`` (possibly up to some floating point slop), this @@ -1775,51 +1740,15 @@ def view_limits(self, vmin, vmax): """ return mtransforms.nonsingular(vmin, vmax) - @cbook.deprecated("3.2") - def autoscale(self): - """Autoscale the view limits.""" - return self.view_limits(*self.axis.get_view_interval()) - - @cbook.deprecated("3.3") - def pan(self, numsteps): - """Pan numticks (can be positive or negative)""" - ticks = self() - numticks = len(ticks) - - vmin, vmax = self.axis.get_view_interval() - vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - if numticks > 2: - step = numsteps * abs(ticks[0] - ticks[1]) - else: - d = abs(vmax - vmin) - step = numsteps * d / 6. - - vmin += step - vmax += step - self.axis.set_view_interval(vmin, vmax, ignore=True) - - @cbook.deprecated("3.3") - def zoom(self, direction): - """Zoom in/out on axis; if direction is >0 zoom in, else zoom out.""" - - vmin, vmax = self.axis.get_view_interval() - vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - interval = abs(vmax - vmin) - step = 0.1 * interval * direction - self.axis.set_view_interval(vmin + step, vmax - step, ignore=True) - - @cbook.deprecated("3.3") - def refresh(self): - """Refresh internal information based on current limits.""" - class IndexLocator(Locator): """ - Place a tick on every multiple of some base number of points - plotted, e.g., on every 5th point. It is assumed that you are doing - index plotting; i.e., the axis is 0, len(data). This is mainly - useful for x ticks. + Place ticks at every nth point plotted. + + IndexLocator assumes index plotting; i.e., that the ticks are placed at integer + values in the range between 0 and len(data) inclusive. """ + def __init__(self, base, offset): """Place ticks every *base* data point, starting at *offset*.""" self._base = base @@ -1843,18 +1772,19 @@ def tick_values(self, vmin, vmax): class FixedLocator(Locator): - """ - Tick locations are fixed. If nbins is not None, - the array of possible positions will be subsampled to - keep the number of ticks <= nbins +1. - The subsampling will be done so as to include the smallest - absolute value; for example, if zero is included in the - array of possibilities, then it is guaranteed to be one of - the chosen ticks. + r""" + Place ticks at a set of fixed values. + + If *nbins* is None ticks are placed at all values. Otherwise, the *locs* array of + possible positions will be subsampled to keep the number of ticks + :math:`\leq nbins + 1`. The subsampling will be done to include the smallest + absolute value; for example, if zero is included in the array of possibilities, then + it will be included in the chosen ticks. """ def __init__(self, locs, nbins=None): self.locs = np.asarray(locs) + _api.check_shape((None,), locs=self.locs) self.nbins = max(nbins, 2) if nbins is not None else None def set_params(self, nbins=None): @@ -1871,9 +1801,7 @@ def tick_values(self, vmin, vmax): .. note:: - Because the values are fixed, vmin and vmax are not used in this - method. - + Because the values are fixed, *vmin* and *vmax* are not used. """ if self.nbins is None: return self.locs @@ -1888,7 +1816,7 @@ def tick_values(self, vmin, vmax): class NullLocator(Locator): """ - No ticks + Place no ticks. """ def __call__(self): @@ -1900,25 +1828,30 @@ def tick_values(self, vmin, vmax): .. note:: - Because the values are Null, vmin and vmax are not used in this - method. + Because there are no ticks, *vmin* and *vmax* are not used. """ return [] class LinearLocator(Locator): """ - Determine the tick locations - - The first time this function is called it will try to set the - number of ticks to make a nice tick partitioning. Thereafter the - number of ticks will be fixed so that interactive navigation will - be nice + Place ticks at evenly spaced values. + The first time this function is called, it will try to set the number of + ticks to make a nice tick partitioning. Thereafter, the number of ticks + will be fixed to avoid jumping during interactive navigation. """ + def __init__(self, numticks=None, presets=None): """ - Use presets to set locs based on lom. A dict mapping vmin, vmax->locs + Parameters + ---------- + numticks : int or None, default None + Number of ticks. If None, *numticks* = 11. + presets : dict or None, default: None + Dictionary mapping ``(vmin, vmax)`` to an array of locations. + Overrides *numticks* if there is an entry for the current + ``(vmin, vmax)``. """ self.numticks = numticks if presets is None: @@ -1949,8 +1882,6 @@ def __call__(self): def tick_values(self, vmin, vmax): vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - if vmax < vmin: - vmin, vmax = vmax, vmin if (vmin, vmax) in self.presets: return self.presets[(vmin, vmax)] @@ -1984,16 +1915,40 @@ def view_limits(self, vmin, vmax): class MultipleLocator(Locator): """ - Set a tick on each integer multiple of a base within the view interval. + Place ticks at every integer multiple of a base plus an offset. """ - def __init__(self, base=1.0): + def __init__(self, base=1.0, offset=0.0): + """ + Parameters + ---------- + base : float > 0, default: 1.0 + Interval between ticks. + offset : float, default: 0.0 + Value added to each multiple of *base*. + + .. versionadded:: 3.8 + """ self._edge = _Edge_integer(base, 0) + self._offset = offset - def set_params(self, base): - """Set parameters within this locator.""" + def set_params(self, base=None, offset=None): + """ + Set parameters within this locator. + + Parameters + ---------- + base : float > 0, optional + Interval between ticks. + offset : float, optional + Value added to each multiple of *base*. + + .. versionadded:: 3.8 + """ if base is not None: self._edge = _Edge_integer(base, 0) + if offset is not None: + self._offset = offset def __call__(self): """Return the locations of the ticks.""" @@ -2004,19 +1959,20 @@ def tick_values(self, vmin, vmax): if vmax < vmin: vmin, vmax = vmax, vmin step = self._edge.step + vmin -= self._offset + vmax -= self._offset vmin = self._edge.ge(vmin) * step n = (vmax - vmin + 0.001 * step) // step - locs = vmin - step + np.arange(n + 3) * step + locs = vmin - step + np.arange(n + 3) * step + self._offset return self.raise_if_exceeds(locs) def view_limits(self, dmin, dmax): """ - Set the view limits to the nearest multiples of base that - contain the data. + Set the view limits to the nearest tick values that contain the data. """ if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': - vmin = self._edge.le(dmin) * self._edge.step - vmax = self._edge.ge(dmax) * self._edge.step + vmin = self._edge.le(dmin - self._offset) * self._edge.step + self._offset + vmax = self._edge.ge(dmax - self._offset) * self._edge.step + self._offset if vmin == vmax: vmin -= 1 vmax += 1 @@ -2040,16 +1996,21 @@ def scale_range(vmin, vmax, n=1, threshold=100): class _Edge_integer: """ - Helper for MaxNLocator, MultipleLocator, etc. + Helper for `.MaxNLocator`, `.MultipleLocator`, etc. - Take floating point precision limitations into account when calculating + Take floating-point precision limitations into account when calculating tick locations as integer multiples of a step. """ + def __init__(self, step, offset): """ - *step* is a positive floating-point interval between ticks. - *offset* is the offset subtracted from the data limits - prior to calculating tick locations. + Parameters + ---------- + step : float > 0 + Interval between ticks. + offset : float + Offset subtracted from the data limits prior to calculating tick + locations. """ if step <= 0: raise ValueError("'step' must be positive") @@ -2083,8 +2044,10 @@ def ge(self, x): class MaxNLocator(Locator): """ - Find nice tick locations with no more than N being within the view limits. - Locations beyond the limits are added to support autoscaling. + Place evenly spaced ticks, with a cap on the total number of ticks. + + Finds nice tick locations with no more than :math:`nbins + 1` ticks being within the + view limits. Locations beyond the limits are added to support autoscaling. """ default_params = dict(nbins=10, steps=None, @@ -2093,7 +2056,7 @@ class MaxNLocator(Locator): prune=None, min_n_ticks=2) - def __init__(self, *args, **kwargs): + def __init__(self, nbins=None, **kwargs): """ Parameters ---------- @@ -2103,12 +2066,12 @@ def __init__(self, *args, **kwargs): automatically determined based on the length of the axis. steps : array-like, optional - Sequence of nice numbers starting with 1 and ending with 10; - e.g., [1, 2, 4, 5, 10], where the values are acceptable - tick multiples. i.e. for the example, 20, 40, 60 would be - an acceptable set of ticks, as would 0.4, 0.6, 0.8, because - they are multiples of 2. However, 30, 60, 90 would not - be allowed because 3 does not appear in the list of steps. + Sequence of acceptable tick multiples, starting with 1 and + ending with 10. For example, if ``steps=[1, 2, 4, 5, 10]``, + ``20, 40, 60`` or ``0.4, 0.6, 0.8`` would be possible + sets of ticks because they are multiples of 2. + ``30, 60, 90`` would not be generated because 3 does not + appear in this example list of steps. integer : bool, default: False If True, ticks will take only integer values, provided at least @@ -2118,29 +2081,18 @@ def __init__(self, *args, **kwargs): If True, autoscaling will result in a range symmetric about zero. prune : {'lower', 'upper', 'both', None}, default: None - Remove edge ticks -- useful for stacked or ganged plots where - the upper tick of one axes overlaps with the lower tick of the - axes above it, primarily when :rc:`axes.autolimit_mode` is - ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will - be removed. If ``prune == 'upper'``, the largest tick will be - removed. If ``prune == 'both'``, the largest and smallest ticks - will be removed. If *prune* is *None*, no ticks will be removed. + Remove the 'lower' tick, the 'upper' tick, or ticks on 'both' sides + *if they fall exactly on an axis' edge* (this typically occurs when + :rc:`axes.autolimit_mode` is 'round_numbers'). Removing such ticks + is mostly useful for stacked or ganged plots, where the upper tick + of an Axes overlaps with the lower tick of the axes above it. min_n_ticks : int, default: 2 Relax *nbins* and *integer* constraints if necessary to obtain this minimum number of ticks. """ - if args: - if 'nbins' in kwargs: - cbook.deprecated("3.1", - message='Calling MaxNLocator with positional ' - 'and keyword parameter *nbins* is ' - 'considered an error and will fail ' - 'in future versions of matplotlib.') - kwargs['nbins'] = args[0] - if len(args) > 1: - raise ValueError( - "Keywords are required for all arguments except 'nbins'") + if nbins is not None: + kwargs['nbins'] = nbins self.set_params(**{**self.default_params, **kwargs}) @staticmethod @@ -2153,18 +2105,16 @@ def _validate_steps(steps): raise ValueError('steps argument must be an increasing sequence ' 'of numbers between 1 and 10 inclusive') if steps[0] != 1: - steps = np.hstack((1, steps)) + steps = np.concatenate([[1], steps]) if steps[-1] != 10: - steps = np.hstack((steps, 10)) + steps = np.concatenate([steps, [10]]) return steps @staticmethod def _staircase(steps): - # Make an extended staircase within which the needed - # step will be found. This is probably much larger - # than necessary. - flights = (0.1 * steps[:-1], steps, 10 * steps[1]) - return np.hstack(flights) + # Make an extended staircase within which the needed step will be + # found. This is probably much larger than necessary. + return np.concatenate([0.1 * steps[:-1], steps, [10 * steps[1]]]) def set_params(self, **kwargs): """ @@ -2193,7 +2143,7 @@ def set_params(self, **kwargs): self._symmetric = kwargs.pop('symmetric') if 'prune' in kwargs: prune = kwargs.pop('prune') - cbook._check_in_list(['upper', 'lower', 'both', None], prune=prune) + _api.check_in_list(['upper', 'lower', 'both', None], prune=prune) self._prune = prune if 'min_n_ticks' in kwargs: self._min_n_ticks = max(1, kwargs.pop('min_n_ticks')) @@ -2207,9 +2157,7 @@ def set_params(self, **kwargs): if 'integer' in kwargs: self._integer = kwargs.pop('integer') if kwargs: - key, _ = kwargs.popitem() - raise TypeError( - f"set_params() got an unexpected keyword argument '{key}'") + raise _api.kwarg_error("set_params", kwargs) def _raw_ticks(self, vmin, vmax): """ @@ -2230,27 +2178,36 @@ def _raw_ticks(self, vmin, vmax): scale, offset = scale_range(vmin, vmax, nbins) _vmin = vmin - offset _vmax = vmax - offset - raw_step = (_vmax - _vmin) / nbins steps = self._extended_steps * scale if self._integer: # For steps > 1, keep only integer values. igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001) steps = steps[igood] - istep = np.nonzero(steps >= raw_step)[0][0] - - # Classic round_numbers mode may require a larger step. + raw_step = ((_vmax - _vmin) / nbins) + if hasattr(self.axis, "axes") and self.axis.axes.name == '3d': + # Due to the change in automargin behavior in mpl3.9, we need to + # adjust the raw step to match the mpl3.8 appearance. The zoom + # factor of 2/48, gives us the 23/24 modifier. + raw_step = raw_step * 23/24 + large_steps = steps >= raw_step if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': - for istep in range(istep, len(steps)): - step = steps[istep] - best_vmin = (_vmin // step) * step - best_vmax = best_vmin + step * nbins - if best_vmax >= _vmax: - break + # Classic round_numbers mode may require a larger step. + # Get first multiple of steps that are <= _vmin + floored_vmins = (_vmin // steps) * steps + floored_vmaxs = floored_vmins + steps * nbins + large_steps = large_steps & (floored_vmaxs >= _vmax) + + # Find index of smallest large step + if any(large_steps): + istep = np.nonzero(large_steps)[0][0] + else: + istep = len(steps) - 1 - # This is an upper limit; move to smaller steps if necessary. - for istep in reversed(range(istep + 1)): - step = steps[istep] + # Start at smallest of the steps greater than the raw step, and check + # if it provides enough ticks. If not, work backwards through + # smaller steps until one is found that provides enough ticks. + for step in steps[:istep+1][::-1]: if (self._integer and np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1): @@ -2306,13 +2263,17 @@ def view_limits(self, dmin, dmax): return dmin, dmax -def is_decade(x, base=10, *, rtol=1e-10): +def _is_decade(x, *, base=10, rtol=None): + """Return True if *x* is an integer power of *base*.""" if not np.isfinite(x): return False if x == 0.0: return True lx = np.log(abs(x)) / np.log(base) - return is_close_to_int(lx, atol=rtol) + if rtol is None: + return np.isclose(lx, np.round(lx)) + else: + return np.isclose(lx, np.round(lx), rtol=rtol) def _decade_less_equal(x, base): @@ -2365,70 +2326,65 @@ def _decade_greater(x, base): return greater -def is_close_to_int(x, *, atol=1e-10): - return abs(x - np.round(x)) < atol +def _is_close_to_int(x): + return math.isclose(x, round(x)) class LogLocator(Locator): """ - Determine the tick locations for log axes + Place logarithmically spaced ticks. + + Places ticks at the values ``subs[j] * base**i``. """ - def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None): + def __init__(self, base=10.0, subs=(1.0,), *, numticks=None): """ - Place ticks on the locations : subs[j] * base**i - Parameters ---------- - subs : None or str or sequence of float, default: (1.0,) - Gives the multiples of integer powers of the base at which - to place ticks. The default places ticks only at - integer powers of the base. - The permitted string values are ``'auto'`` and ``'all'``, - both of which use an algorithm based on the axis view - limits to determine whether and how to put ticks between - integer powers of the base. With ``'auto'``, ticks are - placed only between integer powers; with ``'all'``, the - integer powers are included. A value of None is - equivalent to ``'auto'``. - + base : float, default: 10.0 + The base of the log used, so major ticks are placed at ``base**n``, where + ``n`` is an integer. + subs : None or {'auto', 'all'} or sequence of float, default: (1.0,) + Gives the multiples of integer powers of the base at which to place ticks. + The default of ``(1.0, )`` places ticks only at integer powers of the base. + Permitted string values are ``'auto'`` and ``'all'``. Both of these use an + algorithm based on the axis view limits to determine whether and how to put + ticks between integer powers of the base: + - ``'auto'``: Ticks are placed only between integer powers. + - ``'all'``: Ticks are placed between *and* at integer powers. + - ``None``: Equivalent to ``'auto'``. + numticks : None or int, default: None + The maximum number of ticks to allow on a given axis. The default of + ``None`` will try to choose intelligently as long as this Locator has + already been assigned to an axis using `~.axis.Axis.get_tick_space`, but + otherwise falls back to 9. """ if numticks is None: if mpl.rcParams['_internal.classic_mode']: numticks = 15 else: numticks = 'auto' - self.base(base) - self.subs(subs) - self.numdecs = numdecs + self._base = float(base) + self._set_subs(subs) self.numticks = numticks - def set_params(self, base=None, subs=None, numdecs=None, numticks=None): + def set_params(self, base=None, subs=None, *, numticks=None): """Set parameters within this locator.""" if base is not None: - self.base(base) + self._base = float(base) if subs is not None: - self.subs(subs) - if numdecs is not None: - self.numdecs = numdecs + self._set_subs(subs) if numticks is not None: self.numticks = numticks - # FIXME: these base and subs functions are contrary to our - # usual and desired API. - - def base(self, base): - """Set the log base (major tick every ``base**i``, i integer).""" - self._base = float(base) - - def subs(self, subs): + def _set_subs(self, subs): """ Set the minor ticks for the log scaling every ``base**i*subs[j]``. """ if subs is None: # consistency with previous bad API self._subs = 'auto' elif isinstance(subs, str): - cbook._check_in_list(('all', 'auto'), subs=subs) + _api.check_in_list(('all', 'auto'), subs=subs) self._subs = subs else: try: @@ -2436,96 +2392,134 @@ def subs(self, subs): except ValueError as e: raise ValueError("subs must be None, 'all', 'auto' or " "a sequence of floats, not " - "{}.".format(subs)) from e + f"{subs}.") from e if self._subs.ndim != 1: raise ValueError("A sequence passed to subs must be " "1-dimensional, not " - "{}-dimensional.".format(self._subs.ndim)) + f"{self._subs.ndim}-dimensional.") def __call__(self): """Return the locations of the ticks.""" vmin, vmax = self.axis.get_view_interval() return self.tick_values(vmin, vmax) + def _log_b(self, x): + # Use specialized logs if possible, as they can be more accurate; e.g. + # log(.001) / log(10) = -2.999... (whether math.log or np.log) due to + # floating point error. + return (np.log10(x) if self._base == 10 else + np.log2(x) if self._base == 2 else + np.log(x) / np.log(self._base)) + def tick_values(self, vmin, vmax): - if self.numticks == 'auto': - if self.axis is not None: - numticks = np.clip(self.axis.get_tick_space(), 2, 9) - else: - numticks = 9 - else: - numticks = self.numticks + n_request = ( + self.numticks if self.numticks != "auto" else + np.clip(self.axis.get_tick_space(), 2, 9) if self.axis is not None else + 9) b = self._base - # dummy axis has no axes attribute - if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': - vmax = math.ceil(math.log(vmax) / math.log(b)) - decades = np.arange(vmax - self.numdecs, vmax) - ticklocs = b ** decades - - return ticklocs - if vmin <= 0.0: if self.axis is not None: vmin = self.axis.get_minpos() if vmin <= 0.0 or not np.isfinite(vmin): raise ValueError( - "Data has no positive values, and therefore can not be " - "log-scaled.") - - _log.debug('vmin %s vmax %s', vmin, vmax) + "Data has no positive values, and therefore cannot be log-scaled.") if vmax < vmin: vmin, vmax = vmax, vmin - log_vmin = math.log(vmin) / math.log(b) - log_vmax = math.log(vmax) / math.log(b) - - numdec = math.floor(log_vmax) - math.ceil(log_vmin) + # Min and max exponents, float and int versions; e.g., if vmin=10^0.3, + # vmax=10^6.9, then efmin=0.3, emin=1, emax=6, efmax=6.9, n_avail=6. + efmin, efmax = self._log_b([vmin, vmax]) + emin = math.ceil(efmin) + emax = math.floor(efmax) + n_avail = emax - emin + 1 # Total number of decade ticks available. if isinstance(self._subs, str): - _first = 2.0 if self._subs == 'auto' else 1.0 - if numdec > 10 or b < 3: + if n_avail >= 10 or b < 3: if self._subs == 'auto': return np.array([]) # no minor or major ticks else: subs = np.array([1.0]) # major ticks else: + _first = 2.0 if self._subs == 'auto' else 1.0 subs = np.arange(_first, b) else: subs = self._subs - # Get decades between major ticks. - stride = (max(math.ceil(numdec / (numticks - 1)), 1) - if mpl.rcParams['_internal.classic_mode'] else - (numdec + 1) // numticks + 1) - - # Does subs include anything other than 1? Essentially a hack to know - # whether we're a major or a minor locator. - have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0) - - decades = np.arange(math.floor(log_vmin) - stride, - math.ceil(log_vmax) + 2 * stride, stride) - - if hasattr(self, '_transform'): - ticklocs = self._transform.inverted().transform(decades) - if have_subs: - if stride == 1: - ticklocs = np.ravel(np.outer(subs, ticklocs)) - else: - # No ticklocs if we have >1 decade between major ticks. - ticklocs = np.array([]) + # Get decades between major ticks. Include an extra tick outside the + # lower and the upper limit: QuadContourSet._autolev relies on this. + if mpl.rcParams["_internal.classic_mode"]: # keep historic formulas + stride = max(math.ceil((n_avail - 1) / (n_request - 1)), 1) + decades = np.arange(emin - stride, emax + stride + 1, stride) else: - if have_subs: - if stride == 1: - ticklocs = np.concatenate( - [subs * decade_start for decade_start in b ** decades]) - else: - ticklocs = np.array([]) + # *Determine the actual number of ticks*: Find the largest number + # of ticks, no more than the requested number, that can actually + # be drawn (e.g., with 9 decades ticks, no stride yields 7 + # ticks). For a given value of the stride *s*, there are either + # floor(n_avail/s) or ceil(n_avail/s) ticks depending on the + # offset. Pick the smallest stride such that floor(n_avail/s) < + # n_request, i.e. n_avail/s < n_request+1, then re-set n_request + # to ceil(...) if acceptable, else to floor(...) (which must then + # equal the original n_request, i.e. n_request is kept unchanged). + stride = n_avail // (n_request + 1) + 1 + nr = math.ceil(n_avail / stride) + if nr <= n_request: + n_request = nr else: - ticklocs = b ** decades + assert nr == n_request + 1 + if n_request == 0: # No tick in bounds; two ticks just outside. + decades = [emin - 1, emax + 1] + stride = decades[1] - decades[0] + elif n_request == 1: # A single tick close to center. + mid = round((efmin + efmax) / 2) + stride = max(mid - (emin - 1), (emax + 1) - mid) + decades = [mid - stride, mid, mid + stride] + else: + # *Determine the stride*: Pick the largest stride that yields + # this actual n_request (e.g., with 15 decades, strides of + # 5, 6, or 7 *can* yield 3 ticks; picking a larger stride + # minimizes unticked space at the ends). First try for + # ceil(n_avail/stride) == n_request + # i.e. + # n_avail/n_request <= stride < n_avail/(n_request-1) + # else fallback to + # floor(n_avail/stride) == n_request + # i.e. + # n_avail/(n_request+1) < stride <= n_avail/n_request + # One of these cases must have an integer solution (given the + # choice of n_request above). + stride = (n_avail - 1) // (n_request - 1) + if stride < n_avail / n_request: # fallback to second case + stride = n_avail // n_request + # *Determine the offset*: For a given stride *and offset* + # (0 <= offset < stride), the actual number of ticks is + # ceil((n_avail - offset) / stride), which must be equal to + # n_request. This leads to olo <= offset < ohi, with the + # values defined below. + olo = max(n_avail - stride * n_request, 0) + ohi = min(n_avail - stride * (n_request - 1), stride) + # Try to see if we can pick an offset so that ticks are at + # integer multiples of the stride while satisfying the bounds + # above; if not, fallback to the smallest acceptable offset. + offset = (-emin) % stride + if not olo <= offset < ohi: + offset = olo + decades = range(emin + offset - stride, emax + stride + 1, stride) + + # Guess whether we're a minor locator, based on whether subs include + # anything other than 1. + is_minor = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0) + if is_minor: + if stride == 1 or n_avail <= 1: + # Minor ticks start in the decade preceding the first major tick. + ticklocs = np.concatenate([ + subs * b**decade for decade in range(emin - 1, emax + 1)]) + else: + ticklocs = np.array([]) + else: + ticklocs = b ** np.array(decades) - _log.debug('ticklocs %r', ticklocs) if (len(subs) > 1 and stride == 1 and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1): @@ -2542,13 +2536,9 @@ def view_limits(self, vmin, vmax): vmin, vmax = self.nonsingular(vmin, vmax) - if self.axis.axes.name == 'polar': - vmax = math.ceil(math.log(vmax) / math.log(b)) - vmin = b ** (vmax - self.numdecs) - if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': - vmin = _decade_less_equal(vmin, self._base) - vmax = _decade_greater_equal(vmax, self._base) + vmin = _decade_less_equal(vmin, b) + vmax = _decade_greater_equal(vmax, b) return vmin, vmax @@ -2558,12 +2548,13 @@ def nonsingular(self, vmin, vmax): if not np.isfinite(vmin) or not np.isfinite(vmax): vmin, vmax = 1, 10 # Initial range, no data plotted yet. elif vmax <= 0: - cbook._warn_external( + _api.warn_external( "Data has no positive values, and therefore cannot be " "log-scaled.") vmin, vmax = 1, 10 else: - minpos = self.axis.get_minpos() + # Consider shared axises + minpos = min(axis.get_minpos() for axis in self.axis._get_shared_axis()) if not np.isfinite(minpos): minpos = 1e-300 # This should never take effect. if vmin <= 0: @@ -2576,7 +2567,7 @@ def nonsingular(self, vmin, vmax): class SymmetricalLogLocator(Locator): """ - Determine the tick locations for symmetric log axes. + Place ticks spaced linearly near zero and spaced logarithmically beyond a threshold. """ def __init__(self, transform=None, subs=None, linthresh=None, base=None): @@ -2627,7 +2618,6 @@ def __call__(self): return self.tick_values(vmin, vmax) def tick_values(self, vmin, vmax): - base = self._base linthresh = self._linthresh if vmax < vmin: @@ -2650,11 +2640,11 @@ def tick_values(self, vmin, vmax): # We could also add ticks at t, but that seems to usually be # uninteresting. # - # "simple" mode is when the range falls entirely within (-t, - # t) -- it should just display (vmin, 0, vmax) - if -linthresh < vmin < vmax < linthresh: + # "simple" mode is when the range falls entirely within [-t, t] + # -- it should just display (vmin, 0, vmax) + if -linthresh <= vmin < vmax <= linthresh: # only the linear range is present - return [vmin, vmax] + return sorted({vmin, 0, vmax}) # Lower log range is present has_a = (vmin < -linthresh) @@ -2664,6 +2654,8 @@ def tick_values(self, vmin, vmax): # Check if linear range is present has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh) + base = self._base + def get_log_range(lo, hi): lo = np.floor(np.log(lo) / np.log(base)) hi = np.ceil(np.log(hi) / np.log(base)) @@ -2697,11 +2689,7 @@ def get_log_range(lo, hi): if has_c: decades.extend(base ** (np.arange(c_lo, c_hi, stride))) - # Add the subticks if requested - if self._subs is None: - subs = np.arange(2.0, base) - else: - subs = np.asarray(self._subs) + subs = np.asarray(self._subs) if len(subs) > 1 or subs[0] != 1.0: ticklocs = [] @@ -2728,19 +2716,108 @@ def view_limits(self, vmin, vmax): vmin = _decade_less(vmin, b) vmax = _decade_greater(vmax, b) - result = mtransforms.nonsingular(vmin, vmax) - return result + return mtransforms.nonsingular(vmin, vmax) + + +class AsinhLocator(Locator): + """ + Place ticks spaced evenly on an inverse-sinh scale. + + Generally used with the `~.scale.AsinhScale` class. + + .. note:: + + This API is provisional and may be revised in the future + based on early user feedback. + """ + def __init__(self, linear_width, numticks=11, symthresh=0.2, + base=10, subs=None): + """ + Parameters + ---------- + linear_width : float + The scale parameter defining the extent + of the quasi-linear region. + numticks : int, default: 11 + The approximate number of major ticks that will fit + along the entire axis + symthresh : float, default: 0.2 + The fractional threshold beneath which data which covers + a range that is approximately symmetric about zero + will have ticks that are exactly symmetric. + base : int, default: 10 + The number base used for rounding tick locations + on a logarithmic scale. If this is less than one, + then rounding is to the nearest integer multiple + of powers of ten. + subs : tuple, default: None + Multiples of the number base, typically used + for the minor ticks, e.g. (2, 5) when base=10. + """ + super().__init__() + self.linear_width = linear_width + self.numticks = numticks + self.symthresh = symthresh + self.base = base + self.subs = subs + + def set_params(self, numticks=None, symthresh=None, + base=None, subs=None): + """Set parameters within this locator.""" + if numticks is not None: + self.numticks = numticks + if symthresh is not None: + self.symthresh = symthresh + if base is not None: + self.base = base + if subs is not None: + self.subs = subs if len(subs) > 0 else None + + def __call__(self): + vmin, vmax = self.axis.get_view_interval() + if (vmin * vmax) < 0 and abs(1 + vmax / vmin) < self.symthresh: + # Data-range appears to be almost symmetric, so round up: + bound = max(abs(vmin), abs(vmax)) + return self.tick_values(-bound, bound) + else: + return self.tick_values(vmin, vmax) + + def tick_values(self, vmin, vmax): + # Construct a set of uniformly-spaced "on-screen" locations. + ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax]) + / self.linear_width) + ys = np.linspace(ymin, ymax, self.numticks) + zero_dev = abs(ys / (ymax - ymin)) + if ymin * ymax < 0: + # Ensure that the zero tick-mark is included, if the axis straddles zero. + ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0]) + + # Transform the "on-screen" grid to the data space: + xs = self.linear_width * np.sinh(ys / self.linear_width) + zero_xs = (ys == 0) + + # Round the data-space values to be intuitive base-n numbers, keeping track of + # positive and negative values separately and carefully treating the zero value. + with np.errstate(divide="ignore"): # base ** log(0) = base ** -inf = 0. + if self.base > 1: + pows = (np.sign(xs) + * self.base ** np.floor(np.log(abs(xs)) / math.log(self.base))) + qs = np.outer(pows, self.subs).flatten() if self.subs else pows + else: # No need to adjust sign(pows), as it cancels out when computing qs. + pows = np.where(zero_xs, 1, 10**np.floor(np.log10(abs(xs)))) + qs = pows * np.round(xs / pows) + ticks = np.array(sorted(set(qs))) + + return ticks if len(ticks) >= 2 else np.linspace(vmin, vmax, self.numticks) class LogitLocator(MaxNLocator): """ - Determine the tick locations for logit axes + Place ticks spaced evenly on a logit scale. """ def __init__(self, minor=False, *, nbins="auto"): """ - Place ticks on the logit locations - Parameters ---------- nbins : int or 'auto', optional @@ -2785,7 +2862,7 @@ def tick_values(self, vmin, vmax): # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ... # b-scale : ... -3 -2 -1 0 1 2 3 ... def ideal_ticks(x): - return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2 + return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 0.5 vmin, vmax = self.nonsingular(vmin, vmax) binf = int( @@ -2853,7 +2930,7 @@ def nonsingular(self, vmin, vmax): elif vmax <= 0 or vmin >= 1: # vmax <= 0 occurs when all values are negative # vmin >= 1 occurs when all values are greater than one - cbook._warn_external( + _api.warn_external( "Data has no values between 0 and 1, and therefore cannot be " "logit-scaled." ) @@ -2882,9 +2959,11 @@ def nonsingular(self, vmin, vmax): class AutoLocator(MaxNLocator): """ - Dynamically find major tick positions. This is actually a subclass - of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'* - and *steps = [1, 2, 2.5, 5, 10]*. + Place evenly spaced ticks, with the step size and maximum number of ticks chosen + automatically. + + This is a subclass of `~matplotlib.ticker.MaxNLocator`, with parameters + *nbins = 'auto'* and *steps = [1, 2, 2.5, 5, 10]*. """ def __init__(self): """ @@ -2902,115 +2981,62 @@ def __init__(self): class AutoMinorLocator(Locator): """ - Dynamically find minor tick positions based on the positions of - major ticks. The scale must be linear with major ticks evenly spaced. + Place evenly spaced minor ticks, with the step size and maximum number of ticks + chosen automatically. + + The Axis must use a linear scale and have evenly spaced major ticks. """ + def __init__(self, n=None): """ - *n* is the number of subdivisions of the interval between - major ticks; e.g., n=2 will place a single minor tick midway - between major ticks. + Parameters + ---------- + n : int or 'auto', default: :rc:`xtick.minor.ndivs` or :rc:`ytick.minor.ndivs` + The number of subdivisions of the interval between major ticks; + e.g., n=2 will place a single minor tick midway between major ticks. - If *n* is omitted or None, it will be set to 5 or 4. + If *n* is 'auto', it will be set to 4 or 5: if the distance + between the major ticks equals 1, 2.5, 5 or 10 it can be perfectly + divided in 5 equidistant sub-intervals with a length multiple of + 0.05; otherwise, it is divided in 4 sub-intervals. """ self.ndivs = n def __call__(self): - """Return the locations of the ticks.""" + # docstring inherited if self.axis.get_scale() == 'log': - cbook._warn_external('AutoMinorLocator does not work with ' - 'logarithmic scale') + _api.warn_external('AutoMinorLocator does not work on logarithmic scales') return [] - majorlocs = self.axis.get_majorticklocs() - try: - majorstep = majorlocs[1] - majorlocs[0] - except IndexError: - # Need at least two major ticks to find minor tick locations - # TODO: Figure out a way to still be able to display minor - # ticks without two major ticks visible. For now, just display - # no ticks at all. + majorlocs = np.unique(self.axis.get_majorticklocs()) + if len(majorlocs) < 2: + # Need at least two major ticks to find minor tick locations. + # TODO: Figure out a way to still be able to display minor ticks with less + # than two major ticks visible. For now, just display no ticks at all. return [] + majorstep = majorlocs[1] - majorlocs[0] if self.ndivs is None: + self.ndivs = mpl.rcParams[ + 'ytick.minor.ndivs' if self.axis.axis_name == 'y' + else 'xtick.minor.ndivs'] # for x and z axis - majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1) - - if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any(): - ndivs = 5 - else: - ndivs = 4 + if self.ndivs == 'auto': + majorstep_mantissa = 10 ** (np.log10(majorstep) % 1) + ndivs = 5 if np.isclose(majorstep_mantissa, [1, 2.5, 5, 10]).any() else 4 else: ndivs = self.ndivs minorstep = majorstep / ndivs - vmin, vmax = self.axis.get_view_interval() - if vmin > vmax: - vmin, vmax = vmax, vmin - + vmin, vmax = sorted(self.axis.get_view_interval()) t0 = majorlocs[0] - tmin = ((vmin - t0) // minorstep + 1) * minorstep - tmax = ((vmax - t0) // minorstep + 1) * minorstep - locs = np.arange(tmin, tmax, minorstep) + t0 + tmin = round((vmin - t0) / minorstep) + tmax = round((vmax - t0) / minorstep) + 1 + locs = (np.arange(tmin, tmax) * minorstep) + t0 return self.raise_if_exceeds(locs) def tick_values(self, vmin, vmax): - raise NotImplementedError('Cannot get tick locations for a ' - '%s type.' % type(self)) - - -@cbook.deprecated("3.3") -class OldAutoLocator(Locator): - """ - On autoscale this class picks the best MultipleLocator to set the - view limits and the tick locs. - """ - - def __call__(self): - # docstring inherited - vmin, vmax = self.axis.get_view_interval() - vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - d = abs(vmax - vmin) - locator = self.get_locator(d) - return self.raise_if_exceeds(locator()) - - def tick_values(self, vmin, vmax): - raise NotImplementedError('Cannot get tick locations for a ' - '%s type.' % type(self)) - - def view_limits(self, vmin, vmax): - # docstring inherited - d = abs(vmax - vmin) - locator = self.get_locator(d) - return locator.view_limits(vmin, vmax) - - def get_locator(self, d): - """Pick the best locator based on a distance *d*.""" - d = abs(d) - if d <= 0: - locator = MultipleLocator(0.2) - else: - - try: - ld = math.log10(d) - except OverflowError as err: - raise RuntimeError('AutoLocator illegal data interval ' - 'range') from err - - fld = math.floor(ld) - base = 10 ** fld - - #if ld==fld: base = 10**(fld-1) - #else: base = 10**fld - - if d >= 5 * base: - ticksize = base - elif d >= 2 * base: - ticksize = base / 2.0 - else: - ticksize = base / 5.0 - locator = MultipleLocator(ticksize) - - return locator + raise NotImplementedError( + f"Cannot get tick locations for a {type(self).__name__}") diff --git a/lib/matplotlib/ticker.pyi b/lib/matplotlib/ticker.pyi new file mode 100644 index 000000000000..bed288658909 --- /dev/null +++ b/lib/matplotlib/ticker.pyi @@ -0,0 +1,308 @@ +from collections.abc import Callable, Sequence +from typing import Any, Literal + +from matplotlib.axis import Axis +from matplotlib.transforms import Transform +from matplotlib.projections.polar import _AxisWrapper + +import numpy as np + +class _DummyAxis: + __name__: str + def __init__(self, minpos: float = ...) -> None: ... + def get_view_interval(self) -> tuple[float, float]: ... + def set_view_interval(self, vmin: float, vmax: float) -> None: ... + def get_minpos(self) -> float: ... + def get_data_interval(self) -> tuple[float, float]: ... + def set_data_interval(self, vmin: float, vmax: float) -> None: ... + def get_tick_space(self) -> int: ... + +class TickHelper: + axis: None | Axis | _DummyAxis | _AxisWrapper + def set_axis(self, axis: Axis | _DummyAxis | _AxisWrapper | None) -> None: ... + def create_dummy_axis(self, **kwargs) -> None: ... + +class Formatter(TickHelper): + locs: list[float] + def __call__(self, x: float, pos: int | None = ...) -> str: ... + def format_ticks(self, values: list[float]) -> list[str]: ... + def format_data(self, value: float) -> str: ... + def format_data_short(self, value: float) -> str: ... + def get_offset(self) -> str: ... + def set_locs(self, locs: list[float]) -> None: ... + @staticmethod + def fix_minus(s: str) -> str: ... + +class NullFormatter(Formatter): ... + +class FixedFormatter(Formatter): + seq: Sequence[str] + offset_string: str + def __init__(self, seq: Sequence[str]) -> None: ... + def set_offset_string(self, ofs: str) -> None: ... + +class FuncFormatter(Formatter): + func: Callable[[float, int | None], str] + offset_string: str + # Callable[[float, int | None], str] | Callable[[float], str] + def __init__(self, func: Callable[..., str]) -> None: ... + def set_offset_string(self, ofs: str) -> None: ... + +class FormatStrFormatter(Formatter): + fmt: str + def __init__(self, fmt: str) -> None: ... + +class StrMethodFormatter(Formatter): + fmt: str + def __init__(self, fmt: str) -> None: ... + +class ScalarFormatter(Formatter): + orderOfMagnitude: int + format: str + def __init__( + self, + useOffset: bool | float | None = ..., + useMathText: bool | None = ..., + useLocale: bool | None = ..., + *, + usetex: bool | None = ..., + ) -> None: ... + offset: float + def get_usetex(self) -> bool: ... + def set_usetex(self, val: bool) -> None: ... + @property + def usetex(self) -> bool: ... + @usetex.setter + def usetex(self, val: bool) -> None: ... + def get_useOffset(self) -> bool: ... + def set_useOffset(self, val: bool | float) -> None: ... + @property + def useOffset(self) -> bool: ... + @useOffset.setter + def useOffset(self, val: bool | float) -> None: ... + def get_useLocale(self) -> bool: ... + def set_useLocale(self, val: bool | None) -> None: ... + @property + def useLocale(self) -> bool: ... + @useLocale.setter + def useLocale(self, val: bool | None) -> None: ... + def get_useMathText(self) -> bool: ... + def set_useMathText(self, val: bool | None) -> None: ... + @property + def useMathText(self) -> bool: ... + @useMathText.setter + def useMathText(self, val: bool | None) -> None: ... + def set_scientific(self, b: bool) -> None: ... + def set_powerlimits(self, lims: tuple[int, int]) -> None: ... + def format_data_short(self, value: float | np.ma.MaskedArray) -> str: ... + def format_data(self, value: float) -> str: ... + +class LogFormatter(Formatter): + minor_thresholds: tuple[float, float] + def __init__( + self, + base: float = ..., + labelOnlyBase: bool = ..., + minor_thresholds: tuple[float, float] | None = ..., + linthresh: float | None = ..., + ) -> None: ... + def set_base(self, base: float) -> None: ... + labelOnlyBase: bool + def set_label_minor(self, labelOnlyBase: bool) -> None: ... + def set_locs(self, locs: Any | None = ...) -> None: ... + def format_data(self, value: float) -> str: ... + def format_data_short(self, value: float) -> str: ... + +class LogFormatterExponent(LogFormatter): ... +class LogFormatterMathtext(LogFormatter): ... +class LogFormatterSciNotation(LogFormatterMathtext): ... + +class LogitFormatter(Formatter): + def __init__( + self, + *, + use_overline: bool = ..., + one_half: str = ..., + minor: bool = ..., + minor_threshold: int = ..., + minor_number: int = ... + ) -> None: ... + def use_overline(self, use_overline: bool) -> None: ... + def set_one_half(self, one_half: str) -> None: ... + def set_minor_threshold(self, minor_threshold: int) -> None: ... + def set_minor_number(self, minor_number: int) -> None: ... + def format_data_short(self, value: float) -> str: ... + +class EngFormatter(ScalarFormatter): + ENG_PREFIXES: dict[int, str] + unit: str + places: int | None + sep: str + def __init__( + self, + unit: str = ..., + places: int | None = ..., + sep: str = ..., + *, + usetex: bool | None = ..., + useMathText: bool | None = ..., + useOffset: bool | float | None = ..., + ) -> None: ... + def format_eng(self, num: float) -> str: ... + +class PercentFormatter(Formatter): + xmax: float + decimals: int | None + def __init__( + self, + xmax: float = ..., + decimals: int | None = ..., + symbol: str | None = ..., + is_latex: bool = ..., + ) -> None: ... + def format_pct(self, x: float, display_range: float) -> str: ... + def convert_to_pct(self, x: float) -> float: ... + @property + def symbol(self) -> str: ... + @symbol.setter + def symbol(self, symbol: str) -> None: ... + +class Locator(TickHelper): + MAXTICKS: int + def tick_values(self, vmin: float, vmax: float) -> Sequence[float]: ... + # Implementation accepts **kwargs, but is a no-op other than a warning + # Typing as **kwargs would require each subclass to accept **kwargs for mypy + def set_params(self) -> None: ... + def __call__(self) -> Sequence[float]: ... + def raise_if_exceeds(self, locs: Sequence[float]) -> Sequence[float]: ... + def nonsingular(self, v0: float, v1: float) -> tuple[float, float]: ... + def view_limits(self, vmin: float, vmax: float) -> tuple[float, float]: ... + +class IndexLocator(Locator): + offset: float + def __init__(self, base: float, offset: float) -> None: ... + def set_params( + self, base: float | None = ..., offset: float | None = ... + ) -> None: ... + +class FixedLocator(Locator): + nbins: int | None + def __init__(self, locs: Sequence[float], nbins: int | None = ...) -> None: ... + def set_params(self, nbins: int | None = ...) -> None: ... + +class NullLocator(Locator): ... + +class LinearLocator(Locator): + presets: dict[tuple[float, float], Sequence[float]] + def __init__( + self, + numticks: int | None = ..., + presets: dict[tuple[float, float], Sequence[float]] | None = ..., + ) -> None: ... + @property + def numticks(self) -> int: ... + @numticks.setter + def numticks(self, numticks: int | None) -> None: ... + def set_params( + self, + numticks: int | None = ..., + presets: dict[tuple[float, float], Sequence[float]] | None = ..., + ) -> None: ... + +class MultipleLocator(Locator): + def __init__(self, base: float = ..., offset: float = ...) -> None: ... + def set_params(self, base: float | None = ..., offset: float | None = ...) -> None: ... + def view_limits(self, dmin: float, dmax: float) -> tuple[float, float]: ... + +class _Edge_integer: + step: float + def __init__(self, step: float, offset: float) -> None: ... + def closeto(self, ms: float, edge: float) -> bool: ... + def le(self, x: float) -> float: ... + def ge(self, x: float) -> float: ... + +class MaxNLocator(Locator): + default_params: dict[str, Any] + def __init__(self, nbins: int | Literal["auto"] | None = ..., **kwargs) -> None: ... + def set_params(self, **kwargs) -> None: ... + def view_limits(self, dmin: float, dmax: float) -> tuple[float, float]: ... + +class LogLocator(Locator): + numticks: int | None + def __init__( + self, + base: float = ..., + subs: None | Literal["auto", "all"] | Sequence[float] = ..., + *, + numticks: int | None = ..., + ) -> None: ... + def set_params( + self, + base: float | None = ..., + subs: Literal["auto", "all"] | Sequence[float] | None = ..., + *, + numticks: int | None = ..., + ) -> None: ... + +class SymmetricalLogLocator(Locator): + numticks: int + def __init__( + self, + transform: Transform | None = ..., + subs: Sequence[float] | None = ..., + linthresh: float | None = ..., + base: float | None = ..., + ) -> None: ... + def set_params( + self, subs: Sequence[float] | None = ..., numticks: int | None = ... + ) -> None: ... + +class AsinhLocator(Locator): + linear_width: float + numticks: int + symthresh: float + base: int + subs: Sequence[float] | None + def __init__( + self, + linear_width: float, + numticks: int = ..., + symthresh: float = ..., + base: int = ..., + subs: Sequence[float] | None = ..., + ) -> None: ... + def set_params( + self, + numticks: int | None = ..., + symthresh: float | None = ..., + base: int | None = ..., + subs: Sequence[float] | None = ..., + ) -> None: ... + +class LogitLocator(MaxNLocator): + def __init__( + self, minor: bool = ..., *, nbins: Literal["auto"] | int = ... + ) -> None: ... + def set_params(self, minor: bool | None = ..., **kwargs) -> None: ... + @property + def minor(self) -> bool: ... + @minor.setter + def minor(self, value: bool) -> None: ... + +class AutoLocator(MaxNLocator): + def __init__(self) -> None: ... + +class AutoMinorLocator(Locator): + ndivs: int + def __init__(self, n: int | None = ...) -> None: ... + +__all__ = ('TickHelper', 'Formatter', 'FixedFormatter', + 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', + 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', + 'LogFormatterExponent', 'LogFormatterMathtext', + 'LogFormatterSciNotation', + 'LogitFormatter', 'EngFormatter', 'PercentFormatter', + 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator', + 'LinearLocator', 'LogLocator', 'AutoLocator', + 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator', + 'SymmetricalLogLocator', 'AsinhLocator', 'LogitLocator') diff --git a/lib/matplotlib/tight_bbox.py b/lib/matplotlib/tight_bbox.py deleted file mode 100644 index 5904ebc1fa1c..000000000000 --- a/lib/matplotlib/tight_bbox.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Helper module for the *bbox_inches* parameter in `.Figure.savefig`. -""" - -from matplotlib.transforms import Bbox, TransformedBbox, Affine2D - - -def adjust_bbox(fig, bbox_inches, fixed_dpi=None): - """ - Temporarily adjust the figure so that only the specified area - (bbox_inches) is saved. - - It modifies fig.bbox, fig.bbox_inches, - fig.transFigure._boxout, and fig.patch. While the figure size - changes, the scale of the original figure is conserved. A - function which restores the original values are returned. - """ - origBbox = fig.bbox - origBboxInches = fig.bbox_inches - orig_tight_layout = fig.get_tight_layout() - _boxout = fig.transFigure._boxout - - fig.set_tight_layout(False) - - old_aspect = [] - locator_list = [] - sentinel = object() - for ax in fig.axes: - locator_list.append(ax.get_axes_locator()) - current_pos = ax.get_position(original=False).frozen() - ax.set_axes_locator(lambda a, r, _pos=current_pos: _pos) - # override the method that enforces the aspect ratio on the Axes - if 'apply_aspect' in ax.__dict__: - old_aspect.append(ax.apply_aspect) - else: - old_aspect.append(sentinel) - ax.apply_aspect = lambda pos=None: None - - def restore_bbox(): - for ax, loc, aspect in zip(fig.axes, locator_list, old_aspect): - ax.set_axes_locator(loc) - if aspect is sentinel: - # delete our no-op function which un-hides the original method - del ax.apply_aspect - else: - ax.apply_aspect = aspect - - fig.bbox = origBbox - fig.bbox_inches = origBboxInches - fig.set_tight_layout(orig_tight_layout) - fig.transFigure._boxout = _boxout - fig.transFigure.invalidate() - fig.patch.set_bounds(0, 0, 1, 1) - - if fixed_dpi is None: - fixed_dpi = fig.dpi - tr = Affine2D().scale(fixed_dpi) - dpi_scale = fixed_dpi / fig.dpi - - _bbox = TransformedBbox(bbox_inches, tr) - - fig.bbox_inches = Bbox.from_bounds(0, 0, - bbox_inches.width, bbox_inches.height) - x0, y0 = _bbox.x0, _bbox.y0 - w1, h1 = fig.bbox.width * dpi_scale, fig.bbox.height * dpi_scale - fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, w1, h1) - fig.transFigure.invalidate() - - fig.bbox = TransformedBbox(fig.bbox_inches, tr) - - fig.patch.set_bounds(x0 / w1, y0 / h1, - fig.bbox.width / w1, fig.bbox.height / h1) - - return restore_bbox - - -def process_figure_for_rasterizing(fig, bbox_inches_restore, fixed_dpi=None): - """ - A function that needs to be called when figure dpi changes during the - drawing (e.g., rasterizing). It recovers the bbox and re-adjust it with - the new dpi. - """ - - bbox_inches, restore_bbox = bbox_inches_restore - restore_bbox() - r = adjust_bbox(fig, bbox_inches, fixed_dpi) - - return bbox_inches, r diff --git a/lib/matplotlib/tight_layout.py b/lib/matplotlib/tight_layout.py deleted file mode 100644 index df55005047f9..000000000000 --- a/lib/matplotlib/tight_layout.py +++ /dev/null @@ -1,338 +0,0 @@ -""" -Routines to adjust subplot params so that subplots are -nicely fit in the figure. In doing so, only axis labels, tick labels, axes -titles and offsetboxes that are anchored to axes are currently considered. - -Internally, this module assumes that the margins (left_margin, etc.) which are -differences between ax.get_tightbbox and ax.bbox are independent of axes -position. This may fail if Axes.adjustable is datalim. Also, This will fail -for some cases (for example, left or right margin is affected by xlabel). -""" - -import numpy as np - -from matplotlib import cbook, rcParams -from matplotlib.font_manager import FontProperties -from matplotlib.transforms import TransformedBbox, Bbox - - -def auto_adjust_subplotpars( - fig, renderer, nrows_ncols, num1num2_list, subplot_list, - ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None): - """ - Return a dict of subplot parameters to adjust spacing between subplots - or ``None`` if resulting axes would have zero height or width. - - Note that this function ignores geometry information of subplot - itself, but uses what is given by the *nrows_ncols* and *num1num2_list* - parameters. Also, the results could be incorrect if some subplots have - ``adjustable=datalim``. - - Parameters - ---------- - nrows_ncols : Tuple[int, int] - Number of rows and number of columns of the grid. - num1num2_list : List[int] - List of numbers specifying the area occupied by the subplot - subplot_list : list of subplots - List of subplots that will be used to calculate optimal subplot_params. - pad : float - Padding between the figure edge and the edges of subplots, as a - fraction of the font size. - h_pad, w_pad : float - Padding (height/width) between edges of adjacent subplots, as a - fraction of the font size. Defaults to *pad*. - rect : Tuple[float, float, float, float] - [left, bottom, right, top] in normalized (0, 1) figure coordinates. - """ - rows, cols = nrows_ncols - - font_size_inches = ( - FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72) - pad_inches = pad * font_size_inches - vpad_inches = h_pad * font_size_inches if h_pad is not None else pad_inches - hpad_inches = w_pad * font_size_inches if w_pad is not None else pad_inches - - if len(num1num2_list) != len(subplot_list) or len(subplot_list) == 0: - raise ValueError - - if rect is None: - margin_left = margin_bottom = margin_right = margin_top = None - else: - margin_left, margin_bottom, _right, _top = rect - margin_right = 1 - _right if _right else None - margin_top = 1 - _top if _top else None - - vspaces = np.zeros((rows + 1, cols)) - hspaces = np.zeros((rows, cols + 1)) - - if ax_bbox_list is None: - ax_bbox_list = [ - Bbox.union([ax.get_position(original=True) for ax in subplots]) - for subplots in subplot_list] - - for subplots, ax_bbox, (num1, num2) in zip(subplot_list, - ax_bbox_list, - num1num2_list): - if all(not ax.get_visible() for ax in subplots): - continue - - bb = [] - for ax in subplots: - if ax.get_visible(): - try: - bb += [ax.get_tightbbox(renderer, for_layout_only=True)] - except TypeError: - bb += [ax.get_tightbbox(renderer)] - - tight_bbox_raw = Bbox.union(bb) - tight_bbox = TransformedBbox(tight_bbox_raw, - fig.transFigure.inverted()) - - row1, col1 = divmod(num1, cols) - if num2 is None: - num2 = num1 - row2, col2 = divmod(num2, cols) - - for row_i in range(row1, row2 + 1): - hspaces[row_i, col1] += ax_bbox.xmin - tight_bbox.xmin # left - hspaces[row_i, col2 + 1] += tight_bbox.xmax - ax_bbox.xmax # right - for col_i in range(col1, col2 + 1): - vspaces[row1, col_i] += tight_bbox.ymax - ax_bbox.ymax # top - vspaces[row2 + 1, col_i] += ax_bbox.ymin - tight_bbox.ymin # bot. - - fig_width_inch, fig_height_inch = fig.get_size_inches() - - # margins can be negative for axes with aspect applied, so use max(, 0) to - # make them nonnegative. - if not margin_left: - margin_left = (max(hspaces[:, 0].max(), 0) - + pad_inches / fig_width_inch) - if not margin_right: - margin_right = (max(hspaces[:, -1].max(), 0) - + pad_inches / fig_width_inch) - if not margin_top: - margin_top = (max(vspaces[0, :].max(), 0) - + pad_inches / fig_height_inch) - suptitle = fig._suptitle - if suptitle and suptitle.get_in_layout(): - rel_suptitle_height = fig.transFigure.inverted().transform_bbox( - suptitle.get_window_extent(renderer)).height - margin_top += rel_suptitle_height + pad_inches / fig_height_inch - if not margin_bottom: - margin_bottom = (max(vspaces[-1, :].max(), 0) - + pad_inches / fig_height_inch) - - if margin_left + margin_right >= 1: - cbook._warn_external('Tight layout not applied. The left and right ' - 'margins cannot be made large enough to ' - 'accommodate all axes decorations. ') - return None - if margin_bottom + margin_top >= 1: - cbook._warn_external('Tight layout not applied. The bottom and top ' - 'margins cannot be made large enough to ' - 'accommodate all axes decorations. ') - return None - - kwargs = dict(left=margin_left, - right=1 - margin_right, - bottom=margin_bottom, - top=1 - margin_top) - - if cols > 1: - hspace = hspaces[:, 1:-1].max() + hpad_inches / fig_width_inch - # axes widths: - h_axes = (1 - margin_right - margin_left - hspace * (cols - 1)) / cols - if h_axes < 0: - cbook._warn_external('Tight layout not applied. tight_layout ' - 'cannot make axes width small enough to ' - 'accommodate all axes decorations') - return None - else: - kwargs["wspace"] = hspace / h_axes - if rows > 1: - vspace = vspaces[1:-1, :].max() + vpad_inches / fig_height_inch - v_axes = (1 - margin_top - margin_bottom - vspace * (rows - 1)) / rows - if v_axes < 0: - cbook._warn_external('Tight layout not applied. tight_layout ' - 'cannot make axes height small enough to ' - 'accommodate all axes decorations') - return None - else: - kwargs["hspace"] = vspace / v_axes - - return kwargs - - -def get_renderer(fig): - if fig._cachedRenderer: - return fig._cachedRenderer - else: - canvas = fig.canvas - if canvas and hasattr(canvas, "get_renderer"): - return canvas.get_renderer() - else: - from . import backend_bases - return backend_bases._get_renderer(fig) - - -def get_subplotspec_list(axes_list, grid_spec=None): - """ - Return a list of subplotspec from the given list of axes. - - For an instance of axes that does not support subplotspec, None is inserted - in the list. - - If grid_spec is given, None is inserted for those not from the given - grid_spec. - """ - subplotspec_list = [] - for ax in axes_list: - axes_or_locator = ax.get_axes_locator() - if axes_or_locator is None: - axes_or_locator = ax - - if hasattr(axes_or_locator, "get_subplotspec"): - subplotspec = axes_or_locator.get_subplotspec() - subplotspec = subplotspec.get_topmost_subplotspec() - gs = subplotspec.get_gridspec() - if grid_spec is not None: - if gs != grid_spec: - subplotspec = None - elif gs.locally_modified_subplot_params(): - subplotspec = None - else: - subplotspec = None - - subplotspec_list.append(subplotspec) - - return subplotspec_list - - -def get_tight_layout_figure(fig, axes_list, subplotspec_list, renderer, - pad=1.08, h_pad=None, w_pad=None, rect=None): - """ - Return subplot parameters for tight-layouted-figure with specified padding. - - Parameters - ---------- - fig : Figure - axes_list : list of Axes - subplotspec_list : list of `.SubplotSpec` - The subplotspecs of each axes. - renderer : renderer - pad : float - Padding between the figure edge and the edges of subplots, as a - fraction of the font size. - h_pad, w_pad : float - Padding (height/width) between edges of adjacent subplots. Defaults to - *pad*. - rect : Tuple[float, float, float, float], optional - (left, bottom, right, top) rectangle in normalized figure coordinates - that the whole subplots area (including labels) will fit into. - Defaults to using the entire figure. - - Returns - ------- - subplotspec or None - subplotspec kwargs to be passed to `.Figure.subplots_adjust` or - None if tight_layout could not be accomplished. - - """ - - subplot_list = [] - nrows_list = [] - ncols_list = [] - ax_bbox_list = [] - - # Multiple axes can share same subplot_interface (e.g., axes_grid1); thus - # we need to join them together. - subplot_dict = {} - - subplotspec_list2 = [] - - for ax, subplotspec in zip(axes_list, subplotspec_list): - if subplotspec is None: - continue - - subplots = subplot_dict.setdefault(subplotspec, []) - - if not subplots: - myrows, mycols, _, _ = subplotspec.get_geometry() - nrows_list.append(myrows) - ncols_list.append(mycols) - subplotspec_list2.append(subplotspec) - subplot_list.append(subplots) - ax_bbox_list.append(subplotspec.get_position(fig)) - - subplots.append(ax) - - if len(nrows_list) == 0 or len(ncols_list) == 0: - return {} - - max_nrows = max(nrows_list) - max_ncols = max(ncols_list) - - num1num2_list = [] - for subplotspec in subplotspec_list2: - rows, cols, num1, num2 = subplotspec.get_geometry() - div_row, mod_row = divmod(max_nrows, rows) - div_col, mod_col = divmod(max_ncols, cols) - if mod_row != 0: - cbook._warn_external('tight_layout not applied: number of rows ' - 'in subplot specifications must be ' - 'multiples of one another.') - return {} - if mod_col != 0: - cbook._warn_external('tight_layout not applied: number of ' - 'columns in subplot specifications must be ' - 'multiples of one another.') - return {} - - rowNum1, colNum1 = divmod(num1, cols) - if num2 is None: - rowNum2, colNum2 = rowNum1, colNum1 - else: - rowNum2, colNum2 = divmod(num2, cols) - - num1num2_list.append((rowNum1 * div_row * max_ncols + - colNum1 * div_col, - ((rowNum2 + 1) * div_row - 1) * max_ncols + - (colNum2 + 1) * div_col - 1)) - - kwargs = auto_adjust_subplotpars(fig, renderer, - nrows_ncols=(max_nrows, max_ncols), - num1num2_list=num1num2_list, - subplot_list=subplot_list, - ax_bbox_list=ax_bbox_list, - pad=pad, h_pad=h_pad, w_pad=w_pad) - - # kwargs can be none if tight_layout fails... - if rect is not None and kwargs is not None: - # if rect is given, the whole subplots area (including - # labels) will fit into the rect instead of the - # figure. Note that the rect argument of - # *auto_adjust_subplotpars* specify the area that will be - # covered by the total area of axes.bbox. Thus we call - # auto_adjust_subplotpars twice, where the second run - # with adjusted rect parameters. - - left, bottom, right, top = rect - if left is not None: - left += kwargs["left"] - if bottom is not None: - bottom += kwargs["bottom"] - if right is not None: - right -= (1 - kwargs["right"]) - if top is not None: - top -= (1 - kwargs["top"]) - - kwargs = auto_adjust_subplotpars(fig, renderer, - nrows_ncols=(max_nrows, max_ncols), - num1num2_list=num1num2_list, - subplot_list=subplot_list, - ax_bbox_list=ax_bbox_list, - pad=pad, h_pad=h_pad, w_pad=w_pad, - rect=(left, bottom, right, top)) - - return kwargs diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index ac88e16b7e52..7228f05bcf9e 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -1,7 +1,6 @@ """ -Matplotlib includes a framework for arbitrary geometric -transformations that is used determine the final position of all -elements drawn on the canvas. +Matplotlib includes a framework for arbitrary geometric transformations that is used to +determine the final position of all elements drawn on the canvas. Transforms are composed into trees of `TransformNode` objects whose actual value depends on their children. When the contents of @@ -11,13 +10,13 @@ unnecessary recomputations of transforms, and contributes to better interactive performance. -For example, here is a graph of the transform tree used to plot data -to the graph: +For example, here is a graph of the transform tree used to plot data to the figure: -.. image:: ../_static/transforms.png +.. graphviz:: /api/transforms.dot + :alt: Diagram of transform tree from data to figure coordinates. The framework can be used for both affine and non-affine -transformations. However, for speed, we want use the backend +transformations. However, for speed, we want to use the backend renderers to perform affine transformations whenever possible. Therefore, it is possible to perform just the affine or non-affine part of a transformation on a set of data. The affine is always @@ -27,13 +26,18 @@ The backends are not expected to handle non-affine transformations themselves. + +See the tutorial :ref:`transforms_tutorial` for examples +of how to use transforms. """ # Note: There are a number of places in the code where we use `np.min` or # `np.minimum` instead of the builtin `min`, and likewise for `max`. This is # done so that `nan`s are propagated, instead of being silently dropped. +import copy import functools +import itertools import textwrap import weakref import math @@ -41,9 +45,8 @@ import numpy as np from numpy.linalg import inv -from matplotlib import cbook -from matplotlib._path import ( - affine_transform, count_bboxes_overlapping_bbox, update_path_extents) +from matplotlib import _api +from matplotlib._path import affine_transform, count_bboxes_overlapping_bbox from .path import Path DEBUG = False @@ -84,19 +87,17 @@ class TransformNode: classes that are not really transforms, such as bounding boxes, since some transforms depend on bounding boxes to compute their values. """ - _gid = 0 # Invalidation may affect only the affine part. If the # invalidation was "affine-only", the _invalid member is set to # INVALID_AFFINE_ONLY - INVALID_NON_AFFINE = 1 - INVALID_AFFINE = 2 - INVALID = INVALID_NON_AFFINE | INVALID_AFFINE + + # Possible values for the _invalid attribute. + _VALID, _INVALID_AFFINE_ONLY, _INVALID_FULL = range(3) # Some metadata about the transform, used to determine whether an # invalidation is affine-only is_affine = False - is_bbox = False pass_through = False """ @@ -114,10 +115,8 @@ def __init__(self, shorthand_name=None): ``str(transform)`` when DEBUG=True. """ self._parents = {} - - # TransformNodes start out as invalid until their values are - # computed for the first time. - self._invalid = 1 + # Initially invalid, until first computation. + self._invalid = self._INVALID_FULL self._shorthand_name = shorthand_name or '' if DEBUG: @@ -139,48 +138,41 @@ def __setstate__(self, data_dict): k: weakref.ref(v, lambda _, pop=self._parents.pop, k=k: pop(k)) for k, v in self._parents.items() if v is not None} - def __copy__(self, *args): - raise NotImplementedError( - "TransformNode instances can not be copied. " - "Consider using frozen() instead.") - __deepcopy__ = __copy__ + def __copy__(self): + other = copy.copy(super()) + # If `c = a + b; a1 = copy(a)`, then modifications to `a1` do not + # propagate back to `c`, i.e. we need to clear the parents of `a1`. + other._parents = {} + # If `c = a + b; c1 = copy(c)`, then modifications to `a` also need to + # be propagated to `c1`. + for key, val in vars(self).items(): + if isinstance(val, TransformNode) and id(self) in val._parents: + other.set_children(val) # val == getattr(other, key) + return other def invalidate(self): """ Invalidate this `TransformNode` and triggers an invalidation of its ancestors. Should be called any time the transform changes. """ - value = self.INVALID - if self.is_affine: - value = self.INVALID_AFFINE - return self._invalidate_internal(value, invalidating_node=self) + return self._invalidate_internal( + level=self._INVALID_AFFINE_ONLY if self.is_affine else self._INVALID_FULL, + invalidating_node=self) - def _invalidate_internal(self, value, invalidating_node): + def _invalidate_internal(self, level, invalidating_node): """ Called by :meth:`invalidate` and subsequently ascends the transform stack calling each TransformNode's _invalidate_internal method. """ - # determine if this call will be an extension to the invalidation - # status. If not, then a shortcut means that we needn't invoke an - # invalidation up the transform stack as it will already have been - # invalidated. - - # N.B This makes the invalidation sticky, once a transform has been - # invalidated as NON_AFFINE, then it will always be invalidated as - # NON_AFFINE even when triggered with a AFFINE_ONLY invalidation. - # In most cases this is not a problem (i.e. for interactive panning and - # zooming) and the only side effect will be on performance. - status_changed = self._invalid < value - - if self.pass_through or status_changed: - self._invalid = value - - for parent in list(self._parents.values()): - # Dereference the weak reference - parent = parent() - if parent is not None: - parent._invalidate_internal( - value=value, invalidating_node=self) + # If we are already more invalid than the currently propagated invalidation, + # then we don't need to do anything. + if level <= self._invalid and not self.pass_through: + return + self._invalid = level + for parent in list(self._parents.values()): + parent = parent() # Dereference the weak reference. + if parent is not None: + parent._invalidate_internal(level=level, invalidating_node=self) def set_children(self, *children): """ @@ -192,13 +184,14 @@ def set_children(self, *children): # Parents are stored as weak references, so that if the # parents are destroyed, references from the children won't # keep them alive. + id_self = id(self) for child in children: # Use weak references so this dictionary won't keep obsolete nodes # alive; the callback deletes the dictionary entry. This is a # performance improvement over using WeakValueDictionary. ref = weakref.ref( - self, lambda _, pop=child._parents.pop, k=id(self): pop(k)) - child._parents[id(self)] = ref + self, lambda _, pop=child._parents.pop, k=id_self: pop(k)) + child._parents[id_self] = ref def frozen(self): """ @@ -222,17 +215,16 @@ class BboxBase(TransformNode): and height, but these are not stored explicitly. """ - is_bbox = True is_affine = True if DEBUG: @staticmethod def _check(points): if isinstance(points, np.ma.MaskedArray): - cbook._warn_external("Bbox bounds are a masked array.") + _api.warn_external("Bbox bounds are a masked array.") points = np.asarray(points) if any((points[1, :] - points[0, :]) == 0): - cbook._warn_external("Singular Bbox.") + _api.warn_external("Singular Bbox.") def frozen(self): return Bbox(self.get_points().copy()) @@ -241,18 +233,13 @@ def frozen(self): def __array__(self, *args, **kwargs): return self.get_points() - @cbook.deprecated("3.2") - def is_unit(self): - """Return whether this is the unit box (from (0, 0) to (1, 1)).""" - return self.get_points().tolist() == [[0., 0.], [1., 1.]] - @property def x0(self): """ The first of the pair of *x* coordinates that define the bounding box. This is not guaranteed to be less than :attr:`x1` (for that, use - :attr:`xmin`). + :attr:`~BboxBase.xmin`). """ return self.get_points()[0, 0] @@ -262,7 +249,7 @@ def y0(self): The first of the pair of *y* coordinates that define the bounding box. This is not guaranteed to be less than :attr:`y1` (for that, use - :attr:`ymin`). + :attr:`~BboxBase.ymin`). """ return self.get_points()[0, 1] @@ -272,7 +259,7 @@ def x1(self): The second of the pair of *x* coordinates that define the bounding box. This is not guaranteed to be greater than :attr:`x0` (for that, use - :attr:`xmax`). + :attr:`~BboxBase.xmax`). """ return self.get_points()[1, 0] @@ -282,7 +269,7 @@ def y1(self): The second of the pair of *y* coordinates that define the bounding box. This is not guaranteed to be greater than :attr:`y0` (for that, use - :attr:`ymax`). + :attr:`~BboxBase.ymax`). """ return self.get_points()[1, 1] @@ -292,7 +279,7 @@ def p0(self): The first pair of (*x*, *y*) coordinates that define the bounding box. This is not guaranteed to be the bottom-left corner (for that, use - :attr:`min`). + :attr:`~BboxBase.min`). """ return self.get_points()[0] @@ -302,7 +289,7 @@ def p1(self): The second pair of (*x*, *y*) coordinates that define the bounding box. This is not guaranteed to be the top-right corner (for that, use - :attr:`max`). + :attr:`~BboxBase.max`). """ return self.get_points()[1] @@ -374,7 +361,10 @@ def size(self): @property def bounds(self): - """Return (:attr:`x0`, :attr:`y0`, :attr:`width`, :attr:`height`).""" + """ + Return (:attr:`x0`, :attr:`y0`, :attr:`~BboxBase.width`, + :attr:`~BboxBase.height`). + """ (x0, y0), (x1, y1) = self.get_points() return (x0, y0, x1 - x0, y1 - y0) @@ -476,14 +466,6 @@ def transformed(self, transform): [pts[0], [pts[0, 0], pts[1, 1]], [pts[1, 0], pts[0, 1]]])) return Bbox([ll, [lr[0], ul[1]]]) - @cbook.deprecated("3.3", alternative="transformed(transform.inverted())") - def inverse_transformed(self, transform): - """ - Construct a `Bbox` by statically transforming this one by the inverse - of *transform*. - """ - return self.transformed(transform.inverted()) - coefs = {'C': (0.5, 0.5), 'SW': (0, 0), 'S': (0.5, 0), @@ -494,37 +476,26 @@ def inverse_transformed(self, transform): 'NW': (0, 1.0), 'W': (0, 0.5)} - def anchored(self, c, container=None): + def anchored(self, c, container): """ - Return a copy of the `Bbox` shifted to position *c* within *container*. + Return a copy of the `Bbox` anchored to *c* within *container*. Parameters ---------- - c : (float, float) or str - May be either: - - * A sequence (*cx*, *cy*) where *cx* and *cy* range from 0 - to 1, where 0 is left or bottom and 1 is right or top + c : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...} + Either an (*x*, *y*) pair of relative coordinates (0 is left or + bottom, 1 is right or top), 'C' (center), or a cardinal direction + ('SW', southwest, is bottom left, etc.). + container : `Bbox` + The box within which the `Bbox` is positioned. - * a string: - - 'C' for centered - - 'S' for bottom-center - - 'SE' for bottom-left - - 'E' for left - - etc. - - container : `Bbox`, optional - The box within which the `Bbox` is positioned; it defaults - to the initial `Bbox`. + See Also + -------- + .Axes.set_anchor """ - if container is None: - container = self l, b, w, h = container.bounds - if isinstance(c, str): - cx, cy = self.coefs[c] - else: - cx, cy = c L, B, W, H = self.bounds + cx, cy = self.coefs[c] if isinstance(c, str) else c return Bbox(self._points + [(l + cx * (w - W)) - L, (b + cy * (h - H)) - B]) @@ -573,7 +544,7 @@ def splitx(self, *args): x0, y0, x1, y1 = self.extents w = x1 - x0 return [Bbox([[x0 + xf0 * w, y0], [x0 + xf1 * w, y1]]) - for xf0, xf1 in zip(xf[:-1], xf[1:])] + for xf0, xf1 in itertools.pairwise(xf)] def splity(self, *args): """ @@ -584,7 +555,7 @@ def splity(self, *args): x0, y0, x1, y1 = self.extents h = y1 - y0 return [Bbox([[x0, y0 + yf0 * h], [x1, y0 + yf1 * h]]) - for yf0, yf1 in zip(yf[:-1], yf[1:])] + for yf0, yf1 in itertools.pairwise(yf)] def count_contains(self, vertices): """ @@ -593,7 +564,7 @@ def count_contains(self, vertices): Parameters ---------- - vertices : Nx2 Numpy array. + vertices : (N, 2) array """ if len(vertices) == 0: return 0 @@ -625,10 +596,22 @@ def expanded(self, sw, sh): a = np.array([[-deltaw, -deltah], [deltaw, deltah]]) return Bbox(self._points + a) - def padded(self, p): - """Construct a `Bbox` by padding this one on all four sides by *p*.""" + def padded(self, w_pad, h_pad=None): + """ + Construct a `Bbox` by padding this one on all four sides. + + Parameters + ---------- + w_pad : float + Width pad + h_pad : float, optional + Height pad. Defaults to *w_pad*. + + """ points = self.get_points() - return Bbox(points + [[-p, -p], [p, p]]) + if h_pad is None: + h_pad = w_pad + return Bbox(points + [[-w_pad, -h_pad], [w_pad, h_pad]]) def translated(self, tx, ty): """Construct a `Bbox` by translating this one by *tx* and *ty*.""" @@ -660,13 +643,10 @@ def union(bboxes): """Return a `Bbox` that contains all of the given *bboxes*.""" if not len(bboxes): raise ValueError("'bboxes' cannot be empty") - # needed for 1.14.4 < numpy_version < 1.16 - # can remove once we are at numpy >= 1.16 - with np.errstate(invalid='ignore'): - x0 = np.min([bbox.xmin for bbox in bboxes]) - x1 = np.max([bbox.xmax for bbox in bboxes]) - y0 = np.min([bbox.ymin for bbox in bboxes]) - y1 = np.max([bbox.ymax for bbox in bboxes]) + x0 = np.min([bbox.xmin for bbox in bboxes]) + x1 = np.max([bbox.xmax for bbox in bboxes]) + y0 = np.min([bbox.ymin for bbox in bboxes]) + y1 = np.max([bbox.ymax for bbox in bboxes]) return Bbox([[x0, y0], [x1, y1]]) @staticmethod @@ -682,6 +662,9 @@ def intersection(bbox1, bbox2): return Bbox([[x0, y0], [x1, y1]]) if x0 <= x1 and y0 <= y1 else None +_default_minpos = np.array([np.inf, np.inf]) + + class Bbox(BboxBase): """ A mutable bounding box. @@ -767,8 +750,8 @@ def __init__(self, points, **kwargs): """ Parameters ---------- - points : ndarray - A 2x2 numpy array of the form ``[[x0, y0], [x1, y1]]``. + points : `~numpy.ndarray` + A (2, 2) array of the form ``[[x0, y0], [x1, y1]]``. """ super().__init__(**kwargs) points = np.asarray(points, float) @@ -776,7 +759,7 @@ def __init__(self, points, **kwargs): raise ValueError('Bbox points must be of the form ' '"[[x0, y0], [x1, y1]]".') self._points = points - self._minpos = np.array([np.inf, np.inf]) + self._minpos = _default_minpos.copy() self._ignore = True # it is helpful in some contexts to know if the bbox is a # default or has been mutated; we store the orig points to @@ -793,6 +776,12 @@ def invalidate(self): self._check(self._points) super().invalidate() + def frozen(self): + # docstring inherited + frozen_bbox = super().frozen() + frozen_bbox._minpos = self.minpos.copy() + return frozen_bbox + @staticmethod def unit(): """Create a new unit `Bbox` from (0, 0) to (1, 1).""" @@ -813,13 +802,25 @@ def from_bounds(x0, y0, width, height): return Bbox.from_extents(x0, y0, x0 + width, y0 + height) @staticmethod - def from_extents(*args): + def from_extents(*args, minpos=None): """ Create a new Bbox from *left*, *bottom*, *right* and *top*. The *y*-axis increases upwards. - """ - return Bbox(np.reshape(args, (2, 2))) + + Parameters + ---------- + left, bottom, right, top : float + The four extents of the bounding box. + minpos : float or None + If this is supplied, the Bbox will have a minimum positive value + set. This is useful when dealing with logarithmic scales and other + scales where negative bounds result in floating point errors. + """ + bbox = Bbox(np.reshape(args, (2, 2))) + if minpos is not None: + bbox._minpos[:] = minpos + return bbox def __format__(self, fmt): return ( @@ -838,11 +839,10 @@ def ignore(self, value): by subsequent calls to :meth:`update_from_data_xy`. value : bool - - When ``True``, subsequent calls to :meth:`update_from_data_xy` - will ignore the existing bounds of the `Bbox`. - - - When ``False``, subsequent calls to :meth:`update_from_data_xy` - will include the existing bounds of the `Bbox`. + - When ``True``, subsequent calls to `update_from_data_xy` will + ignore the existing bounds of the `Bbox`. + - When ``False``, subsequent calls to `update_from_data_xy` will + include the existing bounds of the `Bbox`. """ self._ignore = value @@ -855,25 +855,41 @@ def update_from_path(self, path, ignore=None, updatex=True, updatey=True): Parameters ---------- path : `~matplotlib.path.Path` - ignore : bool, optional - - when ``True``, ignore the existing bounds of the `Bbox`. - - when ``False``, include the existing bounds of the `Bbox`. - - when ``None``, use the last value passed to :meth:`ignore`. - + - When ``True``, ignore the existing bounds of the `Bbox`. + - When ``False``, include the existing bounds of the `Bbox`. + - When ``None``, use the last value passed to :meth:`ignore`. updatex, updatey : bool, default: True When ``True``, update the x/y values. """ if ignore is None: ignore = self._ignore - if path.vertices.size == 0: + if path.vertices.size == 0 or not (updatex or updatey): return - points, minpos, changed = update_path_extents( - path, None, self._points, self._minpos, ignore) - - if changed: + if ignore: + points = np.array([[np.inf, np.inf], [-np.inf, -np.inf]]) + minpos = np.array([np.inf, np.inf]) + else: + points = self._points.copy() + minpos = self._minpos.copy() + + valid_points = (np.isfinite(path.vertices[..., 0]) + & np.isfinite(path.vertices[..., 1])) + + if updatex: + x = path.vertices[..., 0][valid_points] + points[0, 0] = min(points[0, 0], np.min(x, initial=np.inf)) + points[1, 0] = max(points[1, 0], np.max(x, initial=-np.inf)) + minpos[0] = min(minpos[0], np.min(x[x > 0], initial=np.inf)) + if updatey: + y = path.vertices[..., 1][valid_points] + points[0, 1] = min(points[0, 1], np.min(y, initial=np.inf)) + points[1, 1] = max(points[1, 1], np.max(y, initial=-np.inf)) + minpos[1] = min(minpos[1], np.min(y[y > 0], initial=np.inf)) + + if np.any(points != self._points) or np.any(minpos != self._minpos): self.invalidate() if updatex: self._points[:, 0] = points[:, 0] @@ -882,24 +898,63 @@ def update_from_path(self, path, ignore=None, updatex=True, updatey=True): self._points[:, 1] = points[:, 1] self._minpos[1] = minpos[1] - def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True): + def update_from_data_x(self, x, ignore=None): """ - Update the bounds of the `Bbox` based on the passed in - data. After updating, the bounds will have positive *width* - and *height*; *x0* and *y0* will be the minimal values. + Update the x-bounds of the `Bbox` based on the passed in data. After + updating, the bounds will have positive *width*, and *x0* will be the + minimal value. Parameters ---------- - xy : ndarray - A numpy array of 2D points. - + x : `~numpy.ndarray` + Array of x-values. ignore : bool, optional - When ``True``, ignore the existing bounds of the `Bbox`. - When ``False``, include the existing bounds of the `Bbox`. - When ``None``, use the last value passed to :meth:`ignore`. + """ + x = np.ravel(x) + # The y-component in np.array([x, *y*]).T is not used. We simply pass + # x again to not spend extra time on creating an array of unused data + self.update_from_data_xy(np.array([x, x]).T, ignore=ignore, updatey=False) + + def update_from_data_y(self, y, ignore=None): + """ + Update the y-bounds of the `Bbox` based on the passed in data. After + updating, the bounds will have positive *height*, and *y0* will be the + minimal value. + + Parameters + ---------- + y : `~numpy.ndarray` + Array of y-values. + ignore : bool, optional + - When ``True``, ignore the existing bounds of the `Bbox`. + - When ``False``, include the existing bounds of the `Bbox`. + - When ``None``, use the last value passed to :meth:`ignore`. + """ + y = np.ravel(y) + # The x-component in np.array([*x*, y]).T is not used. We simply pass + # y again to not spend extra time on creating an array of unused data + self.update_from_data_xy(np.array([y, y]).T, ignore=ignore, updatex=False) + + def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True): + """ + Update the `Bbox` bounds based on the passed in *xy* coordinates. + + After updating, the bounds will have positive *width* and *height*; + *x0* and *y0* will be the minimal values. + Parameters + ---------- + xy : (N, 2) array-like + The (x, y) coordinates. + ignore : bool, optional + - When ``True``, ignore the existing bounds of the `Bbox`. + - When ``False``, include the existing bounds of the `Bbox`. + - When ``None``, use the last value passed to :meth:`ignore`. updatex, updatey : bool, default: True - When ``True``, update the x/y values. + When ``True``, update the x/y values. """ if len(xy) == 0: return @@ -958,29 +1013,62 @@ def bounds(self, bounds): @property def minpos(self): + """ + The minimum positive value in both directions within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum extent instead of *p0*. + """ return self._minpos + @minpos.setter + def minpos(self, val): + self._minpos[:] = val + @property def minposx(self): + """ + The minimum positive value in the *x*-direction within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum *x*-extent instead of *x0*. + """ return self._minpos[0] + @minposx.setter + def minposx(self, val): + self._minpos[0] = val + @property def minposy(self): + """ + The minimum positive value in the *y*-direction within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum *y*-extent instead of *y0*. + """ return self._minpos[1] + @minposy.setter + def minposy(self, val): + self._minpos[1] = val + def get_points(self): """ - Get the points of the bounding box directly as a numpy array - of the form: ``[[x0, y0], [x1, y1]]``. + Get the points of the bounding box as an array of the form + ``[[x0, y0], [x1, y1]]``. """ self._invalid = 0 return self._points def set_points(self, points): """ - Set the points of the bounding box directly from a numpy array - of the form: ``[[x0, y0], [x1, y1]]``. No error checking is - performed, as this method is mainly for internal use. + Set the points of the bounding box directly from an array of the form + ``[[x0, y0], [x1, y1]]``. No error checking is performed, as this + method is mainly for internal use. """ if np.any(self._points != points): self._points = points @@ -1023,9 +1111,8 @@ def __init__(self, bbox, transform, **kwargs): bbox : `Bbox` transform : `Transform` """ - if not bbox.is_bbox: - raise ValueError("'bbox' is not a bbox") - cbook._check_isinstance(Transform, transform=transform) + _api.check_isinstance(BboxBase, bbox=bbox) + _api.check_isinstance(Transform, transform=transform) if transform.input_dims != 2 or transform.output_dims != 2: raise ValueError( "The input and output dimensions of 'transform' must be 2") @@ -1076,6 +1163,14 @@ def get_points(self): self._check(points) return points + def contains(self, x, y): + # Docstring inherited. + return self._bbox.contains(*self._transform.inverted().transform((x, y))) + + def fully_contains(self, x, y): + # Docstring inherited. + return self._bbox.fully_contains(*self._transform.inverted().transform((x, y))) + class LockableBbox(BboxBase): """ @@ -1104,9 +1199,7 @@ def __init__(self, bbox, x0=None, y0=None, x1=None, y1=None, **kwargs): The locked value for y1, or None to leave unlocked. """ - if not bbox.is_bbox: - raise ValueError("'bbox' is not a bbox") - + _api.check_isinstance(BboxBase, bbox=bbox) super().__init__(**kwargs) self._bbox = bbox self.set_children(bbox) @@ -1218,7 +1311,7 @@ class Transform(TransformNode): The following attributes may be overridden if the default is unsuitable: - - :attr:`is_separable` (defaults to True for 1d -> 1d transforms, False + - :attr:`is_separable` (defaults to True for 1D -> 1D transforms, False otherwise) - :attr:`has_inverse` (defaults to True if :meth:`inverted` is overridden, False otherwise) @@ -1330,7 +1423,7 @@ def contains_branch_seperately(self, other_transform): each separate dimension. A common use for this method is to identify if a transform is a blended - transform containing an axes' data transform. e.g.:: + transform containing an Axes' data transform. e.g.:: x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData) @@ -1340,7 +1433,7 @@ def contains_branch_seperately(self, other_transform): 'transforms with 2 output dimensions') # for a non-blended transform each separate dimension is the same, so # just return the appropriate shape. - return [self.contains_branch(other_transform)] * 2 + return (self.contains_branch(other_transform), ) * 2 def __sub__(self, other): """ @@ -1402,15 +1495,15 @@ def transform(self, values): Parameters ---------- - values : array - The input values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`input_dims`). + values : array-like + The input values as an array of length :attr:`~Transform.input_dims` or + shape (N, :attr:`~Transform.input_dims`). Returns ------- array - The output values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`output_dims`), depending on the input. + The output values as an array of length :attr:`~Transform.output_dims` or + shape (N, :attr:`~Transform.output_dims`), depending on the input. """ # Ensure that values is a 2d array (but remember whether # we started with a 1d or 2d array). @@ -1430,8 +1523,8 @@ def transform(self, values): elif ndim == 2: return res raise ValueError( - "Input values must have shape (N x {dims}) " - "or ({dims}).".format(dims=self.input_dims)) + "Input values must have shape (N, {dims}) or ({dims},)" + .format(dims=self.input_dims)) def transform_affine(self, values): """ @@ -1448,14 +1541,14 @@ def transform_affine(self, values): Parameters ---------- values : array - The input values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`input_dims`). + The input values as an array of length :attr:`~Transform.input_dims` or + shape (N, :attr:`~Transform.input_dims`). Returns ------- array - The output values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`output_dims`), depending on the input. + The output values as an array of length :attr:`~Transform.output_dims` or + shape (N, :attr:`~Transform.output_dims`), depending on the input. """ return self.get_affine().transform(values) @@ -1473,14 +1566,17 @@ def transform_non_affine(self, values): Parameters ---------- values : array - The input values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`input_dims`). + The input values as an array of length + :attr:`~matplotlib.transforms.Transform.input_dims` or + shape (N, :attr:`~matplotlib.transforms.Transform.input_dims`). Returns ------- array - The output values as NumPy array of length :attr:`input_dims` or - shape (N x :attr:`output_dims`), depending on the input. + The output values as an array of length + :attr:`~matplotlib.transforms.Transform.output_dims` or shape + (N, :attr:`~matplotlib.transforms.Transform.output_dims`), + depending on the input. """ return values @@ -1575,11 +1671,10 @@ def transform_angles(self, angles, pts, radians=False, pushoff=1e-5): raise NotImplementedError('Only defined in 2D') angles = np.asarray(angles) pts = np.asarray(pts) - if angles.ndim != 1 or angles.shape[0] != pts.shape[0]: - raise ValueError("'angles' must be a column vector and have same " - "number of rows as 'pts'") - if pts.shape[1] != 2: - raise ValueError("'pts' must be array with 2 columns for x, y") + _api.check_shape((None, 2), pts=pts) + _api.check_shape((None,), angles=angles) + if len(angles) != len(pts): + raise ValueError("There must be as many 'angles' as 'pts'") # Convert to radians if desired if not radians: angles = np.deg2rad(angles) @@ -1631,16 +1726,9 @@ def __init__(self, child): *child*: A `Transform` instance. This child may later be replaced with :meth:`set`. """ - cbook._check_isinstance(Transform, child=child) - self._init(child) - self.set_children(child) - - def _init(self, child): - Transform.__init__(self) - self.input_dims = child.input_dims - self.output_dims = child.output_dims - self._set(child) - self._invalid = 0 + _api.check_isinstance(Transform, child=child) + super().__init__() + self.set(child) def __eq__(self, other): return self._child.__eq__(other) @@ -1651,8 +1739,25 @@ def frozen(self): # docstring inherited return self._child.frozen() - def _set(self, child): + def set(self, child): + """ + Replace the current child of this transform with another one. + + The new child must have the same number of input and output + dimensions as the current child. + """ + if hasattr(self, "_child"): # Absent during init. + self.invalidate() + new_dims = (child.input_dims, child.output_dims) + old_dims = (self._child.input_dims, self._child.output_dims) + if new_dims != old_dims: + raise ValueError( + f"The input and output dims of the new child {new_dims} " + f"do not match those of current child {old_dims}") + self._child._parents.pop(id(self), None) + self._child = child + self.set_children(child) self.transform = child.transform self.transform_affine = child.transform_affine @@ -1663,31 +1768,16 @@ def _set(self, child): self.get_affine = child.get_affine self.inverted = child.inverted self.get_matrix = child.get_matrix - # note we do not wrap other properties here since the transform's # child can be changed with WrappedTransform.set and so checking # is_affine and other such properties may be dangerous. - def set(self, child): - """ - Replace the current child of this transform with another one. - - The new child must have the same number of input and output - dimensions as the current child. - """ - if (child.input_dims != self.input_dims or - child.output_dims != self.output_dims): - raise ValueError( - "The new child must have the same number of input and output " - "dimensions as the current child") - - self.set_children(child) - self._set(child) - self._invalid = 0 self.invalidate() self._invalid = 0 + input_dims = property(lambda self: self._child.input_dims) + output_dims = property(lambda self: self._child.output_dims) is_affine = property(lambda self: self._child.is_affine) is_separable = property(lambda self: self._child.is_separable) has_inverse = property(lambda self: self._child.has_inverse) @@ -1709,7 +1799,7 @@ def __array__(self, *args, **kwargs): def __eq__(self, other): if getattr(other, "is_affine", False) and hasattr(other, "get_matrix"): - return np.all(self.get_matrix() == other.get_matrix()) + return (self.get_matrix() == other.get_matrix()).all() return NotImplemented def transform(self, values): @@ -1721,9 +1811,9 @@ def transform_affine(self, values): raise NotImplementedError('Affine subclasses should override this ' 'method.') - def transform_non_affine(self, points): + def transform_non_affine(self, values): # docstring inherited - return points + return values def transform_path(self, path): # docstring inherited @@ -1757,7 +1847,7 @@ class Affine2DBase(AffineBase): affine transformation, use `Affine2D`. Subclasses of this class will generally only need to override a - constructor and :meth:`get_matrix` that generates a custom 3x3 matrix. + constructor and `~.Transform.get_matrix` that generates a custom 3x3 matrix. """ input_dims = 2 output_dims = 2 @@ -1778,39 +1868,26 @@ def to_values(self): mtx = self.get_matrix() return tuple(mtx[:2].swapaxes(0, 1).flat) - @staticmethod - @cbook.deprecated( - "3.2", alternative="Affine2D.from_values(...).get_matrix()") - def matrix_from_values(a, b, c, d, e, f): - """ - Create a new transformation matrix as a 3x3 numpy array of the form:: - - a c e - b d f - 0 0 1 - """ - return np.array([[a, c, e], [b, d, f], [0.0, 0.0, 1.0]], float) - - def transform_affine(self, points): + def transform_affine(self, values): mtx = self.get_matrix() - if isinstance(points, np.ma.MaskedArray): - tpoints = affine_transform(points.data, mtx) - return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(points)) - return affine_transform(points, mtx) + if isinstance(values, np.ma.MaskedArray): + tpoints = affine_transform(values.data, mtx) + return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(values)) + return affine_transform(values, mtx) if DEBUG: _transform_affine = transform_affine - def transform_affine(self, points): + def transform_affine(self, values): # docstring inherited # The major speed trap here is just converting to the # points to an array in the first place. If we can use # more arrays upstream, that should help here. - if not isinstance(points, (np.ma.MaskedArray, np.ndarray)): - cbook._warn_external( - f'A non-numpy array of type {type(points)} was passed in ' + if not isinstance(values, np.ndarray): + _api.warn_external( + f'A non-numpy array of type {type(values)} was passed in ' f'for transformation, which results in poor performance.') - return self._transform_affine(points) + return self._transform_affine(values) def inverted(self): # docstring inherited @@ -1842,11 +1919,18 @@ def __init__(self, matrix=None, **kwargs): super().__init__(**kwargs) if matrix is None: # A bit faster than np.identity(3). - matrix = IdentityTransform._mtx.copy() + matrix = IdentityTransform._mtx self._mtx = matrix.copy() self._invalid = 0 - __str__ = _make_str_method("_mtx") + _base_str = _make_str_method("_mtx") + + def __str__(self): + return (self._base_str() + if (self._mtx != np.diag(np.diag(self._mtx))).any() + else f"Affine2D().scale({self._mtx[0, 0]}, {self._mtx[1, 1]})" + if self._mtx[0, 0] != self._mtx[1, 1] + else f"Affine2D().scale({self._mtx[0, 0]})") @staticmethod def from_values(a, b, c, d, e, f): @@ -1864,7 +1948,7 @@ def from_values(a, b, c, d, e, f): def get_matrix(self): """ - Get the underlying transformation matrix as a 3x3 numpy array:: + Get the underlying transformation matrix as a 3x3 array:: a c e b d f @@ -1879,7 +1963,7 @@ def get_matrix(self): def set_matrix(self, mtx): """ - Set the underlying transformation matrix from a 3x3 numpy array:: + Set the underlying transformation matrix from a 3x3 array:: a c e b d f @@ -1895,20 +1979,10 @@ def set(self, other): Set this transformation from the frozen copy of another `Affine2DBase` object. """ - cbook._check_isinstance(Affine2DBase, other=other) + _api.check_isinstance(Affine2DBase, other=other) self._mtx = other.get_matrix() self.invalidate() - @staticmethod - def identity(): - """ - Return a new `Affine2D` object that is the identity transform. - - Unless this transform will be mutated later on, consider using - the faster `IdentityTransform` class instead. - """ - return Affine2D() - def clear(self): """ Reset the underlying matrix to the identity transform. @@ -1928,9 +2002,16 @@ def rotate(self, theta): """ a = math.cos(theta) b = math.sin(theta) - rotate_mtx = np.array([[a, -b, 0.0], [b, a, 0.0], [0.0, 0.0, 1.0]], - float) - self._mtx = np.dot(rotate_mtx, self._mtx) + mtx = self._mtx + # Operating and assigning one scalar at a time is much faster. + (xx, xy, x0), (yx, yy, y0), _ = mtx.tolist() + # mtx = [[a -b 0], [b a 0], [0 0 1]] * mtx + mtx[0, 0] = a * xx - b * yx + mtx[0, 1] = a * xy - b * yy + mtx[0, 2] = a * x0 - b * y0 + mtx[1, 0] = b * xx + a * yx + mtx[1, 1] = b * xy + a * yy + mtx[1, 2] = b * x0 + a * y0 self.invalidate() return self @@ -2013,11 +2094,18 @@ def skew(self, xShear, yShear): calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` and :meth:`scale`. """ - rotX = math.tan(xShear) - rotY = math.tan(yShear) - skew_mtx = np.array( - [[1.0, rotX, 0.0], [rotY, 1.0, 0.0], [0.0, 0.0, 1.0]], float) - self._mtx = np.dot(skew_mtx, self._mtx) + rx = math.tan(xShear) + ry = math.tan(yShear) + mtx = self._mtx + # Operating and assigning one scalar at a time is much faster. + (xx, xy, x0), (yx, yy, y0), _ = mtx.tolist() + # mtx = [[1 rx 0], [ry 1 0], [0 0 1]] * mtx + mtx[0, 0] += rx * yx + mtx[0, 1] += rx * yy + mtx[0, 2] += rx * y0 + mtx[1, 0] += ry * xx + mtx[1, 1] += ry * xy + mtx[1, 2] += ry * x0 self.invalidate() return self @@ -2052,17 +2140,17 @@ def get_matrix(self): # docstring inherited return self._mtx - def transform(self, points): + def transform(self, values): # docstring inherited - return np.asanyarray(points) + return np.asanyarray(values) - def transform_affine(self, points): + def transform_affine(self, values): # docstring inherited - return np.asanyarray(points) + return np.asanyarray(values) - def transform_non_affine(self, points): + def transform_non_affine(self, values): # docstring inherited - return np.asanyarray(points) + return np.asanyarray(values) def transform_path(self, path): # docstring inherited @@ -2148,26 +2236,26 @@ def frozen(self): # docstring inherited return blended_transform_factory(self._x.frozen(), self._y.frozen()) - def transform_non_affine(self, points): + def transform_non_affine(self, values): # docstring inherited if self._x.is_affine and self._y.is_affine: - return points + return values x = self._x y = self._y if x == y and x.input_dims == 2: - return x.transform_non_affine(points) + return x.transform_non_affine(values) if x.input_dims == 2: - x_points = x.transform_non_affine(points)[:, 0:1] + x_points = x.transform_non_affine(values)[:, 0:1] else: - x_points = x.transform_non_affine(points[:, 0]) + x_points = x.transform_non_affine(values[:, 0]) x_points = x_points.reshape((len(x_points), 1)) if y.input_dims == 2: - y_points = y.transform_non_affine(points)[:, 1:] + y_points = y.transform_non_affine(values)[:, 1:] else: - y_points = y.transform_non_affine(points[:, 1]) + y_points = y.transform_non_affine(values[:, 1]) y_points = y_points.reshape((len(y_points), 1)) if (isinstance(x_points, np.ma.MaskedArray) or @@ -2302,20 +2390,12 @@ def frozen(self): return frozen.frozen() return frozen - def _invalidate_internal(self, value, invalidating_node): - # In some cases for a composite transform, an invalidating call to - # AFFINE_ONLY needs to be extended to invalidate the NON_AFFINE part - # too. These cases are when the right hand transform is non-affine and - # either: - # (a) the left hand transform is non affine - # (b) it is the left hand node which has triggered the invalidation - if (value == Transform.INVALID_AFFINE and - not self._b.is_affine and - (not self._a.is_affine or invalidating_node is self._a)): - value = Transform.INVALID - - super()._invalidate_internal(value=value, - invalidating_node=invalidating_node) + def _invalidate_internal(self, level, invalidating_node): + # When the left child is invalidated at AFFINE_ONLY level and the right child is + # non-affine, the composite transform is FULLY invalidated. + if invalidating_node is self._a and not self._b.is_affine: + level = Transform._INVALID_FULL + super()._invalidate_internal(level, invalidating_node) def __eq__(self, other): if isinstance(other, (CompositeGenericTransform, CompositeAffine2D)): @@ -2330,6 +2410,15 @@ def _iter_break_from_left_to_right(self): for left, right in self._b._iter_break_from_left_to_right(): yield self._a + left, right + def contains_branch_seperately(self, other_transform): + # docstring inherited + if self.output_dims != 2: + raise ValueError('contains_branch_seperately only supports ' + 'transforms with 2 output dimensions') + if self == other_transform: + return (True, True) + return self._b.contains_branch_seperately(other_transform) + depth = property(lambda self: self._a.depth + self._b.depth) is_affine = property(lambda self: self._a.is_affine and self._b.is_affine) is_separable = property( @@ -2339,19 +2428,18 @@ def _iter_break_from_left_to_right(self): __str__ = _make_str_method("_a", "_b") - def transform_affine(self, points): + def transform_affine(self, values): # docstring inherited - return self.get_affine().transform(points) + return self.get_affine().transform(values) - def transform_non_affine(self, points): + def transform_non_affine(self, values): # docstring inherited if self._a.is_affine and self._b.is_affine: - return points + return values elif not self._a.is_affine and self._b.is_affine: - return self._a.transform_non_affine(points) + return self._a.transform_non_affine(values) else: - return self._b.transform_non_affine( - self._a.transform(points)) + return self._b.transform_non_affine(self._a.transform(values)) def transform_path_non_affine(self, path): # docstring inherited @@ -2469,8 +2557,7 @@ def __init__(self, boxin, boxout, **kwargs): Create a new `BboxTransform` that linearly transforms points from *boxin* to *boxout*. """ - if not boxin.is_bbox or not boxout.is_bbox: - raise ValueError("'boxin' and 'boxout' must be bbox") + _api.check_isinstance(BboxBase, boxin=boxin, boxout=boxout) super().__init__(**kwargs) self._boxin = boxin @@ -2491,9 +2578,9 @@ def get_matrix(self): if DEBUG and (x_scale == 0 or y_scale == 0): raise ValueError( "Transforming from or to a singular bounding box") - self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale+outl)], - [0.0 , y_scale, (-inb*y_scale+outb)], - [0.0 , 0.0 , 1.0 ]], + self._mtx = np.array([[x_scale, 0.0, -inl*x_scale+outl], + [ 0.0, y_scale, -inb*y_scale+outb], + [ 0.0, 0.0, 1.0]], float) self._inverted = None self._invalid = 0 @@ -2513,8 +2600,7 @@ def __init__(self, boxout, **kwargs): Create a new `BboxTransformTo` that linearly transforms points from the unit bounding box to *boxout*. """ - if not boxout.is_bbox: - raise ValueError("'boxout' must be bbox") + _api.check_isinstance(BboxBase, boxout=boxout) super().__init__(**kwargs) self._boxout = boxout @@ -2539,26 +2625,6 @@ def get_matrix(self): return self._mtx -class BboxTransformToMaxOnly(BboxTransformTo): - """ - `BboxTransformTo` is a transformation that linearly transforms points from - the unit bounding box to a given `Bbox` with a fixed upper left of (0, 0). - """ - def get_matrix(self): - # docstring inherited - if self._invalid: - xmax, ymax = self._boxout.max - if DEBUG and (xmax == 0 or ymax == 0): - raise ValueError("Transforming to a singular bounding box.") - self._mtx = np.array([[xmax, 0.0, 0.0], - [ 0.0, ymax, 0.0], - [ 0.0, 0.0, 1.0]], - float) - self._inverted = None - self._invalid = 0 - return self._mtx - - class BboxTransformFrom(Affine2DBase): """ `BboxTransformFrom` linearly transforms points from a given `Bbox` to the @@ -2567,8 +2633,7 @@ class BboxTransformFrom(Affine2DBase): is_separable = True def __init__(self, boxin, **kwargs): - if not boxin.is_bbox: - raise ValueError("'boxin' must be bbox") + _api.check_isinstance(BboxBase, boxin=boxin) super().__init__(**kwargs) self._boxin = boxin @@ -2586,9 +2651,9 @@ def get_matrix(self): raise ValueError("Transforming from a singular bounding box.") x_scale = 1.0 / inw y_scale = 1.0 / inh - self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale)], - [0.0 , y_scale, (-inb*y_scale)], - [0.0 , 0.0 , 1.0 ]], + self._mtx = np.array([[x_scale, 0.0, -inl*x_scale], + [ 0.0, y_scale, -inb*y_scale], + [ 0.0, 0.0, 1.0]], float) self._inverted = None self._invalid = 0 @@ -2621,6 +2686,25 @@ def get_matrix(self): return self._mtx +class _ScaledRotation(Affine2DBase): + """ + A transformation that applies rotation by *theta*, after transform by *trans_shift*. + """ + def __init__(self, theta, trans_shift): + super().__init__() + self._theta = theta + self._trans_shift = trans_shift + self._mtx = None + + def get_matrix(self): + if self._invalid: + transformed_coords = self._trans_shift.transform([[self._theta, 0]])[0] + adjusted_theta = transformed_coords[0] + rotation = Affine2D().rotate(adjusted_theta) + self._mtx = rotation.get_matrix() + return self._mtx + + class AffineDeltaTransform(Affine2DBase): r""" A transform wrapper for transforming displacements between pairs of points. @@ -2638,9 +2722,12 @@ class AffineDeltaTransform(Affine2DBase): This class is experimental as of 3.3, and the API may change. """ + pass_through = True + def __init__(self, transform, **kwargs): super().__init__(**kwargs) self._base_transform = transform + self.set_children(transform) __str__ = _make_str_method("_base_transform") @@ -2670,7 +2757,7 @@ def __init__(self, path, transform): path : `~.path.Path` transform : `Transform` """ - cbook._check_isinstance(Transform, transform=transform) + _api.check_isinstance(Transform, transform=transform) super().__init__() self._path = path self._transform = transform @@ -2681,7 +2768,7 @@ def __init__(self, path, transform): def _revalidate(self): # only recompute if the invalidation includes the non_affine part of # the transform - if (self._invalid & self.INVALID_NON_AFFINE == self.INVALID_NON_AFFINE + if (self._invalid == self._INVALID_FULL or self._transformed_path is None): self._transformed_path = \ self._transform.transform_path_non_affine(self._path) @@ -2728,37 +2815,25 @@ class TransformedPatchPath(TransformedPath): `~.patches.Patch`. This cached copy is automatically updated when the non-affine part of the transform or the patch changes. """ + def __init__(self, patch): """ Parameters ---------- patch : `~.patches.Patch` """ - TransformNode.__init__(self) - - transform = patch.get_transform() + # Defer to TransformedPath.__init__. + super().__init__(patch.get_path(), patch.get_transform()) self._patch = patch - self._transform = transform - self.set_children(transform) - self._path = patch.get_path() - self._transformed_path = None - self._transformed_points = None def _revalidate(self): patch_path = self._patch.get_path() - # Only recompute if the invalidation includes the non_affine part of - # the transform, or the Patch's Path has changed. - if (self._transformed_path is None or self._path != patch_path or - (self._invalid & self.INVALID_NON_AFFINE == - self.INVALID_NON_AFFINE)): + # Force invalidation if the patch path changed; otherwise, let base + # class check invalidation. + if patch_path != self._path: self._path = patch_path - self._transformed_path = \ - self._transform.transform_path_non_affine(patch_path) - self._transformed_points = \ - Path._fast_from_codes_and_verts( - self._transform.transform_non_affine(patch_path.vertices), - None, patch_path) - self._invalid = 0 + self._transformed_path = None + super()._revalidate() def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): @@ -2910,6 +2985,7 @@ def offset_copy(trans, fig=None, x=0.0, y=0.0, units='inches'): `Transform` subclass Transform with applied offset. """ + _api.check_in_list(['dots', 'points', 'inches'], units=units) if units == 'dots': return trans + Affine2D().translate(x, y) if fig is None: @@ -2917,8 +2993,5 @@ def offset_copy(trans, fig=None, x=0.0, y=0.0, units='inches'): if units == 'points': x /= 72.0 y /= 72.0 - elif units == 'inches': - pass - else: - cbook._check_in_list(['dots', 'points', 'inches'], units=units) + # Default units are 'inches' return trans + ScaledTranslation(x, y, fig.dpi_scale_trans) diff --git a/lib/matplotlib/transforms.pyi b/lib/matplotlib/transforms.pyi new file mode 100644 index 000000000000..07d299be297c --- /dev/null +++ b/lib/matplotlib/transforms.pyi @@ -0,0 +1,337 @@ +from .path import Path +from .patches import Patch +from .figure import Figure +import numpy as np +from numpy.typing import ArrayLike +from collections.abc import Iterable, Sequence +from typing import Literal + +DEBUG: bool + +class TransformNode: + INVALID_NON_AFFINE: int + INVALID_AFFINE: int + INVALID: int + # Implemented as a standard attr in base class, but functionally readonly and some subclasses implement as such + @property + def is_affine(self) -> bool: ... + pass_through: bool + def __init__(self, shorthand_name: str | None = ...) -> None: ... + def __copy__(self) -> TransformNode: ... + def invalidate(self) -> None: ... + def set_children(self, *children: TransformNode) -> None: ... + def frozen(self) -> TransformNode: ... + +class BboxBase(TransformNode): + is_affine: bool + def frozen(self) -> Bbox: ... + def __array__(self, *args, **kwargs): ... + @property + def x0(self) -> float: ... + @property + def y0(self) -> float: ... + @property + def x1(self) -> float: ... + @property + def y1(self) -> float: ... + @property + def p0(self) -> tuple[float, float]: ... + @property + def p1(self) -> tuple[float, float]: ... + @property + def xmin(self) -> float: ... + @property + def ymin(self) -> float: ... + @property + def xmax(self) -> float: ... + @property + def ymax(self) -> float: ... + @property + def min(self) -> tuple[float, float]: ... + @property + def max(self) -> tuple[float, float]: ... + @property + def intervalx(self) -> tuple[float, float]: ... + @property + def intervaly(self) -> tuple[float, float]: ... + @property + def width(self) -> float: ... + @property + def height(self) -> float: ... + @property + def size(self) -> tuple[float, float]: ... + @property + def bounds(self) -> tuple[float, float, float, float]: ... + @property + def extents(self) -> tuple[float, float, float, float]: ... + def get_points(self) -> np.ndarray: ... + def containsx(self, x: float) -> bool: ... + def containsy(self, y: float) -> bool: ... + def contains(self, x: float, y: float) -> bool: ... + def overlaps(self, other: BboxBase) -> bool: ... + def fully_containsx(self, x: float) -> bool: ... + def fully_containsy(self, y: float) -> bool: ... + def fully_contains(self, x: float, y: float) -> bool: ... + def fully_overlaps(self, other: BboxBase) -> bool: ... + def transformed(self, transform: Transform) -> Bbox: ... + coefs: dict[str, tuple[float, float]] + def anchored( + self, + c: tuple[float, float] | Literal['C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'], + container: BboxBase, + ) -> Bbox: ... + def shrunk(self, mx: float, my: float) -> Bbox: ... + def shrunk_to_aspect( + self, + box_aspect: float, + container: BboxBase | None = ..., + fig_aspect: float = ..., + ) -> Bbox: ... + def splitx(self, *args: float) -> list[Bbox]: ... + def splity(self, *args: float) -> list[Bbox]: ... + def count_contains(self, vertices: ArrayLike) -> int: ... + def count_overlaps(self, bboxes: Iterable[BboxBase]) -> int: ... + def expanded(self, sw: float, sh: float) -> Bbox: ... + def padded(self, w_pad: float, h_pad: float | None = ...) -> Bbox: ... + def translated(self, tx: float, ty: float) -> Bbox: ... + def corners(self) -> np.ndarray: ... + def rotated(self, radians: float) -> Bbox: ... + @staticmethod + def union(bboxes: Sequence[BboxBase]) -> Bbox: ... + @staticmethod + def intersection(bbox1: BboxBase, bbox2: BboxBase) -> Bbox | None: ... + +class Bbox(BboxBase): + def __init__(self, points: ArrayLike, **kwargs) -> None: ... + @staticmethod + def unit() -> Bbox: ... + @staticmethod + def null() -> Bbox: ... + @staticmethod + def from_bounds(x0: float, y0: float, width: float, height: float) -> Bbox: ... + @staticmethod + def from_extents(*args: float, minpos: float | None = ...) -> Bbox: ... + def __format__(self, fmt: str) -> str: ... + def ignore(self, value: bool) -> None: ... + def update_from_path( + self, + path: Path, + ignore: bool | None = ..., + updatex: bool = ..., + updatey: bool = ..., + ) -> None: ... + def update_from_data_x(self, x: ArrayLike, ignore: bool | None = ...) -> None: ... + def update_from_data_y(self, y: ArrayLike, ignore: bool | None = ...) -> None: ... + def update_from_data_xy( + self, + xy: ArrayLike, + ignore: bool | None = ..., + updatex: bool = ..., + updatey: bool = ..., + ) -> None: ... + @property + def minpos(self) -> float: ... + @property + def minposx(self) -> float: ... + @property + def minposy(self) -> float: ... + def get_points(self) -> np.ndarray: ... + def set_points(self, points: ArrayLike) -> None: ... + def set(self, other: Bbox) -> None: ... + def mutated(self) -> bool: ... + def mutatedx(self) -> bool: ... + def mutatedy(self) -> bool: ... + +class TransformedBbox(BboxBase): + def __init__(self, bbox: Bbox, transform: Transform, **kwargs) -> None: ... + def get_points(self) -> np.ndarray: ... + +class LockableBbox(BboxBase): + def __init__( + self, + bbox: BboxBase, + x0: float | None = ..., + y0: float | None = ..., + x1: float | None = ..., + y1: float | None = ..., + **kwargs + ) -> None: ... + @property + def locked_x0(self) -> float | None: ... + @locked_x0.setter + def locked_x0(self, x0: float | None) -> None: ... + @property + def locked_y0(self) -> float | None: ... + @locked_y0.setter + def locked_y0(self, y0: float | None) -> None: ... + @property + def locked_x1(self) -> float | None: ... + @locked_x1.setter + def locked_x1(self, x1: float | None) -> None: ... + @property + def locked_y1(self) -> float | None: ... + @locked_y1.setter + def locked_y1(self, y1: float | None) -> None: ... + +class Transform(TransformNode): + + # Implemented as a standard attrs in base class, but functionally readonly and some subclasses implement as such + @property + def input_dims(self) -> int | None: ... + @property + def output_dims(self) -> int | None: ... + @property + def is_separable(self) -> bool: ... + @property + def has_inverse(self) -> bool: ... + + def __add__(self, other: Transform) -> Transform: ... + @property + def depth(self) -> int: ... + def contains_branch(self, other: Transform) -> bool: ... + def contains_branch_seperately( + self, other_transform: Transform + ) -> Sequence[bool]: ... + def __sub__(self, other: Transform) -> Transform: ... + def __array__(self, *args, **kwargs) -> np.ndarray: ... + def transform(self, values: ArrayLike) -> np.ndarray: ... + def transform_affine(self, values: ArrayLike) -> np.ndarray: ... + def transform_non_affine(self, values: ArrayLike) -> ArrayLike: ... + def transform_bbox(self, bbox: BboxBase) -> Bbox: ... + def get_affine(self) -> Transform: ... + def get_matrix(self) -> np.ndarray: ... + def transform_point(self, point: ArrayLike) -> np.ndarray: ... + def transform_path(self, path: Path) -> Path: ... + def transform_path_affine(self, path: Path) -> Path: ... + def transform_path_non_affine(self, path: Path) -> Path: ... + def transform_angles( + self, + angles: ArrayLike, + pts: ArrayLike, + radians: bool = ..., + pushoff: float = ..., + ) -> np.ndarray: ... + def inverted(self) -> Transform: ... + +class TransformWrapper(Transform): + pass_through: bool + def __init__(self, child: Transform) -> None: ... + def __eq__(self, other: object) -> bool: ... + def frozen(self) -> Transform: ... + def set(self, child: Transform) -> None: ... + +class AffineBase(Transform): + is_affine: Literal[True] + def __init__(self, *args, **kwargs) -> None: ... + def __eq__(self, other: object) -> bool: ... + +class Affine2DBase(AffineBase): + input_dims: Literal[2] + output_dims: Literal[2] + def frozen(self) -> Affine2D: ... + def to_values(self) -> tuple[float, float, float, float, float, float]: ... + +class Affine2D(Affine2DBase): + def __init__(self, matrix: ArrayLike | None = ..., **kwargs) -> None: ... + @staticmethod + def from_values( + a: float, b: float, c: float, d: float, e: float, f: float + ) -> Affine2D: ... + def set_matrix(self, mtx: ArrayLike) -> None: ... + def clear(self) -> Affine2D: ... + def rotate(self, theta: float) -> Affine2D: ... + def rotate_deg(self, degrees: float) -> Affine2D: ... + def rotate_around(self, x: float, y: float, theta: float) -> Affine2D: ... + def rotate_deg_around(self, x: float, y: float, degrees: float) -> Affine2D: ... + def translate(self, tx: float, ty: float) -> Affine2D: ... + def scale(self, sx: float, sy: float | None = ...) -> Affine2D: ... + def skew(self, xShear: float, yShear: float) -> Affine2D: ... + def skew_deg(self, xShear: float, yShear: float) -> Affine2D: ... + +class IdentityTransform(Affine2DBase): ... + +class _BlendedMixin: + def __eq__(self, other: object) -> bool: ... + def contains_branch_seperately(self, transform: Transform) -> Sequence[bool]: ... + +class BlendedGenericTransform(_BlendedMixin, Transform): + input_dims: Literal[2] + output_dims: Literal[2] + pass_through: bool + def __init__( + self, x_transform: Transform, y_transform: Transform, **kwargs + ) -> None: ... + @property + def depth(self) -> int: ... + def contains_branch(self, other: Transform) -> Literal[False]: ... + @property + def is_affine(self) -> bool: ... + +class BlendedAffine2D(_BlendedMixin, Affine2DBase): + def __init__( + self, x_transform: Transform, y_transform: Transform, **kwargs + ) -> None: ... + +def blended_transform_factory( + x_transform: Transform, y_transform: Transform +) -> BlendedGenericTransform | BlendedAffine2D: ... + +class CompositeGenericTransform(Transform): + pass_through: bool + def __init__(self, a: Transform, b: Transform, **kwargs) -> None: ... + +class CompositeAffine2D(Affine2DBase): + def __init__(self, a: Affine2DBase, b: Affine2DBase, **kwargs) -> None: ... + @property + def depth(self) -> int: ... + +def composite_transform_factory(a: Transform, b: Transform) -> Transform: ... + +class BboxTransform(Affine2DBase): + def __init__(self, boxin: BboxBase, boxout: BboxBase, **kwargs) -> None: ... + +class BboxTransformTo(Affine2DBase): + def __init__(self, boxout: BboxBase, **kwargs) -> None: ... + +class BboxTransformFrom(Affine2DBase): + def __init__(self, boxin: BboxBase, **kwargs) -> None: ... + +class ScaledTranslation(Affine2DBase): + def __init__( + self, xt: float, yt: float, scale_trans: Affine2DBase, **kwargs + ) -> None: ... + +class AffineDeltaTransform(Affine2DBase): + def __init__(self, transform: Affine2DBase, **kwargs) -> None: ... + +class TransformedPath(TransformNode): + def __init__(self, path: Path, transform: Transform) -> None: ... + def get_transformed_points_and_affine(self) -> tuple[Path, Transform]: ... + def get_transformed_path_and_affine(self) -> tuple[Path, Transform]: ... + def get_fully_transformed_path(self) -> Path: ... + def get_affine(self) -> Transform: ... + +class TransformedPatchPath(TransformedPath): + def __init__(self, patch: Patch) -> None: ... + +def nonsingular( + vmin: float, + vmax: float, + expander: float = ..., + tiny: float = ..., + increasing: bool = ..., +) -> tuple[float, float]: ... +def interval_contains(interval: tuple[float, float], val: float) -> bool: ... +def interval_contains_open(interval: tuple[float, float], val: float) -> bool: ... +def offset_copy( + trans: Transform, + fig: Figure | None = ..., + x: float = ..., + y: float = ..., + units: Literal["inches", "points", "dots"] = ..., +) -> Transform: ... + + +class _ScaledRotation(Affine2DBase): + def __init__(self, theta: float, trans_shift: Transform) -> None: ... + def get_matrix(self) -> np.ndarray: ... diff --git a/lib/matplotlib/tri/__init__.py b/lib/matplotlib/tri/__init__.py index 7ff2b326920b..e000831d8a08 100644 --- a/lib/matplotlib/tri/__init__.py +++ b/lib/matplotlib/tri/__init__.py @@ -2,11 +2,22 @@ Unstructured triangular grid functions. """ -from .triangulation import * -from .tricontour import * -from .tritools import * -from .trifinder import * -from .triinterpolate import * -from .trirefine import * -from .tripcolor import * -from .triplot import * +from ._triangulation import Triangulation +from ._tricontour import TriContourSet, tricontour, tricontourf +from ._trifinder import TriFinder, TrapezoidMapTriFinder +from ._triinterpolate import (TriInterpolator, LinearTriInterpolator, + CubicTriInterpolator) +from ._tripcolor import tripcolor +from ._triplot import triplot +from ._trirefine import TriRefiner, UniformTriRefiner +from ._tritools import TriAnalyzer + + +__all__ = ["Triangulation", + "TriContourSet", "tricontour", "tricontourf", + "TriFinder", "TrapezoidMapTriFinder", + "TriInterpolator", "LinearTriInterpolator", "CubicTriInterpolator", + "tripcolor", + "triplot", + "TriRefiner", "UniformTriRefiner", + "TriAnalyzer"] diff --git a/lib/matplotlib/tri/_triangulation.py b/lib/matplotlib/tri/_triangulation.py new file mode 100644 index 000000000000..a07192dfc8ca --- /dev/null +++ b/lib/matplotlib/tri/_triangulation.py @@ -0,0 +1,247 @@ +import sys + +import numpy as np + +from matplotlib import _api + + +class Triangulation: + """ + An unstructured triangular grid consisting of npoints points and + ntri triangles. The triangles can either be specified by the user + or automatically generated using a Delaunay triangulation. + + Parameters + ---------- + x, y : (npoints,) array-like + Coordinates of grid points. + triangles : (ntri, 3) array-like of int, optional + For each triangle, the indices of the three points that make + up the triangle, ordered in an anticlockwise manner. If not + specified, the Delaunay triangulation is calculated. + mask : (ntri,) array-like of bool, optional + Which triangles are masked out. + + Attributes + ---------- + triangles : (ntri, 3) array of int + For each triangle, the indices of the three points that make + up the triangle, ordered in an anticlockwise manner. If you want to + take the *mask* into account, use `get_masked_triangles` instead. + mask : (ntri, 3) array of bool or None + Masked out triangles. + is_delaunay : bool + Whether the Triangulation is a calculated Delaunay + triangulation (where *triangles* was not specified) or not. + + Notes + ----- + For a Triangulation to be valid it must not have duplicate points, + triangles formed from colinear points, or overlapping triangles. + """ + def __init__(self, x, y, triangles=None, mask=None): + from matplotlib import _qhull + + self.x = np.asarray(x, dtype=np.float64) + self.y = np.asarray(y, dtype=np.float64) + if self.x.shape != self.y.shape or self.x.ndim != 1: + raise ValueError("x and y must be equal-length 1D arrays, but " + f"found shapes {self.x.shape!r} and " + f"{self.y.shape!r}") + + self.mask = None + self._edges = None + self._neighbors = None + self.is_delaunay = False + + if triangles is None: + # No triangulation specified, so use matplotlib._qhull to obtain + # Delaunay triangulation. + self.triangles, self._neighbors = _qhull.delaunay(x, y, sys.flags.verbose) + self.is_delaunay = True + else: + # Triangulation specified. Copy, since we may correct triangle + # orientation. + try: + self.triangles = np.array(triangles, dtype=np.int32, order='C') + except ValueError as e: + raise ValueError('triangles must be a (N, 3) int array, not ' + f'{triangles!r}') from e + if self.triangles.ndim != 2 or self.triangles.shape[1] != 3: + raise ValueError( + 'triangles must be a (N, 3) int array, but found shape ' + f'{self.triangles.shape!r}') + if self.triangles.max() >= len(self.x): + raise ValueError( + 'triangles are indices into the points and must be in the ' + f'range 0 <= i < {len(self.x)} but found value ' + f'{self.triangles.max()}') + if self.triangles.min() < 0: + raise ValueError( + 'triangles are indices into the points and must be in the ' + f'range 0 <= i < {len(self.x)} but found value ' + f'{self.triangles.min()}') + + # Underlying C++ object is not created until first needed. + self._cpp_triangulation = None + + # Default TriFinder not created until needed. + self._trifinder = None + + self.set_mask(mask) + + def calculate_plane_coefficients(self, z): + """ + Calculate plane equation coefficients for all unmasked triangles from + the point (x, y) coordinates and specified z-array of shape (npoints). + The returned array has shape (npoints, 3) and allows z-value at (x, y) + position in triangle tri to be calculated using + ``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``. + """ + return self.get_cpp_triangulation().calculate_plane_coefficients(z) + + @property + def edges(self): + """ + Return integer array of shape (nedges, 2) containing all edges of + non-masked triangles. + + Each row defines an edge by its start point index and end point + index. Each edge appears only once, i.e. for an edge between points + *i* and *j*, there will only be either *(i, j)* or *(j, i)*. + """ + if self._edges is None: + self._edges = self.get_cpp_triangulation().get_edges() + return self._edges + + def get_cpp_triangulation(self): + """ + Return the underlying C++ Triangulation object, creating it + if necessary. + """ + from matplotlib import _tri + if self._cpp_triangulation is None: + self._cpp_triangulation = _tri.Triangulation( + # For unset arrays use empty tuple which has size of zero. + self.x, self.y, self.triangles, + self.mask if self.mask is not None else (), + self._edges if self._edges is not None else (), + self._neighbors if self._neighbors is not None else (), + not self.is_delaunay) + return self._cpp_triangulation + + def get_masked_triangles(self): + """ + Return an array of triangles taking the mask into account. + """ + if self.mask is not None: + return self.triangles[~self.mask] + else: + return self.triangles + + @staticmethod + def get_from_args_and_kwargs(*args, **kwargs): + """ + Return a Triangulation object from the args and kwargs, and + the remaining args and kwargs with the consumed values removed. + + There are two alternatives: either the first argument is a + Triangulation object, in which case it is returned, or the args + and kwargs are sufficient to create a new Triangulation to + return. In the latter case, see Triangulation.__init__ for + the possible args and kwargs. + """ + if isinstance(args[0], Triangulation): + triangulation, *args = args + if 'triangles' in kwargs: + _api.warn_external( + "Passing the keyword 'triangles' has no effect when also " + "passing a Triangulation") + if 'mask' in kwargs: + _api.warn_external( + "Passing the keyword 'mask' has no effect when also " + "passing a Triangulation") + else: + x, y, triangles, mask, args, kwargs = \ + Triangulation._extract_triangulation_params(args, kwargs) + triangulation = Triangulation(x, y, triangles, mask) + return triangulation, args, kwargs + + @staticmethod + def _extract_triangulation_params(args, kwargs): + x, y, *args = args + # Check triangles in kwargs then args. + triangles = kwargs.pop('triangles', None) + from_args = False + if triangles is None and args: + triangles = args[0] + from_args = True + if triangles is not None: + try: + triangles = np.asarray(triangles, dtype=np.int32) + except ValueError: + triangles = None + if triangles is not None and (triangles.ndim != 2 or + triangles.shape[1] != 3): + triangles = None + if triangles is not None and from_args: + args = args[1:] # Consumed first item in args. + # Check for mask in kwargs. + mask = kwargs.pop('mask', None) + return x, y, triangles, mask, args, kwargs + + def get_trifinder(self): + """ + Return the default `matplotlib.tri.TriFinder` of this + triangulation, creating it if necessary. This allows the same + TriFinder object to be easily shared. + """ + if self._trifinder is None: + # Default TriFinder class. + from matplotlib.tri._trifinder import TrapezoidMapTriFinder + self._trifinder = TrapezoidMapTriFinder(self) + return self._trifinder + + @property + def neighbors(self): + """ + Return integer array of shape (ntri, 3) containing neighbor triangles. + + For each triangle, the indices of the three triangles that + share the same edges, or -1 if there is no such neighboring + triangle. ``neighbors[i, j]`` is the triangle that is the neighbor + to the edge from point index ``triangles[i, j]`` to point index + ``triangles[i, (j+1)%3]``. + """ + if self._neighbors is None: + self._neighbors = self.get_cpp_triangulation().get_neighbors() + return self._neighbors + + def set_mask(self, mask): + """ + Set or clear the mask array. + + Parameters + ---------- + mask : None or bool array of length ntri + """ + if mask is None: + self.mask = None + else: + self.mask = np.asarray(mask, dtype=bool) + if self.mask.shape != (self.triangles.shape[0],): + raise ValueError('mask array must have same length as ' + 'triangles array') + + # Set mask in C++ Triangulation. + if self._cpp_triangulation is not None: + self._cpp_triangulation.set_mask( + self.mask if self.mask is not None else ()) + + # Clear derived fields so they are recalculated when needed. + self._edges = None + self._neighbors = None + + # Recalculate TriFinder if it exists. + if self._trifinder is not None: + self._trifinder._initialize() diff --git a/lib/matplotlib/tri/_triangulation.pyi b/lib/matplotlib/tri/_triangulation.pyi new file mode 100644 index 000000000000..6e00b272eda9 --- /dev/null +++ b/lib/matplotlib/tri/_triangulation.pyi @@ -0,0 +1,33 @@ +from matplotlib import _tri +from matplotlib.tri._trifinder import TriFinder + +import numpy as np +from numpy.typing import ArrayLike +from typing import Any + +class Triangulation: + x: np.ndarray + y: np.ndarray + mask: np.ndarray | None + is_delaunay: bool + triangles: np.ndarray + def __init__( + self, + x: ArrayLike, + y: ArrayLike, + triangles: ArrayLike | None = ..., + mask: ArrayLike | None = ..., + ) -> None: ... + def calculate_plane_coefficients(self, z: ArrayLike) -> np.ndarray: ... + @property + def edges(self) -> np.ndarray: ... + def get_cpp_triangulation(self) -> _tri.Triangulation: ... + def get_masked_triangles(self) -> np.ndarray: ... + @staticmethod + def get_from_args_and_kwargs( + *args, **kwargs + ) -> tuple[Triangulation, tuple[Any, ...], dict[str, Any]]: ... + def get_trifinder(self) -> TriFinder: ... + @property + def neighbors(self) -> np.ndarray: ... + def set_mask(self, mask: None | ArrayLike) -> None: ... diff --git a/lib/matplotlib/tri/_tricontour.py b/lib/matplotlib/tri/_tricontour.py new file mode 100644 index 000000000000..8250515f3ef8 --- /dev/null +++ b/lib/matplotlib/tri/_tricontour.py @@ -0,0 +1,270 @@ +import numpy as np + +from matplotlib import _docstring +from matplotlib.contour import ContourSet +from matplotlib.tri._triangulation import Triangulation + + +@_docstring.interpd +class TriContourSet(ContourSet): + """ + Create and store a set of contour lines or filled regions for + a triangular grid. + + This class is typically not instantiated directly by the user but by + `~.Axes.tricontour` and `~.Axes.tricontourf`. + + %(contour_set_attributes)s + """ + def __init__(self, ax, *args, **kwargs): + """ + Draw triangular grid contour lines or filled regions, + depending on whether keyword arg *filled* is False + (default) or True. + + The first argument of the initializer must be an `~.axes.Axes` + object. The remaining arguments and keyword arguments + are described in the docstring of `~.Axes.tricontour`. + """ + super().__init__(ax, *args, **kwargs) + + def _process_args(self, *args, **kwargs): + """ + Process args and kwargs. + """ + if isinstance(args[0], TriContourSet): + C = args[0]._contour_generator + if self.levels is None: + self.levels = args[0].levels + self.zmin = args[0].zmin + self.zmax = args[0].zmax + self._mins = args[0]._mins + self._maxs = args[0]._maxs + else: + from matplotlib import _tri + tri, z = self._contour_args(args, kwargs) + C = _tri.TriContourGenerator(tri.get_cpp_triangulation(), z) + self._mins = [tri.x.min(), tri.y.min()] + self._maxs = [tri.x.max(), tri.y.max()] + + self._contour_generator = C + return kwargs + + def _contour_args(self, args, kwargs): + tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, + **kwargs) + z, *args = args + z = np.ma.asarray(z) + if z.shape != tri.x.shape: + raise ValueError('z array must have same length as triangulation x' + ' and y arrays') + + # z values must be finite, only need to check points that are included + # in the triangulation. + z_check = z[np.unique(tri.get_masked_triangles())] + if np.ma.is_masked(z_check): + raise ValueError('z must not contain masked points within the ' + 'triangulation') + if not np.isfinite(z_check).all(): + raise ValueError('z array must not contain non-finite values ' + 'within the triangulation') + + z = np.ma.masked_invalid(z, copy=False) + self.zmax = float(z_check.max()) + self.zmin = float(z_check.min()) + if self.logscale and self.zmin <= 0: + func = 'contourf' if self.filled else 'contour' + raise ValueError(f'Cannot {func} log of negative values.') + self._process_contour_level_args(args, z.dtype) + return (tri, z) + + +_docstring.interpd.register(_tricontour_doc=""" +Draw contour %%(type)s on an unstructured triangular grid. + +Call signatures:: + + %%(func)s(triangulation, z, [levels], ...) + %%(func)s(x, y, z, [levels], *, [triangles=triangles], [mask=mask], ...) + +The triangular grid can be specified either by passing a `.Triangulation` +object as the first parameter, or by passing the points *x*, *y* and +optionally the *triangles* and a *mask*. See `.Triangulation` for an +explanation of these parameters. If neither of *triangulation* or +*triangles* are given, the triangulation is calculated on the fly. + +It is possible to pass *triangles* positionally, i.e. +``%%(func)s(x, y, triangles, z, ...)``. However, this is discouraged. For more +clarity, pass *triangles* via keyword argument. + +Parameters +---------- +triangulation : `.Triangulation`, optional + An already created triangular grid. + +x, y, triangles, mask + Parameters defining the triangular grid. See `.Triangulation`. + This is mutually exclusive with specifying *triangulation*. + +z : array-like + The height values over which the contour is drawn. Color-mapping is + controlled by *cmap*, *norm*, *vmin*, and *vmax*. + + .. note:: + All values in *z* must be finite. Hence, nan and inf values must + either be removed or `~.Triangulation.set_mask` be used. + +levels : int or array-like, optional + Determines the number and positions of the contour lines / regions. + + If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries to + automatically choose no more than *n+1* "nice" contour levels between + between minimum and maximum numeric values of *Z*. + + If array-like, draw contour lines at the specified levels. The values must + be in increasing order. + +Returns +------- +`~matplotlib.tri.TriContourSet` + +Other Parameters +---------------- +colors : :mpltype:`color` or list of :mpltype:`color`, optional + The colors of the levels, i.e., the contour %%(type)s. + + The sequence is cycled for the levels in ascending order. If the sequence + is shorter than the number of levels, it is repeated. + + As a shortcut, single color strings may be used in place of one-element + lists, i.e. ``'red'`` instead of ``['red']`` to color all levels with the + same color. This shortcut does only work for color strings, not for other + ways of specifying colors. + + By default (value *None*), the colormap specified by *cmap* will be used. + +alpha : float, default: 1 + The alpha blending value, between 0 (transparent) and 1 (opaque). + +%(cmap_doc)s + + This parameter is ignored if *colors* is set. + +%(norm_doc)s + + This parameter is ignored if *colors* is set. + +%(vmin_vmax_doc)s + + If *vmin* or *vmax* are not given, the default color scaling is based on + *levels*. + + This parameter is ignored if *colors* is set. + +origin : {*None*, 'upper', 'lower', 'image'}, default: None + Determines the orientation and exact position of *z* by specifying the + position of ``z[0, 0]``. This is only relevant, if *X*, *Y* are not given. + + - *None*: ``z[0, 0]`` is at X=0, Y=0 in the lower left corner. + - 'lower': ``z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. + - 'upper': ``z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left corner. + - 'image': Use the value from :rc:`image.origin`. + +extent : (x0, x1, y0, y1), optional + If *origin* is not *None*, then *extent* is interpreted as in `.imshow`: it + gives the outer pixel boundaries. In this case, the position of z[0, 0] is + the center of the pixel, not a corner. If *origin* is *None*, then + (*x0*, *y0*) is the position of z[0, 0], and (*x1*, *y1*) is the position + of z[-1, -1]. + + This argument is ignored if *X* and *Y* are specified in the call to + contour. + +locator : ticker.Locator subclass, optional + The locator is used to determine the contour levels if they are not given + explicitly via *levels*. + Defaults to `~.ticker.MaxNLocator`. + +extend : {'neither', 'both', 'min', 'max'}, default: 'neither' + Determines the ``%%(func)s``-coloring of values that are outside the + *levels* range. + + If 'neither', values outside the *levels* range are not colored. If 'min', + 'max' or 'both', color the values below, above or below and above the + *levels* range. + + Values below ``min(levels)`` and above ``max(levels)`` are mapped to the + under/over values of the `.Colormap`. Note that most colormaps do not have + dedicated colors for these by default, so that the over and under values + are the edge values of the colormap. You may want to set these values + explicitly using `.Colormap.set_under` and `.Colormap.set_over`. + + .. note:: + + An existing `.TriContourSet` does not get notified if properties of its + colormap are changed. Therefore, an explicit call to + `.ContourSet.changed()` is needed after modifying the colormap. The + explicit call can be left out, if a colorbar is assigned to the + `.TriContourSet` because it internally calls `.ContourSet.changed()`. + +xunits, yunits : registered units, optional + Override axis units by specifying an instance of a + :class:`matplotlib.units.ConversionInterface`. + +antialiased : bool, optional + Enable antialiasing, overriding the defaults. For + filled contours, the default is *True*. For line contours, + it is taken from :rc:`lines.antialiased`.""" % _docstring.interpd.params) + + +@_docstring.Substitution(func='tricontour', type='lines') +@_docstring.interpd +def tricontour(ax, *args, **kwargs): + """ + %(_tricontour_doc)s + + linewidths : float or array-like, default: :rc:`contour.linewidth` + The line width of the contour lines. + + If a number, all levels will be plotted with this linewidth. + + If a sequence, the levels in ascending order will be plotted with + the linewidths in the order specified. + + If None, this falls back to :rc:`lines.linewidth`. + + linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional + If *linestyles* is *None*, the default is 'solid' unless the lines are + monochrome. In that case, negative contours will take their linestyle + from :rc:`contour.negative_linestyle` setting. + + *linestyles* can also be an iterable of the above strings specifying a + set of linestyles to be used. If this iterable is shorter than the + number of contour levels it will be repeated as necessary. + """ + kwargs['filled'] = False + return TriContourSet(ax, *args, **kwargs) + + +@_docstring.Substitution(func='tricontourf', type='regions') +@_docstring.interpd +def tricontourf(ax, *args, **kwargs): + """ + %(_tricontour_doc)s + + hatches : list[str], optional + A list of crosshatch patterns to use on the filled areas. + If None, no hatching will be added to the contour. + + Notes + ----- + `.tricontourf` fills intervals that are closed at the top; that is, for + boundaries *z1* and *z2*, the filled region is:: + + z1 < Z <= z2 + + except for the lowest interval, which is closed on both sides (i.e. it + includes the lowest value). + """ + kwargs['filled'] = True + return TriContourSet(ax, *args, **kwargs) diff --git a/lib/matplotlib/tri/_tricontour.pyi b/lib/matplotlib/tri/_tricontour.pyi new file mode 100644 index 000000000000..31929d866156 --- /dev/null +++ b/lib/matplotlib/tri/_tricontour.pyi @@ -0,0 +1,52 @@ +from matplotlib.axes import Axes +from matplotlib.contour import ContourSet +from matplotlib.tri._triangulation import Triangulation + +from numpy.typing import ArrayLike +from typing import overload + +# TODO: more explicit args/kwargs (for all things in this module)? + +class TriContourSet(ContourSet): + def __init__(self, ax: Axes, *args, **kwargs) -> None: ... + +@overload +def tricontour( + ax: Axes, + triangulation: Triangulation, + z: ArrayLike, + levels: int | ArrayLike = ..., + **kwargs +) -> TriContourSet: ... +@overload +def tricontour( + ax: Axes, + x: ArrayLike, + y: ArrayLike, + z: ArrayLike, + levels: int | ArrayLike = ..., + *, + triangles: ArrayLike = ..., + mask: ArrayLike = ..., + **kwargs +) -> TriContourSet: ... +@overload +def tricontourf( + ax: Axes, + triangulation: Triangulation, + z: ArrayLike, + levels: int | ArrayLike = ..., + **kwargs +) -> TriContourSet: ... +@overload +def tricontourf( + ax: Axes, + x: ArrayLike, + y: ArrayLike, + z: ArrayLike, + levels: int | ArrayLike = ..., + *, + triangles: ArrayLike = ..., + mask: ArrayLike = ..., + **kwargs +) -> TriContourSet: ... diff --git a/lib/matplotlib/tri/_trifinder.py b/lib/matplotlib/tri/_trifinder.py new file mode 100644 index 000000000000..852f3d9eebcc --- /dev/null +++ b/lib/matplotlib/tri/_trifinder.py @@ -0,0 +1,96 @@ +import numpy as np + +from matplotlib import _api +from matplotlib.tri import Triangulation + + +class TriFinder: + """ + Abstract base class for classes used to find the triangles of a + Triangulation in which (x, y) points lie. + + Rather than instantiate an object of a class derived from TriFinder, it is + usually better to use the function `.Triangulation.get_trifinder`. + + Derived classes implement __call__(x, y) where x and y are array-like point + coordinates of the same shape. + """ + + def __init__(self, triangulation): + _api.check_isinstance(Triangulation, triangulation=triangulation) + self._triangulation = triangulation + + def __call__(self, x, y): + raise NotImplementedError + + +class TrapezoidMapTriFinder(TriFinder): + """ + `~matplotlib.tri.TriFinder` class implemented using the trapezoid + map algorithm from the book "Computational Geometry, Algorithms and + Applications", second edition, by M. de Berg, M. van Kreveld, M. Overmars + and O. Schwarzkopf. + + The triangulation must be valid, i.e. it must not have duplicate points, + triangles formed from colinear points, or overlapping triangles. The + algorithm has some tolerance to triangles formed from colinear points, but + this should not be relied upon. + """ + + def __init__(self, triangulation): + from matplotlib import _tri + super().__init__(triangulation) + self._cpp_trifinder = _tri.TrapezoidMapTriFinder( + triangulation.get_cpp_triangulation()) + self._initialize() + + def __call__(self, x, y): + """ + Return an array containing the indices of the triangles in which the + specified *x*, *y* points lie, or -1 for points that do not lie within + a triangle. + + *x*, *y* are array-like x and y coordinates of the same shape and any + number of dimensions. + + Returns integer array with the same shape and *x* and *y*. + """ + x = np.asarray(x, dtype=np.float64) + y = np.asarray(y, dtype=np.float64) + if x.shape != y.shape: + raise ValueError("x and y must be array-like with the same shape") + + # C++ does the heavy lifting, and expects 1D arrays. + indices = (self._cpp_trifinder.find_many(x.ravel(), y.ravel()) + .reshape(x.shape)) + return indices + + def _get_tree_stats(self): + """ + Return a python list containing the statistics about the node tree: + 0: number of nodes (tree size) + 1: number of unique nodes + 2: number of trapezoids (tree leaf nodes) + 3: number of unique trapezoids + 4: maximum parent count (max number of times a node is repeated in + tree) + 5: maximum depth of tree (one more than the maximum number of + comparisons needed to search through the tree) + 6: mean of all trapezoid depths (one more than the average number + of comparisons needed to search through the tree) + """ + return self._cpp_trifinder.get_tree_stats() + + def _initialize(self): + """ + Initialize the underlying C++ object. Can be called multiple times if, + for example, the triangulation is modified. + """ + self._cpp_trifinder.initialize() + + def _print_tree(self): + """ + Print a text representation of the node tree, which is useful for + debugging purposes. + """ + self._cpp_trifinder.print_tree() diff --git a/lib/matplotlib/tri/_trifinder.pyi b/lib/matplotlib/tri/_trifinder.pyi new file mode 100644 index 000000000000..41a9990b8988 --- /dev/null +++ b/lib/matplotlib/tri/_trifinder.pyi @@ -0,0 +1,10 @@ +from matplotlib.tri import Triangulation +from numpy.typing import ArrayLike + +class TriFinder: + def __init__(self, triangulation: Triangulation) -> None: ... + def __call__(self, x: ArrayLike, y: ArrayLike) -> ArrayLike: ... + +class TrapezoidMapTriFinder(TriFinder): + def __init__(self, triangulation: Triangulation) -> None: ... + def __call__(self, x: ArrayLike, y: ArrayLike) -> ArrayLike: ... diff --git a/lib/matplotlib/tri/_triinterpolate.py b/lib/matplotlib/tri/_triinterpolate.py new file mode 100644 index 000000000000..90ad6cf3a76c --- /dev/null +++ b/lib/matplotlib/tri/_triinterpolate.py @@ -0,0 +1,1574 @@ +""" +Interpolation inside triangular grids. +""" + +import numpy as np + +from matplotlib import _api +from matplotlib.tri import Triangulation +from matplotlib.tri._trifinder import TriFinder +from matplotlib.tri._tritools import TriAnalyzer + +__all__ = ('TriInterpolator', 'LinearTriInterpolator', 'CubicTriInterpolator') + + +class TriInterpolator: + """ + Abstract base class for classes used to interpolate on a triangular grid. + + Derived classes implement the following methods: + + - ``__call__(x, y)``, + where x, y are array-like point coordinates of the same shape, and + that returns a masked array of the same shape containing the + interpolated z-values. + + - ``gradient(x, y)``, + where x, y are array-like point coordinates of the same + shape, and that returns a list of 2 masked arrays of the same shape + containing the 2 derivatives of the interpolator (derivatives of + interpolated z values with respect to x and y). + """ + + def __init__(self, triangulation, z, trifinder=None): + _api.check_isinstance(Triangulation, triangulation=triangulation) + self._triangulation = triangulation + + self._z = np.asarray(z) + if self._z.shape != self._triangulation.x.shape: + raise ValueError("z array must have same length as triangulation x" + " and y arrays") + + _api.check_isinstance((TriFinder, None), trifinder=trifinder) + self._trifinder = trifinder or self._triangulation.get_trifinder() + + # Default scaling factors : 1.0 (= no scaling) + # Scaling may be used for interpolations for which the order of + # magnitude of x, y has an impact on the interpolant definition. + # Please refer to :meth:`_interpolate_multikeys` for details. + self._unit_x = 1.0 + self._unit_y = 1.0 + + # Default triangle renumbering: None (= no renumbering) + # Renumbering may be used to avoid unnecessary computations + # if complex calculations are done inside the Interpolator. + # Please refer to :meth:`_interpolate_multikeys` for details. + self._tri_renum = None + + # __call__ and gradient docstrings are shared by all subclasses + # (except, if needed, relevant additions). + # However these methods are only implemented in subclasses to avoid + # confusion in the documentation. + _docstring__call__ = """ + Returns a masked array containing interpolated values at the specified + (x, y) points. + + Parameters + ---------- + x, y : array-like + x and y coordinates of the same shape and any number of + dimensions. + + Returns + ------- + np.ma.array + Masked array of the same shape as *x* and *y*; values corresponding + to (*x*, *y*) points outside of the triangulation are masked out. + + """ + + _docstringgradient = r""" + Returns a list of 2 masked arrays containing interpolated derivatives + at the specified (x, y) points. + + Parameters + ---------- + x, y : array-like + x and y coordinates of the same shape and any number of + dimensions. + + Returns + ------- + dzdx, dzdy : np.ma.array + 2 masked arrays of the same shape as *x* and *y*; values + corresponding to (x, y) points outside of the triangulation + are masked out. + The first returned array contains the values of + :math:`\frac{\partial z}{\partial x}` and the second those of + :math:`\frac{\partial z}{\partial y}`. + + """ + + def _interpolate_multikeys(self, x, y, tri_index=None, + return_keys=('z',)): + """ + Versatile (private) method defined for all TriInterpolators. + + :meth:`_interpolate_multikeys` is a wrapper around method + :meth:`_interpolate_single_key` (to be defined in the child + subclasses). + :meth:`_interpolate_single_key actually performs the interpolation, + but only for 1-dimensional inputs and at valid locations (inside + unmasked triangles of the triangulation). + + The purpose of :meth:`_interpolate_multikeys` is to implement the + following common tasks needed in all subclasses implementations: + + - calculation of containing triangles + - dealing with more than one interpolation request at the same + location (e.g., if the 2 derivatives are requested, it is + unnecessary to compute the containing triangles twice) + - scaling according to self._unit_x, self._unit_y + - dealing with points outside of the grid (with fill value np.nan) + - dealing with multi-dimensional *x*, *y* arrays: flattening for + :meth:`_interpolate_params` call and final reshaping. + + (Note that np.vectorize could do most of those things very well for + you, but it does it by function evaluations over successive tuples of + the input arrays. Therefore, this tends to be more time-consuming than + using optimized numpy functions - e.g., np.dot - which can be used + easily on the flattened inputs, in the child-subclass methods + :meth:`_interpolate_single_key`.) + + It is guaranteed that the calls to :meth:`_interpolate_single_key` + will be done with flattened (1-d) array-like input parameters *x*, *y* + and with flattened, valid `tri_index` arrays (no -1 index allowed). + + Parameters + ---------- + x, y : array-like + x and y coordinates where interpolated values are requested. + tri_index : array-like of int, optional + Array of the containing triangle indices, same shape as + *x* and *y*. Defaults to None. If None, these indices + will be computed by a TriFinder instance. + (Note: For point outside the grid, tri_index[ipt] shall be -1). + return_keys : tuple of keys from {'z', 'dzdx', 'dzdy'} + Defines the interpolation arrays to return, and in which order. + + Returns + ------- + list of arrays + Each array-like contains the expected interpolated values in the + order defined by *return_keys* parameter. + """ + # Flattening and rescaling inputs arrays x, y + # (initial shape is stored for output) + x = np.asarray(x, dtype=np.float64) + y = np.asarray(y, dtype=np.float64) + sh_ret = x.shape + if x.shape != y.shape: + raise ValueError("x and y shall have same shapes." + f" Given: {x.shape} and {y.shape}") + x = np.ravel(x) + y = np.ravel(y) + x_scaled = x/self._unit_x + y_scaled = y/self._unit_y + size_ret = np.size(x_scaled) + + # Computes & ravels the element indexes, extract the valid ones. + if tri_index is None: + tri_index = self._trifinder(x, y) + else: + if tri_index.shape != sh_ret: + raise ValueError( + "tri_index array is provided and shall" + " have same shape as x and y. Given: " + f"{tri_index.shape} and {sh_ret}") + tri_index = np.ravel(tri_index) + + mask_in = (tri_index != -1) + if self._tri_renum is None: + valid_tri_index = tri_index[mask_in] + else: + valid_tri_index = self._tri_renum[tri_index[mask_in]] + valid_x = x_scaled[mask_in] + valid_y = y_scaled[mask_in] + + ret = [] + for return_key in return_keys: + # Find the return index associated with the key. + try: + return_index = {'z': 0, 'dzdx': 1, 'dzdy': 2}[return_key] + except KeyError as err: + raise ValueError("return_keys items shall take values in" + " {'z', 'dzdx', 'dzdy'}") from err + + # Sets the scale factor for f & df components + scale = [1., 1./self._unit_x, 1./self._unit_y][return_index] + + # Computes the interpolation + ret_loc = np.empty(size_ret, dtype=np.float64) + ret_loc[~mask_in] = np.nan + ret_loc[mask_in] = self._interpolate_single_key( + return_key, valid_tri_index, valid_x, valid_y) * scale + ret += [np.ma.masked_invalid(ret_loc.reshape(sh_ret), copy=False)] + + return ret + + def _interpolate_single_key(self, return_key, tri_index, x, y): + """ + Interpolate at points belonging to the triangulation + (inside an unmasked triangles). + + Parameters + ---------- + return_key : {'z', 'dzdx', 'dzdy'} + The requested values (z or its derivatives). + tri_index : 1D int array + Valid triangle index (cannot be -1). + x, y : 1D arrays, same shape as `tri_index` + Valid locations where interpolation is requested. + + Returns + ------- + 1-d array + Returned array of the same size as *tri_index* + """ + raise NotImplementedError("TriInterpolator subclasses" + + "should implement _interpolate_single_key!") + + +class LinearTriInterpolator(TriInterpolator): + """ + Linear interpolator on a triangular grid. + + Each triangle is represented by a plane so that an interpolated value at + point (x, y) lies on the plane of the triangle containing (x, y). + Interpolated values are therefore continuous across the triangulation, but + their first derivatives are discontinuous at edges between triangles. + + Parameters + ---------- + triangulation : `~matplotlib.tri.Triangulation` + The triangulation to interpolate over. + z : (npoints,) array-like + Array of values, defined at grid points, to interpolate between. + trifinder : `~matplotlib.tri.TriFinder`, optional + If this is not specified, the Triangulation's default TriFinder will + be used by calling `.Triangulation.get_trifinder`. + + Methods + ------- + `__call__` (x, y) : Returns interpolated values at (x, y) points. + `gradient` (x, y) : Returns interpolated derivatives at (x, y) points. + + """ + def __init__(self, triangulation, z, trifinder=None): + super().__init__(triangulation, z, trifinder) + + # Store plane coefficients for fast interpolation calculations. + self._plane_coefficients = \ + self._triangulation.calculate_plane_coefficients(self._z) + + def __call__(self, x, y): + return self._interpolate_multikeys(x, y, tri_index=None, + return_keys=('z',))[0] + __call__.__doc__ = TriInterpolator._docstring__call__ + + def gradient(self, x, y): + return self._interpolate_multikeys(x, y, tri_index=None, + return_keys=('dzdx', 'dzdy')) + gradient.__doc__ = TriInterpolator._docstringgradient + + def _interpolate_single_key(self, return_key, tri_index, x, y): + _api.check_in_list(['z', 'dzdx', 'dzdy'], return_key=return_key) + if return_key == 'z': + return (self._plane_coefficients[tri_index, 0]*x + + self._plane_coefficients[tri_index, 1]*y + + self._plane_coefficients[tri_index, 2]) + elif return_key == 'dzdx': + return self._plane_coefficients[tri_index, 0] + else: # 'dzdy' + return self._plane_coefficients[tri_index, 1] + + +class CubicTriInterpolator(TriInterpolator): + r""" + Cubic interpolator on a triangular grid. + + In one-dimension - on a segment - a cubic interpolating function is + defined by the values of the function and its derivative at both ends. + This is almost the same in 2D inside a triangle, except that the values + of the function and its 2 derivatives have to be defined at each triangle + node. + + The CubicTriInterpolator takes the value of the function at each node - + provided by the user - and internally computes the value of the + derivatives, resulting in a smooth interpolation. + (As a special feature, the user can also impose the value of the + derivatives at each node, but this is not supposed to be the common + usage.) + + Parameters + ---------- + triangulation : `~matplotlib.tri.Triangulation` + The triangulation to interpolate over. + z : (npoints,) array-like + Array of values, defined at grid points, to interpolate between. + kind : {'min_E', 'geom', 'user'}, optional + Choice of the smoothing algorithm, in order to compute + the interpolant derivatives (defaults to 'min_E'): + + - if 'min_E': (default) The derivatives at each node is computed + to minimize a bending energy. + - if 'geom': The derivatives at each node is computed as a + weighted average of relevant triangle normals. To be used for + speed optimization (large grids). + - if 'user': The user provides the argument *dz*, no computation + is hence needed. + + trifinder : `~matplotlib.tri.TriFinder`, optional + If not specified, the Triangulation's default TriFinder will + be used by calling `.Triangulation.get_trifinder`. + dz : tuple of array-likes (dzdx, dzdy), optional + Used only if *kind* ='user'. In this case *dz* must be provided as + (dzdx, dzdy) where dzdx, dzdy are arrays of the same shape as *z* and + are the interpolant first derivatives at the *triangulation* points. + + Methods + ------- + `__call__` (x, y) : Returns interpolated values at (x, y) points. + `gradient` (x, y) : Returns interpolated derivatives at (x, y) points. + + Notes + ----- + This note is a bit technical and details how the cubic interpolation is + computed. + + The interpolation is based on a Clough-Tocher subdivision scheme of + the *triangulation* mesh (to make it clearer, each triangle of the + grid will be divided in 3 child-triangles, and on each child triangle + the interpolated function is a cubic polynomial of the 2 coordinates). + This technique originates from FEM (Finite Element Method) analysis; + the element used is a reduced Hsieh-Clough-Tocher (HCT) + element. Its shape functions are described in [1]_. + The assembled function is guaranteed to be C1-smooth, i.e. it is + continuous and its first derivatives are also continuous (this + is easy to show inside the triangles but is also true when crossing the + edges). + + In the default case (*kind* ='min_E'), the interpolant minimizes a + curvature energy on the functional space generated by the HCT element + shape functions - with imposed values but arbitrary derivatives at each + node. The minimized functional is the integral of the so-called total + curvature (implementation based on an algorithm from [2]_ - PCG sparse + solver): + + .. math:: + + E(z) = \frac{1}{2} \int_{\Omega} \left( + \left( \frac{\partial^2{z}}{\partial{x}^2} \right)^2 + + \left( \frac{\partial^2{z}}{\partial{y}^2} \right)^2 + + 2\left( \frac{\partial^2{z}}{\partial{y}\partial{x}} \right)^2 + \right) dx\,dy + + If the case *kind* ='geom' is chosen by the user, a simple geometric + approximation is used (weighted average of the triangle normal + vectors), which could improve speed on very large grids. + + References + ---------- + .. [1] Michel Bernadou, Kamal Hassan, "Basis functions for general + Hsieh-Clough-Tocher triangles, complete or reduced.", + International Journal for Numerical Methods in Engineering, + 17(5):784 - 789. 2.01. + .. [2] C.T. Kelley, "Iterative Methods for Optimization". + + """ + def __init__(self, triangulation, z, kind='min_E', trifinder=None, + dz=None): + super().__init__(triangulation, z, trifinder) + + # Loads the underlying c++ _triangulation. + # (During loading, reordering of triangulation._triangles may occur so + # that all final triangles are now anti-clockwise) + self._triangulation.get_cpp_triangulation() + + # To build the stiffness matrix and avoid zero-energy spurious modes + # we will only store internally the valid (unmasked) triangles and + # the necessary (used) points coordinates. + # 2 renumbering tables need to be computed and stored: + # - a triangle renum table in order to translate the result from a + # TriFinder instance into the internal stored triangle number. + # - a node renum table to overwrite the self._z values into the new + # (used) node numbering. + tri_analyzer = TriAnalyzer(self._triangulation) + (compressed_triangles, compressed_x, compressed_y, tri_renum, + node_renum) = tri_analyzer._get_compressed_triangulation() + self._triangles = compressed_triangles + self._tri_renum = tri_renum + # Taking into account the node renumbering in self._z: + valid_node = (node_renum != -1) + self._z[node_renum[valid_node]] = self._z[valid_node] + + # Computing scale factors + self._unit_x = np.ptp(compressed_x) + self._unit_y = np.ptp(compressed_y) + self._pts = np.column_stack([compressed_x / self._unit_x, + compressed_y / self._unit_y]) + # Computing triangle points + self._tris_pts = self._pts[self._triangles] + # Computing eccentricities + self._eccs = self._compute_tri_eccentricities(self._tris_pts) + # Computing dof estimations for HCT triangle shape function + _api.check_in_list(['user', 'geom', 'min_E'], kind=kind) + self._dof = self._compute_dof(kind, dz=dz) + # Loading HCT element + self._ReferenceElement = _ReducedHCT_Element() + + def __call__(self, x, y): + return self._interpolate_multikeys(x, y, tri_index=None, + return_keys=('z',))[0] + __call__.__doc__ = TriInterpolator._docstring__call__ + + def gradient(self, x, y): + return self._interpolate_multikeys(x, y, tri_index=None, + return_keys=('dzdx', 'dzdy')) + gradient.__doc__ = TriInterpolator._docstringgradient + + def _interpolate_single_key(self, return_key, tri_index, x, y): + _api.check_in_list(['z', 'dzdx', 'dzdy'], return_key=return_key) + tris_pts = self._tris_pts[tri_index] + alpha = self._get_alpha_vec(x, y, tris_pts) + ecc = self._eccs[tri_index] + dof = np.expand_dims(self._dof[tri_index], axis=1) + if return_key == 'z': + return self._ReferenceElement.get_function_values( + alpha, ecc, dof) + else: # 'dzdx', 'dzdy' + J = self._get_jacobian(tris_pts) + dzdx = self._ReferenceElement.get_function_derivatives( + alpha, J, ecc, dof) + if return_key == 'dzdx': + return dzdx[:, 0, 0] + else: + return dzdx[:, 1, 0] + + def _compute_dof(self, kind, dz=None): + """ + Compute and return nodal dofs according to kind. + + Parameters + ---------- + kind : {'min_E', 'geom', 'user'} + Choice of the _DOF_estimator subclass to estimate the gradient. + dz : tuple of array-likes (dzdx, dzdy), optional + Used only if *kind*=user; in this case passed to the + :class:`_DOF_estimator_user`. + + Returns + ------- + array-like, shape (npts, 2) + Estimation of the gradient at triangulation nodes (stored as + degree of freedoms of reduced-HCT triangle elements). + """ + if kind == 'user': + if dz is None: + raise ValueError("For a CubicTriInterpolator with " + "*kind*='user', a valid *dz* " + "argument is expected.") + TE = _DOF_estimator_user(self, dz=dz) + elif kind == 'geom': + TE = _DOF_estimator_geom(self) + else: # 'min_E', checked in __init__ + TE = _DOF_estimator_min_E(self) + return TE.compute_dof_from_df() + + @staticmethod + def _get_alpha_vec(x, y, tris_pts): + """ + Fast (vectorized) function to compute barycentric coordinates alpha. + + Parameters + ---------- + x, y : array-like of dim 1 (shape (nx,)) + Coordinates of the points whose points barycentric coordinates are + requested. + tris_pts : array like of dim 3 (shape: (nx, 3, 2)) + Coordinates of the containing triangles apexes. + + Returns + ------- + array of dim 2 (shape (nx, 3)) + Barycentric coordinates of the points inside the containing + triangles. + """ + ndim = tris_pts.ndim-2 + + a = tris_pts[:, 1, :] - tris_pts[:, 0, :] + b = tris_pts[:, 2, :] - tris_pts[:, 0, :] + abT = np.stack([a, b], axis=-1) + ab = _transpose_vectorized(abT) + OM = np.stack([x, y], axis=1) - tris_pts[:, 0, :] + + metric = ab @ abT + # Here we try to deal with the colinear cases. + # metric_inv is in this case set to the Moore-Penrose pseudo-inverse + # meaning that we will still return a set of valid barycentric + # coordinates. + metric_inv = _pseudo_inv22sym_vectorized(metric) + Covar = ab @ _transpose_vectorized(np.expand_dims(OM, ndim)) + ksi = metric_inv @ Covar + alpha = _to_matrix_vectorized([ + [1-ksi[:, 0, 0]-ksi[:, 1, 0]], [ksi[:, 0, 0]], [ksi[:, 1, 0]]]) + return alpha + + @staticmethod + def _get_jacobian(tris_pts): + """ + Fast (vectorized) function to compute triangle jacobian matrix. + + Parameters + ---------- + tris_pts : array like of dim 3 (shape: (nx, 3, 2)) + Coordinates of the containing triangles apexes. + + Returns + ------- + array of dim 3 (shape (nx, 2, 2)) + Barycentric coordinates of the points inside the containing + triangles. + J[itri, :, :] is the jacobian matrix at apex 0 of the triangle + itri, so that the following (matrix) relationship holds: + [dz/dksi] = [J] x [dz/dx] + with x: global coordinates + ksi: element parametric coordinates in triangle first apex + local basis. + """ + a = np.array(tris_pts[:, 1, :] - tris_pts[:, 0, :]) + b = np.array(tris_pts[:, 2, :] - tris_pts[:, 0, :]) + J = _to_matrix_vectorized([[a[:, 0], a[:, 1]], + [b[:, 0], b[:, 1]]]) + return J + + @staticmethod + def _compute_tri_eccentricities(tris_pts): + """ + Compute triangle eccentricities. + + Parameters + ---------- + tris_pts : array like of dim 3 (shape: (nx, 3, 2)) + Coordinates of the triangles apexes. + + Returns + ------- + array like of dim 2 (shape: (nx, 3)) + The so-called eccentricity parameters [1] needed for HCT triangular + element. + """ + a = np.expand_dims(tris_pts[:, 2, :] - tris_pts[:, 1, :], axis=2) + b = np.expand_dims(tris_pts[:, 0, :] - tris_pts[:, 2, :], axis=2) + c = np.expand_dims(tris_pts[:, 1, :] - tris_pts[:, 0, :], axis=2) + # Do not use np.squeeze, this is dangerous if only one triangle + # in the triangulation... + dot_a = (_transpose_vectorized(a) @ a)[:, 0, 0] + dot_b = (_transpose_vectorized(b) @ b)[:, 0, 0] + dot_c = (_transpose_vectorized(c) @ c)[:, 0, 0] + # Note that this line will raise a warning for dot_a, dot_b or dot_c + # zeros, but we choose not to support triangles with duplicate points. + return _to_matrix_vectorized([[(dot_c-dot_b) / dot_a], + [(dot_a-dot_c) / dot_b], + [(dot_b-dot_a) / dot_c]]) + + +# FEM element used for interpolation and for solving minimisation +# problem (Reduced HCT element) +class _ReducedHCT_Element: + """ + Implementation of reduced HCT triangular element with explicit shape + functions. + + Computes z, dz, d2z and the element stiffness matrix for bending energy: + E(f) = integral( (d2z/dx2 + d2z/dy2)**2 dA) + + *** Reference for the shape functions: *** + [1] Basis functions for general Hsieh-Clough-Tocher _triangles, complete or + reduced. + Michel Bernadou, Kamal Hassan + International Journal for Numerical Methods in Engineering. + 17(5):784 - 789. 2.01 + + *** Element description: *** + 9 dofs: z and dz given at 3 apex + C1 (conform) + + """ + # 1) Loads matrices to generate shape functions as a function of + # triangle eccentricities - based on [1] p.11 ''' + M = np.array([ + [ 0.00, 0.00, 0.00, 4.50, 4.50, 0.00, 0.00, 0.00, 0.00, 0.00], + [-0.25, 0.00, 0.00, 0.50, 1.25, 0.00, 0.00, 0.00, 0.00, 0.00], + [-0.25, 0.00, 0.00, 1.25, 0.50, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.50, 1.00, 0.00, -1.50, 0.00, 3.00, 3.00, 0.00, 0.00, 3.00], + [ 0.00, 0.00, 0.00, -0.25, 0.25, 0.00, 1.00, 0.00, 0.00, 0.50], + [ 0.25, 0.00, 0.00, -0.50, -0.25, 1.00, 0.00, 0.00, 0.00, 1.00], + [ 0.50, 0.00, 1.00, 0.00, -1.50, 0.00, 0.00, 3.00, 3.00, 3.00], + [ 0.25, 0.00, 0.00, -0.25, -0.50, 0.00, 0.00, 0.00, 1.00, 1.00], + [ 0.00, 0.00, 0.00, 0.25, -0.25, 0.00, 0.00, 1.00, 0.00, 0.50]]) + M0 = np.array([ + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [-1.00, 0.00, 0.00, 1.50, 1.50, 0.00, 0.00, 0.00, 0.00, -3.00], + [-0.50, 0.00, 0.00, 0.75, 0.75, 0.00, 0.00, 0.00, 0.00, -1.50], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 1.00, 0.00, 0.00, -1.50, -1.50, 0.00, 0.00, 0.00, 0.00, 3.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.50, 0.00, 0.00, -0.75, -0.75, 0.00, 0.00, 0.00, 0.00, 1.50]]) + M1 = np.array([ + [-0.50, 0.00, 0.00, 1.50, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [-0.25, 0.00, 0.00, 0.75, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.50, 0.00, 0.00, -1.50, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.25, 0.00, 0.00, -0.75, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]]) + M2 = np.array([ + [ 0.50, 0.00, 0.00, 0.00, -1.50, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.25, 0.00, 0.00, 0.00, -0.75, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [-0.50, 0.00, 0.00, 0.00, 1.50, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [-0.25, 0.00, 0.00, 0.00, 0.75, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]]) + + # 2) Loads matrices to rotate components of gradient & Hessian + # vectors in the reference basis of triangle first apex (a0) + rotate_dV = np.array([[ 1., 0.], [ 0., 1.], + [ 0., 1.], [-1., -1.], + [-1., -1.], [ 1., 0.]]) + + rotate_d2V = np.array([[1., 0., 0.], [0., 1., 0.], [ 0., 0., 1.], + [0., 1., 0.], [1., 1., 1.], [ 0., -2., -1.], + [1., 1., 1.], [1., 0., 0.], [-2., 0., -1.]]) + + # 3) Loads Gauss points & weights on the 3 sub-_triangles for P2 + # exact integral - 3 points on each subtriangles. + # NOTE: as the 2nd derivative is discontinuous , we really need those 9 + # points! + n_gauss = 9 + gauss_pts = np.array([[13./18., 4./18., 1./18.], + [ 4./18., 13./18., 1./18.], + [ 7./18., 7./18., 4./18.], + [ 1./18., 13./18., 4./18.], + [ 1./18., 4./18., 13./18.], + [ 4./18., 7./18., 7./18.], + [ 4./18., 1./18., 13./18.], + [13./18., 1./18., 4./18.], + [ 7./18., 4./18., 7./18.]], dtype=np.float64) + gauss_w = np.ones([9], dtype=np.float64) / 9. + + # 4) Stiffness matrix for curvature energy + E = np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 2.]]) + + # 5) Loads the matrix to compute DOF_rot from tri_J at apex 0 + J0_to_J1 = np.array([[-1., 1.], [-1., 0.]]) + J0_to_J2 = np.array([[ 0., -1.], [ 1., -1.]]) + + def get_function_values(self, alpha, ecc, dofs): + """ + Parameters + ---------- + alpha : is a (N x 3 x 1) array (array of column-matrices) of + barycentric coordinates, + ecc : is a (N x 3 x 1) array (array of column-matrices) of triangle + eccentricities, + dofs : is a (N x 1 x 9) arrays (arrays of row-matrices) of computed + degrees of freedom. + + Returns + ------- + Returns the N-array of interpolated function values. + """ + subtri = np.argmin(alpha, axis=1)[:, 0] + ksi = _roll_vectorized(alpha, -subtri, axis=0) + E = _roll_vectorized(ecc, -subtri, axis=0) + x = ksi[:, 0, 0] + y = ksi[:, 1, 0] + z = ksi[:, 2, 0] + x_sq = x*x + y_sq = y*y + z_sq = z*z + V = _to_matrix_vectorized([ + [x_sq*x], [y_sq*y], [z_sq*z], [x_sq*z], [x_sq*y], [y_sq*x], + [y_sq*z], [z_sq*y], [z_sq*x], [x*y*z]]) + prod = self.M @ V + prod += _scalar_vectorized(E[:, 0, 0], self.M0 @ V) + prod += _scalar_vectorized(E[:, 1, 0], self.M1 @ V) + prod += _scalar_vectorized(E[:, 2, 0], self.M2 @ V) + s = _roll_vectorized(prod, 3*subtri, axis=0) + return (dofs @ s)[:, 0, 0] + + def get_function_derivatives(self, alpha, J, ecc, dofs): + """ + Parameters + ---------- + *alpha* is a (N x 3 x 1) array (array of column-matrices of + barycentric coordinates) + *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at + triangle first apex) + *ecc* is a (N x 3 x 1) array (array of column-matrices of triangle + eccentricities) + *dofs* is a (N x 1 x 9) arrays (arrays of row-matrices) of computed + degrees of freedom. + + Returns + ------- + Returns the values of interpolated function derivatives [dz/dx, dz/dy] + in global coordinates at locations alpha, as a column-matrices of + shape (N x 2 x 1). + """ + subtri = np.argmin(alpha, axis=1)[:, 0] + ksi = _roll_vectorized(alpha, -subtri, axis=0) + E = _roll_vectorized(ecc, -subtri, axis=0) + x = ksi[:, 0, 0] + y = ksi[:, 1, 0] + z = ksi[:, 2, 0] + x_sq = x*x + y_sq = y*y + z_sq = z*z + dV = _to_matrix_vectorized([ + [ -3.*x_sq, -3.*x_sq], + [ 3.*y_sq, 0.], + [ 0., 3.*z_sq], + [ -2.*x*z, -2.*x*z+x_sq], + [-2.*x*y+x_sq, -2.*x*y], + [ 2.*x*y-y_sq, -y_sq], + [ 2.*y*z, y_sq], + [ z_sq, 2.*y*z], + [ -z_sq, 2.*x*z-z_sq], + [ x*z-y*z, x*y-y*z]]) + # Puts back dV in first apex basis + dV = dV @ _extract_submatrices( + self.rotate_dV, subtri, block_size=2, axis=0) + + prod = self.M @ dV + prod += _scalar_vectorized(E[:, 0, 0], self.M0 @ dV) + prod += _scalar_vectorized(E[:, 1, 0], self.M1 @ dV) + prod += _scalar_vectorized(E[:, 2, 0], self.M2 @ dV) + dsdksi = _roll_vectorized(prod, 3*subtri, axis=0) + dfdksi = dofs @ dsdksi + # In global coordinates: + # Here we try to deal with the simplest colinear cases, returning a + # null matrix. + J_inv = _safe_inv22_vectorized(J) + dfdx = J_inv @ _transpose_vectorized(dfdksi) + return dfdx + + def get_function_hessians(self, alpha, J, ecc, dofs): + """ + Parameters + ---------- + *alpha* is a (N x 3 x 1) array (array of column-matrices) of + barycentric coordinates + *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at + triangle first apex) + *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle + eccentricities + *dofs* is a (N x 1 x 9) arrays (arrays of row-matrices) of computed + degrees of freedom. + + Returns + ------- + Returns the values of interpolated function 2nd-derivatives + [d2z/dx2, d2z/dy2, d2z/dxdy] in global coordinates at locations alpha, + as a column-matrices of shape (N x 3 x 1). + """ + d2sdksi2 = self.get_d2Sidksij2(alpha, ecc) + d2fdksi2 = dofs @ d2sdksi2 + H_rot = self.get_Hrot_from_J(J) + d2fdx2 = d2fdksi2 @ H_rot + return _transpose_vectorized(d2fdx2) + + def get_d2Sidksij2(self, alpha, ecc): + """ + Parameters + ---------- + *alpha* is a (N x 3 x 1) array (array of column-matrices) of + barycentric coordinates + *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle + eccentricities + + Returns + ------- + Returns the arrays d2sdksi2 (N x 3 x 1) Hessian of shape functions + expressed in covariant coordinates in first apex basis. + """ + subtri = np.argmin(alpha, axis=1)[:, 0] + ksi = _roll_vectorized(alpha, -subtri, axis=0) + E = _roll_vectorized(ecc, -subtri, axis=0) + x = ksi[:, 0, 0] + y = ksi[:, 1, 0] + z = ksi[:, 2, 0] + d2V = _to_matrix_vectorized([ + [ 6.*x, 6.*x, 6.*x], + [ 6.*y, 0., 0.], + [ 0., 6.*z, 0.], + [ 2.*z, 2.*z-4.*x, 2.*z-2.*x], + [2.*y-4.*x, 2.*y, 2.*y-2.*x], + [2.*x-4.*y, 0., -2.*y], + [ 2.*z, 0., 2.*y], + [ 0., 2.*y, 2.*z], + [ 0., 2.*x-4.*z, -2.*z], + [ -2.*z, -2.*y, x-y-z]]) + # Puts back d2V in first apex basis + d2V = d2V @ _extract_submatrices( + self.rotate_d2V, subtri, block_size=3, axis=0) + prod = self.M @ d2V + prod += _scalar_vectorized(E[:, 0, 0], self.M0 @ d2V) + prod += _scalar_vectorized(E[:, 1, 0], self.M1 @ d2V) + prod += _scalar_vectorized(E[:, 2, 0], self.M2 @ d2V) + d2sdksi2 = _roll_vectorized(prod, 3*subtri, axis=0) + return d2sdksi2 + + def get_bending_matrices(self, J, ecc): + """ + Parameters + ---------- + *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at + triangle first apex) + *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle + eccentricities + + Returns + ------- + Returns the element K matrices for bending energy expressed in + GLOBAL nodal coordinates. + K_ij = integral [ (d2zi/dx2 + d2zi/dy2) * (d2zj/dx2 + d2zj/dy2) dA] + tri_J is needed to rotate dofs from local basis to global basis + """ + n = np.size(ecc, 0) + + # 1) matrix to rotate dofs in global coordinates + J1 = self.J0_to_J1 @ J + J2 = self.J0_to_J2 @ J + DOF_rot = np.zeros([n, 9, 9], dtype=np.float64) + DOF_rot[:, 0, 0] = 1 + DOF_rot[:, 3, 3] = 1 + DOF_rot[:, 6, 6] = 1 + DOF_rot[:, 1:3, 1:3] = J + DOF_rot[:, 4:6, 4:6] = J1 + DOF_rot[:, 7:9, 7:9] = J2 + + # 2) matrix to rotate Hessian in global coordinates. + H_rot, area = self.get_Hrot_from_J(J, return_area=True) + + # 3) Computes stiffness matrix + # Gauss quadrature. + K = np.zeros([n, 9, 9], dtype=np.float64) + weights = self.gauss_w + pts = self.gauss_pts + for igauss in range(self.n_gauss): + alpha = np.tile(pts[igauss, :], n).reshape(n, 3) + alpha = np.expand_dims(alpha, 2) + weight = weights[igauss] + d2Skdksi2 = self.get_d2Sidksij2(alpha, ecc) + d2Skdx2 = d2Skdksi2 @ H_rot + K += weight * (d2Skdx2 @ self.E @ _transpose_vectorized(d2Skdx2)) + + # 4) With nodal (not elem) dofs + K = _transpose_vectorized(DOF_rot) @ K @ DOF_rot + + # 5) Need the area to compute total element energy + return _scalar_vectorized(area, K) + + def get_Hrot_from_J(self, J, return_area=False): + """ + Parameters + ---------- + *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at + triangle first apex) + + Returns + ------- + Returns H_rot used to rotate Hessian from local basis of first apex, + to global coordinates. + if *return_area* is True, returns also the triangle area (0.5*det(J)) + """ + # Here we try to deal with the simplest colinear cases; a null + # energy and area is imposed. + J_inv = _safe_inv22_vectorized(J) + Ji00 = J_inv[:, 0, 0] + Ji11 = J_inv[:, 1, 1] + Ji10 = J_inv[:, 1, 0] + Ji01 = J_inv[:, 0, 1] + H_rot = _to_matrix_vectorized([ + [Ji00*Ji00, Ji10*Ji10, Ji00*Ji10], + [Ji01*Ji01, Ji11*Ji11, Ji01*Ji11], + [2*Ji00*Ji01, 2*Ji11*Ji10, Ji00*Ji11+Ji10*Ji01]]) + if not return_area: + return H_rot + else: + area = 0.5 * (J[:, 0, 0]*J[:, 1, 1] - J[:, 0, 1]*J[:, 1, 0]) + return H_rot, area + + def get_Kff_and_Ff(self, J, ecc, triangles, Uc): + """ + Build K and F for the following elliptic formulation: + minimization of curvature energy with value of function at node + imposed and derivatives 'free'. + + Build the global Kff matrix in cco format. + Build the full Ff vec Ff = - Kfc x Uc. + + Parameters + ---------- + *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at + triangle first apex) + *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle + eccentricities + *triangles* is a (N x 3) array of nodes indexes. + *Uc* is (N x 3) array of imposed displacements at nodes + + Returns + ------- + (Kff_rows, Kff_cols, Kff_vals) Kff matrix in coo format - Duplicate + (row, col) entries must be summed. + Ff: force vector - dim npts * 3 + """ + ntri = np.size(ecc, 0) + vec_range = np.arange(ntri, dtype=np.int32) + c_indices = np.full(ntri, -1, dtype=np.int32) # for unused dofs, -1 + f_dof = [1, 2, 4, 5, 7, 8] + c_dof = [0, 3, 6] + + # vals, rows and cols indices in global dof numbering + f_dof_indices = _to_matrix_vectorized([[ + c_indices, triangles[:, 0]*2, triangles[:, 0]*2+1, + c_indices, triangles[:, 1]*2, triangles[:, 1]*2+1, + c_indices, triangles[:, 2]*2, triangles[:, 2]*2+1]]) + + expand_indices = np.ones([ntri, 9, 1], dtype=np.int32) + f_row_indices = _transpose_vectorized(expand_indices @ f_dof_indices) + f_col_indices = expand_indices @ f_dof_indices + K_elem = self.get_bending_matrices(J, ecc) + + # Extracting sub-matrices + # Explanation & notations: + # * Subscript f denotes 'free' degrees of freedom (i.e. dz/dx, dz/dx) + # * Subscript c denotes 'condensated' (imposed) degrees of freedom + # (i.e. z at all nodes) + # * F = [Ff, Fc] is the force vector + # * U = [Uf, Uc] is the imposed dof vector + # [ Kff Kfc ] + # * K = [ ] is the laplacian stiffness matrix + # [ Kcf Kff ] + # * As F = K x U one gets straightforwardly: Ff = - Kfc x Uc + + # Computing Kff stiffness matrix in sparse coo format + Kff_vals = np.ravel(K_elem[np.ix_(vec_range, f_dof, f_dof)]) + Kff_rows = np.ravel(f_row_indices[np.ix_(vec_range, f_dof, f_dof)]) + Kff_cols = np.ravel(f_col_indices[np.ix_(vec_range, f_dof, f_dof)]) + + # Computing Ff force vector in sparse coo format + Kfc_elem = K_elem[np.ix_(vec_range, f_dof, c_dof)] + Uc_elem = np.expand_dims(Uc, axis=2) + Ff_elem = -(Kfc_elem @ Uc_elem)[:, :, 0] + Ff_indices = f_dof_indices[np.ix_(vec_range, [0], f_dof)][:, 0, :] + + # Extracting Ff force vector in dense format + # We have to sum duplicate indices - using bincount + Ff = np.bincount(np.ravel(Ff_indices), weights=np.ravel(Ff_elem)) + return Kff_rows, Kff_cols, Kff_vals, Ff + + +# :class:_DOF_estimator, _DOF_estimator_user, _DOF_estimator_geom, +# _DOF_estimator_min_E +# Private classes used to compute the degree of freedom of each triangular +# element for the TriCubicInterpolator. +class _DOF_estimator: + """ + Abstract base class for classes used to estimate a function's first + derivatives, and deduce the dofs for a CubicTriInterpolator using a + reduced HCT element formulation. + + Derived classes implement ``compute_df(self, **kwargs)``, returning + ``np.vstack([dfx, dfy]).T`` where ``dfx, dfy`` are the estimation of the 2 + gradient coordinates. + """ + def __init__(self, interpolator, **kwargs): + _api.check_isinstance(CubicTriInterpolator, interpolator=interpolator) + self._pts = interpolator._pts + self._tris_pts = interpolator._tris_pts + self.z = interpolator._z + self._triangles = interpolator._triangles + (self._unit_x, self._unit_y) = (interpolator._unit_x, + interpolator._unit_y) + self.dz = self.compute_dz(**kwargs) + self.compute_dof_from_df() + + def compute_dz(self, **kwargs): + raise NotImplementedError + + def compute_dof_from_df(self): + """ + Compute reduced-HCT elements degrees of freedom, from the gradient. + """ + J = CubicTriInterpolator._get_jacobian(self._tris_pts) + tri_z = self.z[self._triangles] + tri_dz = self.dz[self._triangles] + tri_dof = self.get_dof_vec(tri_z, tri_dz, J) + return tri_dof + + @staticmethod + def get_dof_vec(tri_z, tri_dz, J): + """ + Compute the dof vector of a triangle, from the value of f, df and + of the local Jacobian at each node. + + Parameters + ---------- + tri_z : shape (3,) array + f nodal values. + tri_dz : shape (3, 2) array + df/dx, df/dy nodal values. + J + Jacobian matrix in local basis of apex 0. + + Returns + ------- + dof : shape (9,) array + For each apex ``iapex``:: + + dof[iapex*3+0] = f(Ai) + dof[iapex*3+1] = df(Ai).(AiAi+) + dof[iapex*3+2] = df(Ai).(AiAi-) + """ + npt = tri_z.shape[0] + dof = np.zeros([npt, 9], dtype=np.float64) + J1 = _ReducedHCT_Element.J0_to_J1 @ J + J2 = _ReducedHCT_Element.J0_to_J2 @ J + + col0 = J @ np.expand_dims(tri_dz[:, 0, :], axis=2) + col1 = J1 @ np.expand_dims(tri_dz[:, 1, :], axis=2) + col2 = J2 @ np.expand_dims(tri_dz[:, 2, :], axis=2) + + dfdksi = _to_matrix_vectorized([ + [col0[:, 0, 0], col1[:, 0, 0], col2[:, 0, 0]], + [col0[:, 1, 0], col1[:, 1, 0], col2[:, 1, 0]]]) + dof[:, 0:7:3] = tri_z + dof[:, 1:8:3] = dfdksi[:, 0] + dof[:, 2:9:3] = dfdksi[:, 1] + return dof + + +class _DOF_estimator_user(_DOF_estimator): + """dz is imposed by user; accounts for scaling if any.""" + + def compute_dz(self, dz): + (dzdx, dzdy) = dz + dzdx = dzdx * self._unit_x + dzdy = dzdy * self._unit_y + return np.vstack([dzdx, dzdy]).T + + +class _DOF_estimator_geom(_DOF_estimator): + """Fast 'geometric' approximation, recommended for large arrays.""" + + def compute_dz(self): + """ + self.df is computed as weighted average of _triangles sharing a common + node. On each triangle itri f is first assumed linear (= ~f), which + allows to compute d~f[itri] + Then the following approximation of df nodal values is then proposed: + f[ipt] = SUM ( w[itri] x d~f[itri] , for itri sharing apex ipt) + The weighted coeff. w[itri] are proportional to the angle of the + triangle itri at apex ipt + """ + el_geom_w = self.compute_geom_weights() + el_geom_grad = self.compute_geom_grads() + + # Sum of weights coeffs + w_node_sum = np.bincount(np.ravel(self._triangles), + weights=np.ravel(el_geom_w)) + + # Sum of weighted df = (dfx, dfy) + dfx_el_w = np.empty_like(el_geom_w) + dfy_el_w = np.empty_like(el_geom_w) + for iapex in range(3): + dfx_el_w[:, iapex] = el_geom_w[:, iapex]*el_geom_grad[:, 0] + dfy_el_w[:, iapex] = el_geom_w[:, iapex]*el_geom_grad[:, 1] + dfx_node_sum = np.bincount(np.ravel(self._triangles), + weights=np.ravel(dfx_el_w)) + dfy_node_sum = np.bincount(np.ravel(self._triangles), + weights=np.ravel(dfy_el_w)) + + # Estimation of df + dfx_estim = dfx_node_sum/w_node_sum + dfy_estim = dfy_node_sum/w_node_sum + return np.vstack([dfx_estim, dfy_estim]).T + + def compute_geom_weights(self): + """ + Build the (nelems, 3) weights coeffs of _triangles angles, + renormalized so that np.sum(weights, axis=1) == np.ones(nelems) + """ + weights = np.zeros([np.size(self._triangles, 0), 3]) + tris_pts = self._tris_pts + for ipt in range(3): + p0 = tris_pts[:, ipt % 3, :] + p1 = tris_pts[:, (ipt+1) % 3, :] + p2 = tris_pts[:, (ipt-1) % 3, :] + alpha1 = np.arctan2(p1[:, 1]-p0[:, 1], p1[:, 0]-p0[:, 0]) + alpha2 = np.arctan2(p2[:, 1]-p0[:, 1], p2[:, 0]-p0[:, 0]) + # In the below formula we could take modulo 2. but + # modulo 1. is safer regarding round-off errors (flat triangles). + angle = np.abs(((alpha2-alpha1) / np.pi) % 1) + # Weight proportional to angle up np.pi/2; null weight for + # degenerated cases 0 and np.pi (note that *angle* is normalized + # by np.pi). + weights[:, ipt] = 0.5 - np.abs(angle-0.5) + return weights + + def compute_geom_grads(self): + """ + Compute the (global) gradient component of f assumed linear (~f). + returns array df of shape (nelems, 2) + df[ielem].dM[ielem] = dz[ielem] i.e. df = dz x dM = dM.T^-1 x dz + """ + tris_pts = self._tris_pts + tris_f = self.z[self._triangles] + + dM1 = tris_pts[:, 1, :] - tris_pts[:, 0, :] + dM2 = tris_pts[:, 2, :] - tris_pts[:, 0, :] + dM = np.dstack([dM1, dM2]) + # Here we try to deal with the simplest colinear cases: a null + # gradient is assumed in this case. + dM_inv = _safe_inv22_vectorized(dM) + + dZ1 = tris_f[:, 1] - tris_f[:, 0] + dZ2 = tris_f[:, 2] - tris_f[:, 0] + dZ = np.vstack([dZ1, dZ2]).T + df = np.empty_like(dZ) + + # With np.einsum: could be ej,eji -> ej + df[:, 0] = dZ[:, 0]*dM_inv[:, 0, 0] + dZ[:, 1]*dM_inv[:, 1, 0] + df[:, 1] = dZ[:, 0]*dM_inv[:, 0, 1] + dZ[:, 1]*dM_inv[:, 1, 1] + return df + + +class _DOF_estimator_min_E(_DOF_estimator_geom): + """ + The 'smoothest' approximation, df is computed through global minimization + of the bending energy: + E(f) = integral[(d2z/dx2 + d2z/dy2 + 2 d2z/dxdy)**2 dA] + """ + def __init__(self, Interpolator): + self._eccs = Interpolator._eccs + super().__init__(Interpolator) + + def compute_dz(self): + """ + Elliptic solver for bending energy minimization. + Uses a dedicated 'toy' sparse Jacobi PCG solver. + """ + # Initial guess for iterative PCG solver. + dz_init = super().compute_dz() + Uf0 = np.ravel(dz_init) + + reference_element = _ReducedHCT_Element() + J = CubicTriInterpolator._get_jacobian(self._tris_pts) + eccs = self._eccs + triangles = self._triangles + Uc = self.z[self._triangles] + + # Building stiffness matrix and force vector in coo format + Kff_rows, Kff_cols, Kff_vals, Ff = reference_element.get_Kff_and_Ff( + J, eccs, triangles, Uc) + + # Building sparse matrix and solving minimization problem + # We could use scipy.sparse direct solver; however to avoid this + # external dependency an implementation of a simple PCG solver with + # a simple diagonal Jacobi preconditioner is implemented. + tol = 1.e-10 + n_dof = Ff.shape[0] + Kff_coo = _Sparse_Matrix_coo(Kff_vals, Kff_rows, Kff_cols, + shape=(n_dof, n_dof)) + Kff_coo.compress_csc() + Uf, err = _cg(A=Kff_coo, b=Ff, x0=Uf0, tol=tol) + # If the PCG did not converge, we return the best guess between Uf0 + # and Uf. + err0 = np.linalg.norm(Kff_coo.dot(Uf0) - Ff) + if err0 < err: + # Maybe a good occasion to raise a warning here ? + _api.warn_external("In TriCubicInterpolator initialization, " + "PCG sparse solver did not converge after " + "1000 iterations. `geom` approximation is " + "used instead of `min_E`") + Uf = Uf0 + + # Building dz from Uf + dz = np.empty([self._pts.shape[0], 2], dtype=np.float64) + dz[:, 0] = Uf[::2] + dz[:, 1] = Uf[1::2] + return dz + + +# The following private :class:_Sparse_Matrix_coo and :func:_cg provide +# a PCG sparse solver for (symmetric) elliptic problems. +class _Sparse_Matrix_coo: + def __init__(self, vals, rows, cols, shape): + """ + Create a sparse matrix in coo format. + *vals*: arrays of values of non-null entries of the matrix + *rows*: int arrays of rows of non-null entries of the matrix + *cols*: int arrays of cols of non-null entries of the matrix + *shape*: 2-tuple (n, m) of matrix shape + """ + self.n, self.m = shape + self.vals = np.asarray(vals, dtype=np.float64) + self.rows = np.asarray(rows, dtype=np.int32) + self.cols = np.asarray(cols, dtype=np.int32) + + def dot(self, V): + """ + Dot product of self by a vector *V* in sparse-dense to dense format + *V* dense vector of shape (self.m,). + """ + assert V.shape == (self.m,) + return np.bincount(self.rows, + weights=self.vals*V[self.cols], + minlength=self.m) + + def compress_csc(self): + """ + Compress rows, cols, vals / summing duplicates. Sort for csc format. + """ + _, unique, indices = np.unique( + self.rows + self.n*self.cols, + return_index=True, return_inverse=True) + self.rows = self.rows[unique] + self.cols = self.cols[unique] + self.vals = np.bincount(indices, weights=self.vals) + + def compress_csr(self): + """ + Compress rows, cols, vals / summing duplicates. Sort for csr format. + """ + _, unique, indices = np.unique( + self.m*self.rows + self.cols, + return_index=True, return_inverse=True) + self.rows = self.rows[unique] + self.cols = self.cols[unique] + self.vals = np.bincount(indices, weights=self.vals) + + def to_dense(self): + """ + Return a dense matrix representing self, mainly for debugging purposes. + """ + ret = np.zeros([self.n, self.m], dtype=np.float64) + nvals = self.vals.size + for i in range(nvals): + ret[self.rows[i], self.cols[i]] += self.vals[i] + return ret + + def __str__(self): + return self.to_dense().__str__() + + @property + def diag(self): + """Return the (dense) vector of the diagonal elements.""" + in_diag = (self.rows == self.cols) + diag = np.zeros(min(self.n, self.n), dtype=np.float64) # default 0. + diag[self.rows[in_diag]] = self.vals[in_diag] + return diag + + +def _cg(A, b, x0=None, tol=1.e-10, maxiter=1000): + """ + Use Preconditioned Conjugate Gradient iteration to solve A x = b + A simple Jacobi (diagonal) preconditioner is used. + + Parameters + ---------- + A : _Sparse_Matrix_coo + *A* must have been compressed before by compress_csc or + compress_csr method. + b : array + Right hand side of the linear system. + x0 : array, optional + Starting guess for the solution. Defaults to the zero vector. + tol : float, optional + Tolerance to achieve. The algorithm terminates when the relative + residual is below tol. Default is 1e-10. + maxiter : int, optional + Maximum number of iterations. Iteration will stop after *maxiter* + steps even if the specified tolerance has not been achieved. Defaults + to 1000. + + Returns + ------- + x : array + The converged solution. + err : float + The absolute error np.linalg.norm(A.dot(x) - b) + """ + n = b.size + assert A.n == n + assert A.m == n + b_norm = np.linalg.norm(b) + + # Jacobi pre-conditioner + kvec = A.diag + # For diag elem < 1e-6 we keep 1e-6. + kvec = np.maximum(kvec, 1e-6) + + # Initial guess + if x0 is None: + x = np.zeros(n) + else: + x = x0 + + r = b - A.dot(x) + w = r/kvec + + p = np.zeros(n) + beta = 0.0 + rho = np.dot(r, w) + k = 0 + + # Following C. T. Kelley + while (np.sqrt(abs(rho)) > tol*b_norm) and (k < maxiter): + p = w + beta*p + z = A.dot(p) + alpha = rho/np.dot(p, z) + r = r - alpha*z + w = r/kvec + rhoold = rho + rho = np.dot(r, w) + x = x + alpha*p + beta = rho/rhoold + # err = np.linalg.norm(A.dot(x) - b) # absolute accuracy - not used + k += 1 + err = np.linalg.norm(A.dot(x) - b) + return x, err + + +# The following private functions: +# :func:`_safe_inv22_vectorized` +# :func:`_pseudo_inv22sym_vectorized` +# :func:`_scalar_vectorized` +# :func:`_transpose_vectorized` +# :func:`_roll_vectorized` +# :func:`_to_matrix_vectorized` +# :func:`_extract_submatrices` +# provide fast numpy implementation of some standard operations on arrays of +# matrices - stored as (:, n_rows, n_cols)-shaped np.arrays. + +# Development note: Dealing with pathologic 'flat' triangles in the +# CubicTriInterpolator code and impact on (2, 2)-matrix inversion functions +# :func:`_safe_inv22_vectorized` and :func:`_pseudo_inv22sym_vectorized`. +# +# Goals: +# 1) The CubicTriInterpolator should be able to handle flat or almost flat +# triangles without raising an error, +# 2) These degenerated triangles should have no impact on the automatic dof +# calculation (associated with null weight for the _DOF_estimator_geom and +# with null energy for the _DOF_estimator_min_E), +# 3) Linear patch test should be passed exactly on degenerated meshes, +# 4) Interpolation (with :meth:`_interpolate_single_key` or +# :meth:`_interpolate_multi_key`) shall be correctly handled even *inside* +# the pathologic triangles, to interact correctly with a TriRefiner class. +# +# Difficulties: +# Flat triangles have rank-deficient *J* (so-called jacobian matrix) and +# *metric* (the metric tensor = J x J.T). Computation of the local +# tangent plane is also problematic. +# +# Implementation: +# Most of the time, when computing the inverse of a rank-deficient matrix it +# is safe to simply return the null matrix (which is the implementation in +# :func:`_safe_inv22_vectorized`). This is because of point 2), itself +# enforced by: +# - null area hence null energy in :class:`_DOF_estimator_min_E` +# - angles close or equal to 0 or np.pi hence null weight in +# :class:`_DOF_estimator_geom`. +# Note that the function angle -> weight is continuous and maximum for an +# angle np.pi/2 (refer to :meth:`compute_geom_weights`) +# The exception is the computation of barycentric coordinates, which is done +# by inversion of the *metric* matrix. In this case, we need to compute a set +# of valid coordinates (1 among numerous possibilities), to ensure point 4). +# We benefit here from the symmetry of metric = J x J.T, which makes it easier +# to compute a pseudo-inverse in :func:`_pseudo_inv22sym_vectorized` +def _safe_inv22_vectorized(M): + """ + Inversion of arrays of (2, 2) matrices, returns 0 for rank-deficient + matrices. + + *M* : array of (2, 2) matrices to inverse, shape (n, 2, 2) + """ + _api.check_shape((None, 2, 2), M=M) + M_inv = np.empty_like(M) + prod1 = M[:, 0, 0]*M[:, 1, 1] + delta = prod1 - M[:, 0, 1]*M[:, 1, 0] + + # We set delta_inv to 0. in case of a rank deficient matrix; a + # rank-deficient input matrix *M* will lead to a null matrix in output + rank2 = (np.abs(delta) > 1e-8*np.abs(prod1)) + if np.all(rank2): + # Normal 'optimized' flow. + delta_inv = 1./delta + else: + # 'Pathologic' flow. + delta_inv = np.zeros(M.shape[0]) + delta_inv[rank2] = 1./delta[rank2] + + M_inv[:, 0, 0] = M[:, 1, 1]*delta_inv + M_inv[:, 0, 1] = -M[:, 0, 1]*delta_inv + M_inv[:, 1, 0] = -M[:, 1, 0]*delta_inv + M_inv[:, 1, 1] = M[:, 0, 0]*delta_inv + return M_inv + + +def _pseudo_inv22sym_vectorized(M): + """ + Inversion of arrays of (2, 2) SYMMETRIC matrices; returns the + (Moore-Penrose) pseudo-inverse for rank-deficient matrices. + + In case M is of rank 1, we have M = trace(M) x P where P is the orthogonal + projection on Im(M), and we return trace(M)^-1 x P == M / trace(M)**2 + In case M is of rank 0, we return the null matrix. + + *M* : array of (2, 2) matrices to inverse, shape (n, 2, 2) + """ + _api.check_shape((None, 2, 2), M=M) + M_inv = np.empty_like(M) + prod1 = M[:, 0, 0]*M[:, 1, 1] + delta = prod1 - M[:, 0, 1]*M[:, 1, 0] + rank2 = (np.abs(delta) > 1e-8*np.abs(prod1)) + + if np.all(rank2): + # Normal 'optimized' flow. + M_inv[:, 0, 0] = M[:, 1, 1] / delta + M_inv[:, 0, 1] = -M[:, 0, 1] / delta + M_inv[:, 1, 0] = -M[:, 1, 0] / delta + M_inv[:, 1, 1] = M[:, 0, 0] / delta + else: + # 'Pathologic' flow. + # Here we have to deal with 2 sub-cases + # 1) First sub-case: matrices of rank 2: + delta = delta[rank2] + M_inv[rank2, 0, 0] = M[rank2, 1, 1] / delta + M_inv[rank2, 0, 1] = -M[rank2, 0, 1] / delta + M_inv[rank2, 1, 0] = -M[rank2, 1, 0] / delta + M_inv[rank2, 1, 1] = M[rank2, 0, 0] / delta + # 2) Second sub-case: rank-deficient matrices of rank 0 and 1: + rank01 = ~rank2 + tr = M[rank01, 0, 0] + M[rank01, 1, 1] + tr_zeros = (np.abs(tr) < 1.e-8) + sq_tr_inv = (1.-tr_zeros) / (tr**2+tr_zeros) + # sq_tr_inv = 1. / tr**2 + M_inv[rank01, 0, 0] = M[rank01, 0, 0] * sq_tr_inv + M_inv[rank01, 0, 1] = M[rank01, 0, 1] * sq_tr_inv + M_inv[rank01, 1, 0] = M[rank01, 1, 0] * sq_tr_inv + M_inv[rank01, 1, 1] = M[rank01, 1, 1] * sq_tr_inv + + return M_inv + + +def _scalar_vectorized(scalar, M): + """ + Scalar product between scalars and matrices. + """ + return scalar[:, np.newaxis, np.newaxis]*M + + +def _transpose_vectorized(M): + """ + Transposition of an array of matrices *M*. + """ + return np.transpose(M, [0, 2, 1]) + + +def _roll_vectorized(M, roll_indices, axis): + """ + Roll an array of matrices along *axis* (0: rows, 1: columns) according to + an array of indices *roll_indices*. + """ + assert axis in [0, 1] + ndim = M.ndim + assert ndim == 3 + ndim_roll = roll_indices.ndim + assert ndim_roll == 1 + sh = M.shape + r, c = sh[-2:] + assert sh[0] == roll_indices.shape[0] + vec_indices = np.arange(sh[0], dtype=np.int32) + + # Builds the rolled matrix + M_roll = np.empty_like(M) + if axis == 0: + for ir in range(r): + for ic in range(c): + M_roll[:, ir, ic] = M[vec_indices, (-roll_indices+ir) % r, ic] + else: # 1 + for ir in range(r): + for ic in range(c): + M_roll[:, ir, ic] = M[vec_indices, ir, (-roll_indices+ic) % c] + return M_roll + + +def _to_matrix_vectorized(M): + """ + Build an array of matrices from individuals np.arrays of identical shapes. + + Parameters + ---------- + M + ncols-list of nrows-lists of shape sh. + + Returns + ------- + M_res : np.array of shape (sh, nrow, ncols) + *M_res* satisfies ``M_res[..., i, j] = M[i][j]``. + """ + assert isinstance(M, (tuple, list)) + assert all(isinstance(item, (tuple, list)) for item in M) + c_vec = np.asarray([len(item) for item in M]) + assert np.all(c_vec-c_vec[0] == 0) + r = len(M) + c = c_vec[0] + M00 = np.asarray(M[0][0]) + dt = M00.dtype + sh = [M00.shape[0], r, c] + M_ret = np.empty(sh, dtype=dt) + for irow in range(r): + for icol in range(c): + M_ret[:, irow, icol] = np.asarray(M[irow][icol]) + return M_ret + + +def _extract_submatrices(M, block_indices, block_size, axis): + """ + Extract selected blocks of a matrices *M* depending on parameters + *block_indices* and *block_size*. + + Returns the array of extracted matrices *Mres* so that :: + + M_res[..., ir, :] = M[(block_indices*block_size+ir), :] + """ + assert block_indices.ndim == 1 + assert axis in [0, 1] + + r, c = M.shape + if axis == 0: + sh = [block_indices.shape[0], block_size, c] + else: # 1 + sh = [block_indices.shape[0], r, block_size] + + dt = M.dtype + M_res = np.empty(sh, dtype=dt) + if axis == 0: + for ir in range(block_size): + M_res[:, ir, :] = M[(block_indices*block_size+ir), :] + else: # 1 + for ic in range(block_size): + M_res[:, :, ic] = M[:, (block_indices*block_size+ic)] + + return M_res diff --git a/lib/matplotlib/tri/_triinterpolate.pyi b/lib/matplotlib/tri/_triinterpolate.pyi new file mode 100644 index 000000000000..33b2fd8be4cd --- /dev/null +++ b/lib/matplotlib/tri/_triinterpolate.pyi @@ -0,0 +1,32 @@ +from matplotlib.tri import Triangulation, TriFinder + +from typing import Literal +import numpy as np +from numpy.typing import ArrayLike + +class TriInterpolator: + def __init__( + self, + triangulation: Triangulation, + z: ArrayLike, + trifinder: TriFinder | None = ..., + ) -> None: ... + # __call__ and gradient are not actually implemented by the ABC, but are specified as required + def __call__(self, x: ArrayLike, y: ArrayLike) -> np.ma.MaskedArray: ... + def gradient( + self, x: ArrayLike, y: ArrayLike + ) -> tuple[np.ma.MaskedArray, np.ma.MaskedArray]: ... + +class LinearTriInterpolator(TriInterpolator): ... + +class CubicTriInterpolator(TriInterpolator): + def __init__( + self, + triangulation: Triangulation, + z: ArrayLike, + kind: Literal["min_E", "geom", "user"] = ..., + trifinder: TriFinder | None = ..., + dz: tuple[ArrayLike, ArrayLike] | None = ..., + ) -> None: ... + +__all__ = ('TriInterpolator', 'LinearTriInterpolator', 'CubicTriInterpolator') diff --git a/lib/matplotlib/tri/_tripcolor.py b/lib/matplotlib/tri/_tripcolor.py new file mode 100644 index 000000000000..f3c26b0b25ff --- /dev/null +++ b/lib/matplotlib/tri/_tripcolor.py @@ -0,0 +1,167 @@ +import numpy as np + +from matplotlib import _api, _docstring +from matplotlib.collections import PolyCollection, TriMesh +from matplotlib.tri._triangulation import Triangulation + + +@_docstring.interpd +def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, + vmax=None, shading='flat', facecolors=None, **kwargs): + """ + Create a pseudocolor plot of an unstructured triangular grid. + + Call signatures:: + + tripcolor(triangulation, c, *, ...) + tripcolor(x, y, c, *, [triangles=triangles], [mask=mask], ...) + + The triangular grid can be specified either by passing a `.Triangulation` + object as the first parameter, or by passing the points *x*, *y* and + optionally the *triangles* and a *mask*. See `.Triangulation` for an + explanation of these parameters. + + It is possible to pass the triangles positionally, i.e. + ``tripcolor(x, y, triangles, c, ...)``. However, this is discouraged. + For more clarity, pass *triangles* via keyword argument. + + If neither of *triangulation* or *triangles* are given, the triangulation + is calculated on the fly. In this case, it does not make sense to provide + colors at the triangle faces via *c* or *facecolors* because there are + multiple possible triangulations for a group of points and you don't know + which triangles will be constructed. + + Parameters + ---------- + triangulation : `.Triangulation` + An already created triangular grid. + x, y, triangles, mask + Parameters defining the triangular grid. See `.Triangulation`. + This is mutually exclusive with specifying *triangulation*. + c : array-like + The color values, either for the points or for the triangles. Which one + is automatically inferred from the length of *c*, i.e. does it match + the number of points or the number of triangles. If there are the same + number of points and triangles in the triangulation it is assumed that + color values are defined at points; to force the use of color values at + triangles use the keyword argument ``facecolors=c`` instead of just + ``c``. + This parameter is position-only. + facecolors : array-like, optional + Can be used alternatively to *c* to specify colors at the triangle + faces. This parameter takes precedence over *c*. + shading : {'flat', 'gouraud'}, default: 'flat' + If 'flat' and the color values *c* are defined at points, the color + values used for each triangle are from the mean c of the triangle's + three points. If *shading* is 'gouraud' then color values must be + defined at points. + %(cmap_doc)s + + %(norm_doc)s + + %(vmin_vmax_doc)s + + %(colorizer_doc)s + + Returns + ------- + `~matplotlib.collections.PolyCollection` or `~matplotlib.collections.TriMesh` + The result depends on *shading*: For ``shading='flat'`` the result is a + `.PolyCollection`, for ``shading='gouraud'`` the result is a `.TriMesh`. + + Other Parameters + ---------------- + **kwargs : `~matplotlib.collections.Collection` properties + + %(Collection:kwdoc)s + """ + _api.check_in_list(['flat', 'gouraud'], shading=shading) + + tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs) + + # Parse the color to be in one of (the other variable will be None): + # - facecolors: if specified at the triangle faces + # - point_colors: if specified at the points + if facecolors is not None: + if args: + _api.warn_external( + "Positional parameter c has no effect when the keyword " + "facecolors is given") + point_colors = None + if len(facecolors) != len(tri.triangles): + raise ValueError("The length of facecolors must match the number " + "of triangles") + else: + # Color from positional parameter c + if not args: + raise TypeError( + "tripcolor() missing 1 required positional argument: 'c'; or " + "1 required keyword-only argument: 'facecolors'") + elif len(args) > 1: + raise TypeError(f"Unexpected positional parameters: {args[1:]!r}") + c = np.asarray(args[0]) + if len(c) == len(tri.x): + # having this before the len(tri.triangles) comparison gives + # precedence to nodes if there are as many nodes as triangles + point_colors = c + facecolors = None + elif len(c) == len(tri.triangles): + point_colors = None + facecolors = c + else: + raise ValueError('The length of c must match either the number ' + 'of points or the number of triangles') + + # Handling of linewidths, shading, edgecolors and antialiased as + # in Axes.pcolor + linewidths = (0.25,) + if 'linewidth' in kwargs: + kwargs['linewidths'] = kwargs.pop('linewidth') + kwargs.setdefault('linewidths', linewidths) + + edgecolors = 'none' + if 'edgecolor' in kwargs: + kwargs['edgecolors'] = kwargs.pop('edgecolor') + ec = kwargs.setdefault('edgecolors', edgecolors) + + if 'antialiased' in kwargs: + kwargs['antialiaseds'] = kwargs.pop('antialiased') + if 'antialiaseds' not in kwargs and ec.lower() == "none": + kwargs['antialiaseds'] = False + + if shading == 'gouraud': + if facecolors is not None: + raise ValueError( + "shading='gouraud' can only be used when the colors " + "are specified at the points, not at the faces.") + collection = TriMesh(tri, alpha=alpha, array=point_colors, + cmap=cmap, norm=norm, **kwargs) + else: # 'flat' + # Vertices of triangles. + maskedTris = tri.get_masked_triangles() + verts = np.stack((tri.x[maskedTris], tri.y[maskedTris]), axis=-1) + + # Color values. + if facecolors is None: + # One color per triangle, the mean of the 3 vertex color values. + colors = point_colors[maskedTris].mean(axis=1) + elif tri.mask is not None: + # Remove color values of masked triangles. + colors = facecolors[~tri.mask] + else: + colors = facecolors + collection = PolyCollection(verts, alpha=alpha, array=colors, + cmap=cmap, norm=norm, **kwargs) + + collection._scale_norm(norm, vmin, vmax) + ax.grid(False) + + minx = tri.x.min() + maxx = tri.x.max() + miny = tri.y.min() + maxy = tri.y.max() + corners = (minx, miny), (maxx, maxy) + ax.update_datalim(corners) + ax.autoscale_view() + ax.add_collection(collection) + return collection diff --git a/lib/matplotlib/tri/_tripcolor.pyi b/lib/matplotlib/tri/_tripcolor.pyi new file mode 100644 index 000000000000..42acffcdc52e --- /dev/null +++ b/lib/matplotlib/tri/_tripcolor.pyi @@ -0,0 +1,71 @@ +from matplotlib.axes import Axes +from matplotlib.collections import PolyCollection, TriMesh +from matplotlib.colors import Normalize, Colormap +from matplotlib.tri._triangulation import Triangulation + +from numpy.typing import ArrayLike + +from typing import overload, Literal + +@overload +def tripcolor( + ax: Axes, + triangulation: Triangulation, + c: ArrayLike = ..., + *, + alpha: float = ..., + norm: str | Normalize | None = ..., + cmap: str | Colormap | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + shading: Literal["flat"] = ..., + facecolors: ArrayLike | None = ..., + **kwargs +) -> PolyCollection: ... +@overload +def tripcolor( + ax: Axes, + x: ArrayLike, + y: ArrayLike, + c: ArrayLike = ..., + *, + alpha: float = ..., + norm: str | Normalize | None = ..., + cmap: str | Colormap | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + shading: Literal["flat"] = ..., + facecolors: ArrayLike | None = ..., + **kwargs +) -> PolyCollection: ... +@overload +def tripcolor( + ax: Axes, + triangulation: Triangulation, + c: ArrayLike = ..., + *, + alpha: float = ..., + norm: str | Normalize | None = ..., + cmap: str | Colormap | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + shading: Literal["gouraud"], + facecolors: ArrayLike | None = ..., + **kwargs +) -> TriMesh: ... +@overload +def tripcolor( + ax: Axes, + x: ArrayLike, + y: ArrayLike, + c: ArrayLike = ..., + *, + alpha: float = ..., + norm: str | Normalize | None = ..., + cmap: str | Colormap | None = ..., + vmin: float | None = ..., + vmax: float | None = ..., + shading: Literal["gouraud"], + facecolors: ArrayLike | None = ..., + **kwargs +) -> TriMesh: ... diff --git a/lib/matplotlib/tri/_triplot.py b/lib/matplotlib/tri/_triplot.py new file mode 100644 index 000000000000..6168946b1531 --- /dev/null +++ b/lib/matplotlib/tri/_triplot.py @@ -0,0 +1,86 @@ +import numpy as np +from matplotlib.tri._triangulation import Triangulation +import matplotlib.cbook as cbook +import matplotlib.lines as mlines + + +def triplot(ax, *args, **kwargs): + """ + Draw an unstructured triangular grid as lines and/or markers. + + Call signatures:: + + triplot(triangulation, ...) + triplot(x, y, [triangles], *, [mask=mask], ...) + + The triangular grid can be specified either by passing a `.Triangulation` + object as the first parameter, or by passing the points *x*, *y* and + optionally the *triangles* and a *mask*. If neither of *triangulation* or + *triangles* are given, the triangulation is calculated on the fly. + + Parameters + ---------- + triangulation : `.Triangulation` + An already created triangular grid. + x, y, triangles, mask + Parameters defining the triangular grid. See `.Triangulation`. + This is mutually exclusive with specifying *triangulation*. + other_parameters + All other args and kwargs are forwarded to `~.Axes.plot`. + + Returns + ------- + lines : `~matplotlib.lines.Line2D` + The drawn triangles edges. + markers : `~matplotlib.lines.Line2D` + The drawn marker nodes. + """ + import matplotlib.axes + + tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs) + x, y, edges = (tri.x, tri.y, tri.edges) + + # Decode plot format string, e.g., 'ro-' + fmt = args[0] if args else "" + linestyle, marker, color = matplotlib.axes._base._process_plot_format(fmt) + + # Insert plot format string into a copy of kwargs (kwargs values prevail). + kw = cbook.normalize_kwargs(kwargs, mlines.Line2D) + for key, val in zip(('linestyle', 'marker', 'color'), + (linestyle, marker, color)): + if val is not None: + kw.setdefault(key, val) + + # Draw lines without markers. + # Note 1: If we drew markers here, most markers would be drawn more than + # once as they belong to several edges. + # Note 2: We insert nan values in the flattened edges arrays rather than + # plotting directly (triang.x[edges].T, triang.y[edges].T) + # as it considerably speeds-up code execution. + linestyle = kw['linestyle'] + kw_lines = { + **kw, + 'marker': 'None', # No marker to draw. + 'zorder': kw.get('zorder', 1), # Path default zorder is used. + } + if linestyle not in [None, 'None', '', ' ']: + tri_lines_x = np.insert(x[edges], 2, np.nan, axis=1) + tri_lines_y = np.insert(y[edges], 2, np.nan, axis=1) + tri_lines = ax.plot(tri_lines_x.ravel(), tri_lines_y.ravel(), + **kw_lines) + else: + tri_lines = ax.plot([], [], **kw_lines) + + # Draw markers separately. + marker = kw['marker'] + kw_markers = { + **kw, + 'linestyle': 'None', # No line to draw. + } + kw_markers.pop('label', None) + if marker not in [None, 'None', '', ' ']: + tri_markers = ax.plot(x, y, **kw_markers) + else: + tri_markers = ax.plot([], [], **kw_markers) + + return tri_lines + tri_markers diff --git a/lib/matplotlib/tri/_triplot.pyi b/lib/matplotlib/tri/_triplot.pyi new file mode 100644 index 000000000000..6224276afdb8 --- /dev/null +++ b/lib/matplotlib/tri/_triplot.pyi @@ -0,0 +1,15 @@ +from matplotlib.tri._triangulation import Triangulation +from matplotlib.axes import Axes +from matplotlib.lines import Line2D + +from typing import overload +from numpy.typing import ArrayLike + +@overload +def triplot( + ax: Axes, triangulation: Triangulation, *args, **kwargs +) -> tuple[Line2D, Line2D]: ... +@overload +def triplot( + ax: Axes, x: ArrayLike, y: ArrayLike, triangles: ArrayLike = ..., *args, **kwargs +) -> tuple[Line2D, Line2D]: ... diff --git a/lib/matplotlib/tri/_trirefine.py b/lib/matplotlib/tri/_trirefine.py new file mode 100644 index 000000000000..6a7037ad74fd --- /dev/null +++ b/lib/matplotlib/tri/_trirefine.py @@ -0,0 +1,307 @@ +""" +Mesh refinement for triangular grids. +""" + +import numpy as np + +from matplotlib import _api +from matplotlib.tri._triangulation import Triangulation +import matplotlib.tri._triinterpolate + + +class TriRefiner: + """ + Abstract base class for classes implementing mesh refinement. + + A TriRefiner encapsulates a Triangulation object and provides tools for + mesh refinement and interpolation. + + Derived classes must implement: + + - ``refine_triangulation(return_tri_index=False, **kwargs)`` , where + the optional keyword arguments *kwargs* are defined in each + TriRefiner concrete implementation, and which returns: + + - a refined triangulation, + - optionally (depending on *return_tri_index*), for each + point of the refined triangulation: the index of + the initial triangulation triangle to which it belongs. + + - ``refine_field(z, triinterpolator=None, **kwargs)``, where: + + - *z* array of field values (to refine) defined at the base + triangulation nodes, + - *triinterpolator* is an optional `~matplotlib.tri.TriInterpolator`, + - the other optional keyword arguments *kwargs* are defined in + each TriRefiner concrete implementation; + + and which returns (as a tuple) a refined triangular mesh and the + interpolated values of the field at the refined triangulation nodes. + """ + + def __init__(self, triangulation): + _api.check_isinstance(Triangulation, triangulation=triangulation) + self._triangulation = triangulation + + +class UniformTriRefiner(TriRefiner): + """ + Uniform mesh refinement by recursive subdivisions. + + Parameters + ---------- + triangulation : `~matplotlib.tri.Triangulation` + The encapsulated triangulation (to be refined) + """ +# See Also +# -------- +# :class:`~matplotlib.tri.CubicTriInterpolator` and +# :class:`~matplotlib.tri.TriAnalyzer`. +# """ + def __init__(self, triangulation): + super().__init__(triangulation) + + def refine_triangulation(self, return_tri_index=False, subdiv=3): + """ + Compute a uniformly refined triangulation *refi_triangulation* of + the encapsulated :attr:`!triangulation`. + + This function refines the encapsulated triangulation by splitting each + father triangle into 4 child sub-triangles built on the edges midside + nodes, recursing *subdiv* times. In the end, each triangle is hence + divided into ``4**subdiv`` child triangles. + + Parameters + ---------- + return_tri_index : bool, default: False + Whether an index table indicating the father triangle index of each + point is returned. + subdiv : int, default: 3 + Recursion level for the subdivision. + Each triangle is divided into ``4**subdiv`` child triangles; + hence, the default results in 64 refined subtriangles for each + triangle of the initial triangulation. + + Returns + ------- + refi_triangulation : `~matplotlib.tri.Triangulation` + The refined triangulation. + found_index : int array + Index of the initial triangulation containing triangle, for each + point of *refi_triangulation*. + Returned only if *return_tri_index* is set to True. + """ + refi_triangulation = self._triangulation + ntri = refi_triangulation.triangles.shape[0] + + # Computes the triangulation ancestors numbers in the reference + # triangulation. + ancestors = np.arange(ntri, dtype=np.int32) + for _ in range(subdiv): + refi_triangulation, ancestors = self._refine_triangulation_once( + refi_triangulation, ancestors) + refi_npts = refi_triangulation.x.shape[0] + refi_triangles = refi_triangulation.triangles + + # Now we compute found_index table if needed + if return_tri_index: + # We have to initialize found_index with -1 because some nodes + # may very well belong to no triangle at all, e.g., in case of + # Delaunay Triangulation with DuplicatePointWarning. + found_index = np.full(refi_npts, -1, dtype=np.int32) + tri_mask = self._triangulation.mask + if tri_mask is None: + found_index[refi_triangles] = np.repeat(ancestors, + 3).reshape(-1, 3) + else: + # There is a subtlety here: we want to avoid whenever possible + # that refined points container is a masked triangle (which + # would result in artifacts in plots). + # So we impose the numbering from masked ancestors first, + # then overwrite it with unmasked ancestor numbers. + ancestor_mask = tri_mask[ancestors] + found_index[refi_triangles[ancestor_mask, :] + ] = np.repeat(ancestors[ancestor_mask], + 3).reshape(-1, 3) + found_index[refi_triangles[~ancestor_mask, :] + ] = np.repeat(ancestors[~ancestor_mask], + 3).reshape(-1, 3) + return refi_triangulation, found_index + else: + return refi_triangulation + + def refine_field(self, z, triinterpolator=None, subdiv=3): + """ + Refine a field defined on the encapsulated triangulation. + + Parameters + ---------- + z : (npoints,) array-like + Values of the field to refine, defined at the nodes of the + encapsulated triangulation. (``n_points`` is the number of points + in the initial triangulation) + triinterpolator : `~matplotlib.tri.TriInterpolator`, optional + Interpolator used for field interpolation. If not specified, + a `~matplotlib.tri.CubicTriInterpolator` will be used. + subdiv : int, default: 3 + Recursion level for the subdivision. + Each triangle is divided into ``4**subdiv`` child triangles. + + Returns + ------- + refi_tri : `~matplotlib.tri.Triangulation` + The returned refined triangulation. + refi_z : 1D array of length: *refi_tri* node count. + The returned interpolated field (at *refi_tri* nodes). + """ + if triinterpolator is None: + interp = matplotlib.tri.CubicTriInterpolator( + self._triangulation, z) + else: + _api.check_isinstance(matplotlib.tri.TriInterpolator, + triinterpolator=triinterpolator) + interp = triinterpolator + + refi_tri, found_index = self.refine_triangulation( + subdiv=subdiv, return_tri_index=True) + refi_z = interp._interpolate_multikeys( + refi_tri.x, refi_tri.y, tri_index=found_index)[0] + return refi_tri, refi_z + + @staticmethod + def _refine_triangulation_once(triangulation, ancestors=None): + """ + Refine a `.Triangulation` by splitting each triangle into 4 + child-masked_triangles built on the edges midside nodes. + + Masked triangles, if present, are also split, but their children + returned masked. + + If *ancestors* is not provided, returns only a new triangulation: + child_triangulation. + + If the array-like key table *ancestor* is given, it shall be of shape + (ntri,) where ntri is the number of *triangulation* masked_triangles. + In this case, the function returns + (child_triangulation, child_ancestors) + child_ancestors is defined so that the 4 child masked_triangles share + the same index as their father: child_ancestors.shape = (4 * ntri,). + """ + + x = triangulation.x + y = triangulation.y + + # According to tri.triangulation doc: + # neighbors[i, j] is the triangle that is the neighbor + # to the edge from point index masked_triangles[i, j] to point + # index masked_triangles[i, (j+1)%3]. + neighbors = triangulation.neighbors + triangles = triangulation.triangles + npts = np.shape(x)[0] + ntri = np.shape(triangles)[0] + if ancestors is not None: + ancestors = np.asarray(ancestors) + if np.shape(ancestors) != (ntri,): + raise ValueError( + "Incompatible shapes provide for " + "triangulation.masked_triangles and ancestors: " + f"{np.shape(triangles)} and {np.shape(ancestors)}") + + # Initiating tables refi_x and refi_y of the refined triangulation + # points + # hint: each apex is shared by 2 masked_triangles except the borders. + borders = np.sum(neighbors == -1) + added_pts = (3*ntri + borders) // 2 + refi_npts = npts + added_pts + refi_x = np.zeros(refi_npts) + refi_y = np.zeros(refi_npts) + + # First part of refi_x, refi_y is just the initial points + refi_x[:npts] = x + refi_y[:npts] = y + + # Second part contains the edge midside nodes. + # Each edge belongs to 1 triangle (if border edge) or is shared by 2 + # masked_triangles (interior edge). + # We first build 2 * ntri arrays of edge starting nodes (edge_elems, + # edge_apexes); we then extract only the masters to avoid overlaps. + # The so-called 'master' is the triangle with biggest index + # The 'slave' is the triangle with lower index + # (can be -1 if border edge) + # For slave and master we will identify the apex pointing to the edge + # start + edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3) + edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri) + edge_neighbors = neighbors[edge_elems, edge_apexes] + mask_masters = (edge_elems > edge_neighbors) + + # Identifying the "masters" and adding to refi_x, refi_y vec + masters = edge_elems[mask_masters] + apex_masters = edge_apexes[mask_masters] + x_add = (x[triangles[masters, apex_masters]] + + x[triangles[masters, (apex_masters+1) % 3]]) * 0.5 + y_add = (y[triangles[masters, apex_masters]] + + y[triangles[masters, (apex_masters+1) % 3]]) * 0.5 + refi_x[npts:] = x_add + refi_y[npts:] = y_add + + # Building the new masked_triangles; each old masked_triangles hosts + # 4 new masked_triangles + # there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and + # 3 new_pt_midside + new_pt_corner = triangles + + # What is the index in refi_x, refi_y of point at middle of apex iapex + # of elem ielem ? + # If ielem is the apex master: simple count, given the way refi_x was + # built. + # If ielem is the apex slave: yet we do not know; but we will soon + # using the neighbors table. + new_pt_midside = np.empty([ntri, 3], dtype=np.int32) + cum_sum = npts + for imid in range(3): + mask_st_loc = (imid == apex_masters) + n_masters_loc = np.sum(mask_st_loc) + elem_masters_loc = masters[mask_st_loc] + new_pt_midside[:, imid][elem_masters_loc] = np.arange( + n_masters_loc, dtype=np.int32) + cum_sum + cum_sum += n_masters_loc + + # Now dealing with slave elems. + # for each slave element we identify the master and then the inode + # once slave_masters is identified, slave_masters_apex is such that: + # neighbors[slaves_masters, slave_masters_apex] == slaves + mask_slaves = np.logical_not(mask_masters) + slaves = edge_elems[mask_slaves] + slaves_masters = edge_neighbors[mask_slaves] + diff_table = np.abs(neighbors[slaves_masters, :] - + np.outer(slaves, np.ones(3, dtype=np.int32))) + slave_masters_apex = np.argmin(diff_table, axis=1) + slaves_apex = edge_apexes[mask_slaves] + new_pt_midside[slaves, slaves_apex] = new_pt_midside[ + slaves_masters, slave_masters_apex] + + # Builds the 4 child masked_triangles + child_triangles = np.empty([ntri*4, 3], dtype=np.int32) + child_triangles[0::4, :] = np.vstack([ + new_pt_corner[:, 0], new_pt_midside[:, 0], + new_pt_midside[:, 2]]).T + child_triangles[1::4, :] = np.vstack([ + new_pt_corner[:, 1], new_pt_midside[:, 1], + new_pt_midside[:, 0]]).T + child_triangles[2::4, :] = np.vstack([ + new_pt_corner[:, 2], new_pt_midside[:, 2], + new_pt_midside[:, 1]]).T + child_triangles[3::4, :] = np.vstack([ + new_pt_midside[:, 0], new_pt_midside[:, 1], + new_pt_midside[:, 2]]).T + child_triangulation = Triangulation(refi_x, refi_y, child_triangles) + + # Builds the child mask + if triangulation.mask is not None: + child_triangulation.set_mask(np.repeat(triangulation.mask, 4)) + + if ancestors is None: + return child_triangulation + else: + return child_triangulation, np.repeat(ancestors, 4) diff --git a/lib/matplotlib/tri/_trirefine.pyi b/lib/matplotlib/tri/_trirefine.pyi new file mode 100644 index 000000000000..7c60dc76e2f1 --- /dev/null +++ b/lib/matplotlib/tri/_trirefine.pyi @@ -0,0 +1,31 @@ +from typing import Literal, overload + +import numpy as np +from numpy.typing import ArrayLike + +from matplotlib.tri._triangulation import Triangulation +from matplotlib.tri._triinterpolate import TriInterpolator + +class TriRefiner: + def __init__(self, triangulation: Triangulation) -> None: ... + +class UniformTriRefiner(TriRefiner): + def __init__(self, triangulation: Triangulation) -> None: ... + @overload + def refine_triangulation( + self, *, return_tri_index: Literal[True], subdiv: int = ... + ) -> tuple[Triangulation, np.ndarray]: ... + @overload + def refine_triangulation( + self, return_tri_index: Literal[False] = ..., subdiv: int = ... + ) -> Triangulation: ... + @overload + def refine_triangulation( + self, return_tri_index: bool = ..., subdiv: int = ... + ) -> tuple[Triangulation, np.ndarray] | Triangulation: ... + def refine_field( + self, + z: ArrayLike, + triinterpolator: TriInterpolator | None = ..., + subdiv: int = ..., + ) -> tuple[Triangulation, np.ndarray]: ... diff --git a/lib/matplotlib/tri/_tritools.py b/lib/matplotlib/tri/_tritools.py new file mode 100644 index 000000000000..9837309f7e81 --- /dev/null +++ b/lib/matplotlib/tri/_tritools.py @@ -0,0 +1,263 @@ +""" +Tools for triangular grids. +""" + +import numpy as np + +from matplotlib import _api +from matplotlib.tri import Triangulation + + +class TriAnalyzer: + """ + Define basic tools for triangular mesh analysis and improvement. + + A TriAnalyzer encapsulates a `.Triangulation` object and provides basic + tools for mesh analysis and mesh improvement. + + Attributes + ---------- + scale_factors + + Parameters + ---------- + triangulation : `~matplotlib.tri.Triangulation` + The encapsulated triangulation to analyze. + """ + + def __init__(self, triangulation): + _api.check_isinstance(Triangulation, triangulation=triangulation) + self._triangulation = triangulation + + @property + def scale_factors(self): + """ + Factors to rescale the triangulation into a unit square. + + Returns + ------- + (float, float) + Scaling factors (kx, ky) so that the triangulation + ``[triangulation.x * kx, triangulation.y * ky]`` + fits exactly inside a unit square. + """ + compressed_triangles = self._triangulation.get_masked_triangles() + node_used = (np.bincount(np.ravel(compressed_triangles), + minlength=self._triangulation.x.size) != 0) + return (1 / np.ptp(self._triangulation.x[node_used]), + 1 / np.ptp(self._triangulation.y[node_used])) + + def circle_ratios(self, rescale=True): + """ + Return a measure of the triangulation triangles flatness. + + The ratio of the incircle radius over the circumcircle radius is a + widely used indicator of a triangle flatness. + It is always ``<= 0.5`` and ``== 0.5`` only for equilateral + triangles. Circle ratios below 0.01 denote very flat triangles. + + To avoid unduly low values due to a difference of scale between the 2 + axis, the triangular mesh can first be rescaled to fit inside a unit + square with `scale_factors` (Only if *rescale* is True, which is + its default value). + + Parameters + ---------- + rescale : bool, default: True + If True, internally rescale (based on `scale_factors`), so that the + (unmasked) triangles fit exactly inside a unit square mesh. + + Returns + ------- + masked array + Ratio of the incircle radius over the circumcircle radius, for + each 'rescaled' triangle of the encapsulated triangulation. + Values corresponding to masked triangles are masked out. + + """ + # Coords rescaling + if rescale: + (kx, ky) = self.scale_factors + else: + (kx, ky) = (1.0, 1.0) + pts = np.vstack([self._triangulation.x*kx, + self._triangulation.y*ky]).T + tri_pts = pts[self._triangulation.triangles] + # Computes the 3 side lengths + a = tri_pts[:, 1, :] - tri_pts[:, 0, :] + b = tri_pts[:, 2, :] - tri_pts[:, 1, :] + c = tri_pts[:, 0, :] - tri_pts[:, 2, :] + a = np.hypot(a[:, 0], a[:, 1]) + b = np.hypot(b[:, 0], b[:, 1]) + c = np.hypot(c[:, 0], c[:, 1]) + # circumcircle and incircle radii + s = (a+b+c)*0.5 + prod = s*(a+b-s)*(a+c-s)*(b+c-s) + # We have to deal with flat triangles with infinite circum_radius + bool_flat = (prod == 0.) + if np.any(bool_flat): + # Pathologic flow + ntri = tri_pts.shape[0] + circum_radius = np.empty(ntri, dtype=np.float64) + circum_radius[bool_flat] = np.inf + abc = a*b*c + circum_radius[~bool_flat] = abc[~bool_flat] / ( + 4.0*np.sqrt(prod[~bool_flat])) + else: + # Normal optimized flow + circum_radius = (a*b*c) / (4.0*np.sqrt(prod)) + in_radius = (a*b*c) / (4.0*circum_radius*s) + circle_ratio = in_radius/circum_radius + mask = self._triangulation.mask + if mask is None: + return circle_ratio + else: + return np.ma.array(circle_ratio, mask=mask) + + def get_flat_tri_mask(self, min_circle_ratio=0.01, rescale=True): + """ + Eliminate excessively flat border triangles from the triangulation. + + Returns a mask *new_mask* which allows to clean the encapsulated + triangulation from its border-located flat triangles + (according to their :meth:`circle_ratios`). + This mask is meant to be subsequently applied to the triangulation + using `.Triangulation.set_mask`. + *new_mask* is an extension of the initial triangulation mask + in the sense that an initially masked triangle will remain masked. + + The *new_mask* array is computed recursively; at each step flat + triangles are removed only if they share a side with the current mesh + border. Thus, no new holes in the triangulated domain will be created. + + Parameters + ---------- + min_circle_ratio : float, default: 0.01 + Border triangles with incircle/circumcircle radii ratio r/R will + be removed if r/R < *min_circle_ratio*. + rescale : bool, default: True + If True, first, internally rescale (based on `scale_factors`) so + that the (unmasked) triangles fit exactly inside a unit square + mesh. This rescaling accounts for the difference of scale which + might exist between the 2 axis. + + Returns + ------- + array of bool + Mask to apply to encapsulated triangulation. + All the initially masked triangles remain masked in the + *new_mask*. + + Notes + ----- + The rationale behind this function is that a Delaunay + triangulation - of an unstructured set of points - sometimes contains + almost flat triangles at its border, leading to artifacts in plots + (especially for high-resolution contouring). + Masked with computed *new_mask*, the encapsulated + triangulation would contain no more unmasked border triangles + with a circle ratio below *min_circle_ratio*, thus improving the + mesh quality for subsequent plots or interpolation. + """ + # Recursively computes the mask_current_borders, true if a triangle is + # at the border of the mesh OR touching the border through a chain of + # invalid aspect ratio masked_triangles. + ntri = self._triangulation.triangles.shape[0] + mask_bad_ratio = self.circle_ratios(rescale) < min_circle_ratio + + current_mask = self._triangulation.mask + if current_mask is None: + current_mask = np.zeros(ntri, dtype=bool) + valid_neighbors = np.copy(self._triangulation.neighbors) + renum_neighbors = np.arange(ntri, dtype=np.int32) + nadd = -1 + while nadd != 0: + # The active wavefront is the triangles from the border (unmasked + # but with a least 1 neighbor equal to -1 + wavefront = (np.min(valid_neighbors, axis=1) == -1) & ~current_mask + # The element from the active wavefront will be masked if their + # circle ratio is bad. + added_mask = wavefront & mask_bad_ratio + current_mask = added_mask | current_mask + nadd = np.sum(added_mask) + + # now we have to update the tables valid_neighbors + valid_neighbors[added_mask, :] = -1 + renum_neighbors[added_mask] = -1 + valid_neighbors = np.where(valid_neighbors == -1, -1, + renum_neighbors[valid_neighbors]) + + return np.ma.filled(current_mask, True) + + def _get_compressed_triangulation(self): + """ + Compress (if masked) the encapsulated triangulation. + + Returns minimal-length triangles array (*compressed_triangles*) and + coordinates arrays (*compressed_x*, *compressed_y*) that can still + describe the unmasked triangles of the encapsulated triangulation. + + Returns + ------- + compressed_triangles : array-like + the returned compressed triangulation triangles + compressed_x : array-like + the returned compressed triangulation 1st coordinate + compressed_y : array-like + the returned compressed triangulation 2nd coordinate + tri_renum : int array + renumbering table to translate the triangle numbers from the + encapsulated triangulation into the new (compressed) renumbering. + -1 for masked triangles (deleted from *compressed_triangles*). + node_renum : int array + renumbering table to translate the point numbers from the + encapsulated triangulation into the new (compressed) renumbering. + -1 for unused points (i.e. those deleted from *compressed_x* and + *compressed_y*). + + """ + # Valid triangles and renumbering + tri_mask = self._triangulation.mask + compressed_triangles = self._triangulation.get_masked_triangles() + ntri = self._triangulation.triangles.shape[0] + if tri_mask is not None: + tri_renum = self._total_to_compress_renum(~tri_mask) + else: + tri_renum = np.arange(ntri, dtype=np.int32) + + # Valid nodes and renumbering + valid_node = (np.bincount(np.ravel(compressed_triangles), + minlength=self._triangulation.x.size) != 0) + compressed_x = self._triangulation.x[valid_node] + compressed_y = self._triangulation.y[valid_node] + node_renum = self._total_to_compress_renum(valid_node) + + # Now renumbering the valid triangles nodes + compressed_triangles = node_renum[compressed_triangles] + + return (compressed_triangles, compressed_x, compressed_y, tri_renum, + node_renum) + + @staticmethod + def _total_to_compress_renum(valid): + """ + Parameters + ---------- + valid : 1D bool array + Validity mask. + + Returns + ------- + int array + Array so that (`valid_array` being a compressed array + based on a `masked_array` with mask ~*valid*): + + - For all i with valid[i] = True: + valid_array[renum[i]] = masked_array[i] + - For all i with valid[i] = False: + renum[i] = -1 (invalid value) + """ + renum = np.full(np.size(valid), -1, dtype=np.int32) + n_valid = np.sum(valid) + renum[valid] = np.arange(n_valid, dtype=np.int32) + return renum diff --git a/lib/matplotlib/tri/_tritools.pyi b/lib/matplotlib/tri/_tritools.pyi new file mode 100644 index 000000000000..9b5e1bec4b81 --- /dev/null +++ b/lib/matplotlib/tri/_tritools.pyi @@ -0,0 +1,12 @@ +from matplotlib.tri import Triangulation + +import numpy as np + +class TriAnalyzer: + def __init__(self, triangulation: Triangulation) -> None: ... + @property + def scale_factors(self) -> tuple[float, float]: ... + def circle_ratios(self, rescale: bool = ...) -> np.ndarray: ... + def get_flat_tri_mask( + self, min_circle_ratio: float = ..., rescale: bool = ... + ) -> np.ndarray: ... diff --git a/lib/matplotlib/tri/meson.build b/lib/matplotlib/tri/meson.build new file mode 100644 index 000000000000..9e71f3348c69 --- /dev/null +++ b/lib/matplotlib/tri/meson.build @@ -0,0 +1,25 @@ +python_sources = [ + '__init__.py', + '_triangulation.py', + '_tricontour.py', + '_trifinder.py', + '_triinterpolate.py', + '_tripcolor.py', + '_triplot.py', + '_trirefine.py', + '_tritools.py', +] + +typing_sources = [ + '_triangulation.pyi', + '_tricontour.pyi', + '_trifinder.pyi', + '_triinterpolate.pyi', + '_tripcolor.pyi', + '_triplot.pyi', + '_trirefine.pyi', + '_tritools.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/tri') diff --git a/lib/matplotlib/tri/triangulation.py b/lib/matplotlib/tri/triangulation.py deleted file mode 100644 index 4331efc54024..000000000000 --- a/lib/matplotlib/tri/triangulation.py +++ /dev/null @@ -1,220 +0,0 @@ -import numpy as np - - -class Triangulation: - """ - An unstructured triangular grid consisting of npoints points and - ntri triangles. The triangles can either be specified by the user - or automatically generated using a Delaunay triangulation. - - Parameters - ---------- - x, y : array-like of shape (npoints) - Coordinates of grid points. - triangles : int array-like of shape (ntri, 3), optional - For each triangle, the indices of the three points that make - up the triangle, ordered in an anticlockwise manner. If not - specified, the Delaunay triangulation is calculated. - mask : bool array-like of shape (ntri), optional - Which triangles are masked out. - - Attributes - ---------- - edges : int array of shape (nedges, 2) - See `~.Triangulation.edges` - neighbors : int array of shape (ntri, 3) - See `~.Triangulation.neighbors` - mask : bool array of shape (ntri, 3) - Masked out triangles. - is_delaunay : bool - Whether the Triangulation is a calculated Delaunay - triangulation (where *triangles* was not specified) or not. - - Notes - ----- - For a Triangulation to be valid it must not have duplicate points, - triangles formed from colinear points, or overlapping triangles. - """ - def __init__(self, x, y, triangles=None, mask=None): - from matplotlib import _qhull - - self.x = np.asarray(x, dtype=np.float64) - self.y = np.asarray(y, dtype=np.float64) - if self.x.shape != self.y.shape or self.x.ndim != 1: - raise ValueError("x and y must be equal-length 1-D arrays") - - self.mask = None - self._edges = None - self._neighbors = None - self.is_delaunay = False - - if triangles is None: - # No triangulation specified, so use matplotlib._qhull to obtain - # Delaunay triangulation. - self.triangles, self._neighbors = _qhull.delaunay(x, y) - self.is_delaunay = True - else: - # Triangulation specified. Copy, since we may correct triangle - # orientation. - self.triangles = np.array(triangles, dtype=np.int32, order='C') - if self.triangles.ndim != 2 or self.triangles.shape[1] != 3: - raise ValueError('triangles must be a (?, 3) array') - if self.triangles.max() >= len(self.x): - raise ValueError('triangles max element is out of bounds') - if self.triangles.min() < 0: - raise ValueError('triangles min element is out of bounds') - - if mask is not None: - self.mask = np.asarray(mask, dtype=bool) - if self.mask.shape != (self.triangles.shape[0],): - raise ValueError('mask array must have same length as ' - 'triangles array') - - # Underlying C++ object is not created until first needed. - self._cpp_triangulation = None - - # Default TriFinder not created until needed. - self._trifinder = None - - def calculate_plane_coefficients(self, z): - """ - Calculate plane equation coefficients for all unmasked triangles from - the point (x, y) coordinates and specified z-array of shape (npoints). - The returned array has shape (npoints, 3) and allows z-value at (x, y) - position in triangle tri to be calculated using - ``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``. - """ - return self.get_cpp_triangulation().calculate_plane_coefficients(z) - - @property - def edges(self): - """ - Return integer array of shape (nedges, 2) containing all edges of - non-masked triangles. - - Each row defines an edge by it's start point index and end point - index. Each edge appears only once, i.e. for an edge between points - *i* and *j*, there will only be either *(i, j)* or *(j, i)*. - """ - if self._edges is None: - self._edges = self.get_cpp_triangulation().get_edges() - return self._edges - - def get_cpp_triangulation(self): - """ - Return the underlying C++ Triangulation object, creating it - if necessary. - """ - from matplotlib import _tri - if self._cpp_triangulation is None: - self._cpp_triangulation = _tri.Triangulation( - self.x, self.y, self.triangles, self.mask, self._edges, - self._neighbors, not self.is_delaunay) - return self._cpp_triangulation - - def get_masked_triangles(self): - """ - Return an array of triangles that are not masked. - """ - if self.mask is not None: - return self.triangles[~self.mask] - else: - return self.triangles - - @staticmethod - def get_from_args_and_kwargs(*args, **kwargs): - """ - Return a Triangulation object from the args and kwargs, and - the remaining args and kwargs with the consumed values removed. - - There are two alternatives: either the first argument is a - Triangulation object, in which case it is returned, or the args - and kwargs are sufficient to create a new Triangulation to - return. In the latter case, see Triangulation.__init__ for - the possible args and kwargs. - """ - if isinstance(args[0], Triangulation): - triangulation, *args = args - else: - x, y, *args = args - - # Check triangles in kwargs then args. - triangles = kwargs.pop('triangles', None) - from_args = False - if triangles is None and args: - triangles = args[0] - from_args = True - - if triangles is not None: - try: - triangles = np.asarray(triangles, dtype=np.int32) - except ValueError: - triangles = None - - if triangles is not None and (triangles.ndim != 2 or - triangles.shape[1] != 3): - triangles = None - - if triangles is not None and from_args: - args = args[1:] # Consumed first item in args. - - # Check for mask in kwargs. - mask = kwargs.pop('mask', None) - - triangulation = Triangulation(x, y, triangles, mask) - return triangulation, args, kwargs - - def get_trifinder(self): - """ - Return the default `matplotlib.tri.TriFinder` of this - triangulation, creating it if necessary. This allows the same - TriFinder object to be easily shared. - """ - if self._trifinder is None: - # Default TriFinder class. - from matplotlib.tri.trifinder import TrapezoidMapTriFinder - self._trifinder = TrapezoidMapTriFinder(self) - return self._trifinder - - @property - def neighbors(self): - """ - Return integer array of shape (ntri, 3) containing neighbor triangles. - - For each triangle, the indices of the three triangles that - share the same edges, or -1 if there is no such neighboring - triangle. ``neighbors[i, j]`` is the triangle that is the neighbor - to the edge from point index ``triangles[i, j]`` to point index - ``triangles[i, (j+1)%3]``. - """ - if self._neighbors is None: - self._neighbors = self.get_cpp_triangulation().get_neighbors() - return self._neighbors - - def set_mask(self, mask): - """ - Set or clear the mask array. - - Parameters - ---------- - mask : None or bool array of length ntri - """ - if mask is None: - self.mask = None - else: - self.mask = np.asarray(mask, dtype=bool) - if self.mask.shape != (self.triangles.shape[0],): - raise ValueError('mask array must have same length as ' - 'triangles array') - - # Set mask in C++ Triangulation. - if self._cpp_triangulation is not None: - self._cpp_triangulation.set_mask(self.mask) - - # Clear derived fields so they are recalculated when needed. - self._edges = None - self._neighbors = None - - # Recalculate TriFinder if it exists. - if self._trifinder is not None: - self._trifinder._initialize() diff --git a/lib/matplotlib/tri/tricontour.py b/lib/matplotlib/tri/tricontour.py deleted file mode 100644 index e236f7dfe687..000000000000 --- a/lib/matplotlib/tri/tricontour.py +++ /dev/null @@ -1,310 +0,0 @@ -import numpy as np - -from matplotlib import docstring -from matplotlib.contour import ContourSet -from matplotlib.tri.triangulation import Triangulation - - -@docstring.dedent_interpd -class TriContourSet(ContourSet): - """ - Create and store a set of contour lines or filled regions for - a triangular grid. - - This class is typically not instantiated directly by the user but by - `~.Axes.tricontour` and `~.Axes.tricontourf`. - - %(contour_set_attributes)s - """ - def __init__(self, ax, *args, **kwargs): - """ - Draw triangular grid contour lines or filled regions, - depending on whether keyword arg 'filled' is False - (default) or True. - - The first argument of the initializer must be an axes - object. The remaining arguments and keyword arguments - are described in the docstring of `~.Axes.tricontour`. - """ - super().__init__(ax, *args, **kwargs) - - def _process_args(self, *args, **kwargs): - """ - Process args and kwargs. - """ - if isinstance(args[0], TriContourSet): - C = args[0].cppContourGenerator - if self.levels is None: - self.levels = args[0].levels - else: - from matplotlib import _tri - tri, z = self._contour_args(args, kwargs) - C = _tri.TriContourGenerator(tri.get_cpp_triangulation(), z) - self._mins = [tri.x.min(), tri.y.min()] - self._maxs = [tri.x.max(), tri.y.max()] - - self.cppContourGenerator = C - return kwargs - - def _get_allsegs_and_allkinds(self): - """ - Create and return allsegs and allkinds by calling underlying C code. - """ - allsegs = [] - if self.filled: - lowers, uppers = self._get_lowers_and_uppers() - allkinds = [] - for lower, upper in zip(lowers, uppers): - segs, kinds = self.cppContourGenerator.create_filled_contour( - lower, upper) - allsegs.append([segs]) - allkinds.append([kinds]) - else: - allkinds = None - for level in self.levels: - segs = self.cppContourGenerator.create_contour(level) - allsegs.append(segs) - return allsegs, allkinds - - def _contour_args(self, args, kwargs): - if self.filled: - fn = 'contourf' - else: - fn = 'contour' - tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, - **kwargs) - z = np.ma.asarray(args[0]) - if z.shape != tri.x.shape: - raise ValueError('z array must have same length as triangulation x' - ' and y arrays') - - # z values must be finite, only need to check points that are included - # in the triangulation. - z_check = z[np.unique(tri.get_masked_triangles())] - if np.ma.is_masked(z_check): - raise ValueError('z must not contain masked points within the ' - 'triangulation') - if not np.isfinite(z_check).all(): - raise ValueError('z array must not contain non-finite values ' - 'within the triangulation') - - z = np.ma.masked_invalid(z, copy=False) - self.zmax = float(z_check.max()) - self.zmin = float(z_check.min()) - if self.logscale and self.zmin <= 0: - raise ValueError('Cannot %s log of negative values.' % fn) - self._process_contour_level_args(args[1:]) - return (tri, z) - - -docstring.interpd.update(_tricontour_doc=""" -Draw contour %(type)s on an unstructured triangular grid. - -The triangulation can be specified in one of two ways; either :: - - %(func)s(triangulation, ...) - -where *triangulation* is a `.Triangulation` object, or :: - - %(func)s(x, y, ...) - %(func)s(x, y, triangles, ...) - %(func)s(x, y, triangles=triangles, ...) - %(func)s(x, y, mask=mask, ...) - %(func)s(x, y, triangles, mask=mask, ...) - -in which case a `.Triangulation` object will be created. See that class' -docstring for an explanation of these cases. - -The remaining arguments may be:: - - %(func)s(..., Z) - -where *Z* is the array of values to contour, one per point in the -triangulation. The level values are chosen automatically. - -:: - - %(func)s(..., Z, levels) - -contour up to *levels+1* automatically chosen contour levels (*levels* -intervals). - -:: - - %(func)s(..., Z, levels) - -draw contour %(type)s at the values specified in sequence *levels*, which must -be in increasing order. - -:: - - %(func)s(Z, **kwargs) - -Use keyword arguments to control colors, linewidth, origin, cmap ... see below -for more details. - -Parameters ----------- -triangulation : `.Triangulation`, optional - The unstructured triangular grid. - - If specified, then *x*, *y*, *triangles*, and *mask* are not accepted. - -x, y : array-like, optional - The coordinates of the values in *Z*. - -triangles : int array-like of shape (ntri, 3), optional - For each triangle, the indices of the three points that make up the - triangle, ordered in an anticlockwise manner. If not specified, the - Delaunay triangulation is calculated. - -mask : bool array-like of shape (ntri), optional - Which triangles are masked out. - -Z : array-like(N, M) - The height values over which the contour is drawn. - -levels : int or array-like, optional - Determines the number and positions of the contour lines / regions. - - If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries to - automatically choose no more than *n+1* "nice" contour levels between - *vmin* and *vmax*. - - If array-like, draw contour lines at the specified levels. The values must - be in increasing order. - -Returns -------- -`~matplotlib.tri.TriContourSet` - -Other Parameters ----------------- -colors : color string or sequence of colors, optional - The colors of the levels, i.e., the contour %(type)s. - - The sequence is cycled for the levels in ascending order. If the sequence - is shorter than the number of levels, it's repeated. - - As a shortcut, single color strings may be used in place of one-element - lists, i.e. ``'red'`` instead of ``['red']`` to color all levels with the - same color. This shortcut does only work for color strings, not for other - ways of specifying colors. - - By default (value *None*), the colormap specified by *cmap* will be used. - -alpha : float, default: 1 - The alpha blending value, between 0 (transparent) and 1 (opaque). - -cmap : str or `.Colormap`, default: :rc:`image.cmap` - A `.Colormap` instance or registered colormap name. The colormap maps the - level values to colors. - - If both *colors* and *cmap* are given, an error is raised. - -norm : `~matplotlib.colors.Normalize`, optional - If a colormap is used, the `.Normalize` instance scales the level values to - the canonical colormap range [0, 1] for mapping to colors. If not given, - the default linear scaling is used. - -origin : {*None*, 'upper', 'lower', 'image'}, default: None - Determines the orientation and exact position of *Z* by specifying the - position of ``Z[0, 0]``. This is only relevant, if *X*, *Y* are not given. - - - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner. - - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. - - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left corner. - - 'image': Use the value from :rc:`image.origin`. - -extent : (x0, x1, y0, y1), optional - If *origin* is not *None*, then *extent* is interpreted as in `.imshow`: it - gives the outer pixel boundaries. In this case, the position of Z[0, 0] is - the center of the pixel, not a corner. If *origin* is *None*, then - (*x0*, *y0*) is the position of Z[0, 0], and (*x1*, *y1*) is the position - of Z[-1, -1]. - - This argument is ignored if *X* and *Y* are specified in the call to - contour. - -locator : ticker.Locator subclass, optional - The locator is used to determine the contour levels if they are not given - explicitly via *levels*. - Defaults to `~.ticker.MaxNLocator`. - -extend : {'neither', 'both', 'min', 'max'}, default: 'neither' - Determines the ``%(func)s``-coloring of values that are outside the - *levels* range. - - If 'neither', values outside the *levels* range are not colored. If 'min', - 'max' or 'both', color the values below, above or below and above the - *levels* range. - - Values below ``min(levels)`` and above ``max(levels)`` are mapped to the - under/over values of the `.Colormap`. Note that most colormaps do not have - dedicated colors for these by default, so that the over and under values - are the edge values of the colormap. You may want to set these values - explicitly using `.Colormap.set_under` and `.Colormap.set_over`. - - .. note:: - - An existing `.TriContourSet` does not get notified if properties of its - colormap are changed. Therefore, an explicit call to - `.ContourSet.changed()` is needed after modifying the colormap. The - explicit call can be left out, if a colorbar is assigned to the - `.TriContourSet` because it internally calls `.ContourSet.changed()`. - -xunits, yunits : registered units, optional - Override axis units by specifying an instance of a - :class:`matplotlib.units.ConversionInterface`.""") - - -@docstring.Substitution(func='tricontour', type='lines') -@docstring.dedent_interpd -def tricontour(ax, *args, **kwargs): - """ - %(_tricontour_doc)s - - linewidths : float or array-like, default: :rc:`contour.linewidth` - The line width of the contour lines. - - If a number, all levels will be plotted with this linewidth. - - If a sequence, the levels in ascending order will be plotted with - the linewidths in the order specified. - - If None, this falls back to :rc:`lines.linewidth`. - - linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional - If *linestyles* is *None*, the default is 'solid' unless the lines are - monochrome. In that case, negative contours will take their linestyle - from :rc:`contour.negative_linestyle` setting. - - *linestyles* can also be an iterable of the above strings specifying a - set of linestyles to be used. If this iterable is shorter than the - number of contour levels it will be repeated as necessary. - """ - kwargs['filled'] = False - return TriContourSet(ax, *args, **kwargs) - - -@docstring.Substitution(func='tricontourf', type='regions') -@docstring.dedent_interpd -def tricontourf(ax, *args, **kwargs): - """ - %(_tricontour_doc)s - - antialiased : bool, default: True - Whether to use antialiasing. - - Notes - ----- - `.tricontourf` fills intervals that are closed at the top; that is, for - boundaries *z1* and *z2*, the filled region is:: - - z1 < Z <= z2 - - except for the lowest interval, which is closed on both sides (i.e. it - includes the lowest value). - """ - kwargs['filled'] = True - return TriContourSet(ax, *args, **kwargs) diff --git a/lib/matplotlib/tri/trifinder.py b/lib/matplotlib/tri/trifinder.py deleted file mode 100644 index 8cf3830125eb..000000000000 --- a/lib/matplotlib/tri/trifinder.py +++ /dev/null @@ -1,93 +0,0 @@ -import numpy as np - -from matplotlib import cbook -from matplotlib.tri import Triangulation - - -class TriFinder: - """ - Abstract base class for classes used to find the triangles of a - Triangulation in which (x, y) points lie. - - Rather than instantiate an object of a class derived from TriFinder, it is - usually better to use the function `.Triangulation.get_trifinder`. - - Derived classes implement __call__(x, y) where x and y are array-like point - coordinates of the same shape. - """ - - def __init__(self, triangulation): - cbook._check_isinstance(Triangulation, triangulation=triangulation) - self._triangulation = triangulation - - -class TrapezoidMapTriFinder(TriFinder): - """ - `~matplotlib.tri.TriFinder` class implemented using the trapezoid - map algorithm from the book "Computational Geometry, Algorithms and - Applications", second edition, by M. de Berg, M. van Kreveld, M. Overmars - and O. Schwarzkopf. - - The triangulation must be valid, i.e. it must not have duplicate points, - triangles formed from colinear points, or overlapping triangles. The - algorithm has some tolerance to triangles formed from colinear points, but - this should not be relied upon. - """ - - def __init__(self, triangulation): - from matplotlib import _tri - super().__init__(triangulation) - self._cpp_trifinder = _tri.TrapezoidMapTriFinder( - triangulation.get_cpp_triangulation()) - self._initialize() - - def __call__(self, x, y): - """ - Return an array containing the indices of the triangles in which the - specified *x*, *y* points lie, or -1 for points that do not lie within - a triangle. - - *x*, *y* are array-like x and y coordinates of the same shape and any - number of dimensions. - - Returns integer array with the same shape and *x* and *y*. - """ - x = np.asarray(x, dtype=np.float64) - y = np.asarray(y, dtype=np.float64) - if x.shape != y.shape: - raise ValueError("x and y must be array-like with the same shape") - - # C++ does the heavy lifting, and expects 1D arrays. - indices = (self._cpp_trifinder.find_many(x.ravel(), y.ravel()) - .reshape(x.shape)) - return indices - - def _get_tree_stats(self): - """ - Return a python list containing the statistics about the node tree: - 0: number of nodes (tree size) - 1: number of unique nodes - 2: number of trapezoids (tree leaf nodes) - 3: number of unique trapezoids - 4: maximum parent count (max number of times a node is repeated in - tree) - 5: maximum depth of tree (one more than the maximum number of - comparisons needed to search through the tree) - 6: mean of all trapezoid depths (one more than the average number - of comparisons needed to search through the tree) - """ - return self._cpp_trifinder.get_tree_stats() - - def _initialize(self): - """ - Initialize the underlying C++ object. Can be called multiple times if, - for example, the triangulation is modified. - """ - self._cpp_trifinder.initialize() - - def _print_tree(self): - """ - Print a text representation of the node tree, which is useful for - debugging purposes. - """ - self._cpp_trifinder.print_tree() diff --git a/lib/matplotlib/tri/triinterpolate.py b/lib/matplotlib/tri/triinterpolate.py deleted file mode 100644 index be5b2f624d52..000000000000 --- a/lib/matplotlib/tri/triinterpolate.py +++ /dev/null @@ -1,1611 +0,0 @@ -""" -Interpolation inside triangular grids. -""" - -import numpy as np - -from matplotlib import cbook -from matplotlib.tri import Triangulation -from matplotlib.tri.trifinder import TriFinder -from matplotlib.tri.tritools import TriAnalyzer - -__all__ = ('TriInterpolator', 'LinearTriInterpolator', 'CubicTriInterpolator') - - -class TriInterpolator: - """ - Abstract base class for classes used to interpolate on a triangular grid. - - Derived classes implement the following methods: - - - ``__call__(x, y)``, - where x, y are array-like point coordinates of the same shape, and - that returns a masked array of the same shape containing the - interpolated z-values. - - - ``gradient(x, y)``, - where x, y are array-like point coordinates of the same - shape, and that returns a list of 2 masked arrays of the same shape - containing the 2 derivatives of the interpolator (derivatives of - interpolated z values with respect to x and y). - """ - - def __init__(self, triangulation, z, trifinder=None): - cbook._check_isinstance(Triangulation, triangulation=triangulation) - self._triangulation = triangulation - - self._z = np.asarray(z) - if self._z.shape != self._triangulation.x.shape: - raise ValueError("z array must have same length as triangulation x" - " and y arrays") - - cbook._check_isinstance((TriFinder, None), trifinder=trifinder) - self._trifinder = trifinder or self._triangulation.get_trifinder() - - # Default scaling factors : 1.0 (= no scaling) - # Scaling may be used for interpolations for which the order of - # magnitude of x, y has an impact on the interpolant definition. - # Please refer to :meth:`_interpolate_multikeys` for details. - self._unit_x = 1.0 - self._unit_y = 1.0 - - # Default triangle renumbering: None (= no renumbering) - # Renumbering may be used to avoid unnecessary computations - # if complex calculations are done inside the Interpolator. - # Please refer to :meth:`_interpolate_multikeys` for details. - self._tri_renum = None - - # __call__ and gradient docstrings are shared by all subclasses - # (except, if needed, relevant additions). - # However these methods are only implemented in subclasses to avoid - # confusion in the documentation. - _docstring__call__ = """ - Returns a masked array containing interpolated values at the specified - (x, y) points. - - Parameters - ---------- - x, y : array-like - x and y coordinates of the same shape and any number of - dimensions. - - Returns - ------- - np.ma.array - Masked array of the same shape as *x* and *y*; values corresponding - to (*x*, *y*) points outside of the triangulation are masked out. - - """ - - _docstringgradient = r""" - Returns a list of 2 masked arrays containing interpolated derivatives - at the specified (x, y) points. - - Parameters - ---------- - x, y : array-like - x and y coordinates of the same shape and any number of - dimensions. - - Returns - ------- - dzdx, dzdy : np.ma.array - 2 masked arrays of the same shape as *x* and *y*; values - corresponding to (x, y) points outside of the triangulation - are masked out. - The first returned array contains the values of - :math:`\frac{\partial z}{\partial x}` and the second those of - :math:`\frac{\partial z}{\partial y}`. - - """ - - def _interpolate_multikeys(self, x, y, tri_index=None, - return_keys=('z',)): - """ - Versatile (private) method defined for all TriInterpolators. - - :meth:`_interpolate_multikeys` is a wrapper around method - :meth:`_interpolate_single_key` (to be defined in the child - subclasses). - :meth:`_interpolate_single_key actually performs the interpolation, - but only for 1-dimensional inputs and at valid locations (inside - unmasked triangles of the triangulation). - - The purpose of :meth:`_interpolate_multikeys` is to implement the - following common tasks needed in all subclasses implementations: - - - calculation of containing triangles - - dealing with more than one interpolation request at the same - location (e.g., if the 2 derivatives are requested, it is - unnecessary to compute the containing triangles twice) - - scaling according to self._unit_x, self._unit_y - - dealing with points outside of the grid (with fill value np.nan) - - dealing with multi-dimensional *x*, *y* arrays: flattening for - :meth:`_interpolate_params` call and final reshaping. - - (Note that np.vectorize could do most of those things very well for - you, but it does it by function evaluations over successive tuples of - the input arrays. Therefore, this tends to be more time consuming than - using optimized numpy functions - e.g., np.dot - which can be used - easily on the flattened inputs, in the child-subclass methods - :meth:`_interpolate_single_key`.) - - It is guaranteed that the calls to :meth:`_interpolate_single_key` - will be done with flattened (1-d) array-like input parameters *x*, *y* - and with flattened, valid `tri_index` arrays (no -1 index allowed). - - Parameters - ---------- - x, y : array-like - x and y coordinates where interpolated values are requested. - tri_index : array-like of int, optional - Array of the containing triangle indices, same shape as - *x* and *y*. Defaults to None. If None, these indices - will be computed by a TriFinder instance. - (Note: For point outside the grid, tri_index[ipt] shall be -1). - return_keys : tuple of keys from {'z', 'dzdx', 'dzdy'} - Defines the interpolation arrays to return, and in which order. - - Returns - ------- - list of arrays - Each array-like contains the expected interpolated values in the - order defined by *return_keys* parameter. - """ - # Flattening and rescaling inputs arrays x, y - # (initial shape is stored for output) - x = np.asarray(x, dtype=np.float64) - y = np.asarray(y, dtype=np.float64) - sh_ret = x.shape - if x.shape != y.shape: - raise ValueError("x and y shall have same shapes." - " Given: {0} and {1}".format(x.shape, y.shape)) - x = np.ravel(x) - y = np.ravel(y) - x_scaled = x/self._unit_x - y_scaled = y/self._unit_y - size_ret = np.size(x_scaled) - - # Computes & ravels the element indexes, extract the valid ones. - if tri_index is None: - tri_index = self._trifinder(x, y) - else: - if tri_index.shape != sh_ret: - raise ValueError( - "tri_index array is provided and shall" - " have same shape as x and y. Given: " - "{0} and {1}".format(tri_index.shape, sh_ret)) - tri_index = np.ravel(tri_index) - - mask_in = (tri_index != -1) - if self._tri_renum is None: - valid_tri_index = tri_index[mask_in] - else: - valid_tri_index = self._tri_renum[tri_index[mask_in]] - valid_x = x_scaled[mask_in] - valid_y = y_scaled[mask_in] - - ret = [] - for return_key in return_keys: - # Find the return index associated with the key. - try: - return_index = {'z': 0, 'dzdx': 1, 'dzdy': 2}[return_key] - except KeyError as err: - raise ValueError("return_keys items shall take values in" - " {'z', 'dzdx', 'dzdy'}") from err - - # Sets the scale factor for f & df components - scale = [1., 1./self._unit_x, 1./self._unit_y][return_index] - - # Computes the interpolation - ret_loc = np.empty(size_ret, dtype=np.float64) - ret_loc[~mask_in] = np.nan - ret_loc[mask_in] = self._interpolate_single_key( - return_key, valid_tri_index, valid_x, valid_y) * scale - ret += [np.ma.masked_invalid(ret_loc.reshape(sh_ret), copy=False)] - - return ret - - def _interpolate_single_key(self, return_key, tri_index, x, y): - """ - Interpolate at points belonging to the triangulation - (inside an unmasked triangles). - - Parameters - ---------- - return_index : {'z', 'dzdx', 'dzdy'} - Identifies the requested values (z or its derivatives) - tri_index : 1d int array - Valid triangle index (-1 prohibited) - x, y : 1d arrays, same shape as `tri_index` - Valid locations where interpolation is requested. - - Returns - ------- - 1-d array - Returned array of the same size as *tri_index* - """ - raise NotImplementedError("TriInterpolator subclasses" + - "should implement _interpolate_single_key!") - - -class LinearTriInterpolator(TriInterpolator): - """ - Linear interpolator on a triangular grid. - - Each triangle is represented by a plane so that an interpolated value at - point (x, y) lies on the plane of the triangle containing (x, y). - Interpolated values are therefore continuous across the triangulation, but - their first derivatives are discontinuous at edges between triangles. - - Parameters - ---------- - triangulation : `~matplotlib.tri.Triangulation` - The triangulation to interpolate over. - z : array-like of shape (npoints,) - Array of values, defined at grid points, to interpolate between. - trifinder : `~matplotlib.tri.TriFinder`, optional - If this is not specified, the Triangulation's default TriFinder will - be used by calling `.Triangulation.get_trifinder`. - - Methods - ------- - `__call__` (x, y) : Returns interpolated values at (x, y) points. - `gradient` (x, y) : Returns interpolated derivatives at (x, y) points. - - """ - def __init__(self, triangulation, z, trifinder=None): - super().__init__(triangulation, z, trifinder) - - # Store plane coefficients for fast interpolation calculations. - self._plane_coefficients = \ - self._triangulation.calculate_plane_coefficients(self._z) - - def __call__(self, x, y): - return self._interpolate_multikeys(x, y, tri_index=None, - return_keys=('z',))[0] - __call__.__doc__ = TriInterpolator._docstring__call__ - - def gradient(self, x, y): - return self._interpolate_multikeys(x, y, tri_index=None, - return_keys=('dzdx', 'dzdy')) - gradient.__doc__ = TriInterpolator._docstringgradient - - def _interpolate_single_key(self, return_key, tri_index, x, y): - if return_key == 'z': - return (self._plane_coefficients[tri_index, 0]*x + - self._plane_coefficients[tri_index, 1]*y + - self._plane_coefficients[tri_index, 2]) - elif return_key == 'dzdx': - return self._plane_coefficients[tri_index, 0] - elif return_key == 'dzdy': - return self._plane_coefficients[tri_index, 1] - else: - raise ValueError("Invalid return_key: " + return_key) - - -class CubicTriInterpolator(TriInterpolator): - r""" - Cubic interpolator on a triangular grid. - - In one-dimension - on a segment - a cubic interpolating function is - defined by the values of the function and its derivative at both ends. - This is almost the same in 2-d inside a triangle, except that the values - of the function and its 2 derivatives have to be defined at each triangle - node. - - The CubicTriInterpolator takes the value of the function at each node - - provided by the user - and internally computes the value of the - derivatives, resulting in a smooth interpolation. - (As a special feature, the user can also impose the value of the - derivatives at each node, but this is not supposed to be the common - usage.) - - Parameters - ---------- - triangulation : `~matplotlib.tri.Triangulation` - The triangulation to interpolate over. - z : array-like of shape (npoints,) - Array of values, defined at grid points, to interpolate between. - kind : {'min_E', 'geom', 'user'}, optional - Choice of the smoothing algorithm, in order to compute - the interpolant derivatives (defaults to 'min_E'): - - - if 'min_E': (default) The derivatives at each node is computed - to minimize a bending energy. - - if 'geom': The derivatives at each node is computed as a - weighted average of relevant triangle normals. To be used for - speed optimization (large grids). - - if 'user': The user provides the argument *dz*, no computation - is hence needed. - - trifinder : `~matplotlib.tri.TriFinder`, optional - If not specified, the Triangulation's default TriFinder will - be used by calling `.Triangulation.get_trifinder`. - dz : tuple of array-likes (dzdx, dzdy), optional - Used only if *kind* ='user'. In this case *dz* must be provided as - (dzdx, dzdy) where dzdx, dzdy are arrays of the same shape as *z* and - are the interpolant first derivatives at the *triangulation* points. - - Methods - ------- - `__call__` (x, y) : Returns interpolated values at (x, y) points. - `gradient` (x, y) : Returns interpolated derivatives at (x, y) points. - - Notes - ----- - This note is a bit technical and details how the cubic interpolation is - computed. - - The interpolation is based on a Clough-Tocher subdivision scheme of - the *triangulation* mesh (to make it clearer, each triangle of the - grid will be divided in 3 child-triangles, and on each child triangle - the interpolated function is a cubic polynomial of the 2 coordinates). - This technique originates from FEM (Finite Element Method) analysis; - the element used is a reduced Hsieh-Clough-Tocher (HCT) - element. Its shape functions are described in [1]_. - The assembled function is guaranteed to be C1-smooth, i.e. it is - continuous and its first derivatives are also continuous (this - is easy to show inside the triangles but is also true when crossing the - edges). - - In the default case (*kind* ='min_E'), the interpolant minimizes a - curvature energy on the functional space generated by the HCT element - shape functions - with imposed values but arbitrary derivatives at each - node. The minimized functional is the integral of the so-called total - curvature (implementation based on an algorithm from [2]_ - PCG sparse - solver): - - .. math:: - - E(z) = \frac{1}{2} \int_{\Omega} \left( - \left( \frac{\partial^2{z}}{\partial{x}^2} \right)^2 + - \left( \frac{\partial^2{z}}{\partial{y}^2} \right)^2 + - 2\left( \frac{\partial^2{z}}{\partial{y}\partial{x}} \right)^2 - \right) dx\,dy - - If the case *kind* ='geom' is chosen by the user, a simple geometric - approximation is used (weighted average of the triangle normal - vectors), which could improve speed on very large grids. - - References - ---------- - .. [1] Michel Bernadou, Kamal Hassan, "Basis functions for general - Hsieh-Clough-Tocher triangles, complete or reduced.", - International Journal for Numerical Methods in Engineering, - 17(5):784 - 789. 2.01. - .. [2] C.T. Kelley, "Iterative Methods for Optimization". - - """ - def __init__(self, triangulation, z, kind='min_E', trifinder=None, - dz=None): - super().__init__(triangulation, z, trifinder) - - # Loads the underlying c++ _triangulation. - # (During loading, reordering of triangulation._triangles may occur so - # that all final triangles are now anti-clockwise) - self._triangulation.get_cpp_triangulation() - - # To build the stiffness matrix and avoid zero-energy spurious modes - # we will only store internally the valid (unmasked) triangles and - # the necessary (used) points coordinates. - # 2 renumbering tables need to be computed and stored: - # - a triangle renum table in order to translate the result from a - # TriFinder instance into the internal stored triangle number. - # - a node renum table to overwrite the self._z values into the new - # (used) node numbering. - tri_analyzer = TriAnalyzer(self._triangulation) - (compressed_triangles, compressed_x, compressed_y, tri_renum, - node_renum) = tri_analyzer._get_compressed_triangulation() - self._triangles = compressed_triangles - self._tri_renum = tri_renum - # Taking into account the node renumbering in self._z: - valid_node = (node_renum != -1) - self._z[node_renum[valid_node]] = self._z[valid_node] - - # Computing scale factors - self._unit_x = np.ptp(compressed_x) - self._unit_y = np.ptp(compressed_y) - self._pts = np.column_stack([compressed_x / self._unit_x, - compressed_y / self._unit_y]) - # Computing triangle points - self._tris_pts = self._pts[self._triangles] - # Computing eccentricities - self._eccs = self._compute_tri_eccentricities(self._tris_pts) - # Computing dof estimations for HCT triangle shape function - self._dof = self._compute_dof(kind, dz=dz) - # Loading HCT element - self._ReferenceElement = _ReducedHCT_Element() - - def __call__(self, x, y): - return self._interpolate_multikeys(x, y, tri_index=None, - return_keys=('z',))[0] - __call__.__doc__ = TriInterpolator._docstring__call__ - - def gradient(self, x, y): - return self._interpolate_multikeys(x, y, tri_index=None, - return_keys=('dzdx', 'dzdy')) - gradient.__doc__ = TriInterpolator._docstringgradient - - def _interpolate_single_key(self, return_key, tri_index, x, y): - tris_pts = self._tris_pts[tri_index] - alpha = self._get_alpha_vec(x, y, tris_pts) - ecc = self._eccs[tri_index] - dof = np.expand_dims(self._dof[tri_index], axis=1) - if return_key == 'z': - return self._ReferenceElement.get_function_values( - alpha, ecc, dof) - elif return_key in ['dzdx', 'dzdy']: - J = self._get_jacobian(tris_pts) - dzdx = self._ReferenceElement.get_function_derivatives( - alpha, J, ecc, dof) - if return_key == 'dzdx': - return dzdx[:, 0, 0] - else: - return dzdx[:, 1, 0] - else: - raise ValueError("Invalid return_key: " + return_key) - - def _compute_dof(self, kind, dz=None): - """ - Compute and return nodal dofs according to kind. - - Parameters - ---------- - kind : {'min_E', 'geom', 'user'} - Choice of the _DOF_estimator subclass to estimate the gradient. - dz : tuple of array-likes (dzdx, dzdy), optional - Used only if *kind*=user; in this case passed to the - :class:`_DOF_estimator_user`. - - Returns - ------- - array-like, shape (npts, 2) - Estimation of the gradient at triangulation nodes (stored as - degree of freedoms of reduced-HCT triangle elements). - """ - if kind == 'user': - if dz is None: - raise ValueError("For a CubicTriInterpolator with " - "*kind*='user', a valid *dz* " - "argument is expected.") - TE = _DOF_estimator_user(self, dz=dz) - elif kind == 'geom': - TE = _DOF_estimator_geom(self) - elif kind == 'min_E': - TE = _DOF_estimator_min_E(self) - else: - cbook._check_in_list(['user', 'geom', 'min_E'], kind=kind) - return TE.compute_dof_from_df() - - @staticmethod - def _get_alpha_vec(x, y, tris_pts): - """ - Fast (vectorized) function to compute barycentric coordinates alpha. - - Parameters - ---------- - x, y : array-like of dim 1 (shape (nx,)) - Coordinates of the points whose points barycentric coordinates are - requested. - tris_pts : array like of dim 3 (shape: (nx, 3, 2)) - Coordinates of the containing triangles apexes. - - Returns - ------- - array of dim 2 (shape (nx, 3)) - Barycentric coordinates of the points inside the containing - triangles. - """ - ndim = tris_pts.ndim-2 - - a = tris_pts[:, 1, :] - tris_pts[:, 0, :] - b = tris_pts[:, 2, :] - tris_pts[:, 0, :] - abT = np.stack([a, b], axis=-1) - ab = _transpose_vectorized(abT) - OM = np.stack([x, y], axis=1) - tris_pts[:, 0, :] - - metric = _prod_vectorized(ab, abT) - # Here we try to deal with the colinear cases. - # metric_inv is in this case set to the Moore-Penrose pseudo-inverse - # meaning that we will still return a set of valid barycentric - # coordinates. - metric_inv = _pseudo_inv22sym_vectorized(metric) - Covar = _prod_vectorized(ab, _transpose_vectorized( - np.expand_dims(OM, ndim))) - ksi = _prod_vectorized(metric_inv, Covar) - alpha = _to_matrix_vectorized([ - [1-ksi[:, 0, 0]-ksi[:, 1, 0]], [ksi[:, 0, 0]], [ksi[:, 1, 0]]]) - return alpha - - @staticmethod - def _get_jacobian(tris_pts): - """ - Fast (vectorized) function to compute triangle jacobian matrix. - - Parameters - ---------- - tris_pts : array like of dim 3 (shape: (nx, 3, 2)) - Coordinates of the containing triangles apexes. - - Returns - ------- - array of dim 3 (shape (nx, 2, 2)) - Barycentric coordinates of the points inside the containing - triangles. - J[itri, :, :] is the jacobian matrix at apex 0 of the triangle - itri, so that the following (matrix) relationship holds: - [dz/dksi] = [J] x [dz/dx] - with x: global coordinates - ksi: element parametric coordinates in triangle first apex - local basis. - """ - a = np.array(tris_pts[:, 1, :] - tris_pts[:, 0, :]) - b = np.array(tris_pts[:, 2, :] - tris_pts[:, 0, :]) - J = _to_matrix_vectorized([[a[:, 0], a[:, 1]], - [b[:, 0], b[:, 1]]]) - return J - - @staticmethod - def _compute_tri_eccentricities(tris_pts): - """ - Compute triangle eccentricities. - - Parameters - ---------- - tris_pts : array like of dim 3 (shape: (nx, 3, 2)) - Coordinates of the triangles apexes. - - Returns - ------- - array like of dim 2 (shape: (nx, 3)) - The so-called eccentricity parameters [1] needed for HCT triangular - element. - """ - a = np.expand_dims(tris_pts[:, 2, :] - tris_pts[:, 1, :], axis=2) - b = np.expand_dims(tris_pts[:, 0, :] - tris_pts[:, 2, :], axis=2) - c = np.expand_dims(tris_pts[:, 1, :] - tris_pts[:, 0, :], axis=2) - # Do not use np.squeeze, this is dangerous if only one triangle - # in the triangulation... - dot_a = _prod_vectorized(_transpose_vectorized(a), a)[:, 0, 0] - dot_b = _prod_vectorized(_transpose_vectorized(b), b)[:, 0, 0] - dot_c = _prod_vectorized(_transpose_vectorized(c), c)[:, 0, 0] - # Note that this line will raise a warning for dot_a, dot_b or dot_c - # zeros, but we choose not to support triangles with duplicate points. - return _to_matrix_vectorized([[(dot_c-dot_b) / dot_a], - [(dot_a-dot_c) / dot_b], - [(dot_b-dot_a) / dot_c]]) - - -# FEM element used for interpolation and for solving minimisation -# problem (Reduced HCT element) -class _ReducedHCT_Element: - """ - Implementation of reduced HCT triangular element with explicit shape - functions. - - Computes z, dz, d2z and the element stiffness matrix for bending energy: - E(f) = integral( (d2z/dx2 + d2z/dy2)**2 dA) - - *** Reference for the shape functions: *** - [1] Basis functions for general Hsieh-Clough-Tocher _triangles, complete or - reduced. - Michel Bernadou, Kamal Hassan - International Journal for Numerical Methods in Engineering. - 17(5):784 - 789. 2.01 - - *** Element description: *** - 9 dofs: z and dz given at 3 apex - C1 (conform) - - """ - # 1) Loads matrices to generate shape functions as a function of - # triangle eccentricities - based on [1] p.11 ''' - M = np.array([ - [ 0.00, 0.00, 0.00, 4.50, 4.50, 0.00, 0.00, 0.00, 0.00, 0.00], - [-0.25, 0.00, 0.00, 0.50, 1.25, 0.00, 0.00, 0.00, 0.00, 0.00], - [-0.25, 0.00, 0.00, 1.25, 0.50, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.50, 1.00, 0.00, -1.50, 0.00, 3.00, 3.00, 0.00, 0.00, 3.00], - [ 0.00, 0.00, 0.00, -0.25, 0.25, 0.00, 1.00, 0.00, 0.00, 0.50], - [ 0.25, 0.00, 0.00, -0.50, -0.25, 1.00, 0.00, 0.00, 0.00, 1.00], - [ 0.50, 0.00, 1.00, 0.00, -1.50, 0.00, 0.00, 3.00, 3.00, 3.00], - [ 0.25, 0.00, 0.00, -0.25, -0.50, 0.00, 0.00, 0.00, 1.00, 1.00], - [ 0.00, 0.00, 0.00, 0.25, -0.25, 0.00, 0.00, 1.00, 0.00, 0.50]]) - M0 = np.array([ - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [-1.00, 0.00, 0.00, 1.50, 1.50, 0.00, 0.00, 0.00, 0.00, -3.00], - [-0.50, 0.00, 0.00, 0.75, 0.75, 0.00, 0.00, 0.00, 0.00, -1.50], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 1.00, 0.00, 0.00, -1.50, -1.50, 0.00, 0.00, 0.00, 0.00, 3.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.50, 0.00, 0.00, -0.75, -0.75, 0.00, 0.00, 0.00, 0.00, 1.50]]) - M1 = np.array([ - [-0.50, 0.00, 0.00, 1.50, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [-0.25, 0.00, 0.00, 0.75, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.50, 0.00, 0.00, -1.50, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.25, 0.00, 0.00, -0.75, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]]) - M2 = np.array([ - [ 0.50, 0.00, 0.00, 0.00, -1.50, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.25, 0.00, 0.00, 0.00, -0.75, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [-0.50, 0.00, 0.00, 0.00, 1.50, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [-0.25, 0.00, 0.00, 0.00, 0.75, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]]) - - # 2) Loads matrices to rotate components of gradient & Hessian - # vectors in the reference basis of triangle first apex (a0) - rotate_dV = np.array([[ 1., 0.], [ 0., 1.], - [ 0., 1.], [-1., -1.], - [-1., -1.], [ 1., 0.]]) - - rotate_d2V = np.array([[1., 0., 0.], [0., 1., 0.], [ 0., 0., 1.], - [0., 1., 0.], [1., 1., 1.], [ 0., -2., -1.], - [1., 1., 1.], [1., 0., 0.], [-2., 0., -1.]]) - - # 3) Loads Gauss points & weights on the 3 sub-_triangles for P2 - # exact integral - 3 points on each subtriangles. - # NOTE: as the 2nd derivative is discontinuous , we really need those 9 - # points! - n_gauss = 9 - gauss_pts = np.array([[13./18., 4./18., 1./18.], - [ 4./18., 13./18., 1./18.], - [ 7./18., 7./18., 4./18.], - [ 1./18., 13./18., 4./18.], - [ 1./18., 4./18., 13./18.], - [ 4./18., 7./18., 7./18.], - [ 4./18., 1./18., 13./18.], - [13./18., 1./18., 4./18.], - [ 7./18., 4./18., 7./18.]], dtype=np.float64) - gauss_w = np.ones([9], dtype=np.float64) / 9. - - # 4) Stiffness matrix for curvature energy - E = np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 2.]]) - - # 5) Loads the matrix to compute DOF_rot from tri_J at apex 0 - J0_to_J1 = np.array([[-1., 1.], [-1., 0.]]) - J0_to_J2 = np.array([[ 0., -1.], [ 1., -1.]]) - - def get_function_values(self, alpha, ecc, dofs): - """ - Parameters - ---------- - alpha : is a (N x 3 x 1) array (array of column-matrices) of - barycentric coordinates, - ecc : is a (N x 3 x 1) array (array of column-matrices) of triangle - eccentricities, - dofs : is a (N x 1 x 9) arrays (arrays of row-matrices) of computed - degrees of freedom. - - Returns - ------- - Returns the N-array of interpolated function values. - """ - subtri = np.argmin(alpha, axis=1)[:, 0] - ksi = _roll_vectorized(alpha, -subtri, axis=0) - E = _roll_vectorized(ecc, -subtri, axis=0) - x = ksi[:, 0, 0] - y = ksi[:, 1, 0] - z = ksi[:, 2, 0] - x_sq = x*x - y_sq = y*y - z_sq = z*z - V = _to_matrix_vectorized([ - [x_sq*x], [y_sq*y], [z_sq*z], [x_sq*z], [x_sq*y], [y_sq*x], - [y_sq*z], [z_sq*y], [z_sq*x], [x*y*z]]) - prod = _prod_vectorized(self.M, V) - prod += _scalar_vectorized(E[:, 0, 0], - _prod_vectorized(self.M0, V)) - prod += _scalar_vectorized(E[:, 1, 0], - _prod_vectorized(self.M1, V)) - prod += _scalar_vectorized(E[:, 2, 0], - _prod_vectorized(self.M2, V)) - s = _roll_vectorized(prod, 3*subtri, axis=0) - return _prod_vectorized(dofs, s)[:, 0, 0] - - def get_function_derivatives(self, alpha, J, ecc, dofs): - """ - Parameters - ---------- - *alpha* is a (N x 3 x 1) array (array of column-matrices of - barycentric coordinates) - *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at - triangle first apex) - *ecc* is a (N x 3 x 1) array (array of column-matrices of triangle - eccentricities) - *dofs* is a (N x 1 x 9) arrays (arrays of row-matrices) of computed - degrees of freedom. - - Returns - ------- - Returns the values of interpolated function derivatives [dz/dx, dz/dy] - in global coordinates at locations alpha, as a column-matrices of - shape (N x 2 x 1). - """ - subtri = np.argmin(alpha, axis=1)[:, 0] - ksi = _roll_vectorized(alpha, -subtri, axis=0) - E = _roll_vectorized(ecc, -subtri, axis=0) - x = ksi[:, 0, 0] - y = ksi[:, 1, 0] - z = ksi[:, 2, 0] - x_sq = x*x - y_sq = y*y - z_sq = z*z - dV = _to_matrix_vectorized([ - [ -3.*x_sq, -3.*x_sq], - [ 3.*y_sq, 0.], - [ 0., 3.*z_sq], - [ -2.*x*z, -2.*x*z+x_sq], - [-2.*x*y+x_sq, -2.*x*y], - [ 2.*x*y-y_sq, -y_sq], - [ 2.*y*z, y_sq], - [ z_sq, 2.*y*z], - [ -z_sq, 2.*x*z-z_sq], - [ x*z-y*z, x*y-y*z]]) - # Puts back dV in first apex basis - dV = _prod_vectorized(dV, _extract_submatrices( - self.rotate_dV, subtri, block_size=2, axis=0)) - - prod = _prod_vectorized(self.M, dV) - prod += _scalar_vectorized(E[:, 0, 0], - _prod_vectorized(self.M0, dV)) - prod += _scalar_vectorized(E[:, 1, 0], - _prod_vectorized(self.M1, dV)) - prod += _scalar_vectorized(E[:, 2, 0], - _prod_vectorized(self.M2, dV)) - dsdksi = _roll_vectorized(prod, 3*subtri, axis=0) - dfdksi = _prod_vectorized(dofs, dsdksi) - # In global coordinates: - # Here we try to deal with the simplest colinear cases, returning a - # null matrix. - J_inv = _safe_inv22_vectorized(J) - dfdx = _prod_vectorized(J_inv, _transpose_vectorized(dfdksi)) - return dfdx - - def get_function_hessians(self, alpha, J, ecc, dofs): - """ - Parameters - ---------- - *alpha* is a (N x 3 x 1) array (array of column-matrices) of - barycentric coordinates - *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at - triangle first apex) - *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle - eccentricities - *dofs* is a (N x 1 x 9) arrays (arrays of row-matrices) of computed - degrees of freedom. - - Returns - ------- - Returns the values of interpolated function 2nd-derivatives - [d2z/dx2, d2z/dy2, d2z/dxdy] in global coordinates at locations alpha, - as a column-matrices of shape (N x 3 x 1). - """ - d2sdksi2 = self.get_d2Sidksij2(alpha, ecc) - d2fdksi2 = _prod_vectorized(dofs, d2sdksi2) - H_rot = self.get_Hrot_from_J(J) - d2fdx2 = _prod_vectorized(d2fdksi2, H_rot) - return _transpose_vectorized(d2fdx2) - - def get_d2Sidksij2(self, alpha, ecc): - """ - Parameters - ---------- - *alpha* is a (N x 3 x 1) array (array of column-matrices) of - barycentric coordinates - *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle - eccentricities - - Returns - ------- - Returns the arrays d2sdksi2 (N x 3 x 1) Hessian of shape functions - expressed in covariant coordinates in first apex basis. - """ - subtri = np.argmin(alpha, axis=1)[:, 0] - ksi = _roll_vectorized(alpha, -subtri, axis=0) - E = _roll_vectorized(ecc, -subtri, axis=0) - x = ksi[:, 0, 0] - y = ksi[:, 1, 0] - z = ksi[:, 2, 0] - d2V = _to_matrix_vectorized([ - [ 6.*x, 6.*x, 6.*x], - [ 6.*y, 0., 0.], - [ 0., 6.*z, 0.], - [ 2.*z, 2.*z-4.*x, 2.*z-2.*x], - [2.*y-4.*x, 2.*y, 2.*y-2.*x], - [2.*x-4.*y, 0., -2.*y], - [ 2.*z, 0., 2.*y], - [ 0., 2.*y, 2.*z], - [ 0., 2.*x-4.*z, -2.*z], - [ -2.*z, -2.*y, x-y-z]]) - # Puts back d2V in first apex basis - d2V = _prod_vectorized(d2V, _extract_submatrices( - self.rotate_d2V, subtri, block_size=3, axis=0)) - prod = _prod_vectorized(self.M, d2V) - prod += _scalar_vectorized(E[:, 0, 0], - _prod_vectorized(self.M0, d2V)) - prod += _scalar_vectorized(E[:, 1, 0], - _prod_vectorized(self.M1, d2V)) - prod += _scalar_vectorized(E[:, 2, 0], - _prod_vectorized(self.M2, d2V)) - d2sdksi2 = _roll_vectorized(prod, 3*subtri, axis=0) - return d2sdksi2 - - def get_bending_matrices(self, J, ecc): - """ - Parameters - ---------- - *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at - triangle first apex) - *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle - eccentricities - - Returns - ------- - Returns the element K matrices for bending energy expressed in - GLOBAL nodal coordinates. - K_ij = integral [ (d2zi/dx2 + d2zi/dy2) * (d2zj/dx2 + d2zj/dy2) dA] - tri_J is needed to rotate dofs from local basis to global basis - """ - n = np.size(ecc, 0) - - # 1) matrix to rotate dofs in global coordinates - J1 = _prod_vectorized(self.J0_to_J1, J) - J2 = _prod_vectorized(self.J0_to_J2, J) - DOF_rot = np.zeros([n, 9, 9], dtype=np.float64) - DOF_rot[:, 0, 0] = 1 - DOF_rot[:, 3, 3] = 1 - DOF_rot[:, 6, 6] = 1 - DOF_rot[:, 1:3, 1:3] = J - DOF_rot[:, 4:6, 4:6] = J1 - DOF_rot[:, 7:9, 7:9] = J2 - - # 2) matrix to rotate Hessian in global coordinates. - H_rot, area = self.get_Hrot_from_J(J, return_area=True) - - # 3) Computes stiffness matrix - # Gauss quadrature. - K = np.zeros([n, 9, 9], dtype=np.float64) - weights = self.gauss_w - pts = self.gauss_pts - for igauss in range(self.n_gauss): - alpha = np.tile(pts[igauss, :], n).reshape(n, 3) - alpha = np.expand_dims(alpha, 2) - weight = weights[igauss] - d2Skdksi2 = self.get_d2Sidksij2(alpha, ecc) - d2Skdx2 = _prod_vectorized(d2Skdksi2, H_rot) - K += weight * _prod_vectorized(_prod_vectorized(d2Skdx2, self.E), - _transpose_vectorized(d2Skdx2)) - - # 4) With nodal (not elem) dofs - K = _prod_vectorized(_prod_vectorized(_transpose_vectorized(DOF_rot), - K), DOF_rot) - - # 5) Need the area to compute total element energy - return _scalar_vectorized(area, K) - - def get_Hrot_from_J(self, J, return_area=False): - """ - Parameters - ---------- - *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at - triangle first apex) - - Returns - ------- - Returns H_rot used to rotate Hessian from local basis of first apex, - to global coordinates. - if *return_area* is True, returns also the triangle area (0.5*det(J)) - """ - # Here we try to deal with the simplest colinear cases; a null - # energy and area is imposed. - J_inv = _safe_inv22_vectorized(J) - Ji00 = J_inv[:, 0, 0] - Ji11 = J_inv[:, 1, 1] - Ji10 = J_inv[:, 1, 0] - Ji01 = J_inv[:, 0, 1] - H_rot = _to_matrix_vectorized([ - [Ji00*Ji00, Ji10*Ji10, Ji00*Ji10], - [Ji01*Ji01, Ji11*Ji11, Ji01*Ji11], - [2*Ji00*Ji01, 2*Ji11*Ji10, Ji00*Ji11+Ji10*Ji01]]) - if not return_area: - return H_rot - else: - area = 0.5 * (J[:, 0, 0]*J[:, 1, 1] - J[:, 0, 1]*J[:, 1, 0]) - return H_rot, area - - def get_Kff_and_Ff(self, J, ecc, triangles, Uc): - """ - Build K and F for the following elliptic formulation: - minimization of curvature energy with value of function at node - imposed and derivatives 'free'. - - Build the global Kff matrix in cco format. - Build the full Ff vec Ff = - Kfc x Uc. - - Parameters - ---------- - *J* is a (N x 2 x 2) array of jacobian matrices (jacobian matrix at - triangle first apex) - *ecc* is a (N x 3 x 1) array (array of column-matrices) of triangle - eccentricities - *triangles* is a (N x 3) array of nodes indexes. - *Uc* is (N x 3) array of imposed displacements at nodes - - Returns - ------- - (Kff_rows, Kff_cols, Kff_vals) Kff matrix in coo format - Duplicate - (row, col) entries must be summed. - Ff: force vector - dim npts * 3 - """ - ntri = np.size(ecc, 0) - vec_range = np.arange(ntri, dtype=np.int32) - c_indices = np.full(ntri, -1, dtype=np.int32) # for unused dofs, -1 - f_dof = [1, 2, 4, 5, 7, 8] - c_dof = [0, 3, 6] - - # vals, rows and cols indices in global dof numbering - f_dof_indices = _to_matrix_vectorized([[ - c_indices, triangles[:, 0]*2, triangles[:, 0]*2+1, - c_indices, triangles[:, 1]*2, triangles[:, 1]*2+1, - c_indices, triangles[:, 2]*2, triangles[:, 2]*2+1]]) - - expand_indices = np.ones([ntri, 9, 1], dtype=np.int32) - f_row_indices = _prod_vectorized(_transpose_vectorized(f_dof_indices), - _transpose_vectorized(expand_indices)) - f_col_indices = _prod_vectorized(expand_indices, f_dof_indices) - K_elem = self.get_bending_matrices(J, ecc) - - # Extracting sub-matrices - # Explanation & notations: - # * Subscript f denotes 'free' degrees of freedom (i.e. dz/dx, dz/dx) - # * Subscript c denotes 'condensated' (imposed) degrees of freedom - # (i.e. z at all nodes) - # * F = [Ff, Fc] is the force vector - # * U = [Uf, Uc] is the imposed dof vector - # [ Kff Kfc ] - # * K = [ ] is the laplacian stiffness matrix - # [ Kcf Kff ] - # * As F = K x U one gets straightforwardly: Ff = - Kfc x Uc - - # Computing Kff stiffness matrix in sparse coo format - Kff_vals = np.ravel(K_elem[np.ix_(vec_range, f_dof, f_dof)]) - Kff_rows = np.ravel(f_row_indices[np.ix_(vec_range, f_dof, f_dof)]) - Kff_cols = np.ravel(f_col_indices[np.ix_(vec_range, f_dof, f_dof)]) - - # Computing Ff force vector in sparse coo format - Kfc_elem = K_elem[np.ix_(vec_range, f_dof, c_dof)] - Uc_elem = np.expand_dims(Uc, axis=2) - Ff_elem = - _prod_vectorized(Kfc_elem, Uc_elem)[:, :, 0] - Ff_indices = f_dof_indices[np.ix_(vec_range, [0], f_dof)][:, 0, :] - - # Extracting Ff force vector in dense format - # We have to sum duplicate indices - using bincount - Ff = np.bincount(np.ravel(Ff_indices), weights=np.ravel(Ff_elem)) - return Kff_rows, Kff_cols, Kff_vals, Ff - - -# :class:_DOF_estimator, _DOF_estimator_user, _DOF_estimator_geom, -# _DOF_estimator_min_E -# Private classes used to compute the degree of freedom of each triangular -# element for the TriCubicInterpolator. -class _DOF_estimator: - """ - Abstract base class for classes used to estimate a function's first - derivatives, and deduce the dofs for a CubicTriInterpolator using a - reduced HCT element formulation. - - Derived classes implement ``compute_df(self, **kwargs)``, returning - ``np.vstack([dfx, dfy]).T`` where ``dfx, dfy`` are the estimation of the 2 - gradient coordinates. - """ - def __init__(self, interpolator, **kwargs): - cbook._check_isinstance( - CubicTriInterpolator, interpolator=interpolator) - self._pts = interpolator._pts - self._tris_pts = interpolator._tris_pts - self.z = interpolator._z - self._triangles = interpolator._triangles - (self._unit_x, self._unit_y) = (interpolator._unit_x, - interpolator._unit_y) - self.dz = self.compute_dz(**kwargs) - self.compute_dof_from_df() - - def compute_dz(self, **kwargs): - raise NotImplementedError - - def compute_dof_from_df(self): - """ - Compute reduced-HCT elements degrees of freedom, from the gradient. - """ - J = CubicTriInterpolator._get_jacobian(self._tris_pts) - tri_z = self.z[self._triangles] - tri_dz = self.dz[self._triangles] - tri_dof = self.get_dof_vec(tri_z, tri_dz, J) - return tri_dof - - @staticmethod - def get_dof_vec(tri_z, tri_dz, J): - """ - Compute the dof vector of a triangle, from the value of f, df and - of the local Jacobian at each node. - - Parameters - ---------- - tri_z : shape (3,) array - f nodal values. - tri_dz : shape (3, 2) array - df/dx, df/dy nodal values. - J - Jacobian matrix in local basis of apex 0. - - Returns - ------- - dof : shape (9,) array - For each apex ``iapex``:: - - dof[iapex*3+0] = f(Ai) - dof[iapex*3+1] = df(Ai).(AiAi+) - dof[iapex*3+2] = df(Ai).(AiAi-) - """ - npt = tri_z.shape[0] - dof = np.zeros([npt, 9], dtype=np.float64) - J1 = _prod_vectorized(_ReducedHCT_Element.J0_to_J1, J) - J2 = _prod_vectorized(_ReducedHCT_Element.J0_to_J2, J) - - col0 = _prod_vectorized(J, np.expand_dims(tri_dz[:, 0, :], axis=2)) - col1 = _prod_vectorized(J1, np.expand_dims(tri_dz[:, 1, :], axis=2)) - col2 = _prod_vectorized(J2, np.expand_dims(tri_dz[:, 2, :], axis=2)) - - dfdksi = _to_matrix_vectorized([ - [col0[:, 0, 0], col1[:, 0, 0], col2[:, 0, 0]], - [col0[:, 1, 0], col1[:, 1, 0], col2[:, 1, 0]]]) - dof[:, 0:7:3] = tri_z - dof[:, 1:8:3] = dfdksi[:, 0] - dof[:, 2:9:3] = dfdksi[:, 1] - return dof - - -class _DOF_estimator_user(_DOF_estimator): - """dz is imposed by user; accounts for scaling if any.""" - - def compute_dz(self, dz): - (dzdx, dzdy) = dz - dzdx = dzdx * self._unit_x - dzdy = dzdy * self._unit_y - return np.vstack([dzdx, dzdy]).T - - -class _DOF_estimator_geom(_DOF_estimator): - """Fast 'geometric' approximation, recommended for large arrays.""" - - def compute_dz(self): - """ - self.df is computed as weighted average of _triangles sharing a common - node. On each triangle itri f is first assumed linear (= ~f), which - allows to compute d~f[itri] - Then the following approximation of df nodal values is then proposed: - f[ipt] = SUM ( w[itri] x d~f[itri] , for itri sharing apex ipt) - The weighted coeff. w[itri] are proportional to the angle of the - triangle itri at apex ipt - """ - el_geom_w = self.compute_geom_weights() - el_geom_grad = self.compute_geom_grads() - - # Sum of weights coeffs - w_node_sum = np.bincount(np.ravel(self._triangles), - weights=np.ravel(el_geom_w)) - - # Sum of weighted df = (dfx, dfy) - dfx_el_w = np.empty_like(el_geom_w) - dfy_el_w = np.empty_like(el_geom_w) - for iapex in range(3): - dfx_el_w[:, iapex] = el_geom_w[:, iapex]*el_geom_grad[:, 0] - dfy_el_w[:, iapex] = el_geom_w[:, iapex]*el_geom_grad[:, 1] - dfx_node_sum = np.bincount(np.ravel(self._triangles), - weights=np.ravel(dfx_el_w)) - dfy_node_sum = np.bincount(np.ravel(self._triangles), - weights=np.ravel(dfy_el_w)) - - # Estimation of df - dfx_estim = dfx_node_sum/w_node_sum - dfy_estim = dfy_node_sum/w_node_sum - return np.vstack([dfx_estim, dfy_estim]).T - - def compute_geom_weights(self): - """ - Build the (nelems, 3) weights coeffs of _triangles angles, - renormalized so that np.sum(weights, axis=1) == np.ones(nelems) - """ - weights = np.zeros([np.size(self._triangles, 0), 3]) - tris_pts = self._tris_pts - for ipt in range(3): - p0 = tris_pts[:, ipt % 3, :] - p1 = tris_pts[:, (ipt+1) % 3, :] - p2 = tris_pts[:, (ipt-1) % 3, :] - alpha1 = np.arctan2(p1[:, 1]-p0[:, 1], p1[:, 0]-p0[:, 0]) - alpha2 = np.arctan2(p2[:, 1]-p0[:, 1], p2[:, 0]-p0[:, 0]) - # In the below formula we could take modulo 2. but - # modulo 1. is safer regarding round-off errors (flat triangles). - angle = np.abs(((alpha2-alpha1) / np.pi) % 1) - # Weight proportional to angle up np.pi/2; null weight for - # degenerated cases 0 and np.pi (note that *angle* is normalized - # by np.pi). - weights[:, ipt] = 0.5 - np.abs(angle-0.5) - return weights - - def compute_geom_grads(self): - """ - Compute the (global) gradient component of f assumed linear (~f). - returns array df of shape (nelems, 2) - df[ielem].dM[ielem] = dz[ielem] i.e. df = dz x dM = dM.T^-1 x dz - """ - tris_pts = self._tris_pts - tris_f = self.z[self._triangles] - - dM1 = tris_pts[:, 1, :] - tris_pts[:, 0, :] - dM2 = tris_pts[:, 2, :] - tris_pts[:, 0, :] - dM = np.dstack([dM1, dM2]) - # Here we try to deal with the simplest colinear cases: a null - # gradient is assumed in this case. - dM_inv = _safe_inv22_vectorized(dM) - - dZ1 = tris_f[:, 1] - tris_f[:, 0] - dZ2 = tris_f[:, 2] - tris_f[:, 0] - dZ = np.vstack([dZ1, dZ2]).T - df = np.empty_like(dZ) - - # With np.einsum: could be ej,eji -> ej - df[:, 0] = dZ[:, 0]*dM_inv[:, 0, 0] + dZ[:, 1]*dM_inv[:, 1, 0] - df[:, 1] = dZ[:, 0]*dM_inv[:, 0, 1] + dZ[:, 1]*dM_inv[:, 1, 1] - return df - - -class _DOF_estimator_min_E(_DOF_estimator_geom): - """ - The 'smoothest' approximation, df is computed through global minimization - of the bending energy: - E(f) = integral[(d2z/dx2 + d2z/dy2 + 2 d2z/dxdy)**2 dA] - """ - def __init__(self, Interpolator): - self._eccs = Interpolator._eccs - super().__init__(Interpolator) - - def compute_dz(self): - """ - Elliptic solver for bending energy minimization. - Uses a dedicated 'toy' sparse Jacobi PCG solver. - """ - # Initial guess for iterative PCG solver. - dz_init = super().compute_dz() - Uf0 = np.ravel(dz_init) - - reference_element = _ReducedHCT_Element() - J = CubicTriInterpolator._get_jacobian(self._tris_pts) - eccs = self._eccs - triangles = self._triangles - Uc = self.z[self._triangles] - - # Building stiffness matrix and force vector in coo format - Kff_rows, Kff_cols, Kff_vals, Ff = reference_element.get_Kff_and_Ff( - J, eccs, triangles, Uc) - - # Building sparse matrix and solving minimization problem - # We could use scipy.sparse direct solver; however to avoid this - # external dependency an implementation of a simple PCG solver with - # a simple diagonal Jacobi preconditioner is implemented. - tol = 1.e-10 - n_dof = Ff.shape[0] - Kff_coo = _Sparse_Matrix_coo(Kff_vals, Kff_rows, Kff_cols, - shape=(n_dof, n_dof)) - Kff_coo.compress_csc() - Uf, err = _cg(A=Kff_coo, b=Ff, x0=Uf0, tol=tol) - # If the PCG did not converge, we return the best guess between Uf0 - # and Uf. - err0 = np.linalg.norm(Kff_coo.dot(Uf0) - Ff) - if err0 < err: - # Maybe a good occasion to raise a warning here ? - cbook._warn_external("In TriCubicInterpolator initialization, " - "PCG sparse solver did not converge after " - "1000 iterations. `geom` approximation is " - "used instead of `min_E`") - Uf = Uf0 - - # Building dz from Uf - dz = np.empty([self._pts.shape[0], 2], dtype=np.float64) - dz[:, 0] = Uf[::2] - dz[:, 1] = Uf[1::2] - return dz - - -# The following private :class:_Sparse_Matrix_coo and :func:_cg provide -# a PCG sparse solver for (symmetric) elliptic problems. -class _Sparse_Matrix_coo: - def __init__(self, vals, rows, cols, shape): - """ - Create a sparse matrix in coo format. - *vals*: arrays of values of non-null entries of the matrix - *rows*: int arrays of rows of non-null entries of the matrix - *cols*: int arrays of cols of non-null entries of the matrix - *shape*: 2-tuple (n, m) of matrix shape - """ - self.n, self.m = shape - self.vals = np.asarray(vals, dtype=np.float64) - self.rows = np.asarray(rows, dtype=np.int32) - self.cols = np.asarray(cols, dtype=np.int32) - - def dot(self, V): - """ - Dot product of self by a vector *V* in sparse-dense to dense format - *V* dense vector of shape (self.m,). - """ - assert V.shape == (self.m,) - return np.bincount(self.rows, - weights=self.vals*V[self.cols], - minlength=self.m) - - def compress_csc(self): - """ - Compress rows, cols, vals / summing duplicates. Sort for csc format. - """ - _, unique, indices = np.unique( - self.rows + self.n*self.cols, - return_index=True, return_inverse=True) - self.rows = self.rows[unique] - self.cols = self.cols[unique] - self.vals = np.bincount(indices, weights=self.vals) - - def compress_csr(self): - """ - Compress rows, cols, vals / summing duplicates. Sort for csr format. - """ - _, unique, indices = np.unique( - self.m*self.rows + self.cols, - return_index=True, return_inverse=True) - self.rows = self.rows[unique] - self.cols = self.cols[unique] - self.vals = np.bincount(indices, weights=self.vals) - - def to_dense(self): - """ - Return a dense matrix representing self, mainly for debugging purposes. - """ - ret = np.zeros([self.n, self.m], dtype=np.float64) - nvals = self.vals.size - for i in range(nvals): - ret[self.rows[i], self.cols[i]] += self.vals[i] - return ret - - def __str__(self): - return self.to_dense().__str__() - - @property - def diag(self): - """Return the (dense) vector of the diagonal elements.""" - in_diag = (self.rows == self.cols) - diag = np.zeros(min(self.n, self.n), dtype=np.float64) # default 0. - diag[self.rows[in_diag]] = self.vals[in_diag] - return diag - - -def _cg(A, b, x0=None, tol=1.e-10, maxiter=1000): - """ - Use Preconditioned Conjugate Gradient iteration to solve A x = b - A simple Jacobi (diagonal) preconditionner is used. - - Parameters - ---------- - A : _Sparse_Matrix_coo - *A* must have been compressed before by compress_csc or - compress_csr method. - b : array - Right hand side of the linear system. - x0 : array, optional - Starting guess for the solution. Defaults to the zero vector. - tol : float, optional - Tolerance to achieve. The algorithm terminates when the relative - residual is below tol. Default is 1e-10. - maxiter : int, optional - Maximum number of iterations. Iteration will stop after *maxiter* - steps even if the specified tolerance has not been achieved. Defaults - to 1000. - - Returns - ------- - x : array - The converged solution. - err : float - The absolute error np.linalg.norm(A.dot(x) - b) - """ - n = b.size - assert A.n == n - assert A.m == n - b_norm = np.linalg.norm(b) - - # Jacobi pre-conditioner - kvec = A.diag - # For diag elem < 1e-6 we keep 1e-6. - kvec = np.maximum(kvec, 1e-6) - - # Initial guess - if x0 is None: - x = np.zeros(n) - else: - x = x0 - - r = b - A.dot(x) - w = r/kvec - - p = np.zeros(n) - beta = 0.0 - rho = np.dot(r, w) - k = 0 - - # Following C. T. Kelley - while (np.sqrt(abs(rho)) > tol*b_norm) and (k < maxiter): - p = w + beta*p - z = A.dot(p) - alpha = rho/np.dot(p, z) - r = r - alpha*z - w = r/kvec - rhoold = rho - rho = np.dot(r, w) - x = x + alpha*p - beta = rho/rhoold - #err = np.linalg.norm(A.dot(x) - b) # absolute accuracy - not used - k += 1 - err = np.linalg.norm(A.dot(x) - b) - return x, err - - -# The following private functions: -# :func:`_safe_inv22_vectorized` -# :func:`_pseudo_inv22sym_vectorized` -# :func:`_prod_vectorized` -# :func:`_scalar_vectorized` -# :func:`_transpose_vectorized` -# :func:`_roll_vectorized` -# :func:`_to_matrix_vectorized` -# :func:`_extract_submatrices` -# provide fast numpy implementation of some standard operations on arrays of -# matrices - stored as (:, n_rows, n_cols)-shaped np.arrays. - -# Development note: Dealing with pathologic 'flat' triangles in the -# CubicTriInterpolator code and impact on (2, 2)-matrix inversion functions -# :func:`_safe_inv22_vectorized` and :func:`_pseudo_inv22sym_vectorized`. -# -# Goals: -# 1) The CubicTriInterpolator should be able to handle flat or almost flat -# triangles without raising an error, -# 2) These degenerated triangles should have no impact on the automatic dof -# calculation (associated with null weight for the _DOF_estimator_geom and -# with null energy for the _DOF_estimator_min_E), -# 3) Linear patch test should be passed exactly on degenerated meshes, -# 4) Interpolation (with :meth:`_interpolate_single_key` or -# :meth:`_interpolate_multi_key`) shall be correctly handled even *inside* -# the pathologic triangles, to interact correctly with a TriRefiner class. -# -# Difficulties: -# Flat triangles have rank-deficient *J* (so-called jacobian matrix) and -# *metric* (the metric tensor = J x J.T). Computation of the local -# tangent plane is also problematic. -# -# Implementation: -# Most of the time, when computing the inverse of a rank-deficient matrix it -# is safe to simply return the null matrix (which is the implementation in -# :func:`_safe_inv22_vectorized`). This is because of point 2), itself -# enforced by: -# - null area hence null energy in :class:`_DOF_estimator_min_E` -# - angles close or equal to 0 or np.pi hence null weight in -# :class:`_DOF_estimator_geom`. -# Note that the function angle -> weight is continuous and maximum for an -# angle np.pi/2 (refer to :meth:`compute_geom_weights`) -# The exception is the computation of barycentric coordinates, which is done -# by inversion of the *metric* matrix. In this case, we need to compute a set -# of valid coordinates (1 among numerous possibilities), to ensure point 4). -# We benefit here from the symmetry of metric = J x J.T, which makes it easier -# to compute a pseudo-inverse in :func:`_pseudo_inv22sym_vectorized` -def _safe_inv22_vectorized(M): - """ - Inversion of arrays of (2, 2) matrices, returns 0 for rank-deficient - matrices. - - *M* : array of (2, 2) matrices to inverse, shape (n, 2, 2) - """ - assert M.ndim == 3 - assert M.shape[-2:] == (2, 2) - M_inv = np.empty_like(M) - prod1 = M[:, 0, 0]*M[:, 1, 1] - delta = prod1 - M[:, 0, 1]*M[:, 1, 0] - - # We set delta_inv to 0. in case of a rank deficient matrix; a - # rank-deficient input matrix *M* will lead to a null matrix in output - rank2 = (np.abs(delta) > 1e-8*np.abs(prod1)) - if np.all(rank2): - # Normal 'optimized' flow. - delta_inv = 1./delta - else: - # 'Pathologic' flow. - delta_inv = np.zeros(M.shape[0]) - delta_inv[rank2] = 1./delta[rank2] - - M_inv[:, 0, 0] = M[:, 1, 1]*delta_inv - M_inv[:, 0, 1] = -M[:, 0, 1]*delta_inv - M_inv[:, 1, 0] = -M[:, 1, 0]*delta_inv - M_inv[:, 1, 1] = M[:, 0, 0]*delta_inv - return M_inv - - -def _pseudo_inv22sym_vectorized(M): - """ - Inversion of arrays of (2, 2) SYMMETRIC matrices; returns the - (Moore-Penrose) pseudo-inverse for rank-deficient matrices. - - In case M is of rank 1, we have M = trace(M) x P where P is the orthogonal - projection on Im(M), and we return trace(M)^-1 x P == M / trace(M)**2 - In case M is of rank 0, we return the null matrix. - - *M* : array of (2, 2) matrices to inverse, shape (n, 2, 2) - """ - assert M.ndim == 3 - assert M.shape[-2:] == (2, 2) - M_inv = np.empty_like(M) - prod1 = M[:, 0, 0]*M[:, 1, 1] - delta = prod1 - M[:, 0, 1]*M[:, 1, 0] - rank2 = (np.abs(delta) > 1e-8*np.abs(prod1)) - - if np.all(rank2): - # Normal 'optimized' flow. - M_inv[:, 0, 0] = M[:, 1, 1] / delta - M_inv[:, 0, 1] = -M[:, 0, 1] / delta - M_inv[:, 1, 0] = -M[:, 1, 0] / delta - M_inv[:, 1, 1] = M[:, 0, 0] / delta - else: - # 'Pathologic' flow. - # Here we have to deal with 2 sub-cases - # 1) First sub-case: matrices of rank 2: - delta = delta[rank2] - M_inv[rank2, 0, 0] = M[rank2, 1, 1] / delta - M_inv[rank2, 0, 1] = -M[rank2, 0, 1] / delta - M_inv[rank2, 1, 0] = -M[rank2, 1, 0] / delta - M_inv[rank2, 1, 1] = M[rank2, 0, 0] / delta - # 2) Second sub-case: rank-deficient matrices of rank 0 and 1: - rank01 = ~rank2 - tr = M[rank01, 0, 0] + M[rank01, 1, 1] - tr_zeros = (np.abs(tr) < 1.e-8) - sq_tr_inv = (1.-tr_zeros) / (tr**2+tr_zeros) - #sq_tr_inv = 1. / tr**2 - M_inv[rank01, 0, 0] = M[rank01, 0, 0] * sq_tr_inv - M_inv[rank01, 0, 1] = M[rank01, 0, 1] * sq_tr_inv - M_inv[rank01, 1, 0] = M[rank01, 1, 0] * sq_tr_inv - M_inv[rank01, 1, 1] = M[rank01, 1, 1] * sq_tr_inv - - return M_inv - - -def _prod_vectorized(M1, M2): - """ - Matrix product between arrays of matrices, or a matrix and an array of - matrices (*M1* and *M2*) - """ - sh1 = M1.shape - sh2 = M2.shape - assert len(sh1) >= 2 - assert len(sh2) >= 2 - assert sh1[-1] == sh2[-2] - - ndim1 = len(sh1) - t1_index = [*range(ndim1-2), ndim1-1, ndim1-2] - return np.sum(np.transpose(M1, t1_index)[..., np.newaxis] * - M2[..., np.newaxis, :], -3) - - -def _scalar_vectorized(scalar, M): - """ - Scalar product between scalars and matrices. - """ - return scalar[:, np.newaxis, np.newaxis]*M - - -def _transpose_vectorized(M): - """ - Transposition of an array of matrices *M*. - """ - return np.transpose(M, [0, 2, 1]) - - -def _roll_vectorized(M, roll_indices, axis): - """ - Roll an array of matrices along *axis* (0: rows, 1: columns) according to - an array of indices *roll_indices*. - """ - assert axis in [0, 1] - ndim = M.ndim - assert ndim == 3 - ndim_roll = roll_indices.ndim - assert ndim_roll == 1 - sh = M.shape - r, c = sh[-2:] - assert sh[0] == roll_indices.shape[0] - vec_indices = np.arange(sh[0], dtype=np.int32) - - # Builds the rolled matrix - M_roll = np.empty_like(M) - if axis == 0: - for ir in range(r): - for ic in range(c): - M_roll[:, ir, ic] = M[vec_indices, (-roll_indices+ir) % r, ic] - elif axis == 1: - for ir in range(r): - for ic in range(c): - M_roll[:, ir, ic] = M[vec_indices, ir, (-roll_indices+ic) % c] - return M_roll - - -def _to_matrix_vectorized(M): - """ - Build an array of matrices from individuals np.arrays of identical shapes. - - Parameters - ---------- - M - ncols-list of nrows-lists of shape sh. - - Returns - ------- - M_res : np.array of shape (sh, nrow, ncols) - *M_res* satisfies ``M_res[..., i, j] = M[i][j]``. - """ - assert isinstance(M, (tuple, list)) - assert all(isinstance(item, (tuple, list)) for item in M) - c_vec = np.asarray([len(item) for item in M]) - assert np.all(c_vec-c_vec[0] == 0) - r = len(M) - c = c_vec[0] - M00 = np.asarray(M[0][0]) - dt = M00.dtype - sh = [M00.shape[0], r, c] - M_ret = np.empty(sh, dtype=dt) - for irow in range(r): - for icol in range(c): - M_ret[:, irow, icol] = np.asarray(M[irow][icol]) - return M_ret - - -def _extract_submatrices(M, block_indices, block_size, axis): - """ - Extract selected blocks of a matrices *M* depending on parameters - *block_indices* and *block_size*. - - Returns the array of extracted matrices *Mres* so that :: - - M_res[..., ir, :] = M[(block_indices*block_size+ir), :] - """ - assert block_indices.ndim == 1 - assert axis in [0, 1] - - r, c = M.shape - if axis == 0: - sh = [block_indices.shape[0], block_size, c] - elif axis == 1: - sh = [block_indices.shape[0], r, block_size] - - dt = M.dtype - M_res = np.empty(sh, dtype=dt) - if axis == 0: - for ir in range(block_size): - M_res[:, ir, :] = M[(block_indices*block_size+ir), :] - elif axis == 1: - for ic in range(block_size): - M_res[:, :, ic] = M[:, (block_indices*block_size+ic)] - - return M_res diff --git a/lib/matplotlib/tri/tripcolor.py b/lib/matplotlib/tri/tripcolor.py deleted file mode 100644 index 18da1e9810b7..000000000000 --- a/lib/matplotlib/tri/tripcolor.py +++ /dev/null @@ -1,131 +0,0 @@ -import numpy as np - -from matplotlib import cbook -from matplotlib.collections import PolyCollection, TriMesh -from matplotlib.colors import Normalize -from matplotlib.tri.triangulation import Triangulation - - -def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, - vmax=None, shading='flat', facecolors=None, **kwargs): - """ - Create a pseudocolor plot of an unstructured triangular grid. - - The triangulation can be specified in one of two ways; either:: - - tripcolor(triangulation, ...) - - where triangulation is a `.Triangulation` object, or - - :: - - tripcolor(x, y, ...) - tripcolor(x, y, triangles, ...) - tripcolor(x, y, triangles=triangles, ...) - tripcolor(x, y, mask=mask, ...) - tripcolor(x, y, triangles, mask=mask, ...) - - in which case a Triangulation object will be created. See `.Triangulation` - for a explanation of these possibilities. - - The next argument must be *C*, the array of color values, either - one per point in the triangulation if color values are defined at - points, or one per triangle in the triangulation if color values - are defined at triangles. If there are the same number of points - and triangles in the triangulation it is assumed that color - values are defined at points; to force the use of color values at - triangles use the kwarg ``facecolors=C`` instead of just ``C``. - - *shading* may be 'flat' (the default) or 'gouraud'. If *shading* - is 'flat' and C values are defined at points, the color values - used for each triangle are from the mean C of the triangle's - three points. If *shading* is 'gouraud' then color values must be - defined at points. - - The remaining kwargs are the same as for `~.Axes.pcolor`. - """ - cbook._check_in_list(['flat', 'gouraud'], shading=shading) - - tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs) - - # C is the colors array defined at either points or faces (i.e. triangles). - # If facecolors is None, C are defined at points. - # If facecolors is not None, C are defined at faces. - if facecolors is not None: - C = facecolors - else: - C = np.asarray(args[0]) - - # If there are a different number of points and triangles in the - # triangulation, can omit facecolors kwarg as it is obvious from - # length of C whether it refers to points or faces. - # Do not do this for gouraud shading. - if (facecolors is None and len(C) == len(tri.triangles) and - len(C) != len(tri.x) and shading != 'gouraud'): - facecolors = C - - # Check length of C is OK. - if ((facecolors is None and len(C) != len(tri.x)) or - (facecolors is not None and len(C) != len(tri.triangles))): - raise ValueError('Length of color values array must be the same ' - 'as either the number of triangulation points ' - 'or triangles') - - # Handling of linewidths, shading, edgecolors and antialiased as - # in Axes.pcolor - linewidths = (0.25,) - if 'linewidth' in kwargs: - kwargs['linewidths'] = kwargs.pop('linewidth') - kwargs.setdefault('linewidths', linewidths) - - edgecolors = 'none' - if 'edgecolor' in kwargs: - kwargs['edgecolors'] = kwargs.pop('edgecolor') - ec = kwargs.setdefault('edgecolors', edgecolors) - - if 'antialiased' in kwargs: - kwargs['antialiaseds'] = kwargs.pop('antialiased') - if 'antialiaseds' not in kwargs and ec.lower() == "none": - kwargs['antialiaseds'] = False - - if shading == 'gouraud': - if facecolors is not None: - raise ValueError('Gouraud shading does not support the use ' - 'of facecolors kwarg') - if len(C) != len(tri.x): - raise ValueError('For gouraud shading, the length of color ' - 'values array must be the same as the ' - 'number of triangulation points') - collection = TriMesh(tri, **kwargs) - else: - # Vertices of triangles. - maskedTris = tri.get_masked_triangles() - verts = np.stack((tri.x[maskedTris], tri.y[maskedTris]), axis=-1) - - # Color values. - if facecolors is None: - # One color per triangle, the mean of the 3 vertex color values. - C = C[maskedTris].mean(axis=1) - elif tri.mask is not None: - # Remove color values of masked triangles. - C = C[~tri.mask] - - collection = PolyCollection(verts, **kwargs) - - collection.set_alpha(alpha) - collection.set_array(C) - cbook._check_isinstance((Normalize, None), norm=norm) - collection.set_cmap(cmap) - collection.set_norm(norm) - collection._scale_norm(norm, vmin, vmax) - ax.grid(False) - - minx = tri.x.min() - maxx = tri.x.max() - miny = tri.y.min() - maxy = tri.y.max() - corners = (minx, miny), (maxx, maxy) - ax.update_datalim(corners) - ax.autoscale_view() - ax.add_collection(collection) - return collection diff --git a/lib/matplotlib/tri/triplot.py b/lib/matplotlib/tri/triplot.py deleted file mode 100644 index 97e3725464e7..000000000000 --- a/lib/matplotlib/tri/triplot.py +++ /dev/null @@ -1,82 +0,0 @@ -import numpy as np -from matplotlib.tri.triangulation import Triangulation - - -def triplot(ax, *args, **kwargs): - """ - Draw a unstructured triangular grid as lines and/or markers. - - The triangulation to plot can be specified in one of two ways; either:: - - triplot(triangulation, ...) - - where triangulation is a `.Triangulation` object, or - - :: - - triplot(x, y, ...) - triplot(x, y, triangles, ...) - triplot(x, y, triangles=triangles, ...) - triplot(x, y, mask=mask, ...) - triplot(x, y, triangles, mask=mask, ...) - - in which case a Triangulation object will be created. See `.Triangulation` - for a explanation of these possibilities. - - The remaining args and kwargs are the same as for `~.Axes.plot`. - - Returns - ------- - lines : `~matplotlib.lines.Line2D` - The drawn triangles edges. - markers : `~matplotlib.lines.Line2D` - The drawn marker nodes. - """ - import matplotlib.axes - - tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs) - x, y, edges = (tri.x, tri.y, tri.edges) - - # Decode plot format string, e.g., 'ro-' - fmt = args[0] if args else "" - linestyle, marker, color = matplotlib.axes._base._process_plot_format(fmt) - - # Insert plot format string into a copy of kwargs (kwargs values prevail). - kw = kwargs.copy() - for key, val in zip(('linestyle', 'marker', 'color'), - (linestyle, marker, color)): - if val is not None: - kw[key] = kwargs.get(key, val) - - # Draw lines without markers. - # Note 1: If we drew markers here, most markers would be drawn more than - # once as they belong to several edges. - # Note 2: We insert nan values in the flattened edges arrays rather than - # plotting directly (triang.x[edges].T, triang.y[edges].T) - # as it considerably speeds-up code execution. - linestyle = kw['linestyle'] - kw_lines = { - **kw, - 'marker': 'None', # No marker to draw. - 'zorder': kw.get('zorder', 1), # Path default zorder is used. - } - if linestyle not in [None, 'None', '', ' ']: - tri_lines_x = np.insert(x[edges], 2, np.nan, axis=1) - tri_lines_y = np.insert(y[edges], 2, np.nan, axis=1) - tri_lines = ax.plot(tri_lines_x.ravel(), tri_lines_y.ravel(), - **kw_lines) - else: - tri_lines = ax.plot([], [], **kw_lines) - - # Draw markers separately. - marker = kw['marker'] - kw_markers = { - **kw, - 'linestyle': 'None', # No line to draw. - } - if marker not in [None, 'None', '', ' ']: - tri_markers = ax.plot(x, y, **kw_markers) - else: - tri_markers = ax.plot([], [], **kw_markers) - - return tri_lines + tri_markers diff --git a/lib/matplotlib/tri/trirefine.py b/lib/matplotlib/tri/trirefine.py deleted file mode 100644 index 98afe18e6615..000000000000 --- a/lib/matplotlib/tri/trirefine.py +++ /dev/null @@ -1,307 +0,0 @@ -""" -Mesh refinement for triangular grids. -""" - -import numpy as np - -from matplotlib import cbook -from matplotlib.tri.triangulation import Triangulation -import matplotlib.tri.triinterpolate - - -class TriRefiner: - """ - Abstract base class for classes implementing mesh refinement. - - A TriRefiner encapsulates a Triangulation object and provides tools for - mesh refinement and interpolation. - - Derived classes must implement: - - - ``refine_triangulation(return_tri_index=False, **kwargs)`` , where - the optional keyword arguments *kwargs* are defined in each - TriRefiner concrete implementation, and which returns: - - - a refined triangulation, - - optionally (depending on *return_tri_index*), for each - point of the refined triangulation: the index of - the initial triangulation triangle to which it belongs. - - - ``refine_field(z, triinterpolator=None, **kwargs)``, where: - - - *z* array of field values (to refine) defined at the base - triangulation nodes, - - *triinterpolator* is an optional `~matplotlib.tri.TriInterpolator`, - - the other optional keyword arguments *kwargs* are defined in - each TriRefiner concrete implementation; - - and which returns (as a tuple) a refined triangular mesh and the - interpolated values of the field at the refined triangulation nodes. - """ - - def __init__(self, triangulation): - cbook._check_isinstance(Triangulation, triangulation=triangulation) - self._triangulation = triangulation - - -class UniformTriRefiner(TriRefiner): - """ - Uniform mesh refinement by recursive subdivisions. - - Parameters - ---------- - triangulation : `~matplotlib.tri.Triangulation` - The encapsulated triangulation (to be refined) - """ -# See Also -# -------- -# :class:`~matplotlib.tri.CubicTriInterpolator` and -# :class:`~matplotlib.tri.TriAnalyzer`. -# """ - def __init__(self, triangulation): - super().__init__(triangulation) - - def refine_triangulation(self, return_tri_index=False, subdiv=3): - """ - Compute an uniformly refined triangulation *refi_triangulation* of - the encapsulated :attr:`triangulation`. - - This function refines the encapsulated triangulation by splitting each - father triangle into 4 child sub-triangles built on the edges midside - nodes, recursing *subdiv* times. In the end, each triangle is hence - divided into ``4**subdiv`` child triangles. - - Parameters - ---------- - return_tri_index : bool, default: False - Whether an index table indicating the father triangle index of each - point is returned. - subdiv : int, default: 3 - Recursion level for the subdivision. - Each triangle is divided into ``4**subdiv`` child triangles; - hence, the default results in 64 refined subtriangles for each - triangle of the initial triangulation. - - Returns - ------- - refi_triangulation : `~matplotlib.tri.Triangulation` - The refined triangulation. - found_index : int array - Index of the initial triangulation containing triangle, for each - point of *refi_triangulation*. - Returned only if *return_tri_index* is set to True. - """ - refi_triangulation = self._triangulation - ntri = refi_triangulation.triangles.shape[0] - - # Computes the triangulation ancestors numbers in the reference - # triangulation. - ancestors = np.arange(ntri, dtype=np.int32) - for _ in range(subdiv): - refi_triangulation, ancestors = self._refine_triangulation_once( - refi_triangulation, ancestors) - refi_npts = refi_triangulation.x.shape[0] - refi_triangles = refi_triangulation.triangles - - # Now we compute found_index table if needed - if return_tri_index: - # We have to initialize found_index with -1 because some nodes - # may very well belong to no triangle at all, e.g., in case of - # Delaunay Triangulation with DuplicatePointWarning. - found_index = np.full(refi_npts, -1, dtype=np.int32) - tri_mask = self._triangulation.mask - if tri_mask is None: - found_index[refi_triangles] = np.repeat(ancestors, - 3).reshape(-1, 3) - else: - # There is a subtlety here: we want to avoid whenever possible - # that refined points container is a masked triangle (which - # would result in artifacts in plots). - # So we impose the numbering from masked ancestors first, - # then overwrite it with unmasked ancestor numbers. - ancestor_mask = tri_mask[ancestors] - found_index[refi_triangles[ancestor_mask, :] - ] = np.repeat(ancestors[ancestor_mask], - 3).reshape(-1, 3) - found_index[refi_triangles[~ancestor_mask, :] - ] = np.repeat(ancestors[~ancestor_mask], - 3).reshape(-1, 3) - return refi_triangulation, found_index - else: - return refi_triangulation - - def refine_field(self, z, triinterpolator=None, subdiv=3): - """ - Refine a field defined on the encapsulated triangulation. - - Parameters - ---------- - z : 1d-array-like of length ``n_points`` - Values of the field to refine, defined at the nodes of the - encapsulated triangulation. (``n_points`` is the number of points - in the initial triangulation) - triinterpolator : `~matplotlib.tri.TriInterpolator`, optional - Interpolator used for field interpolation. If not specified, - a `~matplotlib.tri.CubicTriInterpolator` will be used. - subdiv : int, default: 3 - Recursion level for the subdivision. - Each triangle is divided into ``4**subdiv`` child triangles. - - Returns - ------- - refi_tri : `~matplotlib.tri.Triangulation` - The returned refined triangulation. - refi_z : 1d array of length: *refi_tri* node count. - The returned interpolated field (at *refi_tri* nodes). - """ - if triinterpolator is None: - interp = matplotlib.tri.CubicTriInterpolator( - self._triangulation, z) - else: - cbook._check_isinstance(matplotlib.tri.TriInterpolator, - triinterpolator=triinterpolator) - interp = triinterpolator - - refi_tri, found_index = self.refine_triangulation( - subdiv=subdiv, return_tri_index=True) - refi_z = interp._interpolate_multikeys( - refi_tri.x, refi_tri.y, tri_index=found_index)[0] - return refi_tri, refi_z - - @staticmethod - def _refine_triangulation_once(triangulation, ancestors=None): - """ - Refine a `.Triangulation` by splitting each triangle into 4 - child-masked_triangles built on the edges midside nodes. - - Masked triangles, if present, are also split, but their children - returned masked. - - If *ancestors* is not provided, returns only a new triangulation: - child_triangulation. - - If the array-like key table *ancestor* is given, it shall be of shape - (ntri,) where ntri is the number of *triangulation* masked_triangles. - In this case, the function returns - (child_triangulation, child_ancestors) - child_ancestors is defined so that the 4 child masked_triangles share - the same index as their father: child_ancestors.shape = (4 * ntri,). - """ - - x = triangulation.x - y = triangulation.y - - # According to tri.triangulation doc: - # neighbors[i, j] is the triangle that is the neighbor - # to the edge from point index masked_triangles[i, j] to point - # index masked_triangles[i, (j+1)%3]. - neighbors = triangulation.neighbors - triangles = triangulation.triangles - npts = np.shape(x)[0] - ntri = np.shape(triangles)[0] - if ancestors is not None: - ancestors = np.asarray(ancestors) - if np.shape(ancestors) != (ntri,): - raise ValueError( - "Incompatible shapes provide for triangulation" - ".masked_triangles and ancestors: {0} and {1}".format( - np.shape(triangles), np.shape(ancestors))) - - # Initiating tables refi_x and refi_y of the refined triangulation - # points - # hint: each apex is shared by 2 masked_triangles except the borders. - borders = np.sum(neighbors == -1) - added_pts = (3*ntri + borders) // 2 - refi_npts = npts + added_pts - refi_x = np.zeros(refi_npts) - refi_y = np.zeros(refi_npts) - - # First part of refi_x, refi_y is just the initial points - refi_x[:npts] = x - refi_y[:npts] = y - - # Second part contains the edge midside nodes. - # Each edge belongs to 1 triangle (if border edge) or is shared by 2 - # masked_triangles (interior edge). - # We first build 2 * ntri arrays of edge starting nodes (edge_elems, - # edge_apexes); we then extract only the masters to avoid overlaps. - # The so-called 'master' is the triangle with biggest index - # The 'slave' is the triangle with lower index - # (can be -1 if border edge) - # For slave and master we will identify the apex pointing to the edge - # start - edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3) - edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri) - edge_neighbors = neighbors[edge_elems, edge_apexes] - mask_masters = (edge_elems > edge_neighbors) - - # Identifying the "masters" and adding to refi_x, refi_y vec - masters = edge_elems[mask_masters] - apex_masters = edge_apexes[mask_masters] - x_add = (x[triangles[masters, apex_masters]] + - x[triangles[masters, (apex_masters+1) % 3]]) * 0.5 - y_add = (y[triangles[masters, apex_masters]] + - y[triangles[masters, (apex_masters+1) % 3]]) * 0.5 - refi_x[npts:] = x_add - refi_y[npts:] = y_add - - # Building the new masked_triangles; each old masked_triangles hosts - # 4 new masked_triangles - # there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and - # 3 new_pt_midside - new_pt_corner = triangles - - # What is the index in refi_x, refi_y of point at middle of apex iapex - # of elem ielem ? - # If ielem is the apex master: simple count, given the way refi_x was - # built. - # If ielem is the apex slave: yet we do not know; but we will soon - # using the neighbors table. - new_pt_midside = np.empty([ntri, 3], dtype=np.int32) - cum_sum = npts - for imid in range(3): - mask_st_loc = (imid == apex_masters) - n_masters_loc = np.sum(mask_st_loc) - elem_masters_loc = masters[mask_st_loc] - new_pt_midside[:, imid][elem_masters_loc] = np.arange( - n_masters_loc, dtype=np.int32) + cum_sum - cum_sum += n_masters_loc - - # Now dealing with slave elems. - # for each slave element we identify the master and then the inode - # once slave_masters is identified, slave_masters_apex is such that: - # neighbors[slaves_masters, slave_masters_apex] == slaves - mask_slaves = np.logical_not(mask_masters) - slaves = edge_elems[mask_slaves] - slaves_masters = edge_neighbors[mask_slaves] - diff_table = np.abs(neighbors[slaves_masters, :] - - np.outer(slaves, np.ones(3, dtype=np.int32))) - slave_masters_apex = np.argmin(diff_table, axis=1) - slaves_apex = edge_apexes[mask_slaves] - new_pt_midside[slaves, slaves_apex] = new_pt_midside[ - slaves_masters, slave_masters_apex] - - # Builds the 4 child masked_triangles - child_triangles = np.empty([ntri*4, 3], dtype=np.int32) - child_triangles[0::4, :] = np.vstack([ - new_pt_corner[:, 0], new_pt_midside[:, 0], - new_pt_midside[:, 2]]).T - child_triangles[1::4, :] = np.vstack([ - new_pt_corner[:, 1], new_pt_midside[:, 1], - new_pt_midside[:, 0]]).T - child_triangles[2::4, :] = np.vstack([ - new_pt_corner[:, 2], new_pt_midside[:, 2], - new_pt_midside[:, 1]]).T - child_triangles[3::4, :] = np.vstack([ - new_pt_midside[:, 0], new_pt_midside[:, 1], - new_pt_midside[:, 2]]).T - child_triangulation = Triangulation(refi_x, refi_y, child_triangles) - - # Builds the child mask - if triangulation.mask is not None: - child_triangulation.set_mask(np.repeat(triangulation.mask, 4)) - - if ancestors is None: - return child_triangulation - else: - return child_triangulation, np.repeat(ancestors, 4) diff --git a/lib/matplotlib/tri/tritools.py b/lib/matplotlib/tri/tritools.py deleted file mode 100644 index ddcf7d3e92f7..000000000000 --- a/lib/matplotlib/tri/tritools.py +++ /dev/null @@ -1,263 +0,0 @@ -""" -Tools for triangular grids. -""" - -import numpy as np - -from matplotlib import cbook -from matplotlib.tri import Triangulation - - -class TriAnalyzer: - """ - Define basic tools for triangular mesh analysis and improvement. - - A TriAnalyzer encapsulates a `.Triangulation` object and provides basic - tools for mesh analysis and mesh improvement. - - Attributes - ---------- - scale_factors - - Parameters - ---------- - triangulation : `~matplotlib.tri.Triangulation` - The encapsulated triangulation to analyze. - """ - - def __init__(self, triangulation): - cbook._check_isinstance(Triangulation, triangulation=triangulation) - self._triangulation = triangulation - - @property - def scale_factors(self): - """ - Factors to rescale the triangulation into a unit square. - - Returns - ------- - (float, float) - Scaling factors (kx, ky) so that the triangulation - ``[triangulation.x * kx, triangulation.y * ky]`` - fits exactly inside a unit square. - """ - compressed_triangles = self._triangulation.get_masked_triangles() - node_used = (np.bincount(np.ravel(compressed_triangles), - minlength=self._triangulation.x.size) != 0) - return (1 / np.ptp(self._triangulation.x[node_used]), - 1 / np.ptp(self._triangulation.y[node_used])) - - def circle_ratios(self, rescale=True): - """ - Return a measure of the triangulation triangles flatness. - - The ratio of the incircle radius over the circumcircle radius is a - widely used indicator of a triangle flatness. - It is always ``<= 0.5`` and ``== 0.5`` only for equilateral - triangles. Circle ratios below 0.01 denote very flat triangles. - - To avoid unduly low values due to a difference of scale between the 2 - axis, the triangular mesh can first be rescaled to fit inside a unit - square with `scale_factors` (Only if *rescale* is True, which is - its default value). - - Parameters - ---------- - rescale : bool, default: True - If True, internally rescale (based on `scale_factors`), so that the - (unmasked) triangles fit exactly inside a unit square mesh. - - Returns - ------- - masked array - Ratio of the incircle radius over the circumcircle radius, for - each 'rescaled' triangle of the encapsulated triangulation. - Values corresponding to masked triangles are masked out. - - """ - # Coords rescaling - if rescale: - (kx, ky) = self.scale_factors - else: - (kx, ky) = (1.0, 1.0) - pts = np.vstack([self._triangulation.x*kx, - self._triangulation.y*ky]).T - tri_pts = pts[self._triangulation.triangles] - # Computes the 3 side lengths - a = tri_pts[:, 1, :] - tri_pts[:, 0, :] - b = tri_pts[:, 2, :] - tri_pts[:, 1, :] - c = tri_pts[:, 0, :] - tri_pts[:, 2, :] - a = np.hypot(a[:, 0], a[:, 1]) - b = np.hypot(b[:, 0], b[:, 1]) - c = np.hypot(c[:, 0], c[:, 1]) - # circumcircle and incircle radii - s = (a+b+c)*0.5 - prod = s*(a+b-s)*(a+c-s)*(b+c-s) - # We have to deal with flat triangles with infinite circum_radius - bool_flat = (prod == 0.) - if np.any(bool_flat): - # Pathologic flow - ntri = tri_pts.shape[0] - circum_radius = np.empty(ntri, dtype=np.float64) - circum_radius[bool_flat] = np.inf - abc = a*b*c - circum_radius[~bool_flat] = abc[~bool_flat] / ( - 4.0*np.sqrt(prod[~bool_flat])) - else: - # Normal optimized flow - circum_radius = (a*b*c) / (4.0*np.sqrt(prod)) - in_radius = (a*b*c) / (4.0*circum_radius*s) - circle_ratio = in_radius/circum_radius - mask = self._triangulation.mask - if mask is None: - return circle_ratio - else: - return np.ma.array(circle_ratio, mask=mask) - - def get_flat_tri_mask(self, min_circle_ratio=0.01, rescale=True): - """ - Eliminate excessively flat border triangles from the triangulation. - - Returns a mask *new_mask* which allows to clean the encapsulated - triangulation from its border-located flat triangles - (according to their :meth:`circle_ratios`). - This mask is meant to be subsequently applied to the triangulation - using `.Triangulation.set_mask`. - *new_mask* is an extension of the initial triangulation mask - in the sense that an initially masked triangle will remain masked. - - The *new_mask* array is computed recursively; at each step flat - triangles are removed only if they share a side with the current mesh - border. Thus no new holes in the triangulated domain will be created. - - Parameters - ---------- - min_circle_ratio : float, default: 0.01 - Border triangles with incircle/circumcircle radii ratio r/R will - be removed if r/R < *min_circle_ratio*. - rescale : bool, default: True - If True, first, internally rescale (based on `scale_factors`) so - that the (unmasked) triangles fit exactly inside a unit square - mesh. This rescaling accounts for the difference of scale which - might exist between the 2 axis. - - Returns - ------- - bool array-like - Mask to apply to encapsulated triangulation. - All the initially masked triangles remain masked in the - *new_mask*. - - Notes - ----- - The rationale behind this function is that a Delaunay - triangulation - of an unstructured set of points - sometimes contains - almost flat triangles at its border, leading to artifacts in plots - (especially for high-resolution contouring). - Masked with computed *new_mask*, the encapsulated - triangulation would contain no more unmasked border triangles - with a circle ratio below *min_circle_ratio*, thus improving the - mesh quality for subsequent plots or interpolation. - """ - # Recursively computes the mask_current_borders, true if a triangle is - # at the border of the mesh OR touching the border through a chain of - # invalid aspect ratio masked_triangles. - ntri = self._triangulation.triangles.shape[0] - mask_bad_ratio = self.circle_ratios(rescale) < min_circle_ratio - - current_mask = self._triangulation.mask - if current_mask is None: - current_mask = np.zeros(ntri, dtype=bool) - valid_neighbors = np.copy(self._triangulation.neighbors) - renum_neighbors = np.arange(ntri, dtype=np.int32) - nadd = -1 - while nadd != 0: - # The active wavefront is the triangles from the border (unmasked - # but with a least 1 neighbor equal to -1 - wavefront = (np.min(valid_neighbors, axis=1) == -1) & ~current_mask - # The element from the active wavefront will be masked if their - # circle ratio is bad. - added_mask = wavefront & mask_bad_ratio - current_mask = added_mask | current_mask - nadd = np.sum(added_mask) - - # now we have to update the tables valid_neighbors - valid_neighbors[added_mask, :] = -1 - renum_neighbors[added_mask] = -1 - valid_neighbors = np.where(valid_neighbors == -1, -1, - renum_neighbors[valid_neighbors]) - - return np.ma.filled(current_mask, True) - - def _get_compressed_triangulation(self): - """ - Compress (if masked) the encapsulated triangulation. - - Returns minimal-length triangles array (*compressed_triangles*) and - coordinates arrays (*compressed_x*, *compressed_y*) that can still - describe the unmasked triangles of the encapsulated triangulation. - - Returns - ------- - compressed_triangles : array-like - the returned compressed triangulation triangles - compressed_x : array-like - the returned compressed triangulation 1st coordinate - compressed_y : array-like - the returned compressed triangulation 2nd coordinate - tri_renum : int array - renumbering table to translate the triangle numbers from the - encapsulated triangulation into the new (compressed) renumbering. - -1 for masked triangles (deleted from *compressed_triangles*). - node_renum : int array - renumbering table to translate the point numbers from the - encapsulated triangulation into the new (compressed) renumbering. - -1 for unused points (i.e. those deleted from *compressed_x* and - *compressed_y*). - - """ - # Valid triangles and renumbering - tri_mask = self._triangulation.mask - compressed_triangles = self._triangulation.get_masked_triangles() - ntri = self._triangulation.triangles.shape[0] - if tri_mask is not None: - tri_renum = self._total_to_compress_renum(~tri_mask) - else: - tri_renum = np.arange(ntri, dtype=np.int32) - - # Valid nodes and renumbering - valid_node = (np.bincount(np.ravel(compressed_triangles), - minlength=self._triangulation.x.size) != 0) - compressed_x = self._triangulation.x[valid_node] - compressed_y = self._triangulation.y[valid_node] - node_renum = self._total_to_compress_renum(valid_node) - - # Now renumbering the valid triangles nodes - compressed_triangles = node_renum[compressed_triangles] - - return (compressed_triangles, compressed_x, compressed_y, tri_renum, - node_renum) - - @staticmethod - def _total_to_compress_renum(valid): - """ - Parameters - ---------- - valid : 1d bool array - Validity mask. - - Returns - ------- - int array - Array so that (`valid_array` being a compressed array - based on a `masked_array` with mask ~*valid*): - - - For all i with valid[i] = True: - valid_array[renum[i]] = masked_array[i] - - For all i with valid[i] = False: - renum[i] = -1 (invalid value) - """ - renum = np.full(np.size(valid), -1, dtype=np.int32) - n_valid = np.sum(valid) - renum[valid] = np.arange(n_valid, dtype=np.int32) - return renum diff --git a/lib/matplotlib/ttconv.py b/lib/matplotlib/ttconv.py deleted file mode 100644 index a2e11ef4e4ee..000000000000 --- a/lib/matplotlib/ttconv.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Converting and subsetting TrueType fonts to PS types 3 and 42, and PDF type 3. -""" - -from . import cbook -from ._ttconv import convert_ttf_to_ps, get_pdf_charprocs # noqa - - -cbook.warn_deprecated('3.3', name=__name__, obj_type='module') diff --git a/lib/matplotlib/type1font.py b/lib/matplotlib/type1font.py deleted file mode 100644 index 928285a9c28e..000000000000 --- a/lib/matplotlib/type1font.py +++ /dev/null @@ -1,337 +0,0 @@ -""" -A class representing a Type 1 font. - -This version reads pfa and pfb files and splits them for embedding in -pdf files. It also supports SlantFont and ExtendFont transformations, -similarly to pdfTeX and friends. There is no support yet for subsetting. - -Usage:: - - >>> font = Type1Font(filename) - >>> clear_part, encrypted_part, finale = font.parts - >>> slanted_font = font.transform({'slant': 0.167}) - >>> extended_font = font.transform({'extend': 1.2}) - -Sources: - -* Adobe Technical Note #5040, Supporting Downloadable PostScript - Language Fonts. - -* Adobe Type 1 Font Format, Adobe Systems Incorporated, third printing, - v1.1, 1993. ISBN 0-201-57044-0. -""" - -import binascii -import enum -import itertools -import re -import struct - -import numpy as np - -from matplotlib.cbook import _format_approx - - -# token types -_TokenType = enum.Enum('_TokenType', - 'whitespace name string delimiter number') - - -class Type1Font: - """ - A class representing a Type-1 font, for use by backends. - - Attributes - ---------- - parts : tuple - A 3-tuple of the cleartext part, the encrypted part, and the finale of - zeros. - - prop : Dict[str, Any] - A dictionary of font properties. - - """ - __slots__ = ('parts', 'prop') - - def __init__(self, input): - """ - Initialize a Type-1 font. - - Parameters - ---------- - input : str or 3-tuple - Either a pfb file name, or a 3-tuple of already-decoded Type-1 - font `~.Type1Font.parts`. - """ - if isinstance(input, tuple) and len(input) == 3: - self.parts = input - else: - with open(input, 'rb') as file: - data = self._read(file) - self.parts = self._split(data) - - self._parse() - - def _read(self, file): - """Read the font from a file, decoding into usable parts.""" - rawdata = file.read() - if not rawdata.startswith(b'\x80'): - return rawdata - - data = b'' - while rawdata: - if not rawdata.startswith(b'\x80'): - raise RuntimeError('Broken pfb file (expected byte 128, ' - 'got %d)' % rawdata[0]) - type = rawdata[1] - if type in (1, 2): - length, = struct.unpack('{}/%[]+') - _comment_re = re.compile(br'%[^\r\n\v]*') - _instring_re = re.compile(br'[()\\]') - - @classmethod - def _tokens(cls, text): - """ - A PostScript tokenizer. Yield (token, value) pairs such as - (_TokenType.whitespace, ' ') or (_TokenType.name, '/Foobar'). - """ - pos = 0 - while pos < len(text): - match = (cls._comment_re.match(text[pos:]) or - cls._whitespace_re.match(text[pos:])) - if match: - yield (_TokenType.whitespace, match.group()) - pos += match.end() - elif text[pos] == b'(': - start = pos - pos += 1 - depth = 1 - while depth: - match = cls._instring_re.search(text[pos:]) - if match is None: - return - pos += match.end() - if match.group() == b'(': - depth += 1 - elif match.group() == b')': - depth -= 1 - else: # a backslash - skip the next character - pos += 1 - yield (_TokenType.string, text[start:pos]) - elif text[pos:pos + 2] in (b'<<', b'>>'): - yield (_TokenType.delimiter, text[pos:pos + 2]) - pos += 2 - elif text[pos] == b'<': - start = pos - pos += text[pos:].index(b'>') - yield (_TokenType.string, text[start:pos]) - else: - match = cls._token_re.match(text[pos:]) - if match: - try: - float(match.group()) - yield (_TokenType.number, match.group()) - except ValueError: - yield (_TokenType.name, match.group()) - pos += match.end() - else: - yield (_TokenType.delimiter, text[pos:pos + 1]) - pos += 1 - - def _parse(self): - """ - Find the values of various font properties. This limited kind - of parsing is described in Chapter 10 "Adobe Type Manager - Compatibility" of the Type-1 spec. - """ - # Start with reasonable defaults - prop = {'weight': 'Regular', 'ItalicAngle': 0.0, 'isFixedPitch': False, - 'UnderlinePosition': -100, 'UnderlineThickness': 50} - filtered = ((token, value) - for token, value in self._tokens(self.parts[0]) - if token is not _TokenType.whitespace) - # The spec calls this an ASCII format; in Python 2.x we could - # just treat the strings and names as opaque bytes but let's - # turn them into proper Unicode, and be lenient in case of high bytes. - def convert(x): return x.decode('ascii', 'replace') - for token, value in filtered: - if token is _TokenType.name and value.startswith(b'/'): - key = convert(value[1:]) - token, value = next(filtered) - if token is _TokenType.name: - if value in (b'true', b'false'): - value = value == b'true' - else: - value = convert(value.lstrip(b'/')) - elif token is _TokenType.string: - value = convert(value.lstrip(b'(').rstrip(b')')) - elif token is _TokenType.number: - if b'.' in value: - value = float(value) - else: - value = int(value) - else: # more complicated value such as an array - value = None - if key != 'FontInfo' and value is not None: - prop[key] = value - - # Fill in the various *Name properties - if 'FontName' not in prop: - prop['FontName'] = (prop.get('FullName') or - prop.get('FamilyName') or - 'Unknown') - if 'FullName' not in prop: - prop['FullName'] = prop['FontName'] - if 'FamilyName' not in prop: - extras = ('(?i)([ -](regular|plain|italic|oblique|(semi)?bold|' - '(ultra)?light|extra|condensed))+$') - prop['FamilyName'] = re.sub(extras, '', prop['FullName']) - - self.prop = prop - - @classmethod - def _transformer(cls, tokens, slant, extend): - def fontname(name): - result = name - if slant: - result += b'_Slant_%d' % int(1000 * slant) - if extend != 1.0: - result += b'_Extend_%d' % int(1000 * extend) - return result - - def italicangle(angle): - return b'%a' % round( - float(angle) - np.arctan(slant) / np.pi * 180, - 5 - ) - - def fontmatrix(array): - array = array.lstrip(b'[').rstrip(b']').split() - array = [float(x) for x in array] - oldmatrix = np.eye(3, 3) - oldmatrix[0:3, 0] = array[::2] - oldmatrix[0:3, 1] = array[1::2] - modifier = np.array([[extend, 0, 0], - [slant, 1, 0], - [0, 0, 1]]) - newmatrix = np.dot(modifier, oldmatrix) - array[::2] = newmatrix[0:3, 0] - array[1::2] = newmatrix[0:3, 1] - return ( - '[%s]' % ' '.join(_format_approx(x, 6) for x in array) - ).encode('ascii') - - def replace(fun): - def replacer(tokens): - token, value = next(tokens) # name, e.g., /FontMatrix - yield value - token, value = next(tokens) # possible whitespace - while token is _TokenType.whitespace: - yield value - token, value = next(tokens) - if value != b'[': # name/number/etc. - yield fun(value) - else: # array, e.g., [1 2 3] - result = b'' - while value != b']': - result += value - token, value = next(tokens) - result += value - yield fun(result) - return replacer - - def suppress(tokens): - for _ in itertools.takewhile(lambda x: x[1] != b'def', tokens): - pass - yield b'' - - table = {b'/FontName': replace(fontname), - b'/ItalicAngle': replace(italicangle), - b'/FontMatrix': replace(fontmatrix), - b'/UniqueID': suppress} - - for token, value in tokens: - if token is _TokenType.name and value in table: - yield from table[value]( - itertools.chain([(token, value)], tokens)) - else: - yield value - - def transform(self, effects): - """ - Return a new font that is slanted and/or extended. - - Parameters - ---------- - effects : dict - A dict with optional entries: - - - 'slant' : float, default: 0 - Tangent of the angle that the font is to be slanted to the - right. Negative values slant to the left. - - 'extend' : float, default: 1 - Scaling factor for the font width. Values less than 1 condense - the glyphs. - - Returns - ------- - `Type1Font` - """ - tokenizer = self._tokens(self.parts[0]) - transformed = self._transformer(tokenizer, - slant=effects.get('slant', 0.0), - extend=effects.get('extend', 1.0)) - return Type1Font((b"".join(transformed), self.parts[1], self.parts[2])) diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py new file mode 100644 index 000000000000..df192df76b33 --- /dev/null +++ b/lib/matplotlib/typing.py @@ -0,0 +1,109 @@ +""" +Typing support for Matplotlib + +This module contains Type aliases which are useful for Matplotlib and potentially +downstream libraries. + +.. warning:: + **Provisional status of typing** + + The ``typing`` module and type stub files are considered provisional and may change + at any time without a deprecation period. +""" +from collections.abc import Hashable, Sequence +import pathlib +from typing import Any, Callable, Literal, TypeAlias, TypeVar, Union + +from . import path +from ._enums import JoinStyle, CapStyle +from .artist import Artist +from .backend_bases import RendererBase +from .markers import MarkerStyle +from .transforms import Bbox, Transform + +RGBColorType: TypeAlias = tuple[float, float, float] | str +"""Any RGB color specification accepted by Matplotlib.""" + +RGBAColorType: TypeAlias = ( + str | # "none" or "#RRGGBBAA"/"#RGBA" hex strings + tuple[float, float, float, float] | + # 2 tuple (color, alpha) representations, not infinitely recursive + # RGBColorType includes the (str, float) tuple, even for RGBA strings + tuple[RGBColorType, float] | + # (4-tuple, float) is odd, but accepted as the outer float overriding A of 4-tuple + tuple[tuple[float, float, float, float], float] +) +"""Any RGBA color specification accepted by Matplotlib.""" + +ColorType: TypeAlias = RGBColorType | RGBAColorType +"""Any color specification accepted by Matplotlib. See :mpltype:`color`.""" + +RGBColourType: TypeAlias = RGBColorType +"""Alias of `.RGBColorType`.""" + +RGBAColourType: TypeAlias = RGBAColorType +"""Alias of `.RGBAColorType`.""" + +ColourType: TypeAlias = ColorType +"""Alias of `.ColorType`.""" + +LineStyleType: TypeAlias = ( + Literal["-", "solid", "--", "dashed", "-.", "dashdot", ":", "dotted", + "", "none", " ", "None"] | + tuple[float, Sequence[float]] +) +""" +Any line style specification accepted by Matplotlib. +See :doc:`/gallery/lines_bars_and_markers/linestyles`. +""" + +DrawStyleType: TypeAlias = Literal["default", "steps", "steps-pre", "steps-mid", + "steps-post"] +"""See :doc:`/gallery/lines_bars_and_markers/step_demo`.""" + +MarkEveryType: TypeAlias = ( + None | + int | tuple[int, int] | slice | list[int] | + float | tuple[float, float] | + list[bool] +) +"""See :doc:`/gallery/lines_bars_and_markers/markevery_demo`.""" + +MarkerType: TypeAlias = str | path.Path | MarkerStyle +""" +Marker specification. See :doc:`/gallery/lines_bars_and_markers/marker_reference`. +""" + +FillStyleType: TypeAlias = Literal["full", "left", "right", "bottom", "top", "none"] +"""Marker fill styles. See :doc:`/gallery/lines_bars_and_markers/marker_reference`.""" + +JoinStyleType: TypeAlias = JoinStyle | Literal["miter", "round", "bevel"] +"""Line join styles. See :doc:`/gallery/lines_bars_and_markers/joinstyle`.""" + +CapStyleType: TypeAlias = CapStyle | Literal["butt", "projecting", "round"] +"""Line cap styles. See :doc:`/gallery/lines_bars_and_markers/capstyle`.""" + +CoordsBaseType = Union[ + str, + Artist, + Transform, + Callable[ + [RendererBase], + Union[Bbox, Transform] + ] +] +CoordsType = Union[ + CoordsBaseType, + tuple[CoordsBaseType, CoordsBaseType] +] + +RcStyleType: TypeAlias = ( + str | + dict[str, Any] | + pathlib.Path | + Sequence[str | pathlib.Path | dict[str, Any]] +) + +_HT = TypeVar("_HT", bound=Hashable) +HashableList: TypeAlias = list[_HT | "HashableList[_HT]"] +"""A nested list of Hashable values.""" diff --git a/lib/matplotlib/units.py b/lib/matplotlib/units.py index 311764c26be9..e3480f228bb4 100644 --- a/lib/matplotlib/units.py +++ b/lib/matplotlib/units.py @@ -5,8 +5,8 @@ units and units conversion. Use cases include converters for custom objects, e.g., a list of datetime objects, as well as for objects that are unit aware. We don't assume any particular units implementation; -rather a units implementation must provide the register with the Registry -converter dictionary and a `ConversionInterface`. For example, +rather a units implementation must register with the Registry converter +dictionary and provide a `ConversionInterface`. For example, here is a complete implementation which supports plotting with native datetime objects:: @@ -19,27 +19,25 @@ class DateConverter(units.ConversionInterface): @staticmethod def convert(value, unit, axis): - 'Convert a datetime value to a scalar or array' + "Convert a datetime value to a scalar or array." return dates.date2num(value) @staticmethod def axisinfo(unit, axis): - 'Return major and minor tick locators and formatters' - if unit!='date': return None + "Return major and minor tick locators and formatters." + if unit != 'date': + return None majloc = dates.AutoDateLocator() majfmt = dates.AutoDateFormatter(majloc) - return AxisInfo(majloc=majloc, - majfmt=majfmt, - label='date') + return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='date') @staticmethod def default_units(x, axis): - 'Return the default unit for x or None' + "Return the default unit for x or None." return 'date' # Finally we register our object type with the Matplotlib units registry. units.registry[datetime.date] = DateConverter() - """ from decimal import Decimal @@ -133,22 +131,6 @@ def convert(obj, unit, axis): """ return obj - @staticmethod - def is_numlike(x): - """ - The Matplotlib datalim, autoscaling, locators etc work with scalars - which are the units converted to floats given the current unit. The - converter may be passed these floats, or arrays of them, even when - units are set. - """ - if np.iterable(x): - for thisx in x: - if thisx is ma.masked: - continue - return isinstance(thisx, Number) - else: - return isinstance(x, Number) - class DecimalConverter(ConversionInterface): """Converter for decimal.Decimal data to float.""" @@ -165,25 +147,15 @@ def convert(value, unit, axis): value : decimal.Decimal or iterable Decimal or list of Decimal need to be converted """ - # If value is a Decimal if isinstance(value, Decimal): return float(value) + # value is Iterable[Decimal] + elif isinstance(value, ma.MaskedArray): + return ma.asarray(value, dtype=float) else: - # assume x is a list of Decimal - converter = np.asarray - if isinstance(value, ma.MaskedArray): - converter = ma.asarray - return converter(value, dtype=float) + return np.asarray(value, dtype=float) - @staticmethod - def axisinfo(unit, axis): - # Since Decimal is a kind of Number, don't need specific axisinfo. - return AxisInfo() - - @staticmethod - def default_units(x, axis): - # Return None since Decimal is a kind of Number. - return None + # axisinfo and default_units can be inherited as Decimals are Numbers. class Registry(dict): @@ -191,8 +163,9 @@ class Registry(dict): def get_converter(self, x): """Get the converter interface instance for *x*, or None.""" - if hasattr(x, "values"): - x = x.values # Unpack pandas Series and DataFrames. + # Unpack in case of e.g. Pandas or xarray object + x = cbook._unpack_to_numpy(x) + if isinstance(x, np.ndarray): # In case x in a masked array, access the underlying data (only its # type matters). If x is a regular ndarray, getdata() just returns @@ -207,7 +180,7 @@ def get_converter(self, x): except KeyError: pass try: # If cache lookup fails, look up based on first element... - first = cbook.safe_first_element(x) + first = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass else: diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 7537bebd251b..6b196571814d 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -3,7 +3,7 @@ =================== Widgets that are designed to work for any of the GUI backends. -All of these widgets require you to predefine a `matplotlib.axes.Axes` +All of these widgets require you to predefine an `~.axes.Axes` instance and pass that as the first parameter. Matplotlib doesn't try to be too smart with respect to layout -- you will have to figure out how wide and tall you want your Axes to be to accommodate your widget. @@ -11,15 +11,18 @@ from contextlib import ExitStack import copy -from numbers import Integral +import itertools +from numbers import Integral, Number +from cycler import cycler import numpy as np import matplotlib as mpl -from . import cbook, colors, ticker +from . import (_api, _docstring, backend_tools, cbook, collections, colors, + text as mtext, ticker, transforms) from .lines import Line2D -from .patches import Circle, Rectangle, Ellipse -from .transforms import blended_transform_factory +from .patches import Rectangle, Ellipse, Polygon +from .transforms import TransformedPatchPath, Affine2D class LockDraw: @@ -62,7 +65,7 @@ def locked(self): class Widget: """ - Abstract base class for GUI neutral widgets + Abstract base class for GUI neutral widgets. """ drawon = True eventson = True @@ -103,25 +106,22 @@ class AxesWidget(Widget): Attributes ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. + The parent Axes for the widget. canvas : `~matplotlib.backend_bases.FigureCanvasBase` The parent figure canvas for the widget. active : bool If False, the widget does not respond to events. """ + def __init__(self, ax): self.ax = ax - self.canvas = ax.figure.canvas self._cids = [] - @cbook.deprecated("3.4") - @property - def cids(self): - return self._cids + canvas = property(lambda self: self.ax.get_figure(root=True).canvas) def connect_event(self, event, callback): """ - Connect callback with an event. + Connect a callback function with an event. This should be used in lieu of ``figure.canvas.mpl_connect`` since this function stores callback ids for later clean up. @@ -134,6 +134,16 @@ def disconnect_events(self): for c in self._cids: self.canvas.mpl_disconnect(c) + def _get_data_coords(self, event): + """Return *event*'s data coordinates in this widget's Axes.""" + # This method handles the possibility that event.inaxes != self.ax (which may + # occur if multiple Axes are overlaid), in which case event.xdata/.ydata will + # be wrong. Note that we still special-case the common case where + # event.inaxes == self.ax and avoid re-running the inverse data transform, + # because that can introduce floating point errors for synthetic events. + return ((event.xdata, event.ydata) if event.inaxes is self.ax + else self.ax.transData.inverted().transform((event.x, event.y))) + class Button(AxesWidget): """ @@ -145,9 +155,9 @@ class Button(AxesWidget): Attributes ---------- ax - The `matplotlib.axes.Axes` the button renders into. + The `~.axes.Axes` the button renders into. label - A `matplotlib.text.Text` instance. + A `.Text` instance. color The color of the button when not hovering. hovercolor @@ -155,7 +165,7 @@ class Button(AxesWidget): """ def __init__(self, ax, label, image=None, - color='0.85', hovercolor='0.95'): + color='0.85', hovercolor='0.95', *, useblit=True): """ Parameters ---------- @@ -165,11 +175,16 @@ def __init__(self, ax, label, image=None, The button text. image : array-like or PIL Image The image to place in the button, if not *None*. The parameter is - directly forwarded to `~matplotlib.axes.Axes.imshow`. - color : color + directly forwarded to `~.axes.Axes.imshow`. + color : :mpltype:`color` The color of the button when not activated. - hovercolor : color + hovercolor : :mpltype:`color` The color of the button when the mouse is over it. + useblit : bool, default: True + Use blitting for faster drawing if supported by the backend. + See the tutorial :ref:`blitting` for details. + + .. versionadded:: 3.7 """ super().__init__(ax) @@ -180,7 +195,9 @@ def __init__(self, ax, label, image=None, horizontalalignment='center', transform=ax.transAxes) - self._observers = cbook.CallbackRegistry() + self._useblit = useblit and self.canvas.supports_blit + + self._observers = cbook.CallbackRegistry(signals=["clicked"]) self.connect_event('button_press_event', self._click) self.connect_event('button_release_event', self._release) @@ -192,19 +209,8 @@ def __init__(self, ax, label, image=None, self.color = color self.hovercolor = hovercolor - @cbook.deprecated("3.4") - @property - def cnt(self): - # Not real, but close enough. - return len(self._observers.callbacks['clicked']) - - @cbook.deprecated("3.4") - @property - def observers(self): - return self._observers.callbacks['clicked'] - def _click(self, event): - if self.ignore(event) or event.inaxes != self.ax or not self.eventson: + if not self.eventson or self.ignore(event) or not self.ax.contains(event)[0]: return if event.canvas.mouse_grabber != self.ax: event.canvas.grab_mouse(self.ax) @@ -213,17 +219,21 @@ def _release(self, event): if self.ignore(event) or event.canvas.mouse_grabber != self.ax: return event.canvas.release_mouse(self.ax) - if self.eventson and event.inaxes == self.ax: + if self.eventson and self.ax.contains(event)[0]: self._observers.process('clicked', event) def _motion(self, event): if self.ignore(event): return - c = self.hovercolor if event.inaxes == self.ax else self.color + c = self.hovercolor if self.ax.contains(event)[0] else self.color if not colors.same_color(c, self.ax.get_facecolor()): self.ax.set_facecolor(c) if self.drawon: - self.ax.figure.canvas.draw() + if self._useblit: + self.ax.draw_artist(self.ax) + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw() def on_clicked(self, func): """ @@ -231,18 +241,96 @@ def on_clicked(self, func): Returns a connection id, which can be used to disconnect the callback. """ - return self._observers.connect('clicked', func) + return self._observers.connect('clicked', lambda event: func(event)) def disconnect(self, cid): """Remove the callback function with connection id *cid*.""" self._observers.disconnect(cid) -class Slider(AxesWidget): +class SliderBase(AxesWidget): + """ + The base class for constructing Slider widgets. Not intended for direct + usage. + + For the slider to remain responsive you must maintain a reference to it. + """ + def __init__(self, ax, orientation, closedmin, closedmax, + valmin, valmax, valfmt, dragging, valstep): + if ax.name == '3d': + raise ValueError('Sliders cannot be added to 3D Axes') + + super().__init__(ax) + _api.check_in_list(['horizontal', 'vertical'], orientation=orientation) + + self.orientation = orientation + self.closedmin = closedmin + self.closedmax = closedmax + self.valmin = valmin + self.valmax = valmax + self.valstep = valstep + self.drag_active = False + self.valfmt = valfmt + + if orientation == "vertical": + ax.set_ylim((valmin, valmax)) + axis = ax.yaxis + else: + ax.set_xlim((valmin, valmax)) + axis = ax.xaxis + + self._fmt = axis.get_major_formatter() + if not isinstance(self._fmt, ticker.ScalarFormatter): + self._fmt = ticker.ScalarFormatter() + self._fmt.set_axis(axis) + self._fmt.set_useOffset(False) # No additive offset. + self._fmt.set_useMathText(True) # x sign before multiplicative offset. + + ax.set_axis_off() + ax.set_navigate(False) + + self.connect_event("button_press_event", self._update) + self.connect_event("button_release_event", self._update) + if dragging: + self.connect_event("motion_notify_event", self._update) + self._observers = cbook.CallbackRegistry(signals=["changed"]) + + def _stepped_value(self, val): + """Return *val* coerced to closest number in the ``valstep`` grid.""" + if isinstance(self.valstep, Number): + val = (self.valmin + + round((val - self.valmin) / self.valstep) * self.valstep) + elif self.valstep is not None: + valstep = np.asanyarray(self.valstep) + if valstep.ndim != 1: + raise ValueError( + f"valstep must have 1 dimension but has {valstep.ndim}" + ) + val = valstep[np.argmin(np.abs(valstep - val))] + return val + + def disconnect(self, cid): + """ + Remove the observer with connection id *cid*. + + Parameters + ---------- + cid : int + Connection id of the observer to be removed. + """ + self._observers.disconnect(cid) + + def reset(self): + """Reset the slider to the initial value.""" + if np.any(self.val != self.valinit): + self.set_val(self.valinit) + + +class Slider(SliderBase): """ A slider representing a floating point range. - Create a slider from *valmin* to *valmax* in axes *ax*. For the slider to + Create a slider from *valmin* to *valmax* in Axes *ax*. For the slider to remain responsive you must maintain a reference to it. Call :meth:`on_changed` to connect to the slider event. @@ -252,10 +340,11 @@ class Slider(AxesWidget): Slider value. """ - def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, + def __init__(self, ax, label, valmin, valmax, *, valinit=0.5, valfmt=None, closedmin=True, closedmax=True, slidermin=None, slidermax=None, dragging=True, valstep=None, - orientation='horizontal', **kwargs): + orientation='horizontal', initcolor='r', + track_color='lightgrey', handle_style=None, **kwargs): """ Parameters ---------- @@ -295,12 +384,36 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, dragging : bool, default: True If True the slider can be dragged by the mouse. - valstep : float, default: None - If given, the slider will snap to multiples of *valstep*. + valstep : float or array-like, default: None + If a float, the slider will snap to multiples of *valstep*. + If an array the slider will snap to the values in the array. orientation : {'horizontal', 'vertical'}, default: 'horizontal' The orientation of the slider. + initcolor : :mpltype:`color`, default: 'r' + The color of the line at the *valinit* position. Set to ``'none'`` + for no line. + + track_color : :mpltype:`color`, default: 'lightgrey' + The color of the background track. The track is accessible for + further styling via the *track* attribute. + + handle_style : dict + Properties of the slider handle. Default values are + + ========= ===== ======= ======================================== + Key Value Default Description + ========= ===== ======= ======================================== + facecolor color 'white' The facecolor of the slider handle. + edgecolor color '.75' The edgecolor of the slider handle. + size int 10 The size of the slider handle in points. + ========= ===== ======= ======================================== + + Other values will be transformed as marker{foo} and passed to the + `~.Line2D` constructor. e.g. ``handle_style = {'style'='x'}`` will + result in ``markerstyle = 'x'``. + Notes ----- Additional kwargs are passed on to ``self.poly`` which is the @@ -308,10 +421,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, `.Rectangle` documentation for valid property names (``facecolor``, ``edgecolor``, ``alpha``, etc.). """ - if ax.name == '3d': - raise ValueError('Sliders cannot be added to 3D Axes') - - super().__init__(ax) + super().__init__(ax, orientation, closedmin, closedmax, + valmin, valmax, valfmt, dragging, valstep) if slidermin is not None and not hasattr(slidermin, 'val'): raise ValueError( @@ -319,53 +430,51 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, if slidermax is not None and not hasattr(slidermax, 'val'): raise ValueError( f"Argument slidermax ({type(slidermax)}) has no 'val'") - cbook._check_in_list(['horizontal', 'vertical'], - orientation=orientation) - - self.orientation = orientation - self.closedmin = closedmin - self.closedmax = closedmax self.slidermin = slidermin self.slidermax = slidermax - self.drag_active = False - self.valmin = valmin - self.valmax = valmax - self.valstep = valstep valinit = self._value_in_bounds(valinit) if valinit is None: valinit = valmin self.val = valinit self.valinit = valinit - if orientation == 'vertical': - self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs) - self.hline = ax.axhline(valinit, 0, 1, color='r', lw=1) - else: - self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs) - self.vline = ax.axvline(valinit, 0, 1, color='r', lw=1) + + defaults = {'facecolor': 'white', 'edgecolor': '.75', 'size': 10} + handle_style = {} if handle_style is None else handle_style + marker_props = { + f'marker{k}': v for k, v in {**defaults, **handle_style}.items() + } if orientation == 'vertical': - ax.set_ylim((valmin, valmax)) - axis = ax.yaxis + self.track = Rectangle( + (.25, 0), .5, 1, + transform=ax.transAxes, + facecolor=track_color + ) + ax.add_patch(self.track) + self.poly = ax.axhspan(valmin, valinit, .25, .75, **kwargs) + # Drawing a longer line and clipping it to the track avoids + # pixelation-related asymmetries. + self.hline = ax.axhline(valinit, 0, 1, color=initcolor, lw=1, + clip_path=TransformedPatchPath(self.track)) + handleXY = [[0.5], [valinit]] else: - ax.set_xlim((valmin, valmax)) - axis = ax.xaxis + self.track = Rectangle( + (0, .25), 1, .5, + transform=ax.transAxes, + facecolor=track_color + ) + ax.add_patch(self.track) + self.poly = ax.axvspan(valmin, valinit, .25, .75, **kwargs) + self.vline = ax.axvline(valinit, 0, 1, color=initcolor, lw=1, + clip_path=TransformedPatchPath(self.track)) + handleXY = [[valinit], [0.5]] + self._handle, = ax.plot( + *handleXY, + "o", + **marker_props, + clip_on=False + ) - self.valfmt = valfmt - self._fmt = axis.get_major_formatter() - if not isinstance(self._fmt, ticker.ScalarFormatter): - self._fmt = ticker.ScalarFormatter() - self._fmt.set_axis(axis) - self._fmt.set_useOffset(False) # No additive offset. - self._fmt.set_useMathText(True) # x sign before multiplicative offset. - - ax.set_xticks([]) - ax.set_yticks([]) - ax.set_navigate(False) - - self.connect_event('button_press_event', self._update) - self.connect_event('button_release_event', self._update) - if dragging: - self.connect_event('motion_notify_event', self._update) if orientation == 'vertical': self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes, verticalalignment='bottom', @@ -385,26 +494,11 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, verticalalignment='center', horizontalalignment='left') - self._observers = cbook.CallbackRegistry() - self.set_val(valinit) - @cbook.deprecated("3.4") - @property - def cnt(self): - # Not real, but close enough. - return len(self._observers.callbacks['changed']) - - @cbook.deprecated("3.4") - @property - def observers(self): - return self._observers.callbacks['changed'] - def _value_in_bounds(self, val): """Makes sure *val* is with given bounds.""" - if self.valstep: - val = (self.valmin - + round((val - self.valmin) / self.valstep) * self.valstep) + val = self._stepped_value(val) if val <= self.valmin: if not self.closedmin: @@ -431,23 +525,22 @@ def _update(self, event): if self.ignore(event) or event.button != 1: return - if event.name == 'button_press_event' and event.inaxes == self.ax: + if event.name == 'button_press_event' and self.ax.contains(event)[0]: self.drag_active = True event.canvas.grab_mouse(self.ax) if not self.drag_active: return - elif ((event.name == 'button_release_event') or - (event.name == 'button_press_event' and - event.inaxes != self.ax)): + if (event.name == 'button_release_event' + or event.name == 'button_press_event' and not self.ax.contains(event)[0]): self.drag_active = False event.canvas.release_mouse(self.ax) return - if self.orientation == 'vertical': - val = self._value_in_bounds(event.ydata) - else: - val = self._value_in_bounds(event.xdata) + + xdata, ydata = self._get_data_coords(event) + val = self._value_in_bounds( + xdata if self.orientation == 'horizontal' else ydata) if val not in [None, self.val]: self.set_val(val) @@ -462,31 +555,28 @@ def _format(self, val): def set_val(self, val): """ - Set slider value to *val* + Set slider value to *val*. Parameters ---------- val : float """ - xy = self.poly.xy if self.orientation == 'vertical': - xy[1] = 0, val - xy[2] = 1, val + self.poly.set_height(val - self.poly.get_y()) + self._handle.set_ydata([val]) else: - xy[2] = val, 1 - xy[3] = val, 0 - self.poly.xy = xy + self.poly.set_width(val - self.poly.get_x()) + self._handle.set_xdata([val]) self.valtext.set_text(self._format(val)) if self.drawon: - self.ax.figure.canvas.draw_idle() + self.ax.get_figure(root=True).canvas.draw_idle() self.val = val if self.eventson: self._observers.process('changed', val) def on_changed(self, func): """ - When the slider value is changed call *func* with the new - slider value + Connect *func* as callback function to changes of the slider value. Parameters ---------- @@ -497,25 +587,390 @@ def on_changed(self, func): Returns ------- int - Connection id (which can be used to disconnect *func*) + Connection id (which can be used to disconnect *func*). """ - return self._observers.connect('changed', func) + return self._observers.connect('changed', lambda val: func(val)) - def disconnect(self, cid): + +class RangeSlider(SliderBase): + """ + A slider representing a range of floating point values. Defines the min and + max of the range via the *val* attribute as a tuple of (min, max). + + Create a slider that defines a range contained within [*valmin*, *valmax*] + in Axes *ax*. For the slider to remain responsive you must maintain a + reference to it. Call :meth:`on_changed` to connect to the slider event. + + Attributes + ---------- + val : tuple of float + Slider value. + """ + + def __init__( + self, + ax, + label, + valmin, + valmax, + *, + valinit=None, + valfmt=None, + closedmin=True, + closedmax=True, + dragging=True, + valstep=None, + orientation="horizontal", + track_color='lightgrey', + handle_style=None, + **kwargs, + ): + """ + Parameters + ---------- + ax : Axes + The Axes to put the slider in. + + label : str + Slider label. + + valmin : float + The minimum value of the slider. + + valmax : float + The maximum value of the slider. + + valinit : tuple of float or None, default: None + The initial positions of the slider. If None the initial positions + will be at the 25th and 75th percentiles of the range. + + valfmt : str, default: None + %-format string used to format the slider values. If None, a + `.ScalarFormatter` is used instead. + + closedmin : bool, default: True + Whether the slider interval is closed on the bottom. + + closedmax : bool, default: True + Whether the slider interval is closed on the top. + + dragging : bool, default: True + If True the slider can be dragged by the mouse. + + valstep : float, default: None + If given, the slider will snap to multiples of *valstep*. + + orientation : {'horizontal', 'vertical'}, default: 'horizontal' + The orientation of the slider. + + track_color : :mpltype:`color`, default: 'lightgrey' + The color of the background track. The track is accessible for + further styling via the *track* attribute. + + handle_style : dict + Properties of the slider handles. Default values are + + ========= ===== ======= ========================================= + Key Value Default Description + ========= ===== ======= ========================================= + facecolor color 'white' The facecolor of the slider handles. + edgecolor color '.75' The edgecolor of the slider handles. + size int 10 The size of the slider handles in points. + ========= ===== ======= ========================================= + + Other values will be transformed as marker{foo} and passed to the + `~.Line2D` constructor. e.g. ``handle_style = {'style'='x'}`` will + result in ``markerstyle = 'x'``. + + Notes + ----- + Additional kwargs are passed on to ``self.poly`` which is the + `~matplotlib.patches.Polygon` that draws the slider knob. See the + `.Polygon` documentation for valid property names (``facecolor``, + ``edgecolor``, ``alpha``, etc.). + """ + super().__init__(ax, orientation, closedmin, closedmax, + valmin, valmax, valfmt, dragging, valstep) + + # Set a value to allow _value_in_bounds() to work. + self.val = (valmin, valmax) + if valinit is None: + # Place at the 25th and 75th percentiles + extent = valmax - valmin + valinit = np.array([valmin + extent * 0.25, + valmin + extent * 0.75]) + else: + valinit = self._value_in_bounds(valinit) + self.val = valinit + self.valinit = valinit + + defaults = {'facecolor': 'white', 'edgecolor': '.75', 'size': 10} + handle_style = {} if handle_style is None else handle_style + marker_props = { + f'marker{k}': v for k, v in {**defaults, **handle_style}.items() + } + + if orientation == "vertical": + self.track = Rectangle( + (.25, 0), .5, 2, + transform=ax.transAxes, + facecolor=track_color + ) + ax.add_patch(self.track) + poly_transform = self.ax.get_yaxis_transform(which="grid") + handleXY_1 = [.5, valinit[0]] + handleXY_2 = [.5, valinit[1]] + else: + self.track = Rectangle( + (0, .25), 1, .5, + transform=ax.transAxes, + facecolor=track_color + ) + ax.add_patch(self.track) + poly_transform = self.ax.get_xaxis_transform(which="grid") + handleXY_1 = [valinit[0], .5] + handleXY_2 = [valinit[1], .5] + self.poly = Polygon(np.zeros([5, 2]), **kwargs) + self._update_selection_poly(*valinit) + self.poly.set_transform(poly_transform) + self.poly.get_path()._interpolation_steps = 100 + self.ax.add_patch(self.poly) + self.ax._request_autoscale_view() + self._handles = [ + ax.plot( + *handleXY_1, + "o", + **marker_props, + clip_on=False + )[0], + ax.plot( + *handleXY_2, + "o", + **marker_props, + clip_on=False + )[0] + ] + + if orientation == "vertical": + self.label = ax.text( + 0.5, + 1.02, + label, + transform=ax.transAxes, + verticalalignment="bottom", + horizontalalignment="center", + ) + + self.valtext = ax.text( + 0.5, + -0.02, + self._format(valinit), + transform=ax.transAxes, + verticalalignment="top", + horizontalalignment="center", + ) + else: + self.label = ax.text( + -0.02, + 0.5, + label, + transform=ax.transAxes, + verticalalignment="center", + horizontalalignment="right", + ) + + self.valtext = ax.text( + 1.02, + 0.5, + self._format(valinit), + transform=ax.transAxes, + verticalalignment="center", + horizontalalignment="left", + ) + + self._active_handle = None + self.set_val(valinit) + + def _update_selection_poly(self, vmin, vmax): + """ + Update the vertices of the *self.poly* slider in-place + to cover the data range *vmin*, *vmax*. + """ + # The vertices are positioned + # 1 ------ 2 + # | | + # 0, 4 ---- 3 + verts = self.poly.xy + if self.orientation == "vertical": + verts[0] = verts[4] = .25, vmin + verts[1] = .25, vmax + verts[2] = .75, vmax + verts[3] = .75, vmin + else: + verts[0] = verts[4] = vmin, .25 + verts[1] = vmin, .75 + verts[2] = vmax, .75 + verts[3] = vmax, .25 + + def _min_in_bounds(self, min): + """Ensure the new min value is between valmin and self.val[1].""" + if min <= self.valmin: + if not self.closedmin: + return self.val[0] + min = self.valmin + + if min > self.val[1]: + min = self.val[1] + return self._stepped_value(min) + + def _max_in_bounds(self, max): + """Ensure the new max value is between valmax and self.val[0].""" + if max >= self.valmax: + if not self.closedmax: + return self.val[1] + max = self.valmax + + if max <= self.val[0]: + max = self.val[0] + return self._stepped_value(max) + + def _value_in_bounds(self, vals): + """Clip min, max values to the bounds.""" + return (self._min_in_bounds(vals[0]), self._max_in_bounds(vals[1])) + + def _update_val_from_pos(self, pos): + """Update the slider value based on a given position.""" + idx = np.argmin(np.abs(self.val - pos)) + if idx == 0: + val = self._min_in_bounds(pos) + self.set_min(val) + else: + val = self._max_in_bounds(pos) + self.set_max(val) + if self._active_handle: + if self.orientation == "vertical": + self._active_handle.set_ydata([val]) + else: + self._active_handle.set_xdata([val]) + + def _update(self, event): + """Update the slider position.""" + if self.ignore(event) or event.button != 1: + return + + if event.name == "button_press_event" and self.ax.contains(event)[0]: + self.drag_active = True + event.canvas.grab_mouse(self.ax) + + if not self.drag_active: + return + + if (event.name == "button_release_event" + or event.name == "button_press_event" and not self.ax.contains(event)[0]): + self.drag_active = False + event.canvas.release_mouse(self.ax) + self._active_handle = None + return + + # determine which handle was grabbed + xdata, ydata = self._get_data_coords(event) + handle_index = np.argmin(np.abs( + [h.get_xdata()[0] - xdata for h in self._handles] + if self.orientation == "horizontal" else + [h.get_ydata()[0] - ydata for h in self._handles])) + handle = self._handles[handle_index] + + # these checks ensure smooth behavior if the handles swap which one + # has a higher value. i.e. if one is dragged over and past the other. + if handle is not self._active_handle: + self._active_handle = handle + + self._update_val_from_pos(xdata if self.orientation == "horizontal" else ydata) + + def _format(self, val): + """Pretty-print *val*.""" + if self.valfmt is not None: + return f"({self.valfmt % val[0]}, {self.valfmt % val[1]})" + else: + _, s1, s2, _ = self._fmt.format_ticks( + [self.valmin, *val, self.valmax] + ) + # fmt.get_offset is actually the multiplicative factor, if any. + s1 += self._fmt.get_offset() + s2 += self._fmt.get_offset() + # Use f string to avoid issues with backslashes when cast to a str + return f"({s1}, {s2})" + + def set_min(self, min): """ - Remove the observer with connection id *cid* + Set the lower value of the slider to *min*. Parameters ---------- - cid : int - Connection id of the observer to be removed + min : float """ - self._observers.disconnect(cid) + self.set_val((min, self.val[1])) - def reset(self): - """Reset the slider to the initial value""" - if self.val != self.valinit: - self.set_val(self.valinit) + def set_max(self, max): + """ + Set the lower value of the slider to *max*. + + Parameters + ---------- + max : float + """ + self.set_val((self.val[0], max)) + + def set_val(self, val): + """ + Set slider value to *val*. + + Parameters + ---------- + val : tuple or array-like of float + """ + val = np.sort(val) + _api.check_shape((2,), val=val) + # Reset value to allow _value_in_bounds() to work. + self.val = (self.valmin, self.valmax) + vmin, vmax = self._value_in_bounds(val) + self._update_selection_poly(vmin, vmax) + if self.orientation == "vertical": + self._handles[0].set_ydata([vmin]) + self._handles[1].set_ydata([vmax]) + else: + self._handles[0].set_xdata([vmin]) + self._handles[1].set_xdata([vmax]) + + self.valtext.set_text(self._format((vmin, vmax))) + + if self.drawon: + self.ax.get_figure(root=True).canvas.draw_idle() + self.val = (vmin, vmax) + if self.eventson: + self._observers.process("changed", (vmin, vmax)) + + def on_changed(self, func): + """ + Connect *func* as callback function to changes of the slider value. + + Parameters + ---------- + func : callable + Function to call when slider is changed. The function + must accept a 2-tuple of floats as its argument. + + Returns + ------- + int + Connection id (which can be used to disconnect *func*). + """ + return self._observers.connect('changed', lambda val: func(val)) + + +def _expand_text_props(props): + props = cbook.normalize_kwargs(props, mtext.Text) + return cycler(**props)() if props else itertools.repeat({}) class CheckButtons(AxesWidget): @@ -530,34 +985,53 @@ class CheckButtons(AxesWidget): Attributes ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - labels : list of `.Text` - - rectangles : list of `.Rectangle` - - lines : list of (`.Line2D`, `.Line2D`) pairs - List of lines for the x's in the check boxes. These lines exist for - each box, but have ``set_visible(False)`` when its box is not checked. + The parent Axes for the widget. + labels : list of `~matplotlib.text.Text` + The text label objects of the check buttons. """ - def __init__(self, ax, labels, actives=None): + def __init__(self, ax, labels, actives=None, *, useblit=True, + label_props=None, frame_props=None, check_props=None): """ - Add check buttons to `matplotlib.axes.Axes` instance *ax* + Add check buttons to `~.axes.Axes` instance *ax*. Parameters ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - + The parent Axes for the widget. labels : list of str The labels of the check buttons. - actives : list of bool, optional The initial check states of the buttons. The list must have the same length as *labels*. If not given, all buttons are unchecked. + useblit : bool, default: True + Use blitting for faster drawing if supported by the backend. + See the tutorial :ref:`blitting` for details. + + .. versionadded:: 3.7 + + label_props : dict, optional + Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 + frame_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button frame. Defaults (label font size / 2)**2 size, black + edgecolor, no facecolor, and 1.0 linewidth. + + .. versionadded:: 3.7 + check_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button check. Defaults to (label font size / 2)**2 size, + black color, and 1.0 linewidth. + + .. versionadded:: 3.7 """ super().__init__(ax) + _api.check_isinstance((dict, None), label_props=label_props, + frame_props=frame_props, check_props=check_props) + ax.set_xticks([]) ax.set_yticks([]) ax.set_navigate(False) @@ -565,110 +1039,235 @@ def __init__(self, ax, labels, actives=None): if actives is None: actives = [False] * len(labels) - if len(labels) > 1: - dy = 1. / (len(labels) + 1) - ys = np.linspace(1 - dy, dy, len(labels)) - else: - dy = 0.25 - ys = [0.5] - - axcolor = ax.get_facecolor() + self._useblit = useblit and self.canvas.supports_blit + self._background = None + + ys = np.linspace(1, 0, len(labels)+2)[1:-1] + + label_props = _expand_text_props(label_props) + self.labels = [ + ax.text(0.25, y, label, transform=ax.transAxes, + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + frame_props = { + 's': text_size**2, + 'linewidth': 1, + **cbook.normalize_kwargs(frame_props, collections.PathCollection), + 'marker': 's', + 'transform': ax.transAxes, + } + frame_props.setdefault('facecolor', frame_props.get('color', 'none')) + frame_props.setdefault('edgecolor', frame_props.pop('color', 'black')) + self._frames = ax.scatter([0.15] * len(ys), ys, **frame_props) + check_props = { + 'linewidth': 1, + 's': text_size**2, + **cbook.normalize_kwargs(check_props, collections.PathCollection), + 'marker': 'x', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + check_props.setdefault('facecolor', check_props.pop('color', 'black')) + self._checks = ax.scatter([0.15] * len(ys), ys, **check_props) + # The user may have passed custom colours in check_props, so we need to + # create the checks (above), and modify the visibility after getting + # whatever the user set. + self._init_status(actives) - self.labels = [] - self.lines = [] - self.rectangles = [] + self.connect_event('button_press_event', self._clicked) + if self._useblit: + self.connect_event('draw_event', self._clear) - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': ax.transAxes, 'solid_capstyle': 'butt'} - for y, label, active in zip(ys, labels, actives): - t = ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment='left', - verticalalignment='center') + self._observers = cbook.CallbackRegistry(signals=["clicked"]) - w, h = dy / 2, dy / 2 - x, y = 0.05, y - h / 2 + def _clear(self, event): + """Internal event handler to clear the buttons.""" + if self.ignore(event) or self.canvas.is_saving(): + return + self._background = self.canvas.copy_from_bbox(self.ax.bbox) + self.ax.draw_artist(self._checks) - p = Rectangle(xy=(x, y), width=w, height=h, edgecolor='black', - facecolor=axcolor, transform=ax.transAxes) + def _clicked(self, event): + if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: + return + idxs = [ # Indices of frames and of texts that contain the event. + *self._frames.contains(event)[1]["ind"], + *[i for i, text in enumerate(self.labels) if text.contains(event)[0]]] + if idxs: + coords = self._frames.get_offset_transform().transform( + self._frames.get_offsets()) + self.set_active( # Closest index, only looking in idxs. + idxs[(((event.x, event.y) - coords[idxs]) ** 2).sum(-1).argmin()]) + + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) + .. versionadded:: 3.7 - l1.set_visible(active) - l2.set_visible(active) - self.labels.append(t) - self.rectangles.append(p) - self.lines.append((l1, l2)) - ax.add_patch(p) - ax.add_line(l1) - ax.add_line(l2) + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) - self.connect_event('button_press_event', self._clicked) + def set_frame_props(self, props): + """ + Set properties of the check button frames. - self._observers = cbook.CallbackRegistry() + .. versionadded:: 3.7 - @cbook.deprecated("3.4") - @property - def cnt(self): - # Not real, but close enough. - return len(self._observers.callbacks['clicked']) + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button frames. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._frames.update(props) - @cbook.deprecated("3.4") - @property - def observers(self): - return self._observers.callbacks['clicked'] + def set_check_props(self, props): + """ + Set properties of the check button checks. - def _clicked(self, event): - if self.ignore(event) or event.button != 1 or event.inaxes != self.ax: - return - for i, (p, t) in enumerate(zip(self.rectangles, self.labels)): - if (t.get_window_extent().contains(event.x, event.y) or - p.get_window_extent().contains(event.x, event.y)): - self.set_active(i) - break + .. versionadded:: 3.7 - def set_active(self, index): + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button check. """ - Toggle (activate or deactivate) a check button by index. + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + actives = self.get_status() + self._checks.update(props) + # If new colours are supplied, then we must re-apply the status. + self._init_status(actives) + + def set_active(self, index, state=None): + """ + Modify the state of a check button by index. - Callbacks will be triggered if :attr:`eventson` is True. + Callbacks will be triggered if :attr:`!eventson` is True. Parameters ---------- index : int Index of the check button to toggle. + state : bool, optional + If a boolean value, set the state explicitly. If no value is + provided, the state is toggled. + Raises ------ ValueError If *index* is invalid. + TypeError + If *state* is not boolean. """ if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') + _api.check_isinstance((bool, None), state=state) + + invisible = colors.to_rgba('none') - l1, l2 = self.lines[index] - l1.set_visible(not l1.get_visible()) - l2.set_visible(not l2.get_visible()) + facecolors = self._checks.get_facecolor() + if state is None: + state = colors.same_color(facecolors[index], invisible) + facecolors[index] = self._active_check_colors[index] if state else invisible + self._checks.set_facecolor(facecolors) if self.drawon: - self.ax.figure.canvas.draw() + if self._useblit: + if self._background is not None: + self.canvas.restore_region(self._background) + self.ax.draw_artist(self._checks) + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw() if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) + def _init_status(self, actives): + """ + Initialize properties to match active status. + + The user may have passed custom colours in *check_props* to the + constructor, or to `.set_check_props`, so we need to modify the + visibility after getting whatever the user set. + """ + self._active_check_colors = self._checks.get_facecolor() + if len(self._active_check_colors) == 1: + self._active_check_colors = np.repeat(self._active_check_colors, + len(actives), axis=0) + self._checks.set_facecolor( + [ec if active else "none" + for ec, active in zip(self._active_check_colors, actives)]) + + def clear(self): + """Uncheck all checkboxes.""" + + self._checks.set_facecolor(['none'] * len(self._active_check_colors)) + + if hasattr(self, '_lines'): + for l1, l2 in self._lines: + l1.set_visible(False) + l2.set_visible(False) + + if self.drawon: + self.canvas.draw() + + if self.eventson: + # Call with no label, as all checkboxes are being cleared. + self._observers.process('clicked', None) + def get_status(self): """ - Return a tuple of the status (True/False) of all of the check buttons. + Return a list of the status (True/False) of all of the check buttons. """ - return [l1.get_visible() for (l1, l2) in self.lines] + return [not colors.same_color(color, colors.to_rgba("none")) + for color in self._checks.get_facecolors()] + + def get_checked_labels(self): + """Return a list of labels currently checked by user.""" + + return [l.get_text() for l, box_checked in + zip(self.labels, self.get_status()) + if box_checked] def on_clicked(self, func): """ Connect the callback function *func* to button click events. - Returns a connection id, which can be used to disconnect the callback. + Parameters + ---------- + func : callable + When the button is clicked, call *func* with button label. + When all buttons are cleared, call *func* with None. + The callback func must have the signature:: + + def func(label: str | None) -> Any + + Return values may exist, but are ignored. + + Returns + ------- + A connection id, which can be used to disconnect the callback. """ - return self._observers.connect('clicked', func) + return self._observers.connect('clicked', lambda text: func(text)) def disconnect(self, cid): """Remove the observer with connection id *cid*.""" @@ -689,22 +1288,18 @@ class TextBox(AxesWidget): Attributes ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - label : `.Text` + The parent Axes for the widget. + label : `~matplotlib.text.Text` - color : color + color : :mpltype:`color` The color of the text box when not hovering. - hovercolor : color + hovercolor : :mpltype:`color` The color of the text box when hovering. """ - @cbook.deprecated("3.3") - @property - def params_to_disable(self): - return [key for key in mpl.rcParams if 'keymap' in key] - - def __init__(self, ax, label, initial='', - color='.95', hovercolor='1', label_pad=.01): + def __init__(self, ax, label, initial='', *, + color='.95', hovercolor='1', label_pad=.01, + textalignment="left"): """ Parameters ---------- @@ -714,25 +1309,32 @@ def __init__(self, ax, label, initial='', Label for this text box. initial : str Initial value in the text box. - color : color + color : :mpltype:`color` The color of the box. - hovercolor : color + hovercolor : :mpltype:`color` The color of the box when the mouse is over it. label_pad : float The distance between the label and the right side of the textbox. + textalignment : {'left', 'center', 'right'} + The horizontal location of the text. """ super().__init__(ax) - self.DIST_FROM_LEFT = .05 + self._text_position = _api.check_getitem( + {"left": 0.05, "center": 0.5, "right": 0.95}, + textalignment=textalignment) self.label = ax.text( -label_pad, 0.5, label, transform=ax.transAxes, verticalalignment='center', horizontalalignment='right') + + # TextBox's text object should not parse mathtext at all. self.text_disp = self.ax.text( - self.DIST_FROM_LEFT, 0.5, initial, transform=self.ax.transAxes, - verticalalignment='center', horizontalalignment='left') + self._text_position, 0.5, initial, transform=self.ax.transAxes, + verticalalignment='center', horizontalalignment=textalignment, + parse_math=False) - self._observers = cbook.CallbackRegistry() + self._observers = cbook.CallbackRegistry(signals=["change", "submit"]) ax.set( xlim=(0, 1), ylim=(0, 1), # s.t. cursor appears from first click. @@ -741,7 +1343,7 @@ def __init__(self, ax, label, initial='', self.cursor_index = 0 - self.cursor = ax.vlines(0, 0, 0, visible=False, + self.cursor = ax.vlines(0, 0, 0, visible=False, color="k", lw=1, transform=mpl.transforms.IdentityTransform()) self.connect_event('button_press_event', self._click) @@ -755,22 +1357,6 @@ def __init__(self, ax, label, initial='', self.capturekeystrokes = False - @cbook.deprecated("3.4") - @property - def cnt(self): - # Not real, but close enough. - return sum(len(d) for d in self._observers.callbacks.values()) - - @cbook.deprecated("3.4") - @property - def change_observers(self): - return self._observers.callbacks['change'] - - @cbook.deprecated("3.4") - @property - def submit_observers(self): - return self._observers.callbacks['submit'] - @property def text(self): return self.text_disp.get_text() @@ -784,20 +1370,31 @@ def _rendercursor(self): # This causes a single extra draw if the figure has never been rendered # yet, which should be fine as we're going to repeatedly re-render the # figure later anyways. - if self.ax.figure._cachedRenderer is None: - self.ax.figure.canvas.draw() + fig = self.ax.get_figure(root=True) + if fig._get_renderer() is None: + fig.canvas.draw() text = self.text_disp.get_text() # Save value before overwriting it. widthtext = text[:self.cursor_index] + + bb_text = self.text_disp.get_window_extent() self.text_disp.set_text(widthtext or ",") - bb = self.text_disp.get_window_extent() - if not widthtext: # Use the comma for the height, but keep width to 0. - bb.x1 = bb.x0 + bb_widthtext = self.text_disp.get_window_extent() + + if bb_text.y0 == bb_text.y1: # Restoring the height if no text. + bb_text.y0 -= bb_widthtext.height / 2 + bb_text.y1 += bb_widthtext.height / 2 + elif not widthtext: # Keep width to 0. + bb_text.x1 = bb_text.x0 + else: # Move the cursor using width of bb_widthtext. + bb_text.x1 = bb_text.x0 + bb_widthtext.width + self.cursor.set( - segments=[[(bb.x1, bb.y0), (bb.x1, bb.y1)]], visible=True) + segments=[[(bb_text.x1, bb_text.y0), (bb_text.x1, bb_text.y1)]], + visible=True) self.text_disp.set_text(text) - self.ax.figure.canvas.draw() + fig.canvas.draw() def _release(self, event): if self.ignore(event): @@ -839,7 +1436,7 @@ def _keypress(self, event): self._rendercursor() if self.eventson: self._observers.process('change', self.text) - if key == "enter": + if key in ["enter", "return"]: self._observers.process('submit', self.text) def set_val(self, val): @@ -852,7 +1449,7 @@ def set_val(self, val): self._observers.process('change', self.text) self._observers.process('submit', self.text) - def begin_typing(self, x): + def begin_typing(self): self.capturekeystrokes = True # Disable keypress shortcuts, which may otherwise cause the figure to # be saved, closed, etc., until the user stops typing. The way to @@ -860,16 +1457,16 @@ def begin_typing(self, x): stack = ExitStack() # Register cleanup actions when user stops typing. self._on_stop_typing = stack.close toolmanager = getattr( - self.ax.figure.canvas.manager, "toolmanager", None) + self.ax.get_figure(root=True).canvas.manager, "toolmanager", None) if toolmanager is not None: # If using toolmanager, lock keypresses, and plan to release the # lock when typing stops. toolmanager.keypresslock(self) - stack.push(toolmanager.keypresslock.release, self) + stack.callback(toolmanager.keypresslock.release, self) else: # If not using toolmanager, disable all keypress-related rcParams. # Avoid spurious warnings if keymaps are getting deprecated. - with cbook._suppress_matplotlib_deprecation_warning(): + with _api.suppress_matplotlib_deprecation_warning(): stack.enter_context(mpl.rc_context( {k: [] for k in mpl.rcParams if k.startswith("keymap.")})) @@ -882,27 +1479,16 @@ def stop_typing(self): notifysubmit = False self.capturekeystrokes = False self.cursor.set_visible(False) - self.ax.figure.canvas.draw() + self.ax.get_figure(root=True).canvas.draw() if notifysubmit and self.eventson: # Because process() might throw an error in the user's code, only # call it once we've already done our cleanup. self._observers.process('submit', self.text) - def position_cursor(self, x): - # now, we have to figure out where the cursor goes. - # approximate it based on assuming all characters the same length - if len(self.text) == 0: - self.cursor_index = 0 - else: - bb = self.text_disp.get_window_extent() - ratio = np.clip((x - bb.x0) / bb.width, 0, 1) - self.cursor_index = int(len(self.text) * ratio) - self._rendercursor() - def _click(self, event): if self.ignore(event): return - if event.inaxes != self.ax: + if not self.ax.contains(event)[0]: self.stop_typing() return if not self.eventson: @@ -910,8 +1496,9 @@ def _click(self, event): if event.canvas.mouse_grabber != self.ax: event.canvas.grab_mouse(self.ax) if not self.capturekeystrokes: - self.begin_typing(event.x) - self.position_cursor(event.x) + self.begin_typing() + self.cursor_index = self.text_disp._char_index_at(event.x) + self._rendercursor() def _resize(self, event): self.stop_typing() @@ -919,11 +1506,11 @@ def _resize(self, event): def _motion(self, event): if self.ignore(event): return - c = self.hovercolor if event.inaxes == self.ax else self.color + c = self.hovercolor if self.ax.contains(event)[0] else self.color if not colors.same_color(c, self.ax.get_facecolor()): self.ax.set_facecolor(c) if self.drawon: - self.ax.figure.canvas.draw() + self.ax.get_figure(root=True).canvas.draw() def on_text_change(self, func): """ @@ -931,7 +1518,7 @@ def on_text_change(self, func): A connection id is returned which can be used to disconnect. """ - return self._observers.connect('change', func) + return self._observers.connect('change', lambda text: func(text)) def on_submit(self, func): """ @@ -940,7 +1527,7 @@ def on_submit(self, func): A connection id is returned which can be used to disconnect. """ - return self._observers.connect('submit', func) + return self._observers.connect('submit', lambda text: func(text)) def disconnect(self, cid): """Remove the observer with connection id *cid*.""" @@ -959,127 +1546,249 @@ class RadioButtons(AxesWidget): Attributes ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - activecolor : color + The parent Axes for the widget. + activecolor : :mpltype:`color` The color of the selected button. labels : list of `.Text` The button labels. - circles : list of `~.patches.Circle` - The buttons. value_selected : str The label text of the currently selected button. + index_selected : int + The index of the selected button. """ - def __init__(self, ax, labels, active=0, activecolor='blue'): + def __init__(self, ax, labels, active=0, activecolor=None, *, + useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. Parameters ---------- ax : `~matplotlib.axes.Axes` - The axes to add the buttons to. + The Axes to add the buttons to. labels : list of str The button labels. active : int The index of the initially selected button. - activecolor : color - The color of the selected button. + activecolor : :mpltype:`color` + The color of the selected button. The default is ``'blue'`` if not + specified here or in *radio_props*. + useblit : bool, default: True + Use blitting for faster drawing if supported by the backend. + See the tutorial :ref:`blitting` for details. + + .. versionadded:: 3.7 + + label_props : dict or list of dict, optional + Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 + radio_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + radio buttons. Defaults to (label font size / 2)**2 size, black + edgecolor, and *activecolor* facecolor (when active). + + .. note:: + If a facecolor is supplied in *radio_props*, it will override + *activecolor*. This may be used to provide an active color per + button. + + .. versionadded:: 3.7 """ super().__init__(ax) - self.activecolor = activecolor - self.value_selected = None + + _api.check_isinstance((dict, None), label_props=label_props, + radio_props=radio_props) + + radio_props = cbook.normalize_kwargs(radio_props, + collections.PathCollection) + if activecolor is not None: + if 'facecolor' in radio_props: + _api.warn_external( + 'Both the *activecolor* parameter and the *facecolor* ' + 'key in the *radio_props* parameter has been specified. ' + '*activecolor* will be ignored.') + else: + activecolor = 'blue' # Default. + + self._activecolor = activecolor + self._initial_active = active + self.value_selected = labels[active] + self.index_selected = active ax.set_xticks([]) ax.set_yticks([]) ax.set_navigate(False) - dy = 1. / (len(labels) + 1) - ys = np.linspace(1 - dy, dy, len(labels)) - cnt = 0 - axcolor = ax.get_facecolor() - - # scale the radius of the circle with the spacing between each one - circle_radius = dy / 2 - 0.01 - # default to hard-coded value if the radius becomes too large - circle_radius = min(circle_radius, 0.05) - - self.labels = [] - self.circles = [] - for y, label in zip(ys, labels): - t = ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment='left', - verticalalignment='center') - - if cnt == active: - self.value_selected = label - facecolor = activecolor - else: - facecolor = axcolor - p = Circle(xy=(0.15, y), radius=circle_radius, edgecolor='black', - facecolor=facecolor, transform=ax.transAxes) + ys = np.linspace(1, 0, len(labels) + 2)[1:-1] + + self._useblit = useblit and self.canvas.supports_blit + self._background = None + + label_props = _expand_text_props(label_props) + self.labels = [ + ax.text(0.25, y, label, transform=ax.transAxes, + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + radio_props = { + 's': text_size**2, + **radio_props, + 'marker': 'o', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + radio_props.setdefault('edgecolor', radio_props.get('color', 'black')) + radio_props.setdefault('facecolor', + radio_props.pop('color', activecolor)) + self._buttons = ax.scatter([.15] * len(ys), ys, **radio_props) + # The user may have passed custom colours in radio_props, so we need to + # create the radios, and modify the visibility after getting whatever + # the user set. + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, len(labels), + axis=0) + self._buttons.set_facecolor( + [activecolor if i == active else "none" + for i, activecolor in enumerate(self._active_colors)]) + + self.connect_event('button_press_event', self._clicked) + if self._useblit: + self.connect_event('draw_event', self._clear) + + self._observers = cbook.CallbackRegistry(signals=["clicked"]) + + def _clear(self, event): + """Internal event handler to clear the buttons.""" + if self.ignore(event) or self.canvas.is_saving(): + return + self._background = self.canvas.copy_from_bbox(self.ax.bbox) + self.ax.draw_artist(self._buttons) + + def _clicked(self, event): + if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: + return + idxs = [ # Indices of buttons and of texts that contain the event. + *self._buttons.contains(event)[1]["ind"], + *[i for i, text in enumerate(self.labels) if text.contains(event)[0]]] + if idxs: + coords = self._buttons.get_offset_transform().transform( + self._buttons.get_offsets()) + self.set_active( # Closest index, only looking in idxs. + idxs[(((event.x, event.y) - coords[idxs]) ** 2).sum(-1).argmin()]) + + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + .. versionadded:: 3.7 - self.labels.append(t) - self.circles.append(p) - ax.add_patch(p) - cnt += 1 + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) - self.connect_event('button_press_event', self._clicked) + def set_radio_props(self, props): + """ + Set properties of the `.Text` labels. - self._observers = cbook.CallbackRegistry() + .. versionadded:: 3.7 - @cbook.deprecated("3.4") - @property - def cnt(self): - # Not real, but close enough. - return len(self._observers.callbacks['clicked']) + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the radio + buttons. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._buttons.update(props) + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, + len(self.labels), axis=0) + self._buttons.set_facecolor( + [activecolor if text.get_text() == self.value_selected else "none" + for text, activecolor in zip(self.labels, self._active_colors)]) - @cbook.deprecated("3.4") @property - def observers(self): - return self._observers.callbacks['clicked'] + def activecolor(self): + return self._activecolor - def _clicked(self, event): - if self.ignore(event) or event.button != 1 or event.inaxes != self.ax: - return - pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) - distances = {} - for i, (p, t) in enumerate(zip(self.circles, self.labels)): - if (t.get_window_extent().contains(event.x, event.y) - or np.linalg.norm(pclicked - p.center) < p.radius): - distances[i] = np.linalg.norm(pclicked - p.center) - if len(distances) > 0: - closest = min(distances, key=distances.get) - self.set_active(closest) + @activecolor.setter + def activecolor(self, activecolor): + colors._check_color_like(activecolor=activecolor) + self._activecolor = activecolor + self.set_radio_props({'facecolor': activecolor}) def set_active(self, index): """ Select button with number *index*. - Callbacks will be triggered if :attr:`eventson` is True. + Callbacks will be triggered if :attr:`!eventson` is True. + + Parameters + ---------- + index : int + The index of the button to activate. + + Raises + ------ + ValueError + If the index is invalid. """ if index not in range(len(self.labels)): raise ValueError(f'Invalid RadioButton index: {index}') - self.value_selected = self.labels[index].get_text() - - for i, p in enumerate(self.circles): - if i == index: - color = self.activecolor - else: - color = self.ax.get_facecolor() - p.set_facecolor(color) + self.index_selected = index + button_facecolors = self._buttons.get_facecolor() + button_facecolors[:] = colors.to_rgba("none") + button_facecolors[index] = colors.to_rgba(self._active_colors[index]) + self._buttons.set_facecolor(button_facecolors) if self.drawon: - self.ax.figure.canvas.draw() + if self._useblit: + if self._background is not None: + self.canvas.restore_region(self._background) + self.ax.draw_artist(self._buttons) + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw() if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) + def clear(self): + """Reset the active button to the initially active one.""" + self.set_active(self._initial_active) + def on_clicked(self, func): """ Connect the callback function *func* to button click events. - Returns a connection id, which can be used to disconnect the callback. + Parameters + ---------- + func : callable + When the button is clicked, call *func* with button label. + When all buttons are cleared, call *func* with None. + The callback func must have the signature:: + + def func(label: str | None) -> Any + + Return values may exist, but are ignored. + + Returns + ------- + A connection id, which can be used to disconnect the callback. """ return self._observers.connect('clicked', func) @@ -1090,16 +1799,16 @@ def disconnect(self, cid): class SubplotTool(Widget): """ - A tool to adjust the subplot params of a `matplotlib.figure.Figure`. + A tool to adjust the subplot params of a `.Figure`. """ def __init__(self, targetfig, toolfig): """ Parameters ---------- - targetfig : `.Figure` + targetfig : `~matplotlib.figure.Figure` The figure instance to adjust. - toolfig : `.Figure` + toolfig : `~matplotlib.figure.Figure` The figure instance to embed the subplot tool into. """ @@ -1113,8 +1822,8 @@ def __init__(self, targetfig, toolfig): # The last subplot, removed below, keeps space for the "Reset" button. for name, ax in zip(names, toolfig.subplots(len(names) + 1)): ax.set_navigate(False) - slider = Slider(ax, name, - 0, 1, getattr(targetfig.subplotpars, name)) + slider = Slider(ax, name, 0, 1, + valinit=getattr(targetfig.subplotpars, name)) slider.on_changed(self._on_slider_changed) self._sliders.append(slider) toolfig.axes[-1].remove() @@ -1134,11 +1843,7 @@ def __init__(self, targetfig, toolfig): bax = toolfig.add_axes([0.8, 0.05, 0.15, 0.075]) self.buttonreset = Button(bax, 'Reset') - - # During reset there can be a temporary invalid state depending on the - # order of the reset so we turn off validation for the resetting - with cbook._setattr_cm(toolfig.subplotpars, validate=False): - self.buttonreset.on_clicked(self._on_reset) + self.buttonreset.on_clicked(self._on_reset) def _on_slider_changed(self, _): self.targetfig.subplots_adjust( @@ -1149,77 +1854,30 @@ def _on_slider_changed(self, _): def _on_reset(self, event): with ExitStack() as stack: - # Temporarily disable drawing on self and self's sliders. + # Temporarily disable drawing on self and self's sliders, and + # disconnect slider events (as the subplotparams can be temporarily + # invalid, depending on the order in which they are restored). stack.enter_context(cbook._setattr_cm(self, drawon=False)) for slider in self._sliders: - stack.enter_context(cbook._setattr_cm(slider, drawon=False)) + stack.enter_context( + cbook._setattr_cm(slider, drawon=False, eventson=False)) # Reset the slider to the initial position. for slider in self._sliders: slider.reset() - # Draw the canvas. - if self.drawon: - event.canvas.draw() - self.targetfig.canvas.draw() - - axleft = cbook.deprecated("3.3", name="axleft")( - property(lambda self: self.sliderleft.ax)) - axright = cbook.deprecated("3.3", name="axright")( - property(lambda self: self.sliderright.ax)) - axbottom = cbook.deprecated("3.3", name="axbottom")( - property(lambda self: self.sliderbottom.ax)) - axtop = cbook.deprecated("3.3", name="axtop")( - property(lambda self: self.slidertop.ax)) - axwspace = cbook.deprecated("3.3", name="axwspace")( - property(lambda self: self.sliderwspace.ax)) - axhspace = cbook.deprecated("3.3", name="axhspace")( - property(lambda self: self.sliderhspace.ax)) - - @cbook.deprecated("3.3") - def funcleft(self, val): - self.targetfig.subplots_adjust(left=val) - if self.drawon: - self.targetfig.canvas.draw() - - @cbook.deprecated("3.3") - def funcright(self, val): - self.targetfig.subplots_adjust(right=val) - if self.drawon: - self.targetfig.canvas.draw() - - @cbook.deprecated("3.3") - def funcbottom(self, val): - self.targetfig.subplots_adjust(bottom=val) - if self.drawon: - self.targetfig.canvas.draw() - - @cbook.deprecated("3.3") - def functop(self, val): - self.targetfig.subplots_adjust(top=val) - if self.drawon: - self.targetfig.canvas.draw() - - @cbook.deprecated("3.3") - def funcwspace(self, val): - self.targetfig.subplots_adjust(wspace=val) if self.drawon: - self.targetfig.canvas.draw() - - @cbook.deprecated("3.3") - def funchspace(self, val): - self.targetfig.subplots_adjust(hspace=val) - if self.drawon: - self.targetfig.canvas.draw() + event.canvas.draw() # Redraw the subplottool canvas. + self._on_slider_changed(None) # Apply changes to the target window. class Cursor(AxesWidget): """ - A crosshair cursor that spans the axes and moves with mouse cursor. + A crosshair cursor that spans the Axes and moves with mouse cursor. For the cursor to remain responsive you must keep a reference to it. Parameters ---------- - ax : `matplotlib.axes.Axes` + ax : `~matplotlib.axes.Axes` The `~.axes.Axes` to attach the cursor to. horizOn : bool, default: True Whether to draw the horizontal line. @@ -1227,6 +1885,7 @@ class Cursor(AxesWidget): Whether to draw the vertical line. useblit : bool, default: False Use blitting for faster drawing if supported by the backend. + See the tutorial :ref:`blitting` for details. Other Parameters ---------------- @@ -1238,8 +1897,7 @@ class Cursor(AxesWidget): -------- See :doc:`/gallery/widgets/cursor`. """ - - def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, + def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, **lineprops): super().__init__(ax) @@ -1261,12 +1919,10 @@ def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, def clear(self, event): """Internal event handler to clear the cursor.""" - if self.ignore(event): + if self.ignore(event) or self.canvas.is_saving(): return if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) - self.linev.set_visible(False) - self.lineh.set_visible(False) def onmove(self, event): """Internal event handler to draw the cursor when the mouse moves.""" @@ -1274,26 +1930,22 @@ def onmove(self, event): return if not self.canvas.widgetlock.available(self): return - if event.inaxes != self.ax: + if not self.ax.contains(event)[0]: self.linev.set_visible(False) self.lineh.set_visible(False) - if self.needclear: self.canvas.draw() self.needclear = False return self.needclear = True - if not self.visible: - return - self.linev.set_xdata((event.xdata, event.xdata)) - - self.lineh.set_ydata((event.ydata, event.ydata)) + xdata, ydata = self._get_data_coords(event) + self.linev.set_xdata((xdata, xdata)) self.linev.set_visible(self.visible and self.vertOn) + self.lineh.set_ydata((ydata, ydata)) self.lineh.set_visible(self.visible and self.horizOn) - - self._update() - - def _update(self): + if not (self.visible and (self.vertOn or self.horizOn)): + return + # Redraw. if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) @@ -1302,164 +1954,220 @@ def _update(self): self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() - return False class MultiCursor(Widget): """ Provide a vertical (default) and/or horizontal line cursor shared between - multiple axes. + multiple Axes. For the cursor to remain responsive you must keep a reference to it. - Example usage:: + Parameters + ---------- + canvas : object + This parameter is entirely unused and only kept for back-compatibility. + + axes : list of `~matplotlib.axes.Axes` + The `~.axes.Axes` to attach the cursor to. + + useblit : bool, default: True + Use blitting for faster drawing if supported by the backend. + See the tutorial :ref:`blitting` + for details. - from matplotlib.widgets import MultiCursor - import matplotlib.pyplot as plt - import numpy as np + horizOn : bool, default: False + Whether to draw the horizontal line. - fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True) - t = np.arange(0.0, 2.0, 0.01) - ax1.plot(t, np.sin(2*np.pi*t)) - ax2.plot(t, np.sin(4*np.pi*t)) + vertOn : bool, default: True + Whether to draw the vertical line. - multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, - horizOn=False, vertOn=True) - plt.show() + Other Parameters + ---------------- + **lineprops + `.Line2D` properties that control the appearance of the lines. + See also `~.Axes.axhline`. + Examples + -------- + See :doc:`/gallery/widgets/multicursor`. """ - def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True, + + def __init__(self, canvas, axes, *, useblit=True, horizOn=False, vertOn=True, **lineprops): + # canvas is stored only to provide the deprecated .canvas attribute; + # once it goes away the unused argument won't need to be stored at all. + self._canvas = canvas - self.canvas = canvas self.axes = axes self.horizOn = horizOn self.vertOn = vertOn + self._canvas_infos = { + ax.get_figure(root=True).canvas: + {"cids": [], "background": None} for ax in axes} + xmin, xmax = axes[-1].get_xlim() ymin, ymax = axes[-1].get_ylim() xmid = 0.5 * (xmin + xmax) ymid = 0.5 * (ymin + ymax) self.visible = True - self.useblit = useblit and self.canvas.supports_blit - self.background = None - self.needclear = False + self.useblit = ( + useblit + and all(canvas.supports_blit for canvas in self._canvas_infos)) if self.useblit: lineprops['animated'] = True - if vertOn: - self.vlines = [ax.axvline(xmid, visible=False, **lineprops) - for ax in axes] - else: - self.vlines = [] - - if horizOn: - self.hlines = [ax.axhline(ymid, visible=False, **lineprops) - for ax in axes] - else: - self.hlines = [] + self.vlines = [ax.axvline(xmid, visible=False, **lineprops) + for ax in axes] + self.hlines = [ax.axhline(ymid, visible=False, **lineprops) + for ax in axes] self.connect() def connect(self): """Connect events.""" - self._cidmotion = self.canvas.mpl_connect('motion_notify_event', - self.onmove) - self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear) + for canvas, info in self._canvas_infos.items(): + info["cids"] = [ + canvas.mpl_connect('motion_notify_event', self.onmove), + canvas.mpl_connect('draw_event', self.clear), + ] def disconnect(self): """Disconnect events.""" - self.canvas.mpl_disconnect(self._cidmotion) - self.canvas.mpl_disconnect(self._ciddraw) + for canvas, info in self._canvas_infos.items(): + for cid in info["cids"]: + canvas.mpl_disconnect(cid) + info["cids"].clear() def clear(self, event): """Clear the cursor.""" if self.ignore(event): return if self.useblit: - self.background = ( - self.canvas.copy_from_bbox(self.canvas.figure.bbox)) - for line in self.vlines + self.hlines: - line.set_visible(False) + for canvas, info in self._canvas_infos.items(): + # someone has switched the canvas on us! This happens if + # `savefig` needs to save to a format the previous backend did + # not support (e.g. saving a figure using an Agg based backend + # saved to a vector format). + if canvas is not canvas.figure.canvas: + continue + info["background"] = canvas.copy_from_bbox(canvas.figure.bbox) def onmove(self, event): - if self.ignore(event): + axs = [ax for ax in self.axes if ax.contains(event)[0]] + if self.ignore(event) or not axs or not event.canvas.widgetlock.available(self): return - if event.inaxes is None: - return - if not self.canvas.widgetlock.available(self): - return - self.needclear = True - if not self.visible: + ax = cbook._topmost_artist(axs) + xdata, ydata = ((event.xdata, event.ydata) if event.inaxes is ax + else ax.transData.inverted().transform((event.x, event.y))) + for line in self.vlines: + line.set_xdata((xdata, xdata)) + line.set_visible(self.visible and self.vertOn) + for line in self.hlines: + line.set_ydata((ydata, ydata)) + line.set_visible(self.visible and self.horizOn) + if not (self.visible and (self.vertOn or self.horizOn)): return - if self.vertOn: - for line in self.vlines: - line.set_xdata((event.xdata, event.xdata)) - line.set_visible(self.visible) - if self.horizOn: - for line in self.hlines: - line.set_ydata((event.ydata, event.ydata)) - line.set_visible(self.visible) - self._update() - - def _update(self): + # Redraw. if self.useblit: - if self.background is not None: - self.canvas.restore_region(self.background) + for canvas, info in self._canvas_infos.items(): + if info["background"]: + canvas.restore_region(info["background"]) if self.vertOn: for ax, line in zip(self.axes, self.vlines): ax.draw_artist(line) if self.horizOn: for ax, line in zip(self.axes, self.hlines): ax.draw_artist(line) - self.canvas.blit() + for canvas in self._canvas_infos: + canvas.blit() else: - self.canvas.draw_idle() + for canvas in self._canvas_infos: + canvas.draw_idle() class _SelectorWidget(AxesWidget): - def __init__(self, ax, onselect, useblit=False, button=None, - state_modifier_keys=None): + def __init__(self, ax, onselect=None, useblit=False, button=None, + state_modifier_keys=None, use_data_coordinates=False): super().__init__(ax) - self.visible = True - self.onselect = onselect + self._visible = True + if onselect is None: + self.onselect = lambda *args: None + else: + self.onselect = onselect self.useblit = useblit and self.canvas.supports_blit self.connect_default_events() - self.state_modifier_keys = dict(move=' ', clear='escape', - square='shift', center='control') - self.state_modifier_keys.update(state_modifier_keys or {}) + self._state_modifier_keys = dict(move=' ', clear='escape', + square='shift', center='control', + rotate='r') + self._state_modifier_keys.update(state_modifier_keys or {}) + self._use_data_coordinates = use_data_coordinates self.background = None - self.artists = [] if isinstance(button, Integral): self.validButtons = [button] else: self.validButtons = button + # Set to True when a selection is completed, otherwise is False + self._selection_completed = False + # will save the data (position at mouseclick) - self.eventpress = None + self._eventpress = None # will save the data (pos. at mouserelease) - self.eventrelease = None + self._eventrelease = None self._prev_event = None - self.state = set() + self._state = set() def set_active(self, active): super().set_active(active) if active: self.update_background(None) + def _get_animated_artists(self): + """ + Convenience method to get all animated artists of the figure containing + this widget, excluding those already present in self.artists. + The returned tuple is not sorted by 'z_order': z_order sorting is + valid only when considering all artists and not only a subset of all + artists. + """ + return tuple(a for ax_ in self.ax.get_figure().get_axes() + for a in ax_.get_children() + if a.get_animated() and a not in self.artists) + def update_background(self, event): """Force an update of the background.""" # If you add a call to `ignore` here, you'll want to check edge case: # `release` can call a draw event even when `ignore` is True. - if self.useblit: + if not self.useblit: + return + # Make sure that widget artists don't get accidentally included in the + # background, by re-rendering the background if needed (and then + # re-re-rendering the canvas with the visible widget artists). + # We need to remove all artists which will be drawn when updating + # the selector: if we have animated artists in the figure, it is safer + # to redrawn by default, in case they have updated by the callback + # zorder needs to be respected when redrawing + artists = sorted(self.artists + self._get_animated_artists(), + key=lambda a: a.get_zorder()) + needs_redraw = any(artist.get_visible() for artist in artists) + with ExitStack() as stack: + if needs_redraw: + for artist in artists: + stack.enter_context(artist._cm_set(visible=False)) + self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.ax.bbox) + if needs_redraw: + for artist in artists: + self.ax.draw_artist(artist) def connect_default_events(self): """Connect the major canvas events to methods.""" @@ -1485,37 +2193,44 @@ def ignore(self, event): if (self.validButtons is not None and event.button not in self.validButtons): return True - # If no button was pressed yet ignore the event if it was out - # of the axes - if self.eventpress is None: - return event.inaxes != self.ax + # If no button was pressed yet ignore the event if it was out of the Axes. + if self._eventpress is None: + return not self.ax.contains(event)[0] # If a button was pressed, check if the release-button is the same. - if event.button == self.eventpress.button: + if event.button == self._eventpress.button: return False # If a button was pressed, check if the release-button is the same. - return (event.inaxes != self.ax or - event.button != self.eventpress.button) + return (not self.ax.contains(event)[0] or + event.button != self._eventpress.button) def update(self): """Draw using blit() or draw_idle(), depending on ``self.useblit``.""" - if not self.ax.get_visible(): - return False + if (not self.ax.get_visible() or + self.ax.get_figure(root=True)._get_renderer() is None): + return if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) - for artist in self.artists: + else: + self.update_background(None) + # We need to draw all artists, which are not included in the + # background, therefore we also draw self._get_animated_artists() + # and we make sure that we respect z_order + artists = sorted(self.artists + self._get_animated_artists(), + key=lambda a: a.get_zorder()) + for artist in artists: self.ax.draw_artist(artist) self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() - return False def _get_data(self, event): """Get the xdata and ydata for event, with limits.""" if event.xdata is None: return None, None - xdata = np.clip(event.xdata, *self.ax.get_xbound()) - ydata = np.clip(event.ydata, *self.ax.get_ybound()) + xdata, ydata = self._get_data_coords(event) + xdata = np.clip(xdata, *self.ax.get_xbound()) + ydata = np.clip(ydata, *self.ax.get_ybound()) return xdata, ydata def _clean_event(self, event): @@ -1523,7 +2238,8 @@ def _clean_event(self, event): Preprocess an event: - Replace *event* by the previous event if *event* has no ``xdata``. - - Clip ``xdata`` and ``ydata`` to the axes limits. + - Get ``xdata`` and ``ydata`` from this widget's Axes, and clip them to the axes + limits. - Update the previous event. """ if event.xdata is None: @@ -1538,29 +2254,29 @@ def press(self, event): """Button press handler and validator.""" if not self.ignore(event): event = self._clean_event(event) - self.eventpress = event + self._eventpress = event self._prev_event = event key = event.key or '' key = key.replace('ctrl', 'control') # move state is locked in on a button press - if key == self.state_modifier_keys['move']: - self.state.add('move') + if key == self._state_modifier_keys['move']: + self._state.add('move') self._press(event) return True return False def _press(self, event): - """Button press handler.""" + """Button press event handler.""" def release(self, event): """Button release event handler and validator.""" - if not self.ignore(event) and self.eventpress: + if not self.ignore(event) and self._eventpress: event = self._clean_event(event) - self.eventrelease = event + self._eventrelease = event self._release(event) - self.eventpress = None - self.eventrelease = None - self.state.discard('move') + self._eventpress = None + self._eventrelease = None + self._state.discard('move') return True return False @@ -1569,7 +2285,7 @@ def _release(self, event): def onmove(self, event): """Cursor move event handler and validator.""" - if not self.ignore(event) and self.eventpress: + if not self.ignore(event) and self._eventpress: event = self._clean_event(event) self._onmove(event) return True @@ -1591,14 +2307,20 @@ def on_key_press(self, event): if self.active: key = event.key or '' key = key.replace('ctrl', 'control') - if key == self.state_modifier_keys['clear']: - for artist in self.artists: - artist.set_visible(False) - self.update() + if key == self._state_modifier_keys['clear']: + self.clear() return - for (state, modifier) in self.state_modifier_keys.items(): - if modifier in key: - self.state.add(state) + for (state, modifier) in self._state_modifier_keys.items(): + if modifier in key.split('+'): + # 'rotate' is changing _state on press and is not removed + # from _state when releasing + if state == 'rotate': + if state in self._state: + self._state.discard(state) + else: + self._state.add(state) + else: + self._state.add(state) self._on_key_press(event) def _on_key_press(self, event): @@ -1608,20 +2330,118 @@ def on_key_release(self, event): """Key release event handler and validator.""" if self.active: key = event.key or '' - for (state, modifier) in self.state_modifier_keys.items(): - if modifier in key: - self.state.discard(state) + for (state, modifier) in self._state_modifier_keys.items(): + # 'rotate' is changing _state on press and is not removed + # from _state when releasing + if modifier in key.split('+') and state != 'rotate': + self._state.discard(state) self._on_key_release(event) def _on_key_release(self, event): """Key release event handler.""" def set_visible(self, visible): - """Set the visibility of our artists.""" - self.visible = visible + """Set the visibility of the selector artists.""" + self._visible = visible for artist in self.artists: artist.set_visible(visible) + def get_visible(self): + """Get the visibility of the selector artists.""" + return self._visible + + def clear(self): + """Clear the selection and set the selector ready to make a new one.""" + self._clear_without_update() + self.update() + + def _clear_without_update(self): + self._selection_completed = False + self.set_visible(False) + + @property + def artists(self): + """Tuple of the artists of the selector.""" + handles_artists = getattr(self, '_handles_artists', ()) + return (self._selection_artist,) + handles_artists + + def set_props(self, **props): + """ + Set the properties of the selector artist. + + See the *props* argument in the selector docstring to know which properties are + supported. + """ + artist = self._selection_artist + props = cbook.normalize_kwargs(props, artist) + artist.set(**props) + if self.useblit: + self.update() + + def set_handle_props(self, **handle_props): + """ + Set the properties of the handles selector artist. See the + `handle_props` argument in the selector docstring to know which + properties are supported. + """ + if not hasattr(self, '_handles_artists'): + raise NotImplementedError("This selector doesn't have handles.") + + artist = self._handles_artists[0] + handle_props = cbook.normalize_kwargs(handle_props, artist) + for handle in self._handles_artists: + handle.set(**handle_props) + if self.useblit: + self.update() + self._handle_props.update(handle_props) + + def _validate_state(self, state): + supported_state = [ + key for key, value in self._state_modifier_keys.items() + if key != 'clear' and value != 'not-applicable' + ] + _api.check_in_list(supported_state, state=state) + + def add_state(self, state): + """ + Add a state to define the widget's behavior. See the + `state_modifier_keys` parameters for details. + + Parameters + ---------- + state : str + Must be a supported state of the selector. See the + `state_modifier_keys` parameters for details. + + Raises + ------ + ValueError + When the state is not supported by the selector. + + """ + self._validate_state(state) + self._state.add(state) + + def remove_state(self, state): + """ + Remove a state to define the widget's behavior. See the + `state_modifier_keys` parameters for details. + + Parameters + ---------- + state : str + Must be a supported state of the selector. See the + `state_modifier_keys` parameters for details. + + Raises + ------ + ValueError + When the state is not supported by the selector. + + """ + self._validate_state(state) + self._state.remove(state) + class SpanSelector(_SelectorWidget): """ @@ -1633,34 +2453,64 @@ class SpanSelector(_SelectorWidget): In order to turn off the SpanSelector, set ``span_selector.active`` to False. To turn it back on, set it to True. + Press and release events triggered at the same coordinates outside the + selection will clear the selector, except when + ``ignore_event_outside=True``. + Parameters ---------- - ax : `matplotlib.axes.Axes` + ax : `~matplotlib.axes.Axes` - onselect : func(min, max), min/max are floats + onselect : callable with signature ``func(min: float, max: float)`` + A callback function that is called after a release event and the + selection is created, changed or removed. direction : {"horizontal", "vertical"} The direction along which to draw the span selector. - minspan : float, default: None - If selection is less than *minspan*, do not call *onselect*. + minspan : float, default: 0 + If selection is less than or equal to *minspan*, the selection is + removed (when already existing) or cancelled. useblit : bool, default: False If True, use the backend-dependent blitting features for faster - canvas updates. + canvas updates. See the tutorial :ref:`blitting` for details. - rectprops : dict, default: None - Dictionary of `matplotlib.patches.Patch` properties. + props : dict, default: {'facecolor': 'red', 'alpha': 0.5} + Dictionary of `.Patch` properties. - onmove_callback : func(min, max), min/max are floats, default: None + onmove_callback : callable with signature ``func(min: float, max: float)``, optional Called on mouse move while the span is being selected. - span_stays : bool, default: False - If True, the span stays visible after the mouse is released. + interactive : bool, default: False + Whether to draw a set of handles that allow interaction with the + widget after it is drawn. - button : `.MouseButton` or list of `.MouseButton` + button : `.MouseButton` or list of `.MouseButton`, default: all buttons The mouse buttons which activate the span selector. + handle_props : dict, default: None + Properties of the handle lines at the edges of the span. Only used + when *interactive* is True. See `.Line2D` for valid properties. + + grab_range : float, default: 10 + Distance in pixels within which the interactive tool handles can be activated. + + state_modifier_keys : dict, optional + Keyboard modifiers which affect the widget's behavior. Values + amend the defaults, which are: + + - "clear": Clear the current shape, default: "escape". + + drag_from_anywhere : bool, default: False + If `True`, the widget can be moved by clicking anywhere within its bounds. + + ignore_event_outside : bool, default: False + If `True`, the event triggered outside the span selector will be ignored. + + snap_values : 1D array-like, optional + Snap the selector edges to the given values. + Examples -------- >>> import matplotlib.pyplot as plt @@ -1669,172 +2519,452 @@ class SpanSelector(_SelectorWidget): >>> ax.plot([1, 2, 3], [10, 50, 100]) >>> def onselect(vmin, vmax): ... print(vmin, vmax) - >>> rectprops = dict(facecolor='blue', alpha=0.5) >>> span = mwidgets.SpanSelector(ax, onselect, 'horizontal', - ... rectprops=rectprops) + ... props=dict(facecolor='blue', alpha=0.5)) >>> fig.show() - See also: :doc:`/gallery/widgets/span_selector` - """ + See also: :doc:`/gallery/widgets/span_selector` + """ + + def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, + props=None, onmove_callback=None, interactive=False, + button=None, handle_props=None, grab_range=10, + state_modifier_keys=None, drag_from_anywhere=False, + ignore_event_outside=False, snap_values=None): + + if state_modifier_keys is None: + state_modifier_keys = dict(clear='escape', + square='not-applicable', + center='not-applicable', + rotate='not-applicable') + super().__init__(ax, onselect, useblit=useblit, button=button, + state_modifier_keys=state_modifier_keys) + + if props is None: + props = dict(facecolor='red', alpha=0.5) + + props['animated'] = self.useblit + + self.direction = direction + self._extents_on_press = None + self.snap_values = snap_values + + self.onmove_callback = onmove_callback + self.minspan = minspan + + self.grab_range = grab_range + self._interactive = interactive + self._edge_handles = None + self.drag_from_anywhere = drag_from_anywhere + self.ignore_event_outside = ignore_event_outside + + self.new_axes(ax, _props=props, _init=True) + + # Setup handles + self._handle_props = { + 'color': props.get('facecolor', 'r'), + **cbook.normalize_kwargs(handle_props, Line2D)} + + if self._interactive: + self._edge_order = ['min', 'max'] + self._setup_edge_handles(self._handle_props) + + self._active_handle = None + + def new_axes(self, ax, *, _props=None, _init=False): + """Set SpanSelector to operate on a new Axes.""" + reconnect = False + if _init or self.canvas is not ax.get_figure(root=True).canvas: + if self.canvas is not None: + self.disconnect_events() + reconnect = True + self.ax = ax + if reconnect: + self.connect_default_events() + + # Reset + self._selection_completed = False + + if self.direction == 'horizontal': + trans = ax.get_xaxis_transform() + w, h = 0, 1 + else: + trans = ax.get_yaxis_transform() + w, h = 1, 0 + rect_artist = Rectangle((0, 0), w, h, transform=trans, visible=False) + if _props is not None: + rect_artist.update(_props) + elif self._selection_artist is not None: + rect_artist.update_from(self._selection_artist) + + self.ax.add_patch(rect_artist) + self._selection_artist = rect_artist + + def _setup_edge_handles(self, props): + # Define initial position using the axis bounds to keep the same bounds + if self.direction == 'horizontal': + positions = self.ax.get_xbound() + else: + positions = self.ax.get_ybound() + self._edge_handles = ToolLineHandles(self.ax, positions, + direction=self.direction, + line_props=props, + useblit=self.useblit) + + @property + def _handles_artists(self): + if self._edge_handles is not None: + return self._edge_handles.artists + else: + return () + + def _set_cursor(self, enabled): + """Update the canvas cursor based on direction of the selector.""" + if enabled: + cursor = (backend_tools.Cursors.RESIZE_HORIZONTAL + if self.direction == 'horizontal' else + backend_tools.Cursors.RESIZE_VERTICAL) + else: + cursor = backend_tools.Cursors.POINTER + + self.ax.get_figure(root=True).canvas.set_cursor(cursor) + + def connect_default_events(self): + # docstring inherited + super().connect_default_events() + if getattr(self, '_interactive', False): + self.connect_event('motion_notify_event', self._hover) + + def _press(self, event): + """Button press event handler.""" + self._set_cursor(True) + if self._interactive and self._selection_artist.get_visible(): + self._set_active_handle(event) + else: + self._active_handle = None + + if self._active_handle is None or not self._interactive: + # Clear previous rectangle before drawing new rectangle. + self.update() + + xdata, ydata = self._get_data_coords(event) + v = xdata if self.direction == 'horizontal' else ydata + + if self._active_handle is None and not self.ignore_event_outside: + # when the press event outside the span, we initially set the + # visibility to False and extents to (v, v) + # update will be called when setting the extents + self._visible = False + self._set_extents((v, v)) + # We need to set the visibility back, so the span selector will be + # drawn when necessary (span width > 0) + self._visible = True + else: + self.set_visible(True) + + return False + + @property + def direction(self): + """Direction of the span selector: 'vertical' or 'horizontal'.""" + return self._direction + + @direction.setter + def direction(self, direction): + """Set the direction of the span selector.""" + _api.check_in_list(['horizontal', 'vertical'], direction=direction) + if hasattr(self, '_direction') and direction != self._direction: + # remove previous artists + self._selection_artist.remove() + if self._interactive: + self._edge_handles.remove() + self._direction = direction + self.new_axes(self.ax) + if self._interactive: + self._setup_edge_handles(self._handle_props) + else: + self._direction = direction + + def _release(self, event): + """Button release event handler.""" + self._set_cursor(False) + + if not self._interactive: + self._selection_artist.set_visible(False) + + if (self._active_handle is None and self._selection_completed and + self.ignore_event_outside): + return + + vmin, vmax = self.extents + span = vmax - vmin + + if span <= self.minspan: + # Remove span and set self._selection_completed = False + self.set_visible(False) + if self._selection_completed: + # Call onselect, only when the span is already existing + self.onselect(vmin, vmax) + self._selection_completed = False + else: + self.onselect(vmin, vmax) + self._selection_completed = True + + self.update() + + self._active_handle = None + + return False + + def _hover(self, event): + """Update the canvas cursor if it's over a handle.""" + if self.ignore(event): + return + + if self._active_handle is not None or not self._selection_completed: + # Do nothing if button is pressed and a handle is active, which may + # occur with drag_from_anywhere=True. + # Do nothing if selection is not completed, which occurs when + # a selector has been cleared + return + + _, e_dist = self._edge_handles.closest(event.x, event.y) + self._set_cursor(e_dist <= self.grab_range) + + def _onmove(self, event): + """Motion notify event handler.""" + + xdata, ydata = self._get_data_coords(event) + if self.direction == 'horizontal': + v = xdata + vpress = self._eventpress.xdata + else: + v = ydata + vpress = self._eventpress.ydata + + # move existing span + # When "dragging from anywhere", `self._active_handle` is set to 'C' + # (match notation used in the RectangleSelector) + if self._active_handle == 'C' and self._extents_on_press is not None: + vmin, vmax = self._extents_on_press + dv = v - vpress + vmin += dv + vmax += dv + + # resize an existing shape + elif self._active_handle and self._active_handle != 'C': + vmin, vmax = self._extents_on_press + if self._active_handle == 'min': + vmin = v + else: + vmax = v + # new shape + else: + # Don't create a new span if there is already one when + # ignore_event_outside=True + if self.ignore_event_outside and self._selection_completed: + return + vmin, vmax = vpress, v + if vmin > vmax: + vmin, vmax = vmax, vmin + + self._set_extents((vmin, vmax)) - def __init__(self, ax, onselect, direction, minspan=None, useblit=False, - rectprops=None, onmove_callback=None, span_stays=False, - button=None): + if self.onmove_callback is not None: + self.onmove_callback(vmin, vmax) - super().__init__(ax, onselect, useblit=useblit, button=button) + return False - if rectprops is None: - rectprops = dict(facecolor='red', alpha=0.5) + def _draw_shape(self, vmin, vmax): + if vmin > vmax: + vmin, vmax = vmax, vmin + if self.direction == 'horizontal': + self._selection_artist.set_x(vmin) + self._selection_artist.set_width(vmax - vmin) + else: + self._selection_artist.set_y(vmin) + self._selection_artist.set_height(vmax - vmin) - rectprops['animated'] = self.useblit + def _set_active_handle(self, event): + """Set active handle based on the location of the mouse event.""" + # Note: event.xdata/ydata in data coordinates, event.x/y in pixels + e_idx, e_dist = self._edge_handles.closest(event.x, event.y) - cbook._check_in_list(['horizontal', 'vertical'], direction=direction) - self.direction = direction + # Prioritise center handle over other handles + # Use 'C' to match the notation used in the RectangleSelector + if 'move' in self._state: + self._active_handle = 'C' + elif e_dist > self.grab_range: + # Not close to any handles + self._active_handle = None + if self.drag_from_anywhere and self._contains(event): + # Check if we've clicked inside the region + self._active_handle = 'C' + self._extents_on_press = self.extents + else: + self._active_handle = None + return + else: + # Closest to an edge handle + self._active_handle = self._edge_order[e_idx] - self.rect = None - self.pressv = None + # Save coordinates of rectangle at the start of handle movement. + self._extents_on_press = self.extents - self.rectprops = rectprops - self.onmove_callback = onmove_callback - self.minspan = minspan - self.span_stays = span_stays + def _contains(self, event): + """Return True if event is within the patch.""" + return self._selection_artist.contains(event, radius=0)[0] - # Needed when dragging out of axes - self.prev = (0, 0) + @staticmethod + def _snap(values, snap_values): + """Snap values to a given array values (snap_values).""" + # take into account machine precision + eps = np.min(np.abs(np.diff(snap_values))) * 1e-12 + return tuple( + snap_values[np.abs(snap_values - v + np.sign(v) * eps).argmin()] + for v in values) - # Reset canvas so that `new_axes` connects events. - self.canvas = None - self.new_axes(ax) + @property + def extents(self): + """ + (float, float) + The values, in data coordinates, for the start and end points of the current + selection. If there is no selection then the start and end values will be + the same. + """ + if self.direction == 'horizontal': + vmin = self._selection_artist.get_x() + vmax = vmin + self._selection_artist.get_width() + else: + vmin = self._selection_artist.get_y() + vmax = vmin + self._selection_artist.get_height() + return vmin, vmax - def new_axes(self, ax): - """Set SpanSelector to operate on a new Axes.""" - self.ax = ax - if self.canvas is not ax.figure.canvas: - if self.canvas is not None: - self.disconnect_events() + @extents.setter + def extents(self, extents): + self._set_extents(extents) + self._selection_completed = True - self.canvas = ax.figure.canvas - self.connect_default_events() + def _set_extents(self, extents): + # Update displayed shape + if self.snap_values is not None: + extents = tuple(self._snap(extents, self.snap_values)) + self._draw_shape(*extents) + if self._interactive: + # Update displayed handles + self._edge_handles.set_data(self.extents) + self.set_visible(self._visible) + self.update() - if self.direction == 'horizontal': - trans = blended_transform_factory(self.ax.transData, - self.ax.transAxes) - w, h = 0, 1 - else: - trans = blended_transform_factory(self.ax.transAxes, - self.ax.transData) - w, h = 1, 0 - self.rect = Rectangle((0, 0), w, h, - transform=trans, - visible=False, - **self.rectprops) - if self.span_stays: - self.stay_rect = Rectangle((0, 0), w, h, - transform=trans, - visible=False, - **self.rectprops) - self.stay_rect.set_animated(False) - self.ax.add_patch(self.stay_rect) - - self.ax.add_patch(self.rect) - self.artists = [self.rect] - def ignore(self, event): - # docstring inherited - return super().ignore(event) or not self.visible +class ToolLineHandles: + """ + Control handles for canvas tools. - def _press(self, event): - """on button press event""" - self.rect.set_visible(self.visible) - if self.span_stays: - self.stay_rect.set_visible(False) - # really force a draw so that the stay rect is not in - # the blit background - if self.useblit: - self.canvas.draw() - xdata, ydata = self._get_data(event) - if self.direction == 'horizontal': - self.pressv = xdata - else: - self.pressv = ydata + Parameters + ---------- + ax : `~matplotlib.axes.Axes` + Matplotlib Axes where tool handles are displayed. + positions : 1D array + Positions of handles in data coordinates. + direction : {"horizontal", "vertical"} + Direction of handles, either 'vertical' or 'horizontal' + line_props : dict, optional + Additional line properties. See `.Line2D`. + useblit : bool, default: True + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. + """ - self._set_span_xy(event) - return False + def __init__(self, ax, positions, direction, *, line_props=None, + useblit=True): + self.ax = ax - def _release(self, event): - """on button release event""" - if self.pressv is None: - return + _api.check_in_list(['horizontal', 'vertical'], direction=direction) + self._direction = direction - self.rect.set_visible(False) + line_props = { + **(line_props if line_props is not None else {}), + 'visible': False, + 'animated': useblit, + } - if self.span_stays: - self.stay_rect.set_x(self.rect.get_x()) - self.stay_rect.set_y(self.rect.get_y()) - self.stay_rect.set_width(self.rect.get_width()) - self.stay_rect.set_height(self.rect.get_height()) - self.stay_rect.set_visible(True) + line_fun = ax.axvline if self.direction == 'horizontal' else ax.axhline - self.canvas.draw_idle() - vmin = self.pressv - xdata, ydata = self._get_data(event) - if self.direction == 'horizontal': - vmax = xdata or self.prev[0] - else: - vmax = ydata or self.prev[1] + self._artists = [line_fun(p, **line_props) for p in positions] - if vmin > vmax: - vmin, vmax = vmax, vmin - span = vmax - vmin - if self.minspan is not None and span < self.minspan: - return - self.onselect(vmin, vmax) - self.pressv = None - return False + @property + def artists(self): + return tuple(self._artists) - def _onmove(self, event): - """on motion notify event""" - if self.pressv is None: - return + @property + def positions(self): + """Positions of the handle in data coordinates.""" + method = 'get_xdata' if self.direction == 'horizontal' else 'get_ydata' + return [getattr(line, method)()[0] for line in self.artists] - self._set_span_xy(event) + @property + def direction(self): + """Direction of the handle: 'vertical' or 'horizontal'.""" + return self._direction - if self.onmove_callback is not None: - vmin = self.pressv - xdata, ydata = self._get_data(event) - if self.direction == 'horizontal': - vmax = xdata or self.prev[0] - else: - vmax = ydata or self.prev[1] + def set_data(self, positions): + """ + Set x- or y-positions of handles, depending on if the lines are + vertical or horizontal. - if vmin > vmax: - vmin, vmax = vmax, vmin - self.onmove_callback(vmin, vmax) + Parameters + ---------- + positions : tuple of length 2 + Set the positions of the handle in data coordinates + """ + method = 'set_xdata' if self.direction == 'horizontal' else 'set_ydata' + for line, p in zip(self.artists, positions): + getattr(line, method)([p, p]) - self.update() - return False + def set_visible(self, value): + """Set the visibility state of the handles artist.""" + for artist in self.artists: + artist.set_visible(value) - def _set_span_xy(self, event): - """Set the span coordinates.""" - x, y = self._get_data(event) - if x is None: - return + def set_animated(self, value): + """Set the animated state of the handles artist.""" + for artist in self.artists: + artist.set_animated(value) - self.prev = x, y - if self.direction == 'horizontal': - v = x - else: - v = y + def remove(self): + """Remove the handles artist from the figure.""" + for artist in self._artists: + artist.remove() + + def closest(self, x, y): + """ + Return index and pixel distance to closest handle. + + Parameters + ---------- + x, y : float + x, y position from which the distance will be calculated to + determinate the closest handle - minv, maxv = v, self.pressv - if minv > maxv: - minv, maxv = maxv, minv + Returns + ------- + index, distance : index of the handle and its distance from + position x, y + """ if self.direction == 'horizontal': - self.rect.set_x(minv) - self.rect.set_width(maxv - minv) + p_pts = np.array([ + self.ax.transData.transform((p, 0))[0] for p in self.positions + ]) + dist = abs(p_pts - x) else: - self.rect.set_y(minv) - self.rect.set_height(maxv - minv) + p_pts = np.array([ + self.ax.transData.transform((0, p))[1] for p in self.positions + ]) + dist = abs(p_pts - y) + index = np.argmin(dist) + return index, dist[index] class ToolHandles: @@ -1843,24 +2973,28 @@ class ToolHandles: Parameters ---------- - ax : `matplotlib.axes.Axes` - Matplotlib axes where tool handles are displayed. + ax : `~matplotlib.axes.Axes` + Matplotlib Axes where tool handles are displayed. x, y : 1D arrays Coordinates of control handles. - marker : str - Shape of marker used to display handle. See `matplotlib.pyplot.plot`. - marker_props : dict - Additional marker properties. See `matplotlib.lines.Line2D`. + marker : str, default: 'o' + Shape of marker used to display handle. See `~.pyplot.plot`. + marker_props : dict, optional + Additional marker properties. See `.Line2D`. + useblit : bool, default: True + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. """ - def __init__(self, ax, x, y, marker='o', marker_props=None, useblit=True): + def __init__(self, ax, x, y, *, marker='o', marker_props=None, useblit=True): self.ax = ax - props = dict(marker=marker, markersize=7, mfc='w', ls='none', - alpha=0.5, visible=False, label='_nolegend_') - props.update(marker_props if marker_props is not None else {}) + props = {'marker': marker, 'markersize': 7, 'markerfacecolor': 'w', + 'linestyle': 'none', 'alpha': 0.5, 'visible': False, + 'label': '_nolegend_', + **cbook.normalize_kwargs(marker_props, Line2D._alias_map)} self._markers = Line2D(x, y, animated=useblit, **props) self.ax.add_line(self._markers) - self.artist = self._markers @property def x(self): @@ -1870,8 +3004,12 @@ def x(self): def y(self): return self._markers.get_ydata() + @property + def artists(self): + return (self._markers, ) + def set_data(self, pts, y=None): - """Set x and y positions of handles""" + """Set x and y positions of handles.""" if y is not None: x = pts pts = np.array([x, y]) @@ -1894,271 +3032,407 @@ def closest(self, x, y): return min_index, dist[min_index] -class RectangleSelector(_SelectorWidget): - """ - Select a rectangular region of an axes. - - For the cursor to remain responsive you must keep a reference to it. - - Examples - -------- - :doc:`/gallery/widgets/rectangle_selector` - """ - - _shape_klass = Rectangle - - def __init__(self, ax, onselect, drawtype='box', - minspanx=0, minspany=0, useblit=False, - lineprops=None, rectprops=None, spancoords='data', - button=None, maxdist=10, marker_props=None, - interactive=False, state_modifier_keys=None): - r""" - Parameters - ---------- - ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - - onselect : function - A callback function that is called after a selection is completed. - It must have the signature:: - - def onselect(eclick: MouseEvent, erelease: MouseEvent) - - where *eclick* and *erelease* are the mouse click and release - `.MouseEvent`\s that start and complete the selection. - - drawtype : {"box", "line", "none"}, default: "box" - Whether to draw the full rectangle box, the diagonal line of the - rectangle, or nothing at all. - - minspanx : float, default: 0 - Selections with an x-span less than *minspanx* are ignored. +_RECTANGLESELECTOR_PARAMETERS_DOCSTRING = \ + r""" + Parameters + ---------- + ax : `~matplotlib.axes.Axes` + The parent Axes for the widget. - minspany : float, default: 0 - Selections with an y-span less than *minspany* are ignored. + onselect : function, optional + A callback function that is called after a release event and the + selection is created, changed or removed. + It must have the signature:: - useblit : bool, default: False - Whether to use blitting for faster drawing (if supported by the - backend). + def onselect(eclick: MouseEvent, erelease: MouseEvent) - lineprops : dict, optional - Properties with which the line is drawn, if ``drawtype == "line"``. - Default:: + where *eclick* and *erelease* are the mouse click and release + `.MouseEvent`\s that start and complete the selection. - dict(color="black", linestyle="-", linewidth=2, alpha=0.5) + minspanx : float, default: 0 + Selections with an x-span less than or equal to *minspanx* are removed + (when already existing) or cancelled. - rectprops : dict, optional - Properties with which the rectangle is drawn, if ``drawtype == - "box"``. Default:: + minspany : float, default: 0 + Selections with an y-span less than or equal to *minspanx* are removed + (when already existing) or cancelled. - dict(facecolor="red", edgecolor="black", alpha=0.2, fill=True) + useblit : bool, default: False + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. + + props : dict, optional + Properties with which the __ARTIST_NAME__ is drawn. See + `.Patch` for valid properties. + Default: + + ``dict(facecolor='red', edgecolor='black', alpha=0.2, fill=True)`` + + spancoords : {"data", "pixels"}, default: "data" + Whether to interpret *minspanx* and *minspany* in data or in pixel + coordinates. + + button : `.MouseButton`, list of `.MouseButton`, default: all buttons + Button(s) that trigger rectangle selection. + + grab_range : float, default: 10 + Distance in pixels within which the interactive tool handles can be + activated. + + handle_props : dict, optional + Properties with which the interactive handles (marker artists) are + drawn. See the marker arguments in `.Line2D` for valid + properties. Default values are defined in ``mpl.rcParams`` except for + the default value of ``markeredgecolor`` which will be the same as the + ``edgecolor`` property in *props*. + + interactive : bool, default: False + Whether to draw a set of handles that allow interaction with the + widget after it is drawn. + + state_modifier_keys : dict, optional + Keyboard modifiers which affect the widget's behavior. Values + amend the defaults, which are: + + - "move": Move the existing shape, default: no modifier. + - "clear": Clear the current shape, default: "escape". + - "square": Make the shape square, default: "shift". + - "center": change the shape around its center, default: "ctrl". + - "rotate": Rotate the shape around its center between -45° and 45°, + default: "r". + + "square" and "center" can be combined. The square shape can be defined + in data or display coordinates as determined by the + ``use_data_coordinates`` argument specified when creating the selector. + + drag_from_anywhere : bool, default: False + If `True`, the widget can be moved by clicking anywhere within + its bounds. + + ignore_event_outside : bool, default: False + If `True`, the event triggered outside the span selector will be + ignored. + + use_data_coordinates : bool, default: False + If `True`, the "square" shape of the selector is defined in + data coordinates instead of display coordinates. + """ - spancoords : {"data", "pixels"}, default: "data" - Whether to interpret *minspanx* and *minspany* in data or in pixel - coordinates. - button : `.MouseButton`, list of `.MouseButton`, default: all buttons - Button(s) that trigger rectangle selection. +@_docstring.Substitution(_RECTANGLESELECTOR_PARAMETERS_DOCSTRING.replace( + '__ARTIST_NAME__', 'rectangle')) +class RectangleSelector(_SelectorWidget): + """ + Select a rectangular region of an Axes. - maxdist : float, default: 10 - Distance in pixels within which the interactive tool handles can be - activated. + For the cursor to remain responsive you must keep a reference to it. - marker_props : dict - Properties with which the interactive handles are drawn. Currently - not implemented and ignored. + Press and release events triggered at the same coordinates outside the + selection will clear the selector, except when + ``ignore_event_outside=True``. - interactive : bool, default: False - Whether to draw a set of handles that allow interaction with the - widget after it is drawn. + %s - state_modifier_keys : dict, optional - Keyboard modifiers which affect the widget's behavior. Values - amend the defaults. + Examples + -------- + >>> import matplotlib.pyplot as plt + >>> import matplotlib.widgets as mwidgets + >>> fig, ax = plt.subplots() + >>> ax.plot([1, 2, 3], [10, 50, 100]) + >>> def onselect(eclick, erelease): + ... print(eclick.xdata, eclick.ydata) + ... print(erelease.xdata, erelease.ydata) + >>> props = dict(facecolor='blue', alpha=0.5) + >>> rect = mwidgets.RectangleSelector(ax, onselect, interactive=True, + ... props=props) + >>> fig.show() + >>> rect.add_state('square') - - "move": Move the existing shape, default: no modifier. - - "clear": Clear the current shape, default: "escape". - - "square": Makes the shape square, default: "shift". - - "center": Make the initial point the center of the shape, - default: "ctrl". + See also: :doc:`/gallery/widgets/rectangle_selector` + """ - "square" and "center" can be combined. - """ + def __init__(self, ax, onselect=None, *, minspanx=0, + minspany=0, useblit=False, + props=None, spancoords='data', button=None, grab_range=10, + handle_props=None, interactive=False, + state_modifier_keys=None, drag_from_anywhere=False, + ignore_event_outside=False, use_data_coordinates=False): super().__init__(ax, onselect, useblit=useblit, button=button, - state_modifier_keys=state_modifier_keys) - - self.to_draw = None - self.visible = True - self.interactive = interactive - - if drawtype == 'none': # draw a line but make it invisible - drawtype = 'line' - self.visible = False - - if drawtype == 'box': - if rectprops is None: - rectprops = dict(facecolor='red', edgecolor='black', - alpha=0.2, fill=True) - rectprops['animated'] = self.useblit - self.rectprops = rectprops - self.to_draw = self._shape_klass((0, 0), 0, 1, visible=False, - **self.rectprops) - self.ax.add_patch(self.to_draw) - if drawtype == 'line': - if lineprops is None: - lineprops = dict(color='black', linestyle='-', - linewidth=2, alpha=0.5) - lineprops['animated'] = self.useblit - self.lineprops = lineprops - self.to_draw = Line2D([0, 0], [0, 0], visible=False, - **self.lineprops) - self.ax.add_line(self.to_draw) + state_modifier_keys=state_modifier_keys, + use_data_coordinates=use_data_coordinates) + + self._interactive = interactive + self.drag_from_anywhere = drag_from_anywhere + self.ignore_event_outside = ignore_event_outside + self._rotation = 0.0 + self._aspect_ratio_correction = 1.0 + + # State to allow the option of an interactive selector that can't be + # interactively drawn. This is used in PolygonSelector as an + # interactive bounding box to allow the polygon to be easily resized + self._allow_creation = True + + if props is None: + props = dict(facecolor='red', edgecolor='black', + alpha=0.2, fill=True) + props = {**props, 'animated': self.useblit} + self._visible = props.pop('visible', self._visible) + to_draw = self._init_shape(**props) + self.ax.add_patch(to_draw) + + self._selection_artist = to_draw + self._set_aspect_ratio_correction() self.minspanx = minspanx self.minspany = minspany - cbook._check_in_list(['data', 'pixels'], spancoords=spancoords) + _api.check_in_list(['data', 'pixels'], spancoords=spancoords) self.spancoords = spancoords - self.drawtype = drawtype - self.maxdist = maxdist + self.grab_range = grab_range - if rectprops is None: - props = dict(mec='r') - else: - props = dict(mec=rectprops.get('edgecolor', 'r')) - self._corner_order = ['NW', 'NE', 'SE', 'SW'] - xc, yc = self.corners - self._corner_handles = ToolHandles(self.ax, xc, yc, marker_props=props, - useblit=self.useblit) - - self._edge_order = ['W', 'N', 'E', 'S'] - xe, ye = self.edge_centers - self._edge_handles = ToolHandles(self.ax, xe, ye, marker='s', - marker_props=props, - useblit=self.useblit) + if self._interactive: + self._handle_props = { + 'markeredgecolor': (props or {}).get('edgecolor', 'black'), + **cbook.normalize_kwargs(handle_props, Line2D)} - xc, yc = self.center - self._center_handle = ToolHandles(self.ax, [xc], [yc], marker='s', - marker_props=props, - useblit=self.useblit) + self._corner_order = ['SW', 'SE', 'NE', 'NW'] + xc, yc = self.corners + self._corner_handles = ToolHandles(self.ax, xc, yc, + marker_props=self._handle_props, + useblit=self.useblit) - self.active_handle = None + self._edge_order = ['W', 'S', 'E', 'N'] + xe, ye = self.edge_centers + self._edge_handles = ToolHandles(self.ax, xe, ye, marker='s', + marker_props=self._handle_props, + useblit=self.useblit) - self.artists = [self.to_draw, self._center_handle.artist, - self._corner_handles.artist, - self._edge_handles.artist] + xc, yc = self.center + self._center_handle = ToolHandles(self.ax, [xc], [yc], marker='s', + marker_props=self._handle_props, + useblit=self.useblit) - if not self.interactive: - self.artists = [self.to_draw] + self._active_handle = None self._extents_on_press = None + @property + def _handles_artists(self): + return (*self._center_handle.artists, *self._corner_handles.artists, + *self._edge_handles.artists) + + def _init_shape(self, **props): + return Rectangle((0, 0), 0, 1, visible=False, + rotation_point='center', **props) + def _press(self, event): - """on button press event""" - # make the drawn box/line visible get the click-coordinates, - # button, ... - if self.interactive and self.to_draw.get_visible(): + """Button press event handler.""" + # make the drawn box/line visible get the click-coordinates, button, ... + if self._interactive and self._selection_artist.get_visible(): self._set_active_handle(event) else: - self.active_handle = None + self._active_handle = None - if self.active_handle is None or not self.interactive: + if ((self._active_handle is None or not self._interactive) and + self._allow_creation): # Clear previous rectangle before drawing new rectangle. self.update() - if not self.interactive: - x = event.xdata - y = event.ydata + if (self._active_handle is None and not self.ignore_event_outside and + self._allow_creation): + x, y = self._get_data_coords(event) + self._visible = False self.extents = x, x, y, y + self._visible = True + else: + self.set_visible(True) + + self._extents_on_press = self.extents + self._rotation_on_press = self._rotation + self._set_aspect_ratio_correction() - self.set_visible(self.visible) + return False def _release(self, event): - """on button release event""" - if not self.interactive: - self.to_draw.set_visible(False) + """Button release event handler.""" + if not self._interactive: + self._selection_artist.set_visible(False) + + if (self._active_handle is None and self._selection_completed and + self.ignore_event_outside): + return # update the eventpress and eventrelease with the resulting extents - x1, x2, y1, y2 = self.extents - self.eventpress.xdata = x1 - self.eventpress.ydata = y1 + x0, x1, y0, y1 = self.extents + self._eventpress.xdata = x0 + self._eventpress.ydata = y0 + xy0 = self.ax.transData.transform([x0, y0]) + self._eventpress.x, self._eventpress.y = xy0 + + self._eventrelease.xdata = x1 + self._eventrelease.ydata = y1 xy1 = self.ax.transData.transform([x1, y1]) - self.eventpress.x, self.eventpress.y = xy1 - - self.eventrelease.xdata = x2 - self.eventrelease.ydata = y2 - xy2 = self.ax.transData.transform([x2, y2]) - self.eventrelease.x, self.eventrelease.y = xy2 + self._eventrelease.x, self._eventrelease.y = xy1 # calculate dimensions of box or line if self.spancoords == 'data': - spanx = abs(self.eventpress.xdata - self.eventrelease.xdata) - spany = abs(self.eventpress.ydata - self.eventrelease.ydata) + spanx = abs(self._eventpress.xdata - self._eventrelease.xdata) + spany = abs(self._eventpress.ydata - self._eventrelease.ydata) elif self.spancoords == 'pixels': - spanx = abs(self.eventpress.x - self.eventrelease.x) - spany = abs(self.eventpress.y - self.eventrelease.y) + spanx = abs(self._eventpress.x - self._eventrelease.x) + spany = abs(self._eventpress.y - self._eventrelease.y) else: - cbook._check_in_list(['data', 'pixels'], - spancoords=self.spancoords) + _api.check_in_list(['data', 'pixels'], + spancoords=self.spancoords) # check if drawn distance (if it exists) is not too small in # either x or y-direction - if (self.drawtype != 'none' - and (self.minspanx is not None and spanx < self.minspanx - or self.minspany is not None and spany < self.minspany)): - for artist in self.artists: - artist.set_visible(False) - self.update() - return + if spanx <= self.minspanx or spany <= self.minspany: + if self._selection_completed: + # Call onselect, only when the selection is already existing + self.onselect(self._eventpress, self._eventrelease) + self._clear_without_update() + else: + self.onselect(self._eventpress, self._eventrelease) + self._selection_completed = True - # call desired function - self.onselect(self.eventpress, self.eventrelease) self.update() + self._active_handle = None + self._extents_on_press = None return False def _onmove(self, event): - """on motion notify event if box/line is wanted""" - # resize an existing shape - if self.active_handle and self.active_handle != 'C': - x1, x2, y1, y2 = self._extents_on_press - if self.active_handle in ['E', 'W'] + self._corner_order: - x2 = event.xdata - if self.active_handle in ['N', 'S'] + self._corner_order: - y2 = event.ydata - - # move existing shape - elif (('move' in self.state or self.active_handle == 'C') - and self._extents_on_press is not None): - x1, x2, y1, y2 = self._extents_on_press - dx = event.xdata - self.eventpress.xdata - dy = event.ydata - self.eventpress.ydata + """ + Motion notify event handler. + + This can do one of four things: + - Translate + - Rotate + - Re-size + - Continue the creation of a new shape + """ + eventpress = self._eventpress + # The calculations are done for rotation at zero: we apply inverse + # transformation to events except when we rotate and move + state = self._state + rotate = 'rotate' in state and self._active_handle in self._corner_order + move = self._active_handle == 'C' + resize = self._active_handle and not move + + xdata, ydata = self._get_data_coords(event) + if resize: + inv_tr = self._get_rotation_transform().inverted() + xdata, ydata = inv_tr.transform([xdata, ydata]) + eventpress.xdata, eventpress.ydata = inv_tr.transform( + (eventpress.xdata, eventpress.ydata)) + + dx = xdata - eventpress.xdata + dy = ydata - eventpress.ydata + # refmax is used when moving the corner handle with the square state + # and is the maximum between refx and refy + refmax = None + if self._use_data_coordinates: + refx, refy = dx, dy + else: + # Get dx/dy in display coordinates + refx = event.x - eventpress.x + refy = event.y - eventpress.y + + x0, x1, y0, y1 = self._extents_on_press + # rotate an existing shape + if rotate: + # calculate angle abc + a = (eventpress.xdata, eventpress.ydata) + b = self.center + c = (xdata, ydata) + angle = (np.arctan2(c[1]-b[1], c[0]-b[0]) - + np.arctan2(a[1]-b[1], a[0]-b[0])) + self.rotation = np.rad2deg(self._rotation_on_press + angle) + + elif resize: + size_on_press = [x1 - x0, y1 - y0] + center = (x0 + size_on_press[0] / 2, y0 + size_on_press[1] / 2) + + # Keeping the center fixed + if 'center' in state: + # hh, hw are half-height and half-width + if 'square' in state: + # when using a corner, find which reference to use + if self._active_handle in self._corner_order: + refmax = max(refx, refy, key=abs) + if self._active_handle in ['E', 'W'] or refmax == refx: + hw = xdata - center[0] + hh = hw / self._aspect_ratio_correction + else: + hh = ydata - center[1] + hw = hh * self._aspect_ratio_correction + else: + hw = size_on_press[0] / 2 + hh = size_on_press[1] / 2 + # cancel changes in perpendicular direction + if self._active_handle in ['E', 'W'] + self._corner_order: + hw = abs(xdata - center[0]) + if self._active_handle in ['N', 'S'] + self._corner_order: + hh = abs(ydata - center[1]) + + x0, x1, y0, y1 = (center[0] - hw, center[0] + hw, + center[1] - hh, center[1] + hh) + + else: + # change sign of relative changes to simplify calculation + # Switch variables so that x1 and/or y1 are updated on move + if 'W' in self._active_handle: + x0 = x1 + if 'S' in self._active_handle: + y0 = y1 + if self._active_handle in ['E', 'W'] + self._corner_order: + x1 = xdata + if self._active_handle in ['N', 'S'] + self._corner_order: + y1 = ydata + if 'square' in state: + # when using a corner, find which reference to use + if self._active_handle in self._corner_order: + refmax = max(refx, refy, key=abs) + if self._active_handle in ['E', 'W'] or refmax == refx: + sign = np.sign(ydata - y0) + y1 = y0 + sign * abs(x1 - x0) / self._aspect_ratio_correction + else: + sign = np.sign(xdata - x0) + x1 = x0 + sign * abs(y1 - y0) * self._aspect_ratio_correction + + elif move: + x0, x1, y0, y1 = self._extents_on_press + dx = xdata - eventpress.xdata + dy = ydata - eventpress.ydata + x0 += dx x1 += dx - x2 += dx + y0 += dy y1 += dy - y2 += dy - # new shape else: - center = [self.eventpress.xdata, self.eventpress.ydata] - center_pix = [self.eventpress.x, self.eventpress.y] - dx = (event.xdata - center[0]) / 2. - dy = (event.ydata - center[1]) / 2. + # Create a new shape + self._rotation = 0 + # Don't create a new rectangle if there is already one when + # ignore_event_outside=True + if ((self.ignore_event_outside and self._selection_completed) or + not self._allow_creation): + return + center = [eventpress.xdata, eventpress.ydata] + dx = (xdata - center[0]) / 2 + dy = (ydata - center[1]) / 2 # square shape - if 'square' in self.state: - dx_pix = abs(event.x - center_pix[0]) - dy_pix = abs(event.y - center_pix[1]) - if not dx_pix: - return - maxd = max(abs(dx_pix), abs(dy_pix)) - if abs(dx_pix) < maxd: - dx *= maxd / (abs(dx_pix) + 1e-6) - if abs(dy_pix) < maxd: - dy *= maxd / (abs(dy_pix) + 1e-6) + if 'square' in state: + refmax = max(refx, refy, key=abs) + if refmax == refx: + dy = np.sign(dy) * abs(dx) / self._aspect_ratio_correction + else: + dx = np.sign(dx) * abs(dy) * self._aspect_ratio_correction # from center - if 'center' in self.state: + if 'center' in state: dx *= 2 dy *= 2 @@ -2167,52 +3441,71 @@ def _onmove(self, event): center[0] += dx center[1] += dy - x1, x2, y1, y2 = (center[0] - dx, center[0] + dx, + x0, x1, y0, y1 = (center[0] - dx, center[0] + dx, center[1] - dy, center[1] + dy) - self.extents = x1, x2, y1, y2 + self.extents = x0, x1, y0, y1 @property def _rect_bbox(self): - if self.drawtype == 'box': - x0 = self.to_draw.get_x() - y0 = self.to_draw.get_y() - width = self.to_draw.get_width() - height = self.to_draw.get_height() - return x0, y0, width, height + return self._selection_artist.get_bbox().bounds + + def _set_aspect_ratio_correction(self): + aspect_ratio = self.ax._get_aspect_ratio() + self._selection_artist._aspect_ratio_correction = aspect_ratio + if self._use_data_coordinates: + self._aspect_ratio_correction = 1 else: - x, y = self.to_draw.get_data() - x0, x1 = min(x), max(x) - y0, y1 = min(y), max(y) - return x0, y0, x1 - x0, y1 - y0 + self._aspect_ratio_correction = aspect_ratio + + def _get_rotation_transform(self): + aspect_ratio = self.ax._get_aspect_ratio() + return Affine2D().translate(-self.center[0], -self.center[1]) \ + .scale(1, aspect_ratio) \ + .rotate(self._rotation) \ + .scale(1, 1 / aspect_ratio) \ + .translate(*self.center) @property def corners(self): - """Corners of rectangle from lower left, moving clockwise.""" + """ + Corners of rectangle in data coordinates from lower left, + moving clockwise. + """ x0, y0, width, height = self._rect_bbox xc = x0, x0 + width, x0 + width, x0 yc = y0, y0, y0 + height, y0 + height - return xc, yc + transform = self._get_rotation_transform() + coords = transform.transform(np.array([xc, yc]).T).T + return coords[0], coords[1] @property def edge_centers(self): - """Midpoint of rectangle edges from left, moving clockwise.""" + """ + Midpoint of rectangle edges in data coordinates from left, + moving anti-clockwise. + """ x0, y0, width, height = self._rect_bbox w = width / 2. h = height / 2. xe = x0, x0 + w, x0 + width, x0 + w ye = y0 + h, y0, y0 + h, y0 + height - return xe, ye + transform = self._get_rotation_transform() + coords = transform.transform(np.array([xe, ye]).T).T + return coords[0], coords[1] @property def center(self): - """Center of rectangle""" + """Center of rectangle in data coordinates.""" x0, y0, width, height = self._rect_bbox return x0 + width / 2., y0 + height / 2. @property def extents(self): - """Return (xmin, xmax, ymin, ymax).""" + """ + Return (xmin, xmax, ymin, ymax) in data coordinates as defined by the + bounding box before rotation. + """ x0, y0, width, height = self._rect_bbox xmin, xmax = sorted([x0, x0 + width]) ymin, ymax = sorted([y0, y0 + height]) @@ -2221,15 +3514,34 @@ def extents(self): @extents.setter def extents(self, extents): # Update displayed shape - self.draw_shape(extents) - # Update displayed handles - self._corner_handles.set_data(*self.corners) - self._edge_handles.set_data(*self.edge_centers) - self._center_handle.set_data(*self.center) - self.set_visible(self.visible) + self._draw_shape(extents) + if self._interactive: + # Update displayed handles + self._corner_handles.set_data(*self.corners) + self._edge_handles.set_data(*self.edge_centers) + x, y = self.center + self._center_handle.set_data([x], [y]) + self.set_visible(self._visible) self.update() - def draw_shape(self, extents): + @property + def rotation(self): + """ + Rotation in degree in interval [-45°, 45°]. The rotation is limited in + range to keep the implementation simple. + """ + return np.rad2deg(self._rotation) + + @rotation.setter + def rotation(self, value): + # Restrict to a limited range of rotation [-45°, 45°] to avoid changing + # order of handles + if -45 <= value and value <= 45: + self._rotation = np.deg2rad(value) + # call extents setter to draw shape and update handles positions + self.extents = self.extents + + def _draw_shape(self, extents): x0, x1, y0, y1 = extents xmin, xmax = sorted([x0, x1]) ymin, ymax = sorted([y0, y1]) @@ -2241,131 +3553,100 @@ def draw_shape(self, extents): xmax = min(xmax, xlim[1]) ymax = min(ymax, ylim[1]) - if self.drawtype == 'box': - self.to_draw.set_x(xmin) - self.to_draw.set_y(ymin) - self.to_draw.set_width(xmax - xmin) - self.to_draw.set_height(ymax - ymin) - - elif self.drawtype == 'line': - self.to_draw.set_data([xmin, xmax], [ymin, ymax]) + self._selection_artist.set_x(xmin) + self._selection_artist.set_y(ymin) + self._selection_artist.set_width(xmax - xmin) + self._selection_artist.set_height(ymax - ymin) + self._selection_artist.set_angle(self.rotation) def _set_active_handle(self, event): - """Set active handle based on the location of the mouse event""" + """Set active handle based on the location of the mouse event.""" # Note: event.xdata/ydata in data coordinates, event.x/y in pixels c_idx, c_dist = self._corner_handles.closest(event.x, event.y) e_idx, e_dist = self._edge_handles.closest(event.x, event.y) m_idx, m_dist = self._center_handle.closest(event.x, event.y) - if 'move' in self.state: - self.active_handle = 'C' - self._extents_on_press = self.extents - + if 'move' in self._state: + self._active_handle = 'C' # Set active handle as closest handle, if mouse click is close enough. - elif m_dist < self.maxdist * 2: - self.active_handle = 'C' - elif c_dist > self.maxdist and e_dist > self.maxdist: - self.active_handle = None - return + elif m_dist < self.grab_range * 2: + # Prioritise center handle over other handles + self._active_handle = 'C' + elif c_dist > self.grab_range and e_dist > self.grab_range: + # Not close to any handles + if self.drag_from_anywhere and self._contains(event): + # Check if we've clicked inside the region + self._active_handle = 'C' + else: + self._active_handle = None + return elif c_dist < e_dist: - self.active_handle = self._corner_order[c_idx] + # Closest to a corner handle + self._active_handle = self._corner_order[c_idx] else: - self.active_handle = self._edge_order[e_idx] + # Closest to an edge handle + self._active_handle = self._edge_order[e_idx] - # Save coordinates of rectangle at the start of handle movement. - x1, x2, y1, y2 = self.extents - # Switch variables so that only x2 and/or y2 are updated on move. - if self.active_handle in ['W', 'SW', 'NW']: - x1, x2 = x2, event.xdata - if self.active_handle in ['N', 'NW', 'NE']: - y1, y2 = y2, event.ydata - self._extents_on_press = x1, x2, y1, y2 + def _contains(self, event): + """Return True if event is within the patch.""" + return self._selection_artist.contains(event, radius=0)[0] @property def geometry(self): """ Return an array of shape (2, 5) containing the x (``RectangleSelector.geometry[1, :]``) and - y (``RectangleSelector.geometry[0, :]``) coordinates - of the four corners of the rectangle starting and ending - in the top left corner. + y (``RectangleSelector.geometry[0, :]``) data coordinates of the four + corners of the rectangle starting and ending in the top left corner. """ - if hasattr(self.to_draw, 'get_verts'): + if hasattr(self._selection_artist, 'get_verts'): xfm = self.ax.transData.inverted() - y, x = xfm.transform(self.to_draw.get_verts()).T + y, x = xfm.transform(self._selection_artist.get_verts()).T return np.array([x, y]) else: - return np.array(self.to_draw.get_data()) + return np.array(self._selection_artist.get_data()) +@_docstring.Substitution(_RECTANGLESELECTOR_PARAMETERS_DOCSTRING.replace( + '__ARTIST_NAME__', 'ellipse')) class EllipseSelector(RectangleSelector): """ - Select an elliptical region of an axes. + Select an elliptical region of an Axes. For the cursor to remain responsive you must keep a reference to it. - Example usage:: + Press and release events triggered at the same coordinates outside the + selection will clear the selector, except when + ``ignore_event_outside=True``. - import numpy as np - import matplotlib.pyplot as plt - from matplotlib.widgets import EllipseSelector - - def onselect(eclick, erelease): - "eclick and erelease are matplotlib events at press and release." - print('startposition: (%f, %f)' % (eclick.xdata, eclick.ydata)) - print('endposition : (%f, %f)' % (erelease.xdata, erelease.ydata)) - print('used button : ', eclick.button) - - def toggle_selector(event): - print(' Key pressed.') - if event.key in ['Q', 'q'] and toggle_selector.ES.active: - print('EllipseSelector deactivated.') - toggle_selector.RS.set_active(False) - if event.key in ['A', 'a'] and not toggle_selector.ES.active: - print('EllipseSelector activated.') - toggle_selector.ES.set_active(True) - - x = np.arange(100.) / 99 - y = np.sin(x) - fig, ax = plt.subplots() - ax.plot(x, y) + %s - toggle_selector.ES = EllipseSelector(ax, onselect, drawtype='line') - fig.canvas.mpl_connect('key_press_event', toggle_selector) - plt.show() + Examples + -------- + :doc:`/gallery/widgets/rectangle_selector` """ - _shape_klass = Ellipse + def _init_shape(self, **props): + return Ellipse((0, 0), 0, 1, visible=False, **props) - def draw_shape(self, extents): - x1, x2, y1, y2 = extents - xmin, xmax = sorted([x1, x2]) - ymin, ymax = sorted([y1, y2]) - center = [x1 + (x2 - x1) / 2., y1 + (y2 - y1) / 2.] + def _draw_shape(self, extents): + x0, x1, y0, y1 = extents + xmin, xmax = sorted([x0, x1]) + ymin, ymax = sorted([y0, y1]) + center = [x0 + (x1 - x0) / 2., y0 + (y1 - y0) / 2.] a = (xmax - xmin) / 2. b = (ymax - ymin) / 2. - if self.drawtype == 'box': - self.to_draw.center = center - self.to_draw.width = 2 * a - self.to_draw.height = 2 * b - else: - rad = np.deg2rad(np.arange(31) * 12) - x = a * np.cos(rad) + center[0] - y = b * np.sin(rad) + center[1] - self.to_draw.set_data(x, y) + self._selection_artist.center = center + self._selection_artist.width = 2 * a + self._selection_artist.height = 2 * b + self._selection_artist.angle = self.rotation @property def _rect_bbox(self): - if self.drawtype == 'box': - x, y = self.to_draw.center - width = self.to_draw.width - height = self.to_draw.height - return x - width / 2., y - height / 2., width, height - else: - x, y = self.to_draw.get_data() - x0, x1 = min(x), max(x) - y0, y1 = min(y), max(y) - return x0, y0, x1 - x0, y1 - y0 + x, y = self._selection_artist.center + width = self._selection_artist.width + height = self._selection_artist.height + return x - width / 2., y - height / 2., width, height class LassoSelector(_SelectorWidget): @@ -2379,11 +3660,11 @@ class LassoSelector(_SelectorWidget): In contrast to `Lasso`, `LassoSelector` is written with an interface similar to `RectangleSelector` and `SpanSelector`, and will continue to - interact with the axes until disconnected. + interact with the Axes until disconnected. Example usage:: - ax = subplot(111) + ax = plt.subplot() ax.plot(x, y) def onselect(verts): @@ -2393,94 +3674,136 @@ def onselect(verts): Parameters ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - onselect : function + The parent Axes for the widget. + onselect : function, optional Whenever the lasso is released, the *onselect* function is called and passed the vertices of the selected path. + useblit : bool, default: True + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. + props : dict, optional + Properties with which the line is drawn, see `.Line2D` + for valid properties. Default values are defined in ``mpl.rcParams``. button : `.MouseButton` or list of `.MouseButton`, optional The mouse buttons used for rectangle selection. Default is ``None``, which corresponds to all buttons. """ - def __init__(self, ax, onselect=None, useblit=True, lineprops=None, - button=None): + def __init__(self, ax, onselect=None, *, useblit=True, props=None, button=None): super().__init__(ax, onselect, useblit=useblit, button=button) self.verts = None - if lineprops is None: - lineprops = dict() - # self.useblit may be != useblit, if the canvas doesn't support blit. - lineprops.update(animated=self.useblit, visible=False) - self.line = Line2D([], [], **lineprops) - self.ax.add_line(self.line) - self.artists = [self.line] - - def onpress(self, event): - self.press(event) + props = { + **(props if props is not None else {}), + # Note that self.useblit may be != useblit, if the canvas doesn't + # support blitting. + 'animated': self.useblit, 'visible': False, + } + line = Line2D([], [], **props) + self.ax.add_line(line) + self._selection_artist = line def _press(self, event): self.verts = [self._get_data(event)] - self.line.set_visible(True) - - def onrelease(self, event): - self.release(event) + self._selection_artist.set_visible(True) def _release(self, event): if self.verts is not None: self.verts.append(self._get_data(event)) self.onselect(self.verts) - self.line.set_data([[], []]) - self.line.set_visible(False) + self._selection_artist.set_data([[], []]) + self._selection_artist.set_visible(False) self.verts = None def _onmove(self, event): if self.verts is None: return self.verts.append(self._get_data(event)) - - self.line.set_data(list(zip(*self.verts))) + self._selection_artist.set_data(list(zip(*self.verts))) self.update() class PolygonSelector(_SelectorWidget): """ - Select a polygon region of an axes. + Select a polygon region of an Axes. Place vertices with each mouse click, and make the selection by completing - the polygon (clicking on the first vertex). Hold the *ctrl* key and click - and drag a vertex to reposition it (the *ctrl* key is not necessary if the - polygon has already been completed). Hold the *shift* key and click and - drag anywhere in the axes to move all vertices. Press the *esc* key to - start a new polygon. + the polygon (clicking on the first vertex). Once drawn individual vertices + can be moved by clicking and dragging with the left mouse button, or + removed by clicking the right mouse button. + + In addition, the following modifier keys can be used: + + - Hold *ctrl* and click and drag a vertex to reposition it before the + polygon has been completed. + - Hold the *shift* key and click and drag anywhere in the Axes to move + all vertices. + - Press the *esc* key to start a new polygon. For the selector to remain responsive you must keep a reference to it. Parameters ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. - onselect : function + The parent Axes for the widget. + + onselect : function, optional When a polygon is completed or modified after completion, the *onselect* function is called and passed a list of the vertices as ``(xdata, ydata)`` tuples. + useblit : bool, default: False - lineprops : dict, default: \ -``dict(color='k', linestyle='-', linewidth=2, alpha=0.5)``. - Artist properties for the line representing the edges of the polygon. - markerprops : dict, default: \ -``dict(marker='o', markersize=7, mec='k', mfc='k', alpha=0.5)``. + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. + + props : dict, optional + Properties with which the line is drawn, see `.Line2D` for valid properties. + Default:: + + dict(color='k', linestyle='-', linewidth=2, alpha=0.5) + + handle_props : dict, optional Artist properties for the markers drawn at the vertices of the polygon. - vertex_select_radius : float, default: 15px + See the marker arguments in `.Line2D` for valid + properties. Default values are defined in ``mpl.rcParams`` except for + the default value of ``markeredgecolor`` which will be the same as the + ``color`` property in *props*. + + grab_range : float, default: 10 A vertex is selected (to complete the polygon or to move a vertex) if - the mouse click is within *vertex_select_radius* pixels of the vertex. + the mouse click is within *grab_range* pixels of the vertex. + + draw_bounding_box : bool, optional + If `True`, a bounding box will be drawn around the polygon selector + once it is complete. This box can be used to move and resize the + selector. + + box_handle_props : dict, optional + Properties to set for the box handles. See the documentation for the + *handle_props* argument to `RectangleSelector` for more info. + + box_props : dict, optional + Properties to set for the box. See the documentation for the *props* + argument to `RectangleSelector` for more info. Examples -------- + :doc:`/gallery/widgets/polygon_selector_simple` :doc:`/gallery/widgets/polygon_selector_demo` + + Notes + ----- + If only one point remains after removing points, the selector reverts to an + incomplete state and you can start drawing a new polygon from the existing + point. """ - def __init__(self, ax, onselect, useblit=False, - lineprops=None, markerprops=None, vertex_select_radius=15): + def __init__(self, ax, onselect=None, *, useblit=False, + props=None, handle_props=None, grab_range=10, + draw_bounding_box=False, box_handle_props=None, + box_props=None): # The state modifiers 'move', 'square', and 'center' are expected by # _SelectorWidget but are not supported by PolygonSelector # Note: could not use the existing 'move' state modifier in-place of @@ -2489,161 +3812,288 @@ def __init__(self, ax, onselect, useblit=False, state_modifier_keys = dict(clear='escape', move_vertex='control', move_all='shift', move='not-applicable', square='not-applicable', - center='not-applicable') + center='not-applicable', + rotate='not-applicable') super().__init__(ax, onselect, useblit=useblit, state_modifier_keys=state_modifier_keys) - self._xs, self._ys = [0], [0] - self._polygon_completed = False + self._xys = [(0, 0)] - if lineprops is None: - lineprops = dict(color='k', linestyle='-', linewidth=2, alpha=0.5) - lineprops['animated'] = self.useblit - self.line = Line2D(self._xs, self._ys, **lineprops) - self.ax.add_line(self.line) + if props is None: + props = dict(color='k', linestyle='-', linewidth=2, alpha=0.5) + props = {**props, 'animated': self.useblit} + self._selection_artist = line = Line2D([], [], **props) + self.ax.add_line(line) - if markerprops is None: - markerprops = dict(mec='k', mfc=lineprops.get('color', 'k')) - self._polygon_handles = ToolHandles(self.ax, self._xs, self._ys, + if handle_props is None: + handle_props = dict(markeredgecolor='k', + markerfacecolor=props.get('color', 'k')) + self._handle_props = handle_props + self._polygon_handles = ToolHandles(self.ax, [], [], useblit=self.useblit, - marker_props=markerprops) + marker_props=self._handle_props) self._active_handle_idx = -1 - self.vertex_select_radius = vertex_select_radius + self.grab_range = grab_range - self.artists = [self.line, self._polygon_handles.artist] self.set_visible(True) + self._draw_box = draw_bounding_box + self._box = None + + if box_handle_props is None: + box_handle_props = {} + self._box_handle_props = self._handle_props.update(box_handle_props) + self._box_props = box_props + + def _get_bbox(self): + return self._selection_artist.get_bbox() + + def _add_box(self): + self._box = RectangleSelector(self.ax, + useblit=self.useblit, + grab_range=self.grab_range, + handle_props=self._box_handle_props, + props=self._box_props, + interactive=True) + self._box._state_modifier_keys.pop('rotate') + self._box.connect_event('motion_notify_event', self._scale_polygon) + self._update_box() + # Set state that prevents the RectangleSelector from being created + # by the user + self._box._allow_creation = False + self._box._selection_completed = True + self._draw_polygon() + + def _remove_box(self): + if self._box is not None: + self._box.set_visible(False) + self._box = None + + def _update_box(self): + # Update selection box extents to the extents of the polygon + if self._box is not None: + bbox = self._get_bbox() + self._box.extents = [bbox.x0, bbox.x1, bbox.y0, bbox.y1] + # Save a copy + self._old_box_extents = self._box.extents + + def _scale_polygon(self, event): + """ + Scale the polygon selector points when the bounding box is moved or + scaled. + + This is set as a callback on the bounding box RectangleSelector. + """ + if not self._selection_completed: + return + + if self._old_box_extents == self._box.extents: + return + + # Create transform from old box to new box + x1, y1, w1, h1 = self._box._rect_bbox + old_bbox = self._get_bbox() + t = (transforms.Affine2D() + .translate(-old_bbox.x0, -old_bbox.y0) + .scale(1 / old_bbox.width, 1 / old_bbox.height) + .scale(w1, h1) + .translate(x1, y1)) + + # Update polygon verts. Must be a list of tuples for consistency. + new_verts = [(x, y) for x, y in t.transform(np.array(self.verts))] + self._xys = [*new_verts, new_verts[0]] + self._draw_polygon() + self._old_box_extents = self._box.extents + + @property + def _handles_artists(self): + return self._polygon_handles.artists + + def _remove_vertex(self, i): + """Remove vertex with index i.""" + if (len(self._xys) > 2 and + self._selection_completed and + i in (0, len(self._xys) - 1)): + # If selecting the first or final vertex, remove both first and + # last vertex as they are the same for a closed polygon + self._xys.pop(0) + self._xys.pop(-1) + # Close the polygon again by appending the new first vertex to the + # end + self._xys.append(self._xys[0]) + else: + self._xys.pop(i) + if len(self._xys) <= 2: + # If only one point left, return to incomplete state to let user + # start drawing again + self._selection_completed = False + self._remove_box() def _press(self, event): - """Button press event handler""" + """Button press event handler.""" # Check for selection of a tool handle. - if ((self._polygon_completed or 'move_vertex' in self.state) - and len(self._xs) > 0): + if ((self._selection_completed or 'move_vertex' in self._state) + and len(self._xys) > 0): h_idx, h_dist = self._polygon_handles.closest(event.x, event.y) - if h_dist < self.vertex_select_radius: + if h_dist < self.grab_range: self._active_handle_idx = h_idx # Save the vertex positions at the time of the press event (needed to # support the 'move_all' state modifier). - self._xs_at_press, self._ys_at_press = self._xs.copy(), self._ys.copy() + self._xys_at_press = self._xys.copy() def _release(self, event): - """Button release event handler""" + """Button release event handler.""" # Release active tool handle. if self._active_handle_idx >= 0: + if event.button == 3: + self._remove_vertex(self._active_handle_idx) + self._draw_polygon() self._active_handle_idx = -1 # Complete the polygon. - elif (len(self._xs) > 3 - and self._xs[-1] == self._xs[0] - and self._ys[-1] == self._ys[0]): - self._polygon_completed = True + elif len(self._xys) > 3 and self._xys[-1] == self._xys[0]: + self._selection_completed = True + if self._draw_box and self._box is None: + self._add_box() # Place new vertex. - elif (not self._polygon_completed - and 'move_all' not in self.state - and 'move_vertex' not in self.state): - self._xs.insert(-1, event.xdata) - self._ys.insert(-1, event.ydata) + elif (not self._selection_completed + and 'move_all' not in self._state + and 'move_vertex' not in self._state): + self._xys.insert(-1, self._get_data_coords(event)) - if self._polygon_completed: + if self._selection_completed: self.onselect(self.verts) def onmove(self, event): - """Cursor move event handler and validator""" + """Cursor move event handler and validator.""" # Method overrides _SelectorWidget.onmove because the polygon selector # needs to process the move callback even if there is no button press. # _SelectorWidget.onmove include logic to ignore move event if - # eventpress is None. - if not self.ignore(event): + # _eventpress is None. + if self.ignore(event): + # Hide the cursor when interactive zoom/pan is active + if not self.canvas.widgetlock.available(self) and self._xys: + self._xys[-1] = (np.nan, np.nan) + self._draw_polygon() + return False + + else: event = self._clean_event(event) self._onmove(event) return True - return False def _onmove(self, event): - """Cursor move event handler""" + """Cursor move event handler.""" # Move the active vertex (ToolHandle). if self._active_handle_idx >= 0: idx = self._active_handle_idx - self._xs[idx], self._ys[idx] = event.xdata, event.ydata + self._xys[idx] = self._get_data_coords(event) # Also update the end of the polygon line if the first vertex is # the active handle and the polygon is completed. - if idx == 0 and self._polygon_completed: - self._xs[-1], self._ys[-1] = event.xdata, event.ydata + if idx == 0 and self._selection_completed: + self._xys[-1] = self._get_data_coords(event) # Move all vertices. - elif 'move_all' in self.state and self.eventpress: - dx = event.xdata - self.eventpress.xdata - dy = event.ydata - self.eventpress.ydata - for k in range(len(self._xs)): - self._xs[k] = self._xs_at_press[k] + dx - self._ys[k] = self._ys_at_press[k] + dy + elif 'move_all' in self._state and self._eventpress: + xdata, ydata = self._get_data_coords(event) + dx = xdata - self._eventpress.xdata + dy = ydata - self._eventpress.ydata + for k in range(len(self._xys)): + x_at_press, y_at_press = self._xys_at_press[k] + self._xys[k] = x_at_press + dx, y_at_press + dy # Do nothing if completed or waiting for a move. - elif (self._polygon_completed - or 'move_vertex' in self.state or 'move_all' in self.state): + elif (self._selection_completed + or 'move_vertex' in self._state or 'move_all' in self._state): return # Position pending vertex. else: # Calculate distance to the start vertex. - x0, y0 = self.line.get_transform().transform((self._xs[0], - self._ys[0])) + x0, y0 = \ + self._selection_artist.get_transform().transform(self._xys[0]) v0_dist = np.hypot(x0 - event.x, y0 - event.y) # Lock on to the start vertex if near it and ready to complete. - if len(self._xs) > 3 and v0_dist < self.vertex_select_radius: - self._xs[-1], self._ys[-1] = self._xs[0], self._ys[0] + if len(self._xys) > 3 and v0_dist < self.grab_range: + self._xys[-1] = self._xys[0] else: - self._xs[-1], self._ys[-1] = event.xdata, event.ydata + self._xys[-1] = self._get_data_coords(event) self._draw_polygon() def _on_key_press(self, event): - """Key press event handler""" + """Key press event handler.""" # Remove the pending vertex if entering the 'move_vertex' or # 'move_all' mode - if (not self._polygon_completed - and ('move_vertex' in self.state or 'move_all' in self.state)): - self._xs, self._ys = self._xs[:-1], self._ys[:-1] + if (not self._selection_completed + and ('move_vertex' in self._state or + 'move_all' in self._state)): + self._xys.pop() self._draw_polygon() def _on_key_release(self, event): - """Key release event handler""" + """Key release event handler.""" # Add back the pending vertex if leaving the 'move_vertex' or # 'move_all' mode (by checking the released key) - if (not self._polygon_completed + if (not self._selection_completed and - (event.key == self.state_modifier_keys.get('move_vertex') - or event.key == self.state_modifier_keys.get('move_all'))): - self._xs.append(event.xdata) - self._ys.append(event.ydata) + (event.key == self._state_modifier_keys.get('move_vertex') + or event.key == self._state_modifier_keys.get('move_all'))): + self._xys.append(self._get_data_coords(event)) self._draw_polygon() # Reset the polygon if the released key is the 'clear' key. - elif event.key == self.state_modifier_keys.get('clear'): + elif event.key == self._state_modifier_keys.get('clear'): event = self._clean_event(event) - self._xs, self._ys = [event.xdata], [event.ydata] - self._polygon_completed = False + self._xys = [self._get_data_coords(event)] + self._selection_completed = False + self._remove_box() self.set_visible(True) - def _draw_polygon(self): - """Redraw the polygon based on the new vertex positions.""" - self.line.set_data(self._xs, self._ys) + def _draw_polygon_without_update(self): + """Redraw the polygon based on new vertex positions, no update().""" + xs, ys = zip(*self._xys) if self._xys else ([], []) + self._selection_artist.set_data(xs, ys) + self._update_box() # Only show one tool handle at the start and end vertex of the polygon # if the polygon is completed or the user is locked on to the start # vertex. - if (self._polygon_completed - or (len(self._xs) > 3 - and self._xs[-1] == self._xs[0] - and self._ys[-1] == self._ys[0])): - self._polygon_handles.set_data(self._xs[:-1], self._ys[:-1]) + if (self._selection_completed + or (len(self._xys) > 3 + and self._xys[-1] == self._xys[0])): + self._polygon_handles.set_data(xs[:-1], ys[:-1]) else: - self._polygon_handles.set_data(self._xs, self._ys) + self._polygon_handles.set_data(xs, ys) + + def _draw_polygon(self): + """Redraw the polygon based on the new vertex positions.""" + self._draw_polygon_without_update() self.update() @property def verts(self): """The polygon vertices, as a list of ``(x, y)`` pairs.""" - return list(zip(self._xs[:-1], self._ys[:-1])) + return self._xys[:-1] + + @verts.setter + def verts(self, xys): + """ + Set the polygon vertices. + + This will remove any preexisting vertices, creating a complete polygon + with the new vertices. + """ + self._xys = [*xys, xys[0]] + self._selection_completed = True + self.set_visible(True) + if self._draw_box and self._box is None: + self._add_box() + self._draw_polygon() + + def _clear_without_update(self): + self._selection_completed = False + self._xys = [(0, 0)] + self._draw_polygon_without_update() class Lasso(AxesWidget): @@ -2659,24 +4109,39 @@ class Lasso(AxesWidget): Parameters ---------- ax : `~matplotlib.axes.Axes` - The parent axes for the widget. + The parent Axes for the widget. xy : (float, float) Coordinates of the start of the lasso. callback : callable Whenever the lasso is released, the *callback* function is called and passed the vertices of the selected path. - """ + useblit : bool, default: True + Whether to use blitting for faster drawing (if supported by the + backend). See the tutorial :ref:`blitting` + for details. + props: dict, optional + Lasso line properties. See `.Line2D` for valid properties. + Default *props* are:: - def __init__(self, ax, xy, callback=None, useblit=True): + {'linestyle' : '-', 'color' : 'black', 'lw' : 2} + + .. versionadded:: 3.9 + """ + def __init__(self, ax, xy, callback, *, useblit=True, props=None): super().__init__(ax) self.useblit = useblit and self.canvas.supports_blit if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) + style = {'linestyle': '-', 'color': 'black', 'lw': 2} + + if props is not None: + style.update(props) + x, y = xy self.verts = [(x, y)] - self.line = Line2D([x], [y], linestyle='-', color='black', lw=2) + self.line = Line2D([x], [y], **style) self.ax.add_line(self.line) self.callback = callback self.connect_event('button_release_event', self.onrelease) @@ -2686,24 +4151,20 @@ def onrelease(self, event): if self.ignore(event): return if self.verts is not None: - self.verts.append((event.xdata, event.ydata)) + self.verts.append(self._get_data_coords(event)) if len(self.verts) > 2: self.callback(self.verts) - self.ax.lines.remove(self.line) + self.line.remove() self.verts = None self.disconnect_events() def onmove(self, event): - if self.ignore(event): - return - if self.verts is None: - return - if event.inaxes != self.ax: + if (self.ignore(event) + or self.verts is None + or event.button != 1 + or not self.ax.contains(event)[0]): return - if event.button != 1: - return - self.verts.append((event.xdata, event.ydata)) - + self.verts.append(self._get_data_coords(event)) self.line.set_data(list(zip(*self.verts))) if self.useblit: diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi new file mode 100644 index 000000000000..0fcd1990e17e --- /dev/null +++ b/lib/matplotlib/widgets.pyi @@ -0,0 +1,488 @@ +from .artist import Artist +from .axes import Axes +from .backend_bases import FigureCanvasBase, Event, MouseEvent, MouseButton +from .collections import LineCollection +from .figure import Figure +from .lines import Line2D +from .patches import Polygon, Rectangle +from .text import Text + +import PIL.Image + +from collections.abc import Callable, Collection, Iterable, Sequence +from typing import Any, Literal +from numpy.typing import ArrayLike +from .typing import ColorType +import numpy as np + +class LockDraw: + def __init__(self) -> None: ... + def __call__(self, o: Any) -> None: ... + def release(self, o: Any) -> None: ... + def available(self, o: Any) -> bool: ... + def isowner(self, o: Any) -> bool: ... + def locked(self) -> bool: ... + +class Widget: + drawon: bool + eventson: bool + active: bool + def set_active(self, active: bool) -> None: ... + def get_active(self) -> None: ... + def ignore(self, event) -> bool: ... + +class AxesWidget(Widget): + ax: Axes + def __init__(self, ax: Axes) -> None: ... + @property + def canvas(self) -> FigureCanvasBase | None: ... + def connect_event(self, event: Event, callback: Callable) -> None: ... + def disconnect_events(self) -> None: ... + +class Button(AxesWidget): + label: Text + color: ColorType + hovercolor: ColorType + def __init__( + self, + ax: Axes, + label: str, + image: ArrayLike | PIL.Image.Image | None = ..., + color: ColorType = ..., + hovercolor: ColorType = ..., + *, + useblit: bool = ... + ) -> None: ... + def on_clicked(self, func: Callable[[Event], Any]) -> int: ... + def disconnect(self, cid: int) -> None: ... + +class SliderBase(AxesWidget): + orientation: Literal["horizontal", "vertical"] + closedmin: bool + closedmax: bool + valmin: float + valmax: float + valstep: float | ArrayLike | None + drag_active: bool + valfmt: str + def __init__( + self, + ax: Axes, + orientation: Literal["horizontal", "vertical"], + closedmin: bool, + closedmax: bool, + valmin: float, + valmax: float, + valfmt: str, + dragging: Slider | None, + valstep: float | ArrayLike | None, + ) -> None: ... + def disconnect(self, cid: int) -> None: ... + def reset(self) -> None: ... + +class Slider(SliderBase): + slidermin: Slider | None + slidermax: Slider | None + val: float + valinit: float + track: Rectangle + poly: Polygon + hline: Line2D + vline: Line2D + label: Text + valtext: Text + def __init__( + self, + ax: Axes, + label: str, + valmin: float, + valmax: float, + *, + valinit: float = ..., + valfmt: str | None = ..., + closedmin: bool = ..., + closedmax: bool = ..., + slidermin: Slider | None = ..., + slidermax: Slider | None = ..., + dragging: bool = ..., + valstep: float | ArrayLike | None = ..., + orientation: Literal["horizontal", "vertical"] = ..., + initcolor: ColorType = ..., + track_color: ColorType = ..., + handle_style: dict[str, Any] | None = ..., + **kwargs + ) -> None: ... + def set_val(self, val: float) -> None: ... + def on_changed(self, func: Callable[[float], Any]) -> int: ... + +class RangeSlider(SliderBase): + val: tuple[float, float] + valinit: tuple[float, float] + track: Rectangle + poly: Polygon + label: Text + valtext: Text + def __init__( + self, + ax: Axes, + label: str, + valmin: float, + valmax: float, + *, + valinit: tuple[float, float] | None = ..., + valfmt: str | None = ..., + closedmin: bool = ..., + closedmax: bool = ..., + dragging: bool = ..., + valstep: float | ArrayLike | None = ..., + orientation: Literal["horizontal", "vertical"] = ..., + track_color: ColorType = ..., + handle_style: dict[str, Any] | None = ..., + **kwargs + ) -> None: ... + def set_min(self, min: float) -> None: ... + def set_max(self, max: float) -> None: ... + def set_val(self, val: ArrayLike) -> None: ... + def on_changed(self, func: Callable[[tuple[float, float]], Any]) -> int: ... + +class CheckButtons(AxesWidget): + labels: list[Text] + def __init__( + self, + ax: Axes, + labels: Sequence[str], + actives: Iterable[bool] | None = ..., + *, + useblit: bool = ..., + label_props: dict[str, Any] | None = ..., + frame_props: dict[str, Any] | None = ..., + check_props: dict[str, Any] | None = ..., + ) -> None: ... + def set_label_props(self, props: dict[str, Any]) -> None: ... + def set_frame_props(self, props: dict[str, Any]) -> None: ... + def set_check_props(self, props: dict[str, Any]) -> None: ... + def set_active(self, index: int, state: bool | None = ...) -> None: ... # type: ignore[override] + def clear(self) -> None: ... + def get_status(self) -> list[bool]: ... + def get_checked_labels(self) -> list[str]: ... + def on_clicked(self, func: Callable[[str | None], Any]) -> int: ... + def disconnect(self, cid: int) -> None: ... + +class TextBox(AxesWidget): + label: Text + text_disp: Text + cursor_index: int + cursor: LineCollection + color: ColorType + hovercolor: ColorType + capturekeystrokes: bool + def __init__( + self, + ax: Axes, + label: str, + initial: str = ..., + *, + color: ColorType = ..., + hovercolor: ColorType = ..., + label_pad: float = ..., + textalignment: Literal["left", "center", "right"] = ..., + ) -> None: ... + @property + def text(self) -> str: ... + def set_val(self, val: str) -> None: ... + def begin_typing(self) -> None: ... + def stop_typing(self) -> None: ... + def on_text_change(self, func: Callable[[str], Any]) -> int: ... + def on_submit(self, func: Callable[[str], Any]) -> int: ... + def disconnect(self, cid: int) -> None: ... + +class RadioButtons(AxesWidget): + activecolor: ColorType + value_selected: str + labels: list[Text] + def __init__( + self, + ax: Axes, + labels: Iterable[str], + active: int = ..., + activecolor: ColorType | None = ..., + *, + useblit: bool = ..., + label_props: dict[str, Any] | Sequence[dict[str, Any]] | None = ..., + radio_props: dict[str, Any] | None = ..., + ) -> None: ... + def set_label_props(self, props: dict[str, Any]) -> None: ... + def set_radio_props(self, props: dict[str, Any]) -> None: ... + def set_active(self, index: int) -> None: ... + def clear(self) -> None: ... + def on_clicked(self, func: Callable[[str | None], Any]) -> int: ... + def disconnect(self, cid: int) -> None: ... + +class SubplotTool(Widget): + figure: Figure + targetfig: Figure + buttonreset: Button + def __init__(self, targetfig: Figure, toolfig: Figure) -> None: ... + +class Cursor(AxesWidget): + visible: bool + horizOn: bool + vertOn: bool + useblit: bool + lineh: Line2D + linev: Line2D + background: Any + needclear: bool + def __init__( + self, + ax: Axes, + *, + horizOn: bool = ..., + vertOn: bool = ..., + useblit: bool = ..., + **lineprops + ) -> None: ... + def clear(self, event: Event) -> None: ... + def onmove(self, event: Event) -> None: ... + +class MultiCursor(Widget): + axes: Sequence[Axes] + horizOn: bool + vertOn: bool + visible: bool + useblit: bool + vlines: list[Line2D] + hlines: list[Line2D] + def __init__( + self, + canvas: Any, + axes: Sequence[Axes], + *, + useblit: bool = ..., + horizOn: bool = ..., + vertOn: bool = ..., + **lineprops + ) -> None: ... + def connect(self) -> None: ... + def disconnect(self) -> None: ... + def clear(self, event: Event) -> None: ... + def onmove(self, event: Event) -> None: ... + +class _SelectorWidget(AxesWidget): + onselect: Callable[[float, float], Any] + useblit: bool + background: Any + validButtons: list[MouseButton] + def __init__( + self, + ax: Axes, + onselect: Callable[[float, float], Any] | None = ..., + useblit: bool = ..., + button: MouseButton | Collection[MouseButton] | None = ..., + state_modifier_keys: dict[str, str] | None = ..., + use_data_coordinates: bool = ..., + ) -> None: ... + def update_background(self, event: Event) -> None: ... + def connect_default_events(self) -> None: ... + def ignore(self, event: Event) -> bool: ... + def update(self) -> None: ... + def press(self, event: Event) -> bool: ... + def release(self, event: Event) -> bool: ... + def onmove(self, event: Event) -> bool: ... + def on_scroll(self, event: Event) -> None: ... + def on_key_press(self, event: Event) -> None: ... + def on_key_release(self, event: Event) -> None: ... + def set_visible(self, visible: bool) -> None: ... + def get_visible(self) -> bool: ... + def clear(self) -> None: ... + @property + def artists(self) -> tuple[Artist]: ... + def set_props(self, **props) -> None: ... + def set_handle_props(self, **handle_props) -> None: ... + def add_state(self, state: str) -> None: ... + def remove_state(self, state: str) -> None: ... + +class SpanSelector(_SelectorWidget): + snap_values: ArrayLike | None + onmove_callback: Callable[[float, float], Any] + minspan: float + grab_range: float + drag_from_anywhere: bool + ignore_event_outside: bool + def __init__( + self, + ax: Axes, + onselect: Callable[[float, float], Any], + direction: Literal["horizontal", "vertical"], + *, + minspan: float = ..., + useblit: bool = ..., + props: dict[str, Any] | None = ..., + onmove_callback: Callable[[float, float], Any] | None = ..., + interactive: bool = ..., + button: MouseButton | Collection[MouseButton] | None = ..., + handle_props: dict[str, Any] | None = ..., + grab_range: float = ..., + state_modifier_keys: dict[str, str] | None = ..., + drag_from_anywhere: bool = ..., + ignore_event_outside: bool = ..., + snap_values: ArrayLike | None = ..., + ) -> None: ... + def new_axes( + self, + ax: Axes, + *, + _props: dict[str, Any] | None = ..., + _init: bool = ..., + ) -> None: ... + def connect_default_events(self) -> None: ... + @property + def direction(self) -> Literal["horizontal", "vertical"]: ... + @direction.setter + def direction(self, direction: Literal["horizontal", "vertical"]) -> None: ... + @property + def extents(self) -> tuple[float, float]: ... + @extents.setter + def extents(self, extents: tuple[float, float]) -> None: ... + +class ToolLineHandles: + ax: Axes + def __init__( + self, + ax: Axes, + positions: ArrayLike, + direction: Literal["horizontal", "vertical"], + *, + line_props: dict[str, Any] | None = ..., + useblit: bool = ..., + ) -> None: ... + @property + def artists(self) -> tuple[Line2D]: ... + @property + def positions(self) -> list[float]: ... + @property + def direction(self) -> Literal["horizontal", "vertical"]: ... + def set_data(self, positions: ArrayLike) -> None: ... + def set_visible(self, value: bool) -> None: ... + def set_animated(self, value: bool) -> None: ... + def remove(self) -> None: ... + def closest(self, x: float, y: float) -> tuple[int, float]: ... + +class ToolHandles: + ax: Axes + def __init__( + self, + ax: Axes, + x: ArrayLike, + y: ArrayLike, + *, + marker: str = ..., + marker_props: dict[str, Any] | None = ..., + useblit: bool = ..., + ) -> None: ... + @property + def x(self) -> ArrayLike: ... + @property + def y(self) -> ArrayLike: ... + @property + def artists(self) -> tuple[Line2D]: ... + def set_data(self, pts: ArrayLike, y: ArrayLike | None = ...) -> None: ... + def set_visible(self, val: bool) -> None: ... + def set_animated(self, val: bool) -> None: ... + def closest(self, x: float, y: float) -> tuple[int, float]: ... + +class RectangleSelector(_SelectorWidget): + drag_from_anywhere: bool + ignore_event_outside: bool + minspanx: float + minspany: float + spancoords: Literal["data", "pixels"] + grab_range: float + def __init__( + self, + ax: Axes, + onselect: Callable[[MouseEvent, MouseEvent], Any] | None = ..., + *, + minspanx: float = ..., + minspany: float = ..., + useblit: bool = ..., + props: dict[str, Any] | None = ..., + spancoords: Literal["data", "pixels"] = ..., + button: MouseButton | Collection[MouseButton] | None = ..., + grab_range: float = ..., + handle_props: dict[str, Any] | None = ..., + interactive: bool = ..., + state_modifier_keys: dict[str, str] | None = ..., + drag_from_anywhere: bool = ..., + ignore_event_outside: bool = ..., + use_data_coordinates: bool = ..., + ) -> None: ... + @property + def corners(self) -> tuple[np.ndarray, np.ndarray]: ... + @property + def edge_centers(self) -> tuple[np.ndarray, np.ndarray]: ... + @property + def center(self) -> tuple[float, float]: ... + @property + def extents(self) -> tuple[float, float, float, float]: ... + @extents.setter + def extents(self, extents: tuple[float, float, float, float]) -> None: ... + @property + def rotation(self) -> float: ... + @rotation.setter + def rotation(self, value: float) -> None: ... + @property + def geometry(self) -> np.ndarray: ... + +class EllipseSelector(RectangleSelector): ... + +class LassoSelector(_SelectorWidget): + verts: None | list[tuple[float, float]] + def __init__( + self, + ax: Axes, + onselect: Callable[[list[tuple[float, float]]], Any] | None = ..., + *, + useblit: bool = ..., + props: dict[str, Any] | None = ..., + button: MouseButton | Collection[MouseButton] | None = ..., + ) -> None: ... + +class PolygonSelector(_SelectorWidget): + grab_range: float + def __init__( + self, + ax: Axes, + onselect: Callable[[ArrayLike, ArrayLike], Any] | None = ..., + *, + useblit: bool = ..., + props: dict[str, Any] | None = ..., + handle_props: dict[str, Any] | None = ..., + grab_range: float = ..., + draw_bounding_box: bool = ..., + box_handle_props: dict[str, Any] | None = ..., + box_props: dict[str, Any] | None = ... + ) -> None: ... + def onmove(self, event: Event) -> bool: ... + @property + def verts(self) -> list[tuple[float, float]]: ... + @verts.setter + def verts(self, xys: Sequence[tuple[float, float]]) -> None: ... + +class Lasso(AxesWidget): + useblit: bool + background: Any + verts: list[tuple[float, float]] | None + line: Line2D + callback: Callable[[list[tuple[float, float]]], Any] + def __init__( + self, + ax: Axes, + xy: tuple[float, float], + callback: Callable[[list[tuple[float, float]]], Any], + *, + useblit: bool = ..., + props: dict[str, Any] | None = ..., + ) -> None: ... + def onrelease(self, event: Event) -> None: ... + def onmove(self, event: Event) -> None: ... diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 000000000000..9701a7da98dd --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,8 @@ +python_sources = [ + 'pylab.py', +] + +py3.install_sources(python_sources) + +subdir('matplotlib') +subdir('mpl_toolkits') diff --git a/lib/mpl_toolkits/__init__.py b/lib/mpl_toolkits/__init__.py deleted file mode 100644 index 02de4115d7f8..000000000000 --- a/lib/mpl_toolkits/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - pass # must not have setuptools diff --git a/lib/mpl_toolkits/axes_grid/__init__.py b/lib/mpl_toolkits/axes_grid/__init__.py deleted file mode 100644 index 2d227d50010c..000000000000 --- a/lib/mpl_toolkits/axes_grid/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import axes_size as Size -from .axes_divider import Divider, SubplotDivider, make_axes_locatable -from .axes_grid import Grid, ImageGrid, AxesGrid -#from axes_divider import make_axes_locatable -from matplotlib.cbook import warn_deprecated -warn_deprecated(since='2.1', - name='mpl_toolkits.axes_grid', - alternative='mpl_toolkits.axes_grid1 and' - ' mpl_toolkits.axisartist, which provide' - ' the same functionality', - obj_type='module') diff --git a/lib/mpl_toolkits/axes_grid/anchored_artists.py b/lib/mpl_toolkits/axes_grid/anchored_artists.py deleted file mode 100644 index f486805d98c6..000000000000 --- a/lib/mpl_toolkits/axes_grid/anchored_artists.py +++ /dev/null @@ -1,6 +0,0 @@ -from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox, VPacker,\ - TextArea, AnchoredText, DrawingArea, AnnotationBbox - -from mpl_toolkits.axes_grid1.anchored_artists import \ - AnchoredDrawingArea, AnchoredAuxTransformBox, \ - AnchoredEllipse, AnchoredSizeBar diff --git a/lib/mpl_toolkits/axes_grid/angle_helper.py b/lib/mpl_toolkits/axes_grid/angle_helper.py deleted file mode 100644 index fa14e7f5897b..000000000000 --- a/lib/mpl_toolkits/axes_grid/angle_helper.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.angle_helper import * diff --git a/lib/mpl_toolkits/axes_grid/axes_divider.py b/lib/mpl_toolkits/axes_grid/axes_divider.py deleted file mode 100644 index 33b4e512c9a0..000000000000 --- a/lib/mpl_toolkits/axes_grid/axes_divider.py +++ /dev/null @@ -1,3 +0,0 @@ -from mpl_toolkits.axes_grid1.axes_divider import ( - AxesDivider, AxesLocator, Divider, SubplotDivider, make_axes_locatable) -from mpl_toolkits.axisartist.axislines import Axes diff --git a/lib/mpl_toolkits/axes_grid/axes_grid.py b/lib/mpl_toolkits/axes_grid/axes_grid.py deleted file mode 100644 index 893b7800ccde..000000000000 --- a/lib/mpl_toolkits/axes_grid/axes_grid.py +++ /dev/null @@ -1,2 +0,0 @@ -from mpl_toolkits.axisartist.axes_grid import ( - AxesGrid, CbarAxes, Grid, ImageGrid) diff --git a/lib/mpl_toolkits/axes_grid/axes_rgb.py b/lib/mpl_toolkits/axes_grid/axes_rgb.py deleted file mode 100644 index 30f7e42743f9..000000000000 --- a/lib/mpl_toolkits/axes_grid/axes_rgb.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.axes_rgb import * diff --git a/lib/mpl_toolkits/axes_grid/axes_size.py b/lib/mpl_toolkits/axes_grid/axes_size.py deleted file mode 100644 index 742f7fe6347c..000000000000 --- a/lib/mpl_toolkits/axes_grid/axes_size.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axes_grid1.axes_size import * diff --git a/lib/mpl_toolkits/axes_grid/axis_artist.py b/lib/mpl_toolkits/axes_grid/axis_artist.py deleted file mode 100644 index 11180b10bfef..000000000000 --- a/lib/mpl_toolkits/axes_grid/axis_artist.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.axis_artist import * diff --git a/lib/mpl_toolkits/axes_grid/axisline_style.py b/lib/mpl_toolkits/axes_grid/axisline_style.py deleted file mode 100644 index 0c846e22afa0..000000000000 --- a/lib/mpl_toolkits/axes_grid/axisline_style.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.axisline_style import * diff --git a/lib/mpl_toolkits/axes_grid/axislines.py b/lib/mpl_toolkits/axes_grid/axislines.py deleted file mode 100644 index a8ceb9cc28ad..000000000000 --- a/lib/mpl_toolkits/axes_grid/axislines.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.axislines import * diff --git a/lib/mpl_toolkits/axes_grid/clip_path.py b/lib/mpl_toolkits/axes_grid/clip_path.py deleted file mode 100644 index 5b92d9ae57f6..000000000000 --- a/lib/mpl_toolkits/axes_grid/clip_path.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.clip_path import * diff --git a/lib/mpl_toolkits/axes_grid/colorbar.py b/lib/mpl_toolkits/axes_grid/colorbar.py deleted file mode 100644 index cc5c252da896..000000000000 --- a/lib/mpl_toolkits/axes_grid/colorbar.py +++ /dev/null @@ -1,5 +0,0 @@ -from mpl_toolkits.axes_grid1.colorbar import ( - make_axes_kw_doc, colormap_kw_doc, colorbar_doc, - CbarAxesLocator, ColorbarBase, Colorbar, - make_axes, colorbar -) diff --git a/lib/mpl_toolkits/axes_grid/floating_axes.py b/lib/mpl_toolkits/axes_grid/floating_axes.py deleted file mode 100644 index de8ebb7367be..000000000000 --- a/lib/mpl_toolkits/axes_grid/floating_axes.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.floating_axes import * diff --git a/lib/mpl_toolkits/axes_grid/grid_finder.py b/lib/mpl_toolkits/axes_grid/grid_finder.py deleted file mode 100644 index 6cdec87a7f40..000000000000 --- a/lib/mpl_toolkits/axes_grid/grid_finder.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.grid_finder import * diff --git a/lib/mpl_toolkits/axes_grid/grid_helper_curvelinear.py b/lib/mpl_toolkits/axes_grid/grid_helper_curvelinear.py deleted file mode 100644 index ebb3edf139f5..000000000000 --- a/lib/mpl_toolkits/axes_grid/grid_helper_curvelinear.py +++ /dev/null @@ -1 +0,0 @@ -from mpl_toolkits.axisartist.grid_helper_curvelinear import * diff --git a/lib/mpl_toolkits/axes_grid/inset_locator.py b/lib/mpl_toolkits/axes_grid/inset_locator.py deleted file mode 100644 index 9d656e6edaf7..000000000000 --- a/lib/mpl_toolkits/axes_grid/inset_locator.py +++ /dev/null @@ -1,4 +0,0 @@ -from mpl_toolkits.axes_grid1.inset_locator import InsetPosition, \ - AnchoredSizeLocator, \ - AnchoredZoomLocator, BboxPatch, BboxConnector, BboxConnectorPatch, \ - inset_axes, zoomed_inset_axes, mark_inset diff --git a/lib/mpl_toolkits/axes_grid/parasite_axes.py b/lib/mpl_toolkits/axes_grid/parasite_axes.py deleted file mode 100644 index 9c212098467c..000000000000 --- a/lib/mpl_toolkits/axes_grid/parasite_axes.py +++ /dev/null @@ -1,10 +0,0 @@ -from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory, - parasite_axes_auxtrans_class_factory, subplot_class_factory) -from mpl_toolkits.axisartist.axislines import Axes - - -ParasiteAxes = parasite_axes_class_factory(Axes) -ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes) -HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) diff --git a/lib/mpl_toolkits/axes_grid1/__init__.py b/lib/mpl_toolkits/axes_grid1/__init__.py index 0f359c9e01f3..c55302485e3f 100644 --- a/lib/mpl_toolkits/axes_grid1/__init__.py +++ b/lib/mpl_toolkits/axes_grid1/__init__.py @@ -1,5 +1,10 @@ from . import axes_size as Size from .axes_divider import Divider, SubplotDivider, make_axes_locatable -from .axes_grid import Grid, ImageGrid, AxesGrid +from .axes_grid import AxesGrid, Grid, ImageGrid from .parasite_axes import host_subplot, host_axes + +__all__ = ["Size", + "Divider", "SubplotDivider", "make_axes_locatable", + "AxesGrid", "Grid", "ImageGrid", + "host_subplot", "host_axes"] diff --git a/lib/mpl_toolkits/axes_grid1/anchored_artists.py b/lib/mpl_toolkits/axes_grid1/anchored_artists.py index 9f200892580f..a8be06800a07 100644 --- a/lib/mpl_toolkits/axes_grid1/anchored_artists.py +++ b/lib/mpl_toolkits/axes_grid1/anchored_artists.py @@ -1,12 +1,12 @@ from matplotlib import transforms from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox, DrawingArea, TextArea, VPacker) -from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle, +from matplotlib.patches import (Rectangle, ArrowStyle, FancyArrowPatch, PathPatch) from matplotlib.text import TextPath __all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox', - 'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows'] + 'AnchoredSizeBar', 'AnchoredDirectionArrows'] class AnchoredDrawingArea(AnchoredOffsetbox): @@ -14,7 +14,7 @@ def __init__(self, width, height, xdescent, ydescent, loc, pad=0.4, borderpad=0.5, prop=None, frameon=True, **kwargs): """ - An anchored container with a fixed size and fillable DrawingArea. + An anchored container with a fixed size and fillable `.DrawingArea`. Artists added to the *drawing_area* will have their coordinates interpreted as pixels. Any transformations set on the artists will be @@ -23,50 +23,36 @@ def __init__(self, width, height, xdescent, ydescent, Parameters ---------- width, height : float - width and height of the container, in pixels. - + Width and height of the container, in pixels. xdescent, ydescent : float - descent of the container in the x- and y- direction, in pixels. - - loc : int - Location of this artist. Valid location codes are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - + Descent of the container in the x- and y- direction, in pixels. + loc : str + Location of this artist. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. pad : float, default: 0.4 Padding around the child objects, in fraction of the font size. - borderpad : float, default: 0.5 Border padding, in fraction of the font size. - - prop : `matplotlib.font_manager.FontProperties`, optional + prop : `~matplotlib.font_manager.FontProperties`, optional Font property used as a reference for paddings. - frameon : bool, default: True - If True, draw a box around this artists. - + If True, draw a box around this artist. **kwargs - Keyworded arguments to pass to - :class:`matplotlib.offsetbox.AnchoredOffsetbox`. + Keyword arguments forwarded to `.AnchoredOffsetbox`. Attributes ---------- - drawing_area : `matplotlib.offsetbox.DrawingArea` + drawing_area : `~matplotlib.offsetbox.DrawingArea` A container for artists to display. Examples -------- To display blue and red circles of different sizes in the upper right - of an axes *ax*: + of an Axes *ax*: >>> ada = AnchoredDrawingArea(20, 20, 0, 0, ... loc='upper right', frameon=False) @@ -95,43 +81,30 @@ def __init__(self, transform, loc, Parameters ---------- - transform : `matplotlib.transforms.Transform` + transform : `~matplotlib.transforms.Transform` The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - - loc : int - Location of this artist. Valid location codes are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - + :attr:`!matplotlib.axes.Axes.transData`. + loc : str + Location of this artist. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. pad : float, default: 0.4 Padding around the child objects, in fraction of the font size. - borderpad : float, default: 0.5 Border padding, in fraction of the font size. - - prop : `matplotlib.font_manager.FontProperties`, optional + prop : `~matplotlib.font_manager.FontProperties`, optional Font property used as a reference for paddings. - frameon : bool, default: True - If True, draw a box around this artists. - + If True, draw a box around this artist. **kwargs - Keyworded arguments to pass to - :class:`matplotlib.offsetbox.AnchoredOffsetbox`. + Keyword arguments forwarded to `.AnchoredOffsetbox`. Attributes ---------- - drawing_area : `matplotlib.offsetbox.AuxTransformBox` + drawing_area : `~matplotlib.offsetbox.AuxTransformBox` A container for artists to display. Examples @@ -151,69 +124,6 @@ def __init__(self, transform, loc, **kwargs) -class AnchoredEllipse(AnchoredOffsetbox): - def __init__(self, transform, width, height, angle, loc, - pad=0.1, borderpad=0.1, prop=None, frameon=True, **kwargs): - """ - Draw an anchored ellipse of a given size. - - Parameters - ---------- - transform : `matplotlib.transforms.Transform` - The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - - width, height : float - Width and height of the ellipse, given in coordinates of - *transform*. - - angle : float - Rotation of the ellipse, in degrees, anti-clockwise. - - loc : int - Location of this size bar. Valid location codes are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - - pad : float, optional - Padding around the ellipse, in fraction of the font size. Defaults - to 0.1. - - borderpad : float, default: 0.1 - Border padding, in fraction of the font size. - - frameon : bool, default: True - If True, draw a box around the ellipse. - - prop : `matplotlib.font_manager.FontProperties`, optional - Font property used as a reference for paddings. - - **kwargs - Keyworded arguments to pass to - :class:`matplotlib.offsetbox.AnchoredOffsetbox`. - - Attributes - ---------- - ellipse : `matplotlib.patches.Ellipse` - Ellipse patch drawn. - """ - self._box = AuxTransformBox(transform) - self.ellipse = Ellipse((0, 0), width, height, angle) - self._box.add_artist(self.ellipse) - - super().__init__(loc, pad=pad, borderpad=borderpad, child=self._box, - prop=prop, frameon=frameon, **kwargs) - - class AnchoredSizeBar(AnchoredOffsetbox): def __init__(self, transform, size, label, loc, pad=0.1, borderpad=0.1, sep=2, @@ -225,79 +135,58 @@ def __init__(self, transform, size, label, loc, Parameters ---------- - transform : `matplotlib.transforms.Transform` + transform : `~matplotlib.transforms.Transform` The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - + :attr:`!matplotlib.axes.Axes.transData`. size : float Horizontal length of the size bar, given in coordinates of *transform*. - label : str Label to display. - - loc : int - Location of this size bar. Valid location codes are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - + loc : str + Location of the size bar. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. pad : float, default: 0.1 Padding around the label and size bar, in fraction of the font size. - borderpad : float, default: 0.1 Border padding, in fraction of the font size. - sep : float, default: 2 Separation between the label and the size bar, in points. - frameon : bool, default: True If True, draw a box around the horizontal bar and label. - size_vertical : float, default: 0 Vertical length of the size bar, given in coordinates of *transform*. - color : str, default: 'black' Color for the size bar and label. - label_top : bool, default: False If True, the label will be over the size bar. - - fontproperties : `matplotlib.font_manager.FontProperties`, optional + fontproperties : `~matplotlib.font_manager.FontProperties`, optional Font properties for the label text. - fill_bar : bool, optional - If True and if size_vertical is nonzero, the size bar will + If True and if *size_vertical* is nonzero, the size bar will be filled in with the color specified by the size bar. - Defaults to True if `size_vertical` is greater than + Defaults to True if *size_vertical* is greater than zero and False otherwise. - **kwargs - Keyworded arguments to pass to - :class:`matplotlib.offsetbox.AnchoredOffsetbox`. + Keyword arguments forwarded to `.AnchoredOffsetbox`. Attributes ---------- - size_bar : `matplotlib.offsetbox.AuxTransformBox` + size_bar : `~matplotlib.offsetbox.AuxTransformBox` Container for the size bar. - - txt_label : `matplotlib.offsetbox.TextArea` + txt_label : `~matplotlib.offsetbox.TextArea` Container for the label of the size bar. Notes ----- - If *prop* is passed as a keyworded argument, but *fontproperties* is - not, then *prop* is be assumed to be the intended *fontproperties*. + If *prop* is passed as a keyword argument, but *fontproperties* is + not, then *prop* is assumed to be the intended *fontproperties*. Using both *prop* and *fontproperties* is not supported. Examples @@ -337,10 +226,7 @@ def __init__(self, transform, size, label, loc, else: textprops = {'color': color, 'fontproperties': fontproperties} - self.txt_label = TextArea( - label, - minimumdescent=False, - textprops=textprops) + self.txt_label = TextArea(label, textprops=textprops) if label_top: _box_children = [self.txt_label, self.size_bar] @@ -357,8 +243,8 @@ def __init__(self, transform, size, label, loc, class AnchoredDirectionArrows(AnchoredOffsetbox): def __init__(self, transform, label_x, label_y, length=0.15, - fontsize=0.08, loc=2, angle=0, aspect_ratio=1, pad=0.4, - borderpad=0.4, frameon=False, color='w', alpha=1, + fontsize=0.08, loc='upper left', angle=0, aspect_ratio=1, + pad=0.4, borderpad=0.4, frameon=False, color='w', alpha=1, sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15, head_width=10, head_length=15, tail_width=2, text_props=None, arrow_props=None, @@ -368,100 +254,71 @@ def __init__(self, transform, label_x, label_y, length=0.15, Parameters ---------- - transform : `matplotlib.transforms.Transform` + transform : `~matplotlib.transforms.Transform` The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transAxes`. - + :attr:`!matplotlib.axes.Axes.transAxes`. label_x, label_y : str Label text for the x and y arrows - length : float, default: 0.15 Length of the arrow, given in coordinates of *transform*. - fontsize : float, default: 0.08 Size of label strings, given in coordinates of *transform*. - - loc : int, default: 2 - Location of the direction arrows. Valid location codes are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - + loc : str, default: 'upper left' + Location of the arrow. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. angle : float, default: 0 The angle of the arrows in degrees. - aspect_ratio : float, default: 1 The ratio of the length of arrow_x and arrow_y. Negative numbers can be used to change the direction. - pad : float, default: 0.4 Padding around the labels and arrows, in fraction of the font size. - borderpad : float, default: 0.4 Border padding, in fraction of the font size. - frameon : bool, default: False If True, draw a box around the arrows and labels. - color : str, default: 'white' Color for the arrows and labels. - alpha : float, default: 1 Alpha values of the arrows and labels - sep_x, sep_y : float, default: 0.01 and 0 respectively Separation between the arrows and labels in coordinates of *transform*. - - fontproperties : `matplotlib.font_manager.FontProperties`, optional + fontproperties : `~matplotlib.font_manager.FontProperties`, optional Font properties for the label text. - back_length : float, default: 0.15 Fraction of the arrow behind the arrow crossing. - head_width : float, default: 10 - Width of arrow head, sent to ArrowStyle. - + Width of arrow head, sent to `.ArrowStyle`. head_length : float, default: 15 - Length of arrow head, sent to ArrowStyle. - + Length of arrow head, sent to `.ArrowStyle`. tail_width : float, default: 2 - Width of arrow tail, sent to ArrowStyle. - + Width of arrow tail, sent to `.ArrowStyle`. text_props, arrow_props : dict - Properties of the text and arrows, passed to - `.textpath.TextPath` and `.patches.FancyArrowPatch`. - + Properties of the text and arrows, passed to `.TextPath` and + `.FancyArrowPatch`. **kwargs - Keyworded arguments to pass to - :class:`matplotlib.offsetbox.AnchoredOffsetbox`. + Keyword arguments forwarded to `.AnchoredOffsetbox`. Attributes ---------- - arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch` + arrow_x, arrow_y : `~matplotlib.patches.FancyArrowPatch` Arrow x and y - - text_path_x, text_path_y : `matplotlib.textpath.TextPath` + text_path_x, text_path_y : `~matplotlib.text.TextPath` Path for arrow labels - - p_x, p_y : `matplotlib.patches.PathPatch` + p_x, p_y : `~matplotlib.patches.PathPatch` Patch for arrow labels - - box : `matplotlib.offsetbox.AuxTransformBox` + box : `~matplotlib.offsetbox.AuxTransformBox` Container for the arrows and labels. Notes ----- If *prop* is passed as a keyword argument, but *fontproperties* is - not, then *prop* is be assumed to be the intended *fontproperties*. + not, then *prop* is assumed to be the intended *fontproperties*. Using both *prop* and *fontproperties* is not supported. Examples diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index 99e9a6301dbb..50365f482b72 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -2,11 +2,13 @@ Helper classes to adjust the positions of multiple axes at drawing time. """ +import functools + import numpy as np -from matplotlib import cbook -from matplotlib.axes import SubplotBase -from matplotlib.gridspec import SubplotSpec, GridSpec +import matplotlib as mpl +from matplotlib import _api +from matplotlib.gridspec import SubplotSpec import matplotlib.transforms as mtransforms from . import axes_size as Size @@ -35,10 +37,11 @@ def __init__(self, fig, pos, horizontal, vertical, Sizes for horizontal division. vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` Sizes for vertical division. - aspect : bool + aspect : bool, optional Whether overall rectangular area is reduced so that the relative part of the horizontal and vertical scales have the same scale. - anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'} + anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ +'NW', 'W'}, default: 'C' Placement of the reduced rectangle, when *aspect* is True. """ @@ -47,43 +50,17 @@ def __init__(self, fig, pos, horizontal, vertical, self._horizontal = horizontal self._vertical = vertical self._anchor = anchor + self.set_anchor(anchor) self._aspect = aspect self._xrefindex = 0 self._yrefindex = 0 self._locator = None def get_horizontal_sizes(self, renderer): - return [s.get_size(renderer) for s in self.get_horizontal()] + return np.array([s.get_size(renderer) for s in self.get_horizontal()]) def get_vertical_sizes(self, renderer): - return [s.get_size(renderer) for s in self.get_vertical()] - - def get_vsize_hsize(self): - vsize = Size.AddList(self.get_vertical()) - hsize = Size.AddList(self.get_horizontal()) - return vsize, hsize - - @staticmethod - def _calc_k(l, total_size): - - rs_sum, as_sum = 0., 0. - - for _rs, _as in l: - rs_sum += _rs - as_sum += _as - - if rs_sum != 0.: - k = (total_size - as_sum) / rs_sum - return k - else: - return 0. - - @staticmethod - def _calc_offsets(l, k): - offsets = [0.] - for _rs, _as in l: - offsets.append(offsets[-1] + _rs*k + _as) - return offsets + return np.array([s.get_size(renderer) for s in self.get_vertical()]) def set_position(self, pos): """ @@ -104,32 +81,29 @@ def set_anchor(self, anchor): """ Parameters ---------- - anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'} - anchor position - - ===== ============ - value description - ===== ============ - 'C' Center - 'SW' bottom left - 'S' bottom - 'SE' bottom right - 'E' right - 'NE' top right - 'N' top - 'NW' top left - 'W' left - ===== ============ - - """ - if len(anchor) != 2: - cbook._check_in_list(mtransforms.Bbox.coefs, anchor=anchor) + anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ +'NW', 'W'} + Either an (*x*, *y*) pair of relative coordinates (0 is left or + bottom, 1 is right or top), 'C' (center), or a cardinal direction + ('SW', southwest, is bottom left, etc.). + + See Also + -------- + .Axes.set_anchor + """ + if isinstance(anchor, str): + _api.check_in_list(mtransforms.Bbox.coefs, anchor=anchor) + elif not isinstance(anchor, (tuple, list)) or len(anchor) != 2: + raise TypeError("anchor must be str or 2-tuple") self._anchor = anchor def get_anchor(self): """Return the anchor.""" return self._anchor + def get_subplotspec(self): + return None + def set_horizontal(self, h): """ Parameters @@ -180,40 +154,82 @@ def get_position_runtime(self, ax, renderer): else: return self._locator(ax, renderer).bounds - def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): + @staticmethod + def _calc_k(sizes, total): + # sizes is a (n, 2) array of (rel_size, abs_size); this method finds + # the k factor such that sum(rel_size * k + abs_size) == total. + rel_sum, abs_sum = sizes.sum(0) + return (total - abs_sum) / rel_sum if rel_sum else 0 + + @staticmethod + def _calc_offsets(sizes, k): + # Apply k factors to (n, 2) sizes array of (rel_size, abs_size); return + # the resulting cumulative offset positions. + return np.cumsum([0, *(sizes @ [k, 1])]) + + def new_locator(self, nx, ny, nx1=None, ny1=None): """ + Return an axes locator callable for the specified cell. + Parameters ---------- nx, nx1 : int Integers specifying the column-position of the cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* + specified. Otherwise, location of columns spanning between *nx* to *nx1* (but excluding *nx1*-th column) is specified. ny, ny1 : int Same as *nx* and *nx1*, but for row positions. - axes - renderer """ - - figW, figH = self._fig.get_size_inches() + if nx1 is None: + nx1 = nx + 1 + if ny1 is None: + ny1 = ny + 1 + # append_size("left") adds a new size at the beginning of the + # horizontal size lists; this shift transforms e.g. + # new_locator(nx=2, ...) into effectively new_locator(nx=3, ...). To + # take that into account, instead of recording nx, we record + # nx-self._xrefindex, where _xrefindex is shifted by 1 by each + # append_size("left"), and re-add self._xrefindex back to nx in + # _locate, when the actual axes position is computed. Ditto for y. + xref = self._xrefindex + yref = self._yrefindex + locator = functools.partial( + self._locate, nx - xref, ny - yref, nx1 - xref, ny1 - yref) + locator.get_subplotspec = self.get_subplotspec + return locator + + def _locate(self, nx, ny, nx1, ny1, axes, renderer): + """ + Implementation of ``divider.new_locator().__call__``. + + The axes locator callable returned by ``new_locator()`` is created as + a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1* + specifying the requested cell. + """ + nx += self._xrefindex + nx1 += self._xrefindex + ny += self._yrefindex + ny1 += self._yrefindex + + fig_w, fig_h = self._fig.bbox.size / self._fig.dpi x, y, w, h = self.get_position_runtime(axes, renderer) hsizes = self.get_horizontal_sizes(renderer) vsizes = self.get_vertical_sizes(renderer) - k_h = self._calc_k(hsizes, figW*w) - k_v = self._calc_k(vsizes, figH*h) + k_h = self._calc_k(hsizes, fig_w * w) + k_v = self._calc_k(vsizes, fig_h * h) if self.get_aspect(): k = min(k_h, k_v) ox = self._calc_offsets(hsizes, k) oy = self._calc_offsets(vsizes, k) - ww = (ox[-1] - ox[0]) / figW - hh = (oy[-1] - oy[0]) / figH + ww = (ox[-1] - ox[0]) / fig_w + hh = (oy[-1] - oy[0]) / fig_h pb = mtransforms.Bbox.from_bounds(x, y, w, h) pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) - pb1_anchored = pb1.anchored(self.get_anchor(), pb) - x0, y0 = pb1_anchored.x0, pb1_anchored.y0 + x0, y0 = pb1.anchored(self.get_anchor(), pb).p0 else: ox = self._calc_offsets(hsizes, k_h) @@ -221,32 +237,18 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): x0, y0 = x, y if nx1 is None: - nx1 = nx + 1 + nx1 = -1 if ny1 is None: - ny1 = ny + 1 + ny1 = -1 - x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW - y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH + x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w + y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) - def new_locator(self, nx, ny, nx1=None, ny1=None): - """ - Return a new `AxesLocator` for the specified cell. - - Parameters - ---------- - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - """ - return AxesLocator(self, nx, ny, nx1, ny1) - def append_size(self, position, size): + _api.check_in_list(["left", "right", "bottom", "top"], + position=position) if position == "left": self._horizontal.insert(0, size) self._xrefindex += 1 @@ -255,73 +257,27 @@ def append_size(self, position, size): elif position == "bottom": self._vertical.insert(0, size) self._yrefindex += 1 - elif position == "top": + else: # 'top' self._vertical.append(size) - else: - cbook._check_in_list(["left", "right", "bottom", "top"], - position=position) def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None): - if adjust_dirs is None: - adjust_dirs = ["left", "right", "bottom", "top"] - from .axes_size import Padded, SizeFromFunc, GetExtentHelper - for d in adjust_dirs: - helper = GetExtentHelper(use_axes, d) - size = SizeFromFunc(helper) - padded_size = Padded(size, pad) # pad in inch - self.append_size(d, padded_size) - - -class AxesLocator: - """ - A simple callable object, initialized with AxesDivider class, - returns the position and size of the given cell. - """ - def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None): """ + Add auto-adjustable padding around *use_axes* to take their decorations + (title, labels, ticks, ticklabels) into account during layout. + Parameters ---------- - axes_divider : AxesDivider - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. + use_axes : `~matplotlib.axes.Axes` or list of `~matplotlib.axes.Axes` + The Axes whose decorations are taken into account. + pad : float, default: 0.1 + Additional padding in inches. + adjust_dirs : list of {"left", "right", "bottom", "top"}, optional + The sides where padding is added; defaults to all four sides. """ - self._axes_divider = axes_divider - - _xrefindex = axes_divider._xrefindex - _yrefindex = axes_divider._yrefindex - - self._nx, self._ny = nx - _xrefindex, ny - _yrefindex - - if nx1 is None: - nx1 = nx + 1 - if ny1 is None: - ny1 = ny + 1 - - self._nx1 = nx1 - _xrefindex - self._ny1 = ny1 - _yrefindex - - def __call__(self, axes, renderer): - - _xrefindex = self._axes_divider._xrefindex - _yrefindex = self._axes_divider._yrefindex - - return self._axes_divider.locate(self._nx + _xrefindex, - self._ny + _yrefindex, - self._nx1 + _xrefindex, - self._ny1 + _yrefindex, - axes, - renderer) - - def get_subplotspec(self): - if hasattr(self._axes_divider, "get_subplotspec"): - return self._axes_divider.get_subplotspec() - else: - return None + if adjust_dirs is None: + adjust_dirs = ["left", "right", "bottom", "top"] + for d in adjust_dirs: + self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad) class SubplotDivider(Divider): @@ -334,7 +290,7 @@ def __init__(self, fig, *args, horizontal=None, vertical=None, """ Parameters ---------- - fig : `matplotlib.figure.Figure` + fig : `~matplotlib.figure.Figure` *args : tuple (*nrows*, *ncols*, *index*) or int The array of subplots in the figure has dimensions ``(nrows, @@ -345,34 +301,26 @@ def __init__(self, fig, *args, horizontal=None, vertical=None, If *nrows*, *ncols*, and *index* are all single digit numbers, then *args* can be passed as a single 3-digit number (e.g. 234 for (2, 3, 4)). + horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional + Sizes for horizontal division. + vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional + Sizes for vertical division. + aspect : bool, optional + Whether overall rectangular area is reduced so that the relative + part of the horizontal and vertical scales have the same scale. + anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ +'NW', 'W'}, default: 'C' + Placement of the reduced rectangle, when *aspect* is True. """ self.figure = fig - self._subplotspec = SubplotSpec._from_subplot_args(fig, args) - self.update_params() # sets self.figbox - super().__init__(fig, pos=self.figbox.bounds, + super().__init__(fig, [0, 0, 1, 1], horizontal=horizontal or [], vertical=vertical or [], aspect=aspect, anchor=anchor) + self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args)) def get_position(self): """Return the bounds of the subplot box.""" - self.update_params() # update self.figbox - return self.figbox.bounds - - def update_params(self): - """Update the subplot position from fig.subplotpars.""" - self.figbox = self.get_subplotspec().get_position(self.figure) - - 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)[num-1] - self.update_params() - self.set_position(self.figbox) + return self.get_subplotspec().get_position(self.figure).bounds def get_subplotspec(self): """Get the SubplotSpec instance.""" @@ -381,11 +329,12 @@ def get_subplotspec(self): def set_subplotspec(self, subplotspec): """Set the SubplotSpec instance.""" self._subplotspec = subplotspec + self.set_position(subplotspec.get_position(self.figure)) class AxesDivider(Divider): """ - Divider based on the pre-existing axes. + Divider based on the preexisting axes. """ def __init__(self, axes, xref=None, yref=None): @@ -413,135 +362,92 @@ def __init__(self, axes, xref=None, yref=None): def _get_new_axes(self, *, axes_class=None, **kwargs): axes = self._axes if axes_class is None: - if isinstance(axes, SubplotBase): - axes_class = axes._axes_class - else: - axes_class = type(axes) + axes_class = type(axes) return axes_class(axes.get_figure(), axes.get_position(original=True), **kwargs) def new_horizontal(self, size, pad=None, pack_start=False, **kwargs): """ - Add a new axes on the right (or left) side of the main axes. + Helper method for ``append_axes("left")`` and ``append_axes("right")``. - Parameters - ---------- - size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - A width of the axes. If float or string is given, *from_any* - function is used to create the size, with *ref_size* set to AxesX - instance of the current axes. - pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - Pad between the axes. It takes same argument as *size*. - pack_start : bool - If False, the new axes is appended at the end - of the list, i.e., it became the right-most axes. If True, it is - inserted at the start of the list, and becomes the left-most axes. - **kwargs - All extra keywords arguments are passed to the created axes. - If *axes_class* is given, the new axes will be created as an - instance of the given class. Otherwise, the same class of the - main axes will be used. + See the documentation of `append_axes` for more details. + + :meta private: """ if pad is None: - cbook.warn_deprecated( - "3.2", message="In a future version, 'pad' will default to " - "rcParams['figure.subplot.wspace']. Set pad=0 to keep the " - "old behavior.") + pad = mpl.rcParams["figure.subplot.wspace"] * self._xref + pos = "left" if pack_start else "right" if pad: if not isinstance(pad, Size._Base): pad = Size.from_any(pad, fraction_ref=self._xref) - if pack_start: - self._horizontal.insert(0, pad) - self._xrefindex += 1 - else: - self._horizontal.append(pad) + self.append_size(pos, pad) if not isinstance(size, Size._Base): size = Size.from_any(size, fraction_ref=self._xref) - if pack_start: - self._horizontal.insert(0, size) - self._xrefindex += 1 - locator = self.new_locator(nx=0, ny=self._yrefindex) - else: - self._horizontal.append(size) - locator = self.new_locator( - nx=len(self._horizontal) - 1, ny=self._yrefindex) + self.append_size(pos, size) + locator = self.new_locator( + nx=0 if pack_start else len(self._horizontal) - 1, + ny=self._yrefindex) ax = self._get_new_axes(**kwargs) ax.set_axes_locator(locator) return ax def new_vertical(self, size, pad=None, pack_start=False, **kwargs): """ - Add a new axes on the top (or bottom) side of the main axes. + Helper method for ``append_axes("top")`` and ``append_axes("bottom")``. - Parameters - ---------- - size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - A height of the axes. If float or string is given, *from_any* - function is used to create the size, with *ref_size* set to AxesX - instance of the current axes. - pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - Pad between the axes. It takes same argument as *size*. - pack_start : bool - If False, the new axes is appended at the end - of the list, i.e., it became the right-most axes. If True, it is - inserted at the start of the list, and becomes the left-most axes. - **kwargs - All extra keywords arguments are passed to the created axes. - If *axes_class* is given, the new axes will be created as an - instance of the given class. Otherwise, the same class of the - main axes will be used. + See the documentation of `append_axes` for more details. + + :meta private: """ if pad is None: - cbook.warn_deprecated( - "3.2", message="In a future version, 'pad' will default to " - "rcParams['figure.subplot.hspace']. Set pad=0 to keep the " - "old behavior.") + pad = mpl.rcParams["figure.subplot.hspace"] * self._yref + pos = "bottom" if pack_start else "top" if pad: if not isinstance(pad, Size._Base): pad = Size.from_any(pad, fraction_ref=self._yref) - if pack_start: - self._vertical.insert(0, pad) - self._yrefindex += 1 - else: - self._vertical.append(pad) + self.append_size(pos, pad) if not isinstance(size, Size._Base): size = Size.from_any(size, fraction_ref=self._yref) - if pack_start: - self._vertical.insert(0, size) - self._yrefindex += 1 - locator = self.new_locator(nx=self._xrefindex, ny=0) - else: - self._vertical.append(size) - locator = self.new_locator( - nx=self._xrefindex, ny=len(self._vertical)-1) + self.append_size(pos, size) + locator = self.new_locator( + nx=self._xrefindex, + ny=0 if pack_start else len(self._vertical) - 1) ax = self._get_new_axes(**kwargs) ax.set_axes_locator(locator) return ax - def append_axes(self, position, size, pad=None, add_to_figure=True, + def append_axes(self, position, size, pad=None, *, axes_class=None, **kwargs): """ - Create an axes at the given *position* with the same height - (or width) of the main axes. + Add a new axes on a given side of the main axes. - *position* - ["left"|"right"|"bottom"|"top"] - - *size* and *pad* should be axes_grid.axes_size compatible. + Parameters + ---------- + position : {"left", "right", "bottom", "top"} + Where the new axes is positioned relative to the main axes. + size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str + The axes width or height. float or str arguments are interpreted + as ``axes_size.from_any(size, AxesX())`` for left or + right axes, and likewise with ``AxesY`` for bottom or top axes. + pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str + Padding between the axes. float or str arguments are interpreted + as for *size*. Defaults to :rc:`figure.subplot.wspace` times the + main Axes width (left or right axes) or :rc:`figure.subplot.hspace` + times the main Axes height (bottom or top axes). + axes_class : subclass type of `~.axes.Axes`, optional + The type of the new axes. Defaults to the type of the main axes. + **kwargs + All extra keywords arguments are passed to the created axes. """ - if position == "left": - ax = self.new_horizontal(size, pad, pack_start=True, **kwargs) - elif position == "right": - ax = self.new_horizontal(size, pad, pack_start=False, **kwargs) - elif position == "bottom": - ax = self.new_vertical(size, pad, pack_start=True, **kwargs) - elif position == "top": - ax = self.new_vertical(size, pad, pack_start=False, **kwargs) - else: - cbook._check_in_list(["left", "right", "bottom", "top"], - position=position) - if add_to_figure: - self._fig.add_axes(ax) + create_axes, pack_start = _api.check_getitem({ + "left": (self.new_horizontal, True), + "right": (self.new_horizontal, False), + "bottom": (self.new_vertical, True), + "top": (self.new_vertical, False), + }, position=position) + ax = create_axes( + size, pad, pack_start=pack_start, axes_class=axes_class, **kwargs) + self._fig.add_axes(ax) return ax def get_aspect(self): @@ -568,170 +474,120 @@ def get_anchor(self): return self._anchor def get_subplotspec(self): - if hasattr(self._axes, "get_subplotspec"): - return self._axes.get_subplotspec() - else: - return None + return self._axes.get_subplotspec() + + +# Helper for HBoxDivider/VBoxDivider. +# The variable names are written for a horizontal layout, but the calculations +# work identically for vertical layouts. +def _locate(x, y, w, h, summed_widths, equal_heights, fig_w, fig_h, anchor): + + total_width = fig_w * w + max_height = fig_h * h + + # Determine the k factors. + n = len(equal_heights) + eq_rels, eq_abss = equal_heights.T + sm_rels, sm_abss = summed_widths.T + A = np.diag([*eq_rels, 0]) + A[:n, -1] = -1 + A[-1, :-1] = sm_rels + B = [*(-eq_abss), total_width - sm_abss.sum()] + # A @ K = B: This finds factors {k_0, ..., k_{N-1}, H} so that + # eq_rel_i * k_i + eq_abs_i = H for all i: all axes have the same height + # sum(sm_rel_i * k_i + sm_abs_i) = total_width: fixed total width + # (foo_rel_i * k_i + foo_abs_i will end up being the size of foo.) + *karray, height = np.linalg.solve(A, B) + if height > max_height: # Additionally, upper-bound the height. + karray = (max_height - eq_abss) / eq_rels + + # Compute the offsets corresponding to these factors. + ox = np.cumsum([0, *(sm_rels * karray + sm_abss)]) + ww = (ox[-1] - ox[0]) / fig_w + h0_rel, h0_abs = equal_heights[0] + hh = (karray[0]*h0_rel + h0_abs) / fig_h + pb = mtransforms.Bbox.from_bounds(x, y, w, h) + pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) + x0, y0 = pb1.anchored(anchor, pb).p0 + + return x0, y0, ox, hh class HBoxDivider(SubplotDivider): + """ + A `.SubplotDivider` for laying out axes horizontally, while ensuring that + they have equal heights. - @staticmethod - def _determine_karray(equivalent_sizes, appended_sizes, - max_equivalent_size, - total_appended_size): - - n = len(equivalent_sizes) - eq_rs, eq_as = np.asarray(equivalent_sizes).T - ap_rs, ap_as = np.asarray(appended_sizes).T - A = np.zeros((n + 1, n + 1)) - B = np.zeros(n + 1) - np.fill_diagonal(A[:n, :n], eq_rs) - A[:n, -1] = -1 - A[-1, :-1] = ap_rs - B[:n] = -eq_as - B[-1] = total_appended_size - sum(ap_as) - - karray_H = np.linalg.solve(A, B) # A @ K = B - karray = karray_H[:-1] - H = karray_H[-1] - - if H > max_equivalent_size: - karray = (max_equivalent_size - eq_as) / eq_rs - return karray - - @staticmethod - def _calc_offsets(appended_sizes, karray): - offsets = [0.] - for (r, a), k in zip(appended_sizes, karray): - offsets.append(offsets[-1] + r*k + a) - return offsets + Examples + -------- + .. plot:: gallery/axes_grid1/demo_axes_hbox_divider.py + """ def new_locator(self, nx, nx1=None): """ - Create a new `~mpl_toolkits.axes_grid.axes_divider.AxesLocator` for - the specified cell. - - Parameters - ---------- - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - """ - return AxesLocator(self, nx, 0, nx1, None) - - def _locate(self, x, y, w, h, - y_equivalent_sizes, x_appended_sizes, - figW, figH): - equivalent_sizes = y_equivalent_sizes - appended_sizes = x_appended_sizes - - max_equivalent_size = figH * h - total_appended_size = figW * w - karray = self._determine_karray(equivalent_sizes, appended_sizes, - max_equivalent_size, - total_appended_size) - - ox = self._calc_offsets(appended_sizes, karray) - - ww = (ox[-1] - ox[0]) / figW - ref_h = equivalent_sizes[0] - hh = (karray[0]*ref_h[0] + ref_h[1]) / figH - pb = mtransforms.Bbox.from_bounds(x, y, w, h) - pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) - pb1_anchored = pb1.anchored(self.get_anchor(), pb) - x0, y0 = pb1_anchored.x0, pb1_anchored.y0 + Create an axes locator callable for the specified cell. - return x0, y0, ox, hh - - def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): - """ Parameters ---------- - axes_divider : AxesDivider nx, nx1 : int Integers specifying the column-position of the cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* + specified. Otherwise, location of columns spanning between *nx* to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - axes - renderer """ + return super().new_locator(nx, 0, nx1, 0) - figW, figH = self._fig.get_size_inches() + def _locate(self, nx, ny, nx1, ny1, axes, renderer): + # docstring inherited + nx += self._xrefindex + nx1 += self._xrefindex + fig_w, fig_h = self._fig.bbox.size / self._fig.dpi x, y, w, h = self.get_position_runtime(axes, renderer) - - y_equivalent_sizes = self.get_vertical_sizes(renderer) - x_appended_sizes = self.get_horizontal_sizes(renderer) - x0, y0, ox, hh = self._locate(x, y, w, h, - y_equivalent_sizes, x_appended_sizes, - figW, figH) + summed_ws = self.get_horizontal_sizes(renderer) + equal_hs = self.get_vertical_sizes(renderer) + x0, y0, ox, hh = _locate( + x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor()) if nx1 is None: - nx1 = nx + 1 - - x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW + nx1 = -1 + x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w y1, h1 = y0, hh - return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) -class VBoxDivider(HBoxDivider): +class VBoxDivider(SubplotDivider): """ - The Divider class whose rectangle area is specified as a subplot geometry. + A `.SubplotDivider` for laying out axes vertically, while ensuring that + they have equal widths. """ def new_locator(self, ny, ny1=None): """ - Create a new `~mpl_toolkits.axes_grid.axes_divider.AxesLocator` for - the specified cell. + Create an axes locator callable for the specified cell. Parameters ---------- ny, ny1 : int Integers specifying the row-position of the cell. When *ny1* is None, a single *ny*-th row is - specified. Otherwise location of rows spanning between *ny* + specified. Otherwise, location of rows spanning between *ny* to *ny1* (but excluding *ny1*-th row) is specified. """ - return AxesLocator(self, 0, ny, None, ny1) + return super().new_locator(0, ny, 0, ny1) - def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): - """ - Parameters - ---------- - axes_divider : AxesDivider - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - axes - renderer - """ - - figW, figH = self._fig.get_size_inches() + def _locate(self, nx, ny, nx1, ny1, axes, renderer): + # docstring inherited + ny += self._yrefindex + ny1 += self._yrefindex + fig_w, fig_h = self._fig.bbox.size / self._fig.dpi x, y, w, h = self.get_position_runtime(axes, renderer) - - x_equivalent_sizes = self.get_horizontal_sizes(renderer) - y_appended_sizes = self.get_vertical_sizes(renderer) - - y0, x0, oy, ww = self._locate(y, x, h, w, - x_equivalent_sizes, y_appended_sizes, - figH, figW) + summed_hs = self.get_vertical_sizes(renderer) + equal_ws = self.get_horizontal_sizes(renderer) + y0, x0, oy, ww = _locate( + y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor()) if ny1 is None: - ny1 = ny + 1 - + ny1 = -1 x1, w1 = x0, ww - y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH - + y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) @@ -743,15 +599,20 @@ def make_axes_locatable(axes): return divider -def make_axes_area_auto_adjustable(ax, - use_axes=None, pad=0.1, - adjust_dirs=None): +def make_axes_area_auto_adjustable( + ax, use_axes=None, pad=0.1, adjust_dirs=None): + """ + Add auto-adjustable padding around *ax* to take its decorations (title, + labels, ticks, ticklabels) into account during layout, using + `.Divider.add_auto_adjustable_area`. + + By default, padding is determined from the decorations of *ax*. + Pass *use_axes* to consider the decorations of other Axes instead. + """ if adjust_dirs is None: adjust_dirs = ["left", "right", "bottom", "top"] divider = make_axes_locatable(ax) - if use_axes is None: use_axes = ax - divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad, adjust_dirs=adjust_dirs) diff --git a/lib/mpl_toolkits/axes_grid1/axes_grid.py b/lib/mpl_toolkits/axes_grid1/axes_grid.py index e5b2cc930e94..64bc8f465f19 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_grid.py +++ b/lib/mpl_toolkits/axes_grid1/axes_grid.py @@ -1,118 +1,75 @@ from numbers import Number import functools +from types import MethodType import numpy as np -import matplotlib as mpl -from matplotlib import cbook -import matplotlib.ticker as ticker +from matplotlib import _api, cbook from matplotlib.gridspec import SubplotSpec from .axes_divider import Size, SubplotDivider, Divider -from .mpl_axes import Axes - - -def _tick_only(ax, bottom_on, left_on): - bottom_off = not bottom_on - left_off = not left_on - ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off) - ax.axis["left"].toggle(ticklabels=left_off, label=left_off) +from .mpl_axes import Axes, SimpleAxisArtist class CbarAxesBase: def __init__(self, *args, orientation, **kwargs): self.orientation = orientation - self._default_label_on = True - self._locator = None # deprecated. super().__init__(*args, **kwargs) - @cbook._rename_parameter("3.2", "locator", "ticks") - def colorbar(self, mappable, *, ticks=None, **kwargs): - - if self.orientation in ["top", "bottom"]: - orientation = "horizontal" - else: - orientation = "vertical" - - if mpl.rcParams["mpl_toolkits.legacy_colorbar"]: - cbook.warn_deprecated( - "3.2", message="Since %(since)s, mpl_toolkits's own colorbar " - "implementation is deprecated; it will be removed " - "%(removal)s. Set the 'mpl_toolkits.legacy_colorbar' rcParam " - "to False to use Matplotlib's default colorbar implementation " - "and suppress this deprecation warning.") - if ticks is None: - ticks = ticker.MaxNLocator(5) # For backcompat. - from .colorbar import Colorbar - cb = Colorbar( - self, mappable, orientation=orientation, ticks=ticks, **kwargs) - self._cbid = mappable.callbacksSM.connect( - 'changed', cb.update_normal) - mappable.colorbar = cb - self._locator = cb.cbar_axis.get_major_locator() - else: - cb = mpl.colorbar.colorbar_factory( - self, mappable, orientation=orientation, ticks=ticks, **kwargs) - self._cbid = mappable.colorbar_cid # deprecated. - self._locator = cb.locator # deprecated. - - self._config_axes() - return cb - - cbid = cbook._deprecate_privatize_attribute( - "3.3", alternative="mappable.colorbar_cid") - locator = cbook._deprecate_privatize_attribute( - "3.3", alternative=".colorbar().locator") + def colorbar(self, mappable, **kwargs): + return self.get_figure(root=False).colorbar( + mappable, cax=self, location=self.orientation, **kwargs) - def _config_axes(self): - """Make an axes patch and outline.""" - ax = self - ax.set_navigate(False) - ax.axis[:].toggle(all=False) - b = self._default_label_on - ax.axis[self.orientation].toggle(all=b) - def toggle_label(self, b): - self._default_label_on = b - axis = self.axis[self.orientation] - axis.toggle(ticklabels=b, label=b) - - def cla(self): - super().cla() - self._config_axes() - - -class CbarAxes(CbarAxesBase, Axes): - pass +_cbaraxes_class_factory = cbook._make_class_factory(CbarAxesBase, "Cbar{}") class Grid: """ A grid of Axes. - In Matplotlib, the axes location (and size) is specified in normalized + In Matplotlib, the Axes location (and size) is specified in normalized figure coordinates. This may not be ideal for images that needs to be displayed with a given aspect ratio; for example, it is difficult to display multiple images of a same size with some fixed padding between them. AxesGrid can be used in such case. + + Attributes + ---------- + axes_all : list of Axes + A flat list of Axes. Note that you can also access this directly + from the grid. The following is equivalent :: + + grid[i] == grid.axes_all[i] + len(grid) == len(grid.axes_all) + + axes_column : list of list of Axes + A 2D list of Axes where the first index is the column. This results + in the usage pattern ``grid.axes_column[col][row]``. + axes_row : list of list of Axes + A 2D list of Axes where the first index is the row. This results + in the usage pattern ``grid.axes_row[row][col]``. + axes_llc : Axes + The Axes in the lower left corner. + n_axes : int + Number of Axes in the grid. """ _defaultAxesClass = Axes - @cbook._delete_parameter("3.3", "add_all") + @_api.rename_parameter("3.11", "ngrids", "n_axes") def __init__(self, fig, rect, nrows_ncols, - ngrids=None, + n_axes=None, direction="row", axes_pad=0.02, - add_all=True, + *, share_all=False, share_x=True, share_y=True, label_mode="L", axes_class=None, - *, aspect=False, ): """ @@ -120,22 +77,22 @@ def __init__(self, fig, ---------- fig : `.Figure` The parent figure. - rect : (float, float, float, float) or int - The axes position, as a ``(left, bottom, width, height)`` tuple or - as a three-digit subplot position code (e.g., "121"). + rect : (float, float, float, float), (int, int, int), int, or \ + `~.SubplotSpec` + The axes position, as a ``(left, bottom, width, height)`` tuple, + as a three-digit subplot position code (e.g., ``(1, 2, 1)`` or + ``121``), or as a `~.SubplotSpec`. nrows_ncols : (int, int) Number of rows and columns in the grid. - ngrids : int or None, default: None - If not None, only the first *ngrids* axes in the grid are created. + n_axes : int or None, default: None + If not None, only the first *n_axes* axes in the grid are created. direction : {"row", "column"}, default: "row" Whether axes are created in row-major ("row by row") or - column-major order ("column by column"). + column-major order ("column by column"). This also affects the + order in which axes are accessed using indexing (``grid[index]``). axes_pad : float or (float, float), default: 0.02 Padding or (horizontal padding, vertical padding) between axes, in inches. - add_all : bool, default: True - Whether to add the axes to the figure using `.Figure.add_axes`. - This parameter is deprecated. share_all : bool, default: False Whether all axes share their x- and y-axis. Overrides *share_x* and *share_y*. @@ -143,33 +100,34 @@ def __init__(self, fig, Whether all axes of a column share their x-axis. share_y : bool, default: True Whether all axes of a row share their y-axis. - label_mode : {"L", "1", "all"}, default: "L" + label_mode : {"L", "1", "all", "keep"}, default: "L" Determines which axes will get tick labels: - "L": All axes on the left column get vertical tick labels; all axes on the bottom row get horizontal tick labels. - "1": Only the bottom left axes is labelled. - - "all": all axes are labelled. + - "all": All axes are labelled. + - "keep": Do not do anything. - axes_class : subclass of `matplotlib.axes.Axes`, default: None + axes_class : subclass of `matplotlib.axes.Axes`, default: `.mpl_axes.Axes` + The type of Axes to create. aspect : bool, default: False Whether the axes aspect ratio follows the aspect ratio of the data limits. """ self._nrows, self._ncols = nrows_ncols - if ngrids is None: - ngrids = self._nrows * self._ncols + if n_axes is None: + n_axes = self._nrows * self._ncols else: - if not 0 < ngrids <= self._nrows * self._ncols: - raise Exception("") - - self.ngrids = ngrids + if not 0 < n_axes <= self._nrows * self._ncols: + raise ValueError( + "n_axes must be positive and not larger than nrows*ncols") self._horiz_pad_size, self._vert_pad_size = map( Size.Fixed, np.broadcast_to(axes_pad, 2)) - cbook._check_in_list(["column", "row"], direction=direction) + _api.check_in_list(["column", "row"], direction=direction) self._direction = direction if axes_class is None: @@ -179,19 +137,19 @@ def __init__(self, fig, axes_class = functools.partial(cls, **kwargs) kw = dict(horizontal=[], vertical=[], aspect=aspect) - if isinstance(rect, (str, Number, SubplotSpec)): + if isinstance(rect, (Number, SubplotSpec)): self._divider = SubplotDivider(fig, rect, **kw) elif len(rect) == 3: self._divider = SubplotDivider(fig, *rect, **kw) elif len(rect) == 4: self._divider = Divider(fig, rect, **kw) else: - raise Exception("") + raise TypeError("Incorrect rect format") rect = self._divider.get_position() axes_array = np.full((self._nrows, self._ncols), None, dtype=object) - for i in range(self.ngrids): + for i in range(n_axes): col, row = self._get_col_row(i) if share_all: sharex = sharey = axes_array[0, 0] @@ -200,47 +158,28 @@ def __init__(self, fig, sharey = axes_array[row, 0] if share_y else None axes_array[row, col] = axes_class( fig, rect, sharex=sharex, sharey=sharey) - self.axes_all = axes_array.ravel().tolist() - self.axes_column = axes_array.T.tolist() - self.axes_row = axes_array.tolist() + self.axes_all = axes_array.ravel( + order="C" if self._direction == "row" else "F").tolist()[:n_axes] + self.axes_row = [[ax for ax in row if ax] for row in axes_array] + self.axes_column = [[ax for ax in col if ax] for col in axes_array.T] self.axes_llc = self.axes_column[0][-1] self._init_locators() - if add_all: - for ax in self.axes_all: - fig.add_axes(ax) + for ax in self.axes_all: + fig.add_axes(ax) self.set_label_mode(label_mode) def _init_locators(self): - - h = [] - h_ax_pos = [] - for _ in range(self._ncols): - if h: - h.append(self._horiz_pad_size) - h_ax_pos.append(len(h)) - sz = Size.Scaled(1) - h.append(sz) - - v = [] - v_ax_pos = [] - for _ in range(self._nrows): - if v: - v.append(self._vert_pad_size) - v_ax_pos.append(len(v)) - sz = Size.Scaled(1) - v.append(sz) - - for i in range(self.ngrids): + self._divider.set_horizontal( + [Size.Scaled(1), self._horiz_pad_size] * (self._ncols-1) + [Size.Scaled(1)]) + self._divider.set_vertical( + [Size.Scaled(1), self._vert_pad_size] * (self._nrows-1) + [Size.Scaled(1)]) + for i in range(self.n_axes): col, row = self._get_col_row(i) - locator = self._divider.new_locator( - nx=h_ax_pos[col], ny=v_ax_pos[self._nrows - 1 - row]) - self.axes_all[i].set_axes_locator(locator) - - self._divider.set_horizontal(h) - self._divider.set_vertical(v) + self.axes_all[i].set_axes_locator( + self._divider.new_locator(nx=2 * col, ny=2 * (self._nrows - 1 - row))) def _get_col_row(self, n): if self._direction == "column": @@ -250,6 +189,9 @@ def _get_col_row(self, n): return col, row + n_axes = property(lambda self: len(self.axes_all)) + ngrids = _api.deprecated(property(lambda self: len(self.axes_all))) + # Good to propagate __len__ if we have __getitem__ def __len__(self): return len(self.axes_all) @@ -301,40 +243,37 @@ def set_label_mode(self, mode): Parameters ---------- - mode : {"L", "1", "all"} + mode : {"L", "1", "all", "keep"} The label mode: - "L": All axes on the left column get vertical tick labels; all axes on the bottom row get horizontal tick labels. - "1": Only the bottom left axes is labelled. - - "all": all axes are labelled. + - "all": All axes are labelled. + - "keep": Do not do anything. """ - if mode == "all": - for ax in self.axes_all: - _tick_only(ax, False, False) - elif mode == "L": - # left-most axes - for ax in self.axes_column[0][:-1]: - _tick_only(ax, bottom_on=True, left_on=False) - # lower-left axes - ax = self.axes_column[0][-1] - _tick_only(ax, bottom_on=False, left_on=False) - - for col in self.axes_column[1:]: - # axes with no labels - for ax in col[:-1]: - _tick_only(ax, bottom_on=True, left_on=True) - - # bottom - ax = col[-1] - _tick_only(ax, bottom_on=False, left_on=True) - - elif mode == "1": - for ax in self.axes_all: - _tick_only(ax, bottom_on=True, left_on=True) - - ax = self.axes_llc - _tick_only(ax, bottom_on=False, left_on=False) + _api.check_in_list(["all", "L", "1", "keep"], mode=mode) + if mode == "keep": + return + for i, j in np.ndindex(self._nrows, self._ncols): + try: + ax = self.axes_row[i][j] + except IndexError: + continue + if isinstance(ax.axis, MethodType): + bottom_axis = SimpleAxisArtist(ax.xaxis, 1, ax.spines["bottom"]) + left_axis = SimpleAxisArtist(ax.yaxis, 1, ax.spines["left"]) + else: + bottom_axis = ax.axis["bottom"] + left_axis = ax.axis["left"] + display_at_bottom = (i == self._nrows - 1 if mode == "L" else + i == self._nrows - 1 and j == 0 if mode == "1" else + True) # if mode == "all" + display_at_left = (j == 0 if mode == "L" else + i == self._nrows - 1 and j == 0 if mode == "1" else + True) # if mode == "all" + bottom_axis.toggle(ticklabels=display_at_bottom, label=display_at_bottom) + left_axis.toggle(ticklabels=display_at_left, label=display_at_left) def get_divider(self): return self._divider @@ -345,23 +284,24 @@ def set_axes_locator(self, locator): def get_axes_locator(self): return self._divider.get_locator() - def get_vsize_hsize(self): - return self._divider.get_vsize_hsize() - class ImageGrid(Grid): - # docstring inherited + """ + A grid of Axes for Image display. - _defaultCbarAxesClass = CbarAxes + This class is a specialization of `~.axes_grid1.axes_grid.Grid` for displaying a + grid of images. In particular, it forces all axes in a column to share their x-axis + and all axes in a row to share their y-axis. It further provides helpers to add + colorbars to some or all axes. + """ - @cbook._delete_parameter("3.3", "add_all") def __init__(self, fig, rect, nrows_ncols, - ngrids=None, + n_axes=None, direction="row", axes_pad=0.02, - add_all=True, + *, share_all=False, aspect=True, label_mode="L", @@ -382,8 +322,8 @@ def __init__(self, fig, as a three-digit subplot position code (e.g., "121"). nrows_ncols : (int, int) Number of rows and columns in the grid. - ngrids : int or None, default: None - If not None, only the first *ngrids* axes in the grid are created. + n_axes : int or None, default: None + If not None, only the first *n_axes* axes in the grid are created. direction : {"row", "column"}, default: "row" Whether axes are created in row-major ("row by row") or column-major order ("column by column"). This also affects the @@ -391,11 +331,10 @@ def __init__(self, fig, axes_pad : float or (float, float), default: 0.02in Padding or (horizontal padding, vertical padding) between axes, in inches. - add_all : bool, default: True - Whether to add the axes to the figure using `.Figure.add_axes`. - This parameter is deprecated. share_all : bool, default: False - Whether all axes share their x- and y-axis. + Whether all axes share their x- and y-axis. Note that in any case, + all axes in a column share their x-axis and all axes in a row share + their y-axis. aspect : bool, default: True Whether the axes aspect ratio follows the aspect ratio of the data limits. @@ -411,39 +350,40 @@ def __init__(self, fig, Whether to create a colorbar for "each" axes, a "single" colorbar for the entire grid, colorbars only for axes on the "edge" determined by *cbar_location*, or no colorbars. The colorbars are - stored in the :attr:`cbar_axes` attribute. + stored in the :attr:`!cbar_axes` attribute. cbar_location : {"left", "right", "bottom", "top"}, default: "right" cbar_pad : float, default: None Padding between the image axes and the colorbar axes. - cbar_size : size specification (see `.Size.from_any`), default: "5%" + + .. versionchanged:: 3.10 + ``cbar_mode="single"`` no longer adds *axes_pad* between the axes + and the colorbar if the *cbar_location* is "left" or "bottom". + + cbar_size : size specification (see `!.Size.from_any`), default: "5%" Colorbar size. cbar_set_cax : bool, default: True If True, each axes in the grid has a *cax* attribute that is bound to associated *cbar_axes*. axes_class : subclass of `matplotlib.axes.Axes`, default: None """ + _api.check_in_list(["each", "single", "edge", None], + cbar_mode=cbar_mode) + _api.check_in_list(["left", "right", "bottom", "top"], + cbar_location=cbar_location) self._colorbar_mode = cbar_mode self._colorbar_location = cbar_location self._colorbar_pad = cbar_pad self._colorbar_size = cbar_size # The colorbar axes are created in _init_locators(). - if add_all: - super().__init__( - fig, rect, nrows_ncols, ngrids, - direction=direction, axes_pad=axes_pad, - share_all=share_all, share_x=True, share_y=True, aspect=aspect, - label_mode=label_mode, axes_class=axes_class) - else: # Only show deprecation in that case. - super().__init__( - fig, rect, nrows_ncols, ngrids, - direction=direction, axes_pad=axes_pad, add_all=add_all, - share_all=share_all, share_x=True, share_y=True, aspect=aspect, - label_mode=label_mode, axes_class=axes_class) - - if add_all: - for ax in self.cbar_axes: - fig.add_axes(ax) + super().__init__( + fig, rect, nrows_ncols, n_axes, + direction=direction, axes_pad=axes_pad, + share_all=share_all, share_x=True, share_y=True, aspect=aspect, + label_mode=label_mode, axes_class=axes_class) + + for ax in self.cbar_axes: + fig.add_axes(ax) if cbar_set_cax: if self._colorbar_mode == "single": @@ -470,10 +410,10 @@ def _init_locators(self): else: self._colorbar_pad = self._vert_pad_size.fixed_size self.cbar_axes = [ - self._defaultCbarAxesClass( - self.axes_all[0].figure, self._divider.get_position(), + _cbaraxes_class_factory(self._defaultAxesClass)( + self.axes_all[0].get_figure(root=False), self._divider.get_position(), orientation=self._colorbar_location) - for _ in range(self.ngrids)] + for _ in range(self.n_axes)] cb_mode = self._colorbar_mode cb_location = self._colorbar_location @@ -494,13 +434,13 @@ def _init_locators(self): v.append(Size.from_any(self._colorbar_size, sz)) v.append(Size.from_any(self._colorbar_pad, sz)) locator = self._divider.new_locator(nx=0, nx1=-1, ny=0) - for i in range(self.ngrids): + for i in range(self.n_axes): self.cbar_axes[i].set_visible(False) self.cbar_axes[0].set_axes_locator(locator) self.cbar_axes[0].set_visible(True) for col, ax in enumerate(self.axes_row[0]): - if h: + if col != 0: h.append(self._horiz_pad_size) if ax: @@ -529,7 +469,7 @@ def _init_locators(self): v_ax_pos = [] v_cb_pos = [] for row, ax in enumerate(self.axes_column[0][::-1]): - if v: + if row != 0: v.append(self._vert_pad_size) if ax: @@ -555,7 +495,7 @@ def _init_locators(self): v_cb_pos.append(len(v)) v.append(Size.from_any(self._colorbar_size, sz)) - for i in range(self.ngrids): + for i in range(self.n_axes): col, row = self._get_col_row(i) locator = self._divider.new_locator(nx=h_ax_pos[col], ny=v_ax_pos[self._nrows-1-row]) @@ -595,12 +535,12 @@ def _init_locators(self): v.append(Size.from_any(self._colorbar_size, sz)) locator = self._divider.new_locator(nx=0, nx1=-1, ny=-2) if cb_location in ("right", "top"): - for i in range(self.ngrids): + for i in range(self.n_axes): self.cbar_axes[i].set_visible(False) self.cbar_axes[0].set_axes_locator(locator) self.cbar_axes[0].set_visible(True) elif cb_mode == "each": - for i in range(self.ngrids): + for i in range(self.n_axes): self.cbar_axes[i].set_visible(True) elif cb_mode == "edge": if cb_location in ("right", "left"): @@ -609,10 +549,10 @@ def _init_locators(self): count = self._ncols for i in range(count): self.cbar_axes[i].set_visible(True) - for j in range(i + 1, self.ngrids): + for j in range(i + 1, self.n_axes): self.cbar_axes[j].set_visible(False) else: - for i in range(self.ngrids): + for i in range(self.n_axes): self.cbar_axes[i].set_visible(False) self.cbar_axes[i].set_position([1., 1., 0.001, 0.001], which="active") diff --git a/lib/mpl_toolkits/axes_grid1/axes_rgb.py b/lib/mpl_toolkits/axes_grid1/axes_rgb.py index 0c06c8c756dd..52fd707e8704 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_rgb.py +++ b/lib/mpl_toolkits/axes_grid1/axes_rgb.py @@ -1,17 +1,24 @@ +from types import MethodType + import numpy as np -from matplotlib import cbook from .axes_divider import make_axes_locatable, Size -from .mpl_axes import Axes +from .mpl_axes import Axes, SimpleAxisArtist -@cbook._delete_parameter("3.3", "add_all") -def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True, **kwargs): +def make_rgb_axes(ax, pad=0.01, axes_class=None, **kwargs): """ Parameters ---------- - pad : float - Fraction of the axes height. + ax : `~matplotlib.axes.Axes` + Axes instance to create the RGB Axes in. + pad : float, optional + Fraction of the Axes height to pad. + axes_class : `matplotlib.axes.Axes` or None, optional + Axes class to use for the R, G, and B Axes. If None, use + the same class as *ax*. + **kwargs + Forwarded to *axes_class* init for the R, G, and B Axes. """ divider = make_axes_locatable(ax) @@ -28,10 +35,7 @@ def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True, **kwargs): ax_rgb = [] if axes_class is None: - try: - axes_class = ax._axes_class - except AttributeError: - axes_class = type(ax) + axes_class = type(ax) for ny in [4, 2, 0]: ax1 = axes_class(ax.get_figure(), ax.get_position(original=True), @@ -48,84 +52,75 @@ def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True, **kwargs): ax_rgb.append(ax1) - if add_all: - fig = ax.get_figure() - for ax1 in ax_rgb: - fig.add_axes(ax1) + fig = ax.get_figure() + for ax1 in ax_rgb: + fig.add_axes(ax1) return ax_rgb -@cbook.deprecated("3.3", alternative="ax.imshow(np.dstack([r, g, b]))") -def imshow_rgb(ax, r, g, b, **kwargs): - return ax.imshow(np.dstack([r, g, b]), **kwargs) - - class RGBAxes: """ - 4-panel imshow (RGB, R, G, B). + 4-panel `~.Axes.imshow` (RGB, R, G, B). + + Layout:: - Layout: - +---------------+-----+ - | | R | - + +-----+ - | RGB | G | - + +-----+ - | | B | - +---------------+-----+ + ┌───────────────┬─────┠+ │ │ R │ + │ ├─────┤ + │ RGB │ G │ + │ ├─────┤ + │ │ B │ + └───────────────┴─────┘ Subclasses can override the ``_defaultAxesClass`` attribute. + By default RGBAxes uses `.mpl_axes.Axes`. Attributes ---------- RGB : ``_defaultAxesClass`` - The axes object for the three-channel imshow. + The Axes object for the three-channel `~.Axes.imshow`. R : ``_defaultAxesClass`` - The axes object for the red channel imshow. + The Axes object for the red channel `~.Axes.imshow`. G : ``_defaultAxesClass`` - The axes object for the green channel imshow. + The Axes object for the green channel `~.Axes.imshow`. B : ``_defaultAxesClass`` - The axes object for the blue channel imshow. + The Axes object for the blue channel `~.Axes.imshow`. """ _defaultAxesClass = Axes - @cbook._delete_parameter("3.3", "add_all") - def __init__(self, *args, pad=0, add_all=True, **kwargs): + def __init__(self, *args, pad=0, **kwargs): """ Parameters ---------- pad : float, default: 0 - fraction of the axes height to put as padding. - add_all : bool, default: True - Whether to add the {rgb, r, g, b} axes to the figure. - This parameter is deprecated. - axes_class : matplotlib.axes.Axes - + Fraction of the Axes height to put as padding. + axes_class : `~matplotlib.axes.Axes` + Axes class to use. If not provided, ``_defaultAxesClass`` is used. *args - Unpacked into axes_class() init for RGB + Forwarded to *axes_class* init for the RGB Axes **kwargs - Unpacked into axes_class() init for RGB, R, G, B axes + Forwarded to *axes_class* init for the RGB, R, G, and B Axes """ axes_class = kwargs.pop("axes_class", self._defaultAxesClass) self.RGB = ax = axes_class(*args, **kwargs) - if add_all: - ax.get_figure().add_axes(ax) - else: - kwargs["add_all"] = add_all # only show deprecation in that case + ax.get_figure().add_axes(ax) self.R, self.G, self.B = make_rgb_axes( ax, pad=pad, axes_class=axes_class, **kwargs) # Set the line color and ticks for the axes. for ax1 in [self.RGB, self.R, self.G, self.B]: - ax1.axis[:].line.set_color("w") - ax1.axis[:].major_ticks.set_markeredgecolor("w") - - @cbook.deprecated("3.3") - def add_RGB_to_figure(self): - """Add red, green and blue axes to the RGB composite's axes figure.""" - self.RGB.get_figure().add_axes(self.R) - self.RGB.get_figure().add_axes(self.G) - self.RGB.get_figure().add_axes(self.B) + if isinstance(ax1.axis, MethodType): + ad = Axes.AxisDict(self) + ad.update( + bottom=SimpleAxisArtist(ax1.xaxis, 1, ax1.spines["bottom"]), + top=SimpleAxisArtist(ax1.xaxis, 2, ax1.spines["top"]), + left=SimpleAxisArtist(ax1.yaxis, 1, ax1.spines["left"]), + right=SimpleAxisArtist(ax1.yaxis, 2, ax1.spines["right"])) + else: + ad = ax1.axis + ad[:].line.set_color("w") + ad[:].major_ticks.set_markeredgecolor("w") def imshow_rgb(self, r, g, b, **kwargs): """ @@ -133,21 +128,17 @@ def imshow_rgb(self, r, g, b, **kwargs): Parameters ---------- - r : array-like - The red array - g : array-like - The green array - b : array-like - The blue array - kwargs : imshow kwargs - kwargs get unpacked into the imshow calls for the four images + r, g, b : array-like + The red, green, and blue arrays. + **kwargs + Forwarded to `~.Axes.imshow` calls for the four images. Returns ------- - rgb : matplotlib.image.AxesImage - r : matplotlib.image.AxesImage - g : matplotlib.image.AxesImage - b : matplotlib.image.AxesImage + rgb : `~matplotlib.image.AxesImage` + r : `~matplotlib.image.AxesImage` + g : `~matplotlib.image.AxesImage` + b : `~matplotlib.image.AxesImage` """ if not (r.shape == g.shape == b.shape): raise ValueError( @@ -164,8 +155,3 @@ def imshow_rgb(self, r, g, b, **kwargs): im_g = self.G.imshow(G, **kwargs) im_b = self.B.imshow(B, **kwargs) return im_rgb, im_r, im_g, im_b - - -@cbook.deprecated("3.3", alternative="RGBAxes") -class RGBAxesBase(RGBAxes): - pass diff --git a/lib/mpl_toolkits/axes_grid1/axes_size.py b/lib/mpl_toolkits/axes_grid1/axes_size.py index 6b2c3fe1579c..55820827cd6a 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_size.py +++ b/lib/mpl_toolkits/axes_grid1/axes_size.py @@ -1,33 +1,65 @@ """ -Provides classes of simple units that will be used with AxesDivider -class (or others) to determine the size of each axes. The unit -classes define `get_size` method that returns a tuple of two floats, +Provides classes of simple units that will be used with `.AxesDivider` +class (or others) to determine the size of each Axes. The unit +classes define `!get_size` method that returns a tuple of two floats, meaning relative and absolute sizes, respectively. Note that this class is nothing more than a simple tuple of two floats. Take a look at the Divider class to see how these two values are used. + +Once created, the unit classes can be modified by simple arithmetic +operations: addition /subtraction with another unit type or a real number and scaling +(multiplication or division) by a real number. """ -from numbers import Number +from numbers import Real -from matplotlib import cbook +from matplotlib import _api from matplotlib.axes import Axes class _Base: - def __rmul__(self, other): + return self * other + + def __mul__(self, other): + if not isinstance(other, Real): + return NotImplemented return Fraction(other, self) + def __div__(self, other): + return (1 / other) * self + def __add__(self, other): if isinstance(other, _Base): return Add(self, other) else: return Add(self, Fixed(other)) + def __neg__(self): + return -1 * self + + def __radd__(self, other): + # other cannot be a _Base instance, because A + B would trigger + # A.__add__(B) first. + return Add(self, Fixed(other)) + + def __sub__(self, other): + return self + (-other) + + def get_size(self, renderer): + """ + Return two-float tuple with relative and absolute sizes. + """ + raise NotImplementedError("Subclasses must implement") + class Add(_Base): + """ + Sum of two sizes. + """ + def __init__(self, a, b): self._a = a self._b = b @@ -38,23 +70,13 @@ def get_size(self, renderer): return a_rel_size + b_rel_size, a_abs_size + b_abs_size -class AddList(_Base): - def __init__(self, add_list): - self._list = add_list - - def get_size(self, renderer): - sum_rel_size = sum([a.get_size(renderer)[0] for a in self._list]) - sum_abs_size = sum([a.get_size(renderer)[1] for a in self._list]) - return sum_rel_size, sum_abs_size - - class Fixed(_Base): """ Simple fixed size with absolute part = *fixed_size* and relative part = 0. """ def __init__(self, fixed_size): - cbook._check_isinstance(Number, fixed_size=fixed_size) + _api.check_isinstance(Real, fixed_size=fixed_size) self.fixed_size = fixed_size def get_size(self, renderer): @@ -148,7 +170,7 @@ class MaxExtent(_Base): def __init__(self, artist_list, w_or_h): self._artist_list = artist_list - cbook._check_in_list(["width", "height"], w_or_h=w_or_h) + _api.check_in_list(["width", "height"], w_or_h=w_or_h) self._w_or_h = w_or_h def add_artist(self, a): @@ -189,7 +211,7 @@ class Fraction(_Base): """ def __init__(self, fraction, ref_size): - cbook._check_isinstance(Number, fraction=fraction) + _api.check_isinstance(Real, fraction=fraction) self._fraction_ref = ref_size self._fraction = fraction @@ -203,33 +225,17 @@ def get_size(self, renderer): return rel_size, abs_size -class Padded(_Base): - """ - Return a instance where the absolute part of *size* is - increase by the amount of *pad*. - """ - - def __init__(self, size, pad): - self._size = size - self._pad = pad - - def get_size(self, renderer): - r, a = self._size.get_size(renderer) - rel_size = r - abs_size = a + self._pad - return rel_size, abs_size - - def from_any(size, fraction_ref=None): """ Create a Fixed unit when the first argument is a float, or a Fraction unit if that is a string that ends with %. The second argument is only meaningful when Fraction unit is created. - >>> a = Size.from_any(1.2) # => Size.Fixed(1.2) - >>> Size.from_any("50%", a) # => Size.Fraction(0.5, a) + >>> from mpl_toolkits.axes_grid1.axes_size import from_any + >>> a = from_any(1.2) # => Fixed(1.2) + >>> from_any("50%", a) # => Fraction(0.5, a) """ - if isinstance(size, Number): + if isinstance(size, Real): return Fixed(size) elif isinstance(size, str): if size[-1] == "%": @@ -237,36 +243,29 @@ def from_any(size, fraction_ref=None): raise ValueError("Unknown format") -class SizeFromFunc(_Base): - def __init__(self, func): - self._func = func - - def get_size(self, renderer): - rel_size = 0. - - bb = self._func(renderer) - dpi = renderer.points_to_pixels(72.) - abs_size = bb/dpi - - return rel_size, abs_size - +class _AxesDecorationsSize(_Base): + """ + Fixed size, corresponding to the size of decorations on a given Axes side. + """ -class GetExtentHelper: - _get_func_map = { - "left": lambda self, axes_bbox: axes_bbox.xmin - self.xmin, - "right": lambda self, axes_bbox: self.xmax - axes_bbox.xmax, - "bottom": lambda self, axes_bbox: axes_bbox.ymin - self.ymin, - "top": lambda self, axes_bbox: self.ymax - axes_bbox.ymax, + _get_size_map = { + "left": lambda tight_bb, axes_bb: axes_bb.xmin - tight_bb.xmin, + "right": lambda tight_bb, axes_bb: tight_bb.xmax - axes_bb.xmax, + "bottom": lambda tight_bb, axes_bb: axes_bb.ymin - tight_bb.ymin, + "top": lambda tight_bb, axes_bb: tight_bb.ymax - axes_bb.ymax, } def __init__(self, ax, direction): - cbook._check_in_list(self._get_func_map, direction=direction) - self._ax_list = [ax] if isinstance(ax, Axes) else ax + _api.check_in_list(self._get_size_map, direction=direction) self._direction = direction + self._ax_list = [ax] if isinstance(ax, Axes) else ax - def __call__(self, renderer): - get_func = self._get_func_map[self._direction] - vl = [get_func(ax.get_tightbbox(renderer, call_axes_locator=False), - ax.bbox) - for ax in self._ax_list] - return max(vl) + def get_size(self, renderer): + sz = max([ + self._get_size_map[self._direction]( + ax.get_tightbbox(renderer, call_axes_locator=False), ax.bbox) + for ax in self._ax_list]) + dpi = renderer.points_to_pixels(72) + abs_size = sz / dpi + rel_size = 0 + return rel_size, abs_size diff --git a/lib/mpl_toolkits/axes_grid1/colorbar.py b/lib/mpl_toolkits/axes_grid1/colorbar.py deleted file mode 100644 index ce28ff98b3c7..000000000000 --- a/lib/mpl_toolkits/axes_grid1/colorbar.py +++ /dev/null @@ -1,806 +0,0 @@ -""" -Colorbar toolkit with two classes and a function: - - :class:`ColorbarBase` - the base class with full colorbar drawing functionality. - It can be used as-is to make a colorbar for a given colormap; - a mappable object (e.g., image) is not needed. - - :class:`Colorbar` - the derived class for use with images or contour plots. - - :func:`make_axes` - a function for resizing an axes and adding a second axes - suitable for a colorbar - -The :meth:`~matplotlib.figure.Figure.colorbar` method uses :func:`make_axes` -and :class:`Colorbar`; the :func:`~matplotlib.pyplot.colorbar` function -is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`. -""" - -import numpy as np -import matplotlib as mpl -from matplotlib import cbook -import matplotlib.colors as colors -import matplotlib.cm as cm -from matplotlib import docstring -import matplotlib.ticker as ticker -import matplotlib.collections as collections -import matplotlib.contour as contour -from matplotlib.path import Path -from matplotlib.patches import PathPatch -from matplotlib.transforms import Bbox - - -cbook.warn_deprecated( - "3.2", name=__name__, obj_type="module", alternative="matplotlib.colorbar") - - -make_axes_kw_doc = ''' - - ============= ==================================================== - Property Description - ============= ==================================================== - *orientation* vertical or horizontal - *fraction* 0.15; fraction of original axes to use for colorbar - *pad* Defaults to 0.05 if vertical, 0.15 if horizontal; fraction - of original axes between colorbar and new image axes. - Defaults to 0.05 for both if `.get_constrained_layout` - is *True*. - *shrink* 1.0; fraction by which to shrink the colorbar - *aspect* 20; ratio of long to short dimensions - ============= ==================================================== - -''' - -colormap_kw_doc = ''' - - =========== ==================================================== - Property Description - =========== ==================================================== - *extend* [ 'neither' | 'both' | 'min' | 'max' ] - If not 'neither', make pointed end(s) for out-of- - range values. These are set for a given colormap - using the colormap set_under and set_over methods. - *spacing* [ 'uniform' | 'proportional' ] - Uniform spacing gives each discrete color the same - space; proportional makes the space proportional to - the data interval. - *ticks* [ None | list of ticks | Locator object ] - If None, ticks are determined automatically from the - input. - *format* [ None | format string | Formatter object ] - If None, the - :class:`~matplotlib.ticker.ScalarFormatter` is used. - If a format string is given, e.g., '%.3f', that is - used. An alternative - :class:`~matplotlib.ticker.Formatter` object may be - given instead. - *drawedges* bool - Whether to draw lines at color boundaries. - =========== ==================================================== - - The following will probably be useful only in the context of - indexed colors (that is, when the mappable has norm=NoNorm()), - or other unusual circumstances. - - ============ =================================================== - Property Description - ============ =================================================== - *boundaries* None or a sequence - *values* None or a sequence which must be of length 1 less - than the sequence of *boundaries*. For each region - delimited by adjacent entries in *boundaries*, the - color mapped to the corresponding value in values - will be used. - ============ =================================================== - -''' - -colorbar_doc = ''' - -Add a colorbar to a plot. - -Function signatures for the :mod:`~matplotlib.pyplot` interface; all -but the first are also method signatures for the -:meth:`~matplotlib.figure.Figure.colorbar` method:: - - colorbar(**kwargs) - colorbar(mappable, **kwargs) - colorbar(mappable, cax=cax, **kwargs) - colorbar(mappable, ax=ax, **kwargs) - -arguments: - - *mappable* - the :class:`~matplotlib.image.Image`, - :class:`~matplotlib.contour.ContourSet`, etc. to - which the colorbar applies; this argument is mandatory for the - :meth:`~matplotlib.figure.Figure.colorbar` method but optional for the - :func:`~matplotlib.pyplot.colorbar` function, which sets the - default to the current image. - -keyword arguments: - - *cax* - None | axes object into which the colorbar will be drawn - *ax* - None | parent axes object from which space for a new - colorbar axes will be stolen - - -Additional keyword arguments are of two kinds: - - axes properties: - %s - colorbar properties: - %s - -If *mappable* is a :class:`~matplotlib.contours.ContourSet`, its *extend* -kwarg is included automatically. - -Note that the *shrink* kwarg provides a simple way to keep a vertical -colorbar, for example, from being taller than the axes of the mappable -to which the colorbar is attached; but it is a manual method requiring -some trial and error. If the colorbar is too tall (or a horizontal -colorbar is too wide) use a smaller value of *shrink*. - -For more precise control, you can manually specify the positions of -the axes objects in which the mappable and the colorbar are drawn. In -this case, do not use any of the axes properties kwargs. - -It is known that some vector graphics viewer (svg and pdf) renders white gaps -between segments of the colorbar. This is due to bugs in the viewers not -matplotlib. As a workaround the colorbar can be rendered with overlapping -segments:: - - cbar = colorbar() - cbar.solids.set_edgecolor("face") - draw() - -However this has negative consequences in other circumstances. Particularly -with semi transparent images (alpha < 1) and colorbar extensions and is not -enabled by default see (issue #1188). - -returns: - :class:`~matplotlib.colorbar.Colorbar` instance; see also its base class, - :class:`~matplotlib.colorbar.ColorbarBase`. Call the - :meth:`~matplotlib.colorbar.ColorbarBase.set_label` method - to label the colorbar. - - -The transData of the *cax* is adjusted so that the limits in the -longest axis actually corresponds to the limits in colorbar range. On -the other hand, the shortest axis has a data limits of [1,2], whose -unconventional value is to prevent underflow when log scale is used. -''' % (make_axes_kw_doc, colormap_kw_doc) - -#docstring.interpd.update(colorbar_doc=colorbar_doc) - - -class CbarAxesLocator: - """ - CbarAxesLocator is a axes_locator for colorbar axes. It adjust the - position of the axes to make a room for extended ends, i.e., the - extended ends are located outside the axes area. - """ - - def __init__(self, locator=None, extend="neither", orientation="vertical"): - """ - *locator* : the bbox returned from the locator is used as a - initial axes location. If None, axes.bbox is used. - - *extend* : same as in ColorbarBase - *orientation* : same as in ColorbarBase - - """ - self._locator = locator - self.extesion_fraction = 0.05 - self.extend = extend - self.orientation = orientation - - def get_original_position(self, axes, renderer): - """Return the original position of the axes.""" - if self._locator is None: - bbox = axes.get_position(original=True) - else: - bbox = self._locator(axes, renderer) - return bbox - - def get_end_vertices(self): - """ - Return a tuple of two vertices for the colorbar extended ends. - - The first vertices is for the minimum end, and the second is for - the maximum end. - """ - # Note that concatenating two vertices needs to make a - # vertices for the frame. - extesion_fraction = self.extesion_fraction - - corx = extesion_fraction*2. - cory = 1./(1. - corx) - x1, y1, w, h = 0, 0, 1, 1 - x2, y2 = x1 + w, y1 + h - dw, dh = w*extesion_fraction, h*extesion_fraction*cory - - if self.extend in ["min", "both"]: - bottom = [(x1, y1), - (x1+w/2., y1-dh), - (x2, y1)] - else: - bottom = [(x1, y1), - (x2, y1)] - - if self.extend in ["max", "both"]: - top = [(x2, y2), - (x1+w/2., y2+dh), - (x1, y2)] - else: - top = [(x2, y2), - (x1, y2)] - - if self.orientation == "horizontal": - bottom = [(y, x) for (x, y) in bottom] - top = [(y, x) for (x, y) in top] - - return bottom, top - - def get_path_patch(self): - """Return the path for axes patch.""" - end1, end2 = self.get_end_vertices() - verts = [] + end1 + end2 + end1[:1] - return Path(verts) - - def get_path_ends(self): - """Return the paths for extended ends.""" - end1, end2 = self.get_end_vertices() - return Path(end1), Path(end2) - - def __call__(self, axes, renderer): - """Return the adjusted position of the axes.""" - bbox0 = self.get_original_position(axes, renderer) - bbox = bbox0 - - x1, y1, w, h = bbox.bounds - extesion_fraction = self.extesion_fraction - dw, dh = w*extesion_fraction, h*extesion_fraction - - if self.extend in ["min", "both"]: - if self.orientation == "horizontal": - x1 = x1 + dw - else: - y1 = y1+dh - - if self.extend in ["max", "both"]: - if self.orientation == "horizontal": - w = w-2*dw - else: - h = h-2*dh - - return Bbox.from_bounds(x1, y1, w, h) - - -class ColorbarBase(cm.ScalarMappable): - """ - Draw a colorbar in an existing axes. - - This is a base class for the :class:`Colorbar` class, which is the - basis for the :func:`~matplotlib.pyplot.colorbar` method and pyplot - function. - - It is also useful by itself for showing a colormap. If the *cmap* - kwarg is given but *boundaries* and *values* are left as None, - then the colormap will be displayed on a 0-1 scale. To show the - under- and over-value colors, specify the *norm* as:: - - colors.Normalize(clip=False) - - To show the colors versus index instead of on the 0-1 scale, - use:: - - norm=colors.NoNorm. - - Useful attributes: - - :attr:`ax` - the Axes instance in which the colorbar is drawn - - :attr:`lines` - a LineCollection if lines were drawn, otherwise None - - :attr:`dividers` - a LineCollection if *drawedges* is True, otherwise None - - Useful public methods are :meth:`set_label` and :meth:`add_lines`. - """ - - def __init__(self, ax, - cmap=None, - norm=None, - alpha=1.0, - values=None, - boundaries=None, - orientation='vertical', - extend='neither', - spacing='uniform', # uniform or proportional - ticks=None, - format=None, - drawedges=False, - filled=True, - ): - self.ax = ax - - if cmap is None: - cmap = cm.get_cmap() - if norm is None: - norm = colors.Normalize() - self.alpha = alpha - super().__init__(cmap=cmap, norm=norm) - self.values = values - self.boundaries = boundaries - self.extend = extend - self.spacing = spacing - self.orientation = orientation - self.drawedges = drawedges - self.filled = filled - - # artists - self.solids = None - self.lines = None - self.dividers = None - self.extension_patch1 = None - self.extension_patch2 = None - - if orientation == "vertical": - self.cbar_axis = self.ax.yaxis - else: - self.cbar_axis = self.ax.xaxis - - if format is None: - if isinstance(self.norm, colors.LogNorm): - # change both axis for proper aspect - self.ax.set_xscale("log") - self.ax.set_yscale("log") - self.cbar_axis.set_minor_locator(ticker.NullLocator()) - formatter = ticker.LogFormatter() - else: - formatter = None - elif isinstance(format, str): - formatter = ticker.FormatStrFormatter(format) - else: - formatter = format # Assume it is a Formatter - - if formatter is None: - formatter = self.cbar_axis.get_major_formatter() - else: - self.cbar_axis.set_major_formatter(formatter) - - if np.iterable(ticks): - self.cbar_axis.set_ticks(ticks) - elif ticks is not None: - self.cbar_axis.set_major_locator(ticks) - else: - self._select_locator() - - self._config_axes() - - self.update_artists() - - self.set_label_text('') - - def _get_colorbar_limits(self): - """ - initial limits for colorbar range. The returned min, max values - will be used to create colorbar solid(?) and etc. - """ - if self.boundaries is not None: - C = self.boundaries - if self.extend in ["min", "both"]: - C = C[1:] - - if self.extend in ["max", "both"]: - C = C[:-1] - return min(C), max(C) - else: - return self.get_clim() - - def _config_axes(self): - """ - Adjust the properties of the axes to be adequate for colorbar display. - """ - ax = self.ax - - axes_locator = CbarAxesLocator(ax.get_axes_locator(), - extend=self.extend, - orientation=self.orientation) - ax.set_axes_locator(axes_locator) - - # override the get_data_ratio for the aspect works. - def _f(): - return 1. - ax.get_data_ratio = _f - ax.get_data_ratio_log = _f - - ax.set_frame_on(True) - ax.set_navigate(False) - - self.ax.set_autoscalex_on(False) - self.ax.set_autoscaley_on(False) - - if self.orientation == 'horizontal': - ax.xaxis.set_label_position('bottom') - ax.set_yticks([]) - else: - ax.set_xticks([]) - ax.yaxis.set_label_position('right') - ax.yaxis.set_ticks_position('right') - - def update_artists(self): - """ - Update the colorbar associated artists, *filled* and - *ends*. Note that *lines* are not updated. This needs to be - called whenever clim of associated image changes. - """ - self._process_values() - self._add_ends() - - X, Y = self._mesh() - if self.filled: - C = self._values[:, np.newaxis] - self._add_solids(X, Y, C) - - ax = self.ax - vmin, vmax = self._get_colorbar_limits() - if self.orientation == 'horizontal': - ax.set_ylim(1, 2) - ax.set_xlim(vmin, vmax) - else: - ax.set_xlim(1, 2) - ax.set_ylim(vmin, vmax) - - def _add_ends(self): - """ - Create patches from extended ends and add them to the axes. - """ - - del self.extension_patch1 - del self.extension_patch2 - - path1, path2 = self.ax.get_axes_locator().get_path_ends() - fc = mpl.rcParams['axes.facecolor'] - ec = mpl.rcParams['axes.edgecolor'] - linewidths = 0.5 * mpl.rcParams['axes.linewidth'] - self.extension_patch1 = PathPatch(path1, - fc=fc, ec=ec, lw=linewidths, - zorder=2., - transform=self.ax.transAxes, - clip_on=False) - self.extension_patch2 = PathPatch(path2, - fc=fc, ec=ec, lw=linewidths, - zorder=2., - transform=self.ax.transAxes, - clip_on=False) - self.ax.add_artist(self.extension_patch1) - self.ax.add_artist(self.extension_patch2) - - def _set_label_text(self): - """Set the colorbar label.""" - self.cbar_axis.set_label_text(self._label, **self._labelkw) - - def set_label_text(self, label, **kw): - """Label the long axis of the colorbar.""" - self._label = label - self._labelkw = kw - self._set_label_text() - - def _edges(self, X, Y): - """Return the separator line segments; helper for _add_solids.""" - N = X.shape[0] - # Using the non-array form of these line segments is much - # simpler than making them into arrays. - if self.orientation == 'vertical': - return [list(zip(X[i], Y[i])) for i in range(1, N-1)] - else: - return [list(zip(Y[i], X[i])) for i in range(1, N-1)] - - def _add_solids(self, X, Y, C): - """ - Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`; - optionally add separators. - """ - ## Change to pcolorfast after fixing bugs in some backends... - - if self.extend in ["min", "both"]: - cc = self.to_rgba([C[0][0]]) - self.extension_patch1.set_facecolor(cc[0]) - X, Y, C = X[1:], Y[1:], C[1:] - - if self.extend in ["max", "both"]: - cc = self.to_rgba([C[-1][0]]) - self.extension_patch2.set_facecolor(cc[0]) - X, Y, C = X[:-1], Y[:-1], C[:-1] - - if self.orientation == 'vertical': - args = (X, Y, C) - else: - args = (np.transpose(Y), np.transpose(X), np.transpose(C)) - - del self.solids - del self.dividers - - col = self.ax.pcolormesh( - *args, - cmap=self.cmap, norm=self.norm, shading='flat', alpha=self.alpha) - - self.solids = col - if self.drawedges: - self.dividers = collections.LineCollection( - self._edges(X, Y), - colors=(mpl.rcParams['axes.edgecolor'],), - linewidths=(0.5*mpl.rcParams['axes.linewidth'],), - ) - self.ax.add_collection(self.dividers) - else: - self.dividers = None - - def add_lines(self, levels, colors, linewidths): - """Draw lines on the colorbar. It deletes preexisting lines.""" - X, Y = np.meshgrid([1, 2], levels) - if self.orientation == 'vertical': - xy = np.stack([X, Y], axis=-1) - else: - xy = np.stack([Y, X], axis=-1) - col = collections.LineCollection(xy, linewidths=linewidths) - self.lines = col - col.set_color(colors) - self.ax.add_collection(col) - - def _select_locator(self): - """Select a suitable locator.""" - if self.boundaries is None: - if isinstance(self.norm, colors.NoNorm): - nv = len(self._values) - base = 1 + int(nv/10) - locator = ticker.IndexLocator(base=base, offset=0) - elif isinstance(self.norm, colors.BoundaryNorm): - b = self.norm.boundaries - locator = ticker.FixedLocator(b, nbins=10) - elif isinstance(self.norm, colors.LogNorm): - locator = ticker.LogLocator() - else: - locator = ticker.MaxNLocator(nbins=5) - else: - b = self._boundaries[self._inside] - locator = ticker.FixedLocator(b) - - self.cbar_axis.set_major_locator(locator) - - def _process_values(self, b=None): - """ - Set the :attr:`_boundaries` and :attr:`_values` attributes - based on the input boundaries and values. Input boundaries - can be *self.boundaries* or the argument *b*. - """ - if b is None: - b = self.boundaries - if b is not None: - self._boundaries = np.asarray(b, dtype=float) - if self.values is None: - self._values = (self._boundaries[:-1] - + self._boundaries[1:]) / 2 - if isinstance(self.norm, colors.NoNorm): - self._values = (self._values + 0.00001).astype(np.int16) - return - self._values = np.array(self.values) - return - if self.values is not None: - self._values = np.array(self.values) - if self.boundaries is None: - b = np.zeros(len(self.values) + 1) - b[1:-1] = 0.5*(self._values[:-1] - self._values[1:]) - b[0] = 2.0*b[1] - b[2] - b[-1] = 2.0*b[-2] - b[-3] - self._boundaries = b - return - self._boundaries = np.array(self.boundaries) - return - # Neither boundaries nor values are specified; - # make reasonable ones based on cmap and norm. - if isinstance(self.norm, colors.NoNorm): - self._boundaries = ( - self._uniform_y(self.cmap.N + 1) * self.cmap.N - 0.5) - self._values = np.arange(self.cmap.N, dtype=np.int16) - return - elif isinstance(self.norm, colors.BoundaryNorm): - self._boundaries = np.array(self.norm.boundaries) - self._values = (self._boundaries[:-1] + self._boundaries[1:]) / 2 - return - else: - b = self._uniform_y(self.cmap.N + 1) - - self._process_values(b) - - def _uniform_y(self, N): - """ - Return colorbar data coordinates for *N* uniformly spaced boundaries. - """ - vmin, vmax = self._get_colorbar_limits() - if isinstance(self.norm, colors.LogNorm): - y = np.geomspace(vmin, vmax, N) - else: - y = np.linspace(vmin, vmax, N) - return y - - def _mesh(self): - """ - Return X,Y, the coordinate arrays for the colorbar pcolormesh. - These are suitable for a vertical colorbar; swapping and - transposition for a horizontal colorbar are done outside - this function. - """ - x = np.array([1.0, 2.0]) - if self.spacing == 'uniform': - y = self._uniform_y(len(self._boundaries)) - else: - y = self._boundaries - self._y = y - - X, Y = np.meshgrid(x, y) - return X, Y - - def set_alpha(self, alpha): - """Set the alpha value for transparency.""" - self.alpha = alpha - - -class Colorbar(ColorbarBase): - def __init__(self, ax, mappable, **kw): - # Ensure mappable.norm.vmin, vmax are set when colorbar is called, even - # if mappable.draw has not yet been called. This will not change vmin, - # vmax if they are already set. - mappable.autoscale_None() - - self.mappable = mappable - kw['cmap'] = mappable.cmap - kw['norm'] = mappable.norm - kw['alpha'] = mappable.get_alpha() - if isinstance(mappable, contour.ContourSet): - CS = mappable - kw['boundaries'] = CS._levels - kw['values'] = CS.cvalues - kw['extend'] = CS.extend - #kw['ticks'] = CS._levels - kw.setdefault('ticks', ticker.FixedLocator(CS.levels, nbins=10)) - kw['filled'] = CS.filled - super().__init__(ax, **kw) - if not CS.filled: - self.add_lines(CS) - else: - super().__init__(ax, **kw) - - def add_lines(self, CS): - """Add the lines from a non-filled `.ContourSet` to the colorbar.""" - if not isinstance(CS, contour.ContourSet) or CS.filled: - raise ValueError('add_lines is only for a ContourSet of lines') - tcolors = [c[0] for c in CS.tcolors] - tlinewidths = [t[0] for t in CS.tlinewidths] - # The following was an attempt to get the colorbar lines - # to follow subsequent changes in the contour lines, - # but more work is needed: specifically, a careful - # look at event sequences, and at how - # to make one object track another automatically. - #tcolors = [col.get_colors()[0] for col in CS.collections] - #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections] - super().add_lines(CS.levels, tcolors, tlinewidths) - - def update_normal(self, mappable): - """ - Update solid patches, lines, etc. - - This is meant to be called when the norm of the image or contour plot - to which this colorbar belongs changes. - - If the norm on the mappable is different than before, this resets the - locator and formatter for the axis, so if these have been customized, - they will need to be customized again. However, if the norm only - changes values of *vmin*, *vmax* or *cmap* then the old formatter - and locator will be preserved. - """ - self.mappable = mappable - self.set_alpha(mappable.get_alpha()) - self.cmap = mappable.cmap - if mappable.norm != self.norm: - self.norm = mappable.norm - self._reset_locator_formatter_scale() - - self.draw_all() - if isinstance(self.mappable, contour.ContourSet): - CS = self.mappable - if not CS.filled: - self.add_lines(CS) - self.stale = True - - def update_bruteforce(self, mappable): - """ - Update the colorbar artists to reflect the change of the - associated mappable. - """ - self.update_artists() - - if isinstance(mappable, contour.ContourSet): - if not mappable.filled: - self.add_lines(mappable) - - -@docstring.Substitution(make_axes_kw_doc) -def make_axes(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw): - """ - Resize and reposition a parent axes, and return a child - axes suitable for a colorbar - - :: - - cax, kw = make_axes(parent, **kw) - - Keyword arguments may include the following (with defaults): - - *orientation* - 'vertical' or 'horizontal' - - %s - - All but the first of these are stripped from the input kw set. - - Returns (cax, kw), the child axes and the reduced kw dictionary. - """ - orientation = kw.setdefault('orientation', 'vertical') - #pb = transforms.PBox(parent.get_position()) - pb = parent.get_position(original=True).frozen() - if orientation == 'vertical': - pad = kw.pop('pad', 0.05) - x1 = 1.0-fraction - pb1, pbx, pbcb = pb.splitx(x1-pad, x1) - pbcb = pbcb.shrunk(1.0, shrink).anchored('C', pbcb) - anchor = (0.0, 0.5) - panchor = (1.0, 0.5) - else: - pad = kw.pop('pad', 0.15) - pbcb, pbx, pb1 = pb.splity(fraction, fraction+pad) - pbcb = pbcb.shrunk(shrink, 1.0).anchored('C', pbcb) - aspect = 1.0/aspect - anchor = (0.5, 1.0) - panchor = (0.5, 0.0) - parent.set_position(pb1) - parent.set_anchor(panchor) - fig = parent.get_figure() - cax = fig.add_axes(pbcb) - cax.set_aspect(aspect, anchor=anchor, adjustable='box') - return cax, kw - - -@docstring.Substitution(colorbar_doc) -def colorbar(mappable, cax=None, ax=None, **kw): - """ - Create a colorbar for a ScalarMappable instance. - - Documentation for the pyplot thin wrapper: - - %s - """ - import matplotlib.pyplot as plt - if ax is None: - ax = plt.gca() - if cax is None: - cax, kw = make_axes(ax, **kw) - cb = Colorbar(cax, mappable, **kw) - - def on_changed(m): - cb.set_cmap(m.get_cmap()) - cb.set_clim(m.get_clim()) - cb.update_bruteforce(m) - - mappable.callbacksSM.connect('changed', on_changed) - mappable.colorbar = cb - ax.figure.sca(ax) - return cb diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index e30fc7e09880..52fe6efc0618 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -2,61 +2,17 @@ A collection of functions and objects for creating or placing inset axes. """ -from matplotlib import cbook, docstring +from matplotlib import _api, _docstring from matplotlib.offsetbox import AnchoredOffsetbox from matplotlib.patches import Patch, Rectangle from matplotlib.path import Path -from matplotlib.transforms import Bbox, BboxTransformTo +from matplotlib.transforms import Bbox from matplotlib.transforms import IdentityTransform, TransformedBbox from . import axes_size as Size from .parasite_axes import HostAxes -class InsetPosition: - @docstring.dedent_interpd - def __init__(self, parent, lbwh): - """ - An object for positioning an inset axes. - - This is created by specifying the normalized coordinates in the axes, - instead of the figure. - - Parameters - ---------- - parent : `matplotlib.axes.Axes` - Axes to use for normalizing coordinates. - - lbwh : iterable of four floats - The left edge, bottom edge, width, and height of the inset axes, in - units of the normalized coordinate of the *parent* axes. - - See Also - -------- - :meth:`matplotlib.axes.Axes.set_axes_locator` - - Examples - -------- - The following bounds the inset axes to a box with 20%% of the parent - axes's height and 40%% of the width. The size of the axes specified - ([0, 0, 1, 1]) ensures that the axes completely fills the bounding box: - - >>> parent_axes = plt.gca() - >>> ax_ins = plt.axes([0, 0, 1, 1]) - >>> ip = InsetPosition(ax, [0.5, 0.1, 0.4, 0.2]) - >>> ax_ins.set_axes_locator(ip) - """ - self.parent = parent - self.lbwh = lbwh - - def __call__(self, ax, renderer): - bbox_parent = self.parent.get_position(original=False) - trans = BboxTransformTo(bbox_parent) - bbox_inset = Bbox.from_bounds(*self.lbwh) - bb = TransformedBbox(bbox_inset, trans) - return bb - - class AnchoredLocatorBase(AnchoredOffsetbox): def __init__(self, bbox_to_anchor, offsetbox, loc, borderpad=0.5, bbox_transform=None): @@ -69,19 +25,15 @@ def draw(self, renderer): raise RuntimeError("No draw method should be called") def __call__(self, ax, renderer): + fig = ax.get_figure(root=False) + if renderer is None: + renderer = fig._get_renderer() self.axes = ax - - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - self._update_offset_func(renderer, fontsize) - - width, height, xdescent, ydescent = self.get_extent(renderer) - - px, py = self.get_offset(width, height, 0, 0, renderer) - bbox_canvas = Bbox.from_bounds(px, py, width, height) - tr = ax.figure.transFigure.inverted() - bb = TransformedBbox(bbox_canvas, tr) - - return bb + bbox = self.get_window_extent(renderer) + px, py = self.get_offset(bbox.width, bbox.height, 0, 0, renderer) + bbox_canvas = Bbox.from_bounds(px, py, bbox.width, bbox.height) + tr = fig.transSubfigure.inverted() + return TransformedBbox(bbox_canvas, tr) class AnchoredSizeLocator(AnchoredLocatorBase): @@ -95,7 +47,7 @@ def __init__(self, bbox_to_anchor, x_size, y_size, loc, self.x_size = Size.from_any(x_size) self.y_size = Size.from_any(y_size) - def get_extent(self, renderer): + def get_bbox(self, renderer): bbox = self.get_bbox_to_anchor() dpi = renderer.points_to_pixels(72.) @@ -104,12 +56,10 @@ def get_extent(self, renderer): r, a = self.y_size.get_size(renderer) height = bbox.height * r + a * dpi - xd, yd = 0, 0 - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) pad = self.pad * fontsize - return width + 2 * pad, height + 2 * pad, xd + pad, yd + pad + return Bbox.from_bounds(0, 0, width, height).padded(pad) class AnchoredZoomLocator(AnchoredLocatorBase): @@ -125,30 +75,31 @@ def __init__(self, parent_axes, zoom, loc, bbox_to_anchor, None, loc, borderpad=borderpad, bbox_transform=bbox_transform) - def get_extent(self, renderer): - bb = TransformedBbox(self.axes.viewLim, self.parent_axes.transData) + def get_bbox(self, renderer): + bb = self.parent_axes.transData.transform_bbox(self.axes.viewLim) fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) pad = self.pad * fontsize - return (abs(bb.width * self.zoom) + 2 * pad, - abs(bb.height * self.zoom) + 2 * pad, - pad, pad) + return ( + Bbox.from_bounds( + 0, 0, abs(bb.width * self.zoom), abs(bb.height * self.zoom)) + .padded(pad)) class BboxPatch(Patch): - @docstring.dedent_interpd + @_docstring.interpd def __init__(self, bbox, **kwargs): """ Patch showing the shape bounded by a Bbox. Parameters ---------- - bbox : `matplotlib.transforms.Bbox` + bbox : `~matplotlib.transforms.Bbox` Bbox to use for the extents of this patch. **kwargs Patch properties. Valid arguments include: - %(Patch)s + %(Patch:kwdoc)s """ if "transform" in kwargs: raise ValueError("transform should not be set") @@ -160,32 +111,15 @@ def __init__(self, bbox, **kwargs): def get_path(self): # docstring inherited x0, y0, x1, y1 = self.bbox.extents - return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], - closed=True) + return Path._create_closed([(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) class BboxConnector(Patch): @staticmethod def get_bbox_edge_pos(bbox, loc): """ - Helper function to obtain the location of a corner of a bbox - - Parameters - ---------- - bbox : `matplotlib.transforms.Bbox` - - loc : {1, 2, 3, 4} - Corner of *bbox*. Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - Returns - ------- - x, y : float - Coordinates of the corner specified by *loc*. + Return the ``(x, y)`` coordinates of corner *loc* of *bbox*; parameters + behave as documented for the `.BboxConnector` constructor. """ x0, y0, x1, y1 = bbox.extents if loc == 1: @@ -200,35 +134,9 @@ def get_bbox_edge_pos(bbox, loc): @staticmethod def connect_bbox(bbox1, bbox2, loc1, loc2=None): """ - Helper function to obtain a Path from one bbox to another. - - Parameters - ---------- - bbox1, bbox2 : `matplotlib.transforms.Bbox` - Bounding boxes to connect. - - loc1 : {1, 2, 3, 4} - Corner of *bbox1* to use. Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - loc2 : {1, 2, 3, 4}, optional - Corner of *bbox2* to use. If None, defaults to *loc1*. - Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - Returns - ------- - path : `matplotlib.path.Path` - A line segment from the *loc1* corner of *bbox1* to the *loc2* - corner of *bbox2*. + Construct a `.Path` connecting corner *loc1* of *bbox1* to corner + *loc2* of *bbox2*, where parameters behave as documented as for the + `.BboxConnector` constructor. """ if isinstance(bbox1, Rectangle): bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform()) @@ -240,47 +148,38 @@ def connect_bbox(bbox1, bbox2, loc1, loc2=None): x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2) return Path([[x1, y1], [x2, y2]]) - @docstring.dedent_interpd + @_docstring.interpd def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs): """ Connect two bboxes with a straight line. Parameters ---------- - bbox1, bbox2 : `matplotlib.transforms.Bbox` + bbox1, bbox2 : `~matplotlib.transforms.Bbox` Bounding boxes to connect. - loc1 : {1, 2, 3, 4} - Corner of *bbox1* to draw the line. Valid values are:: + loc1, loc2 : {1, 2, 3, 4} + Corner of *bbox1* and *bbox2* to draw the line. Valid values are:: 'upper right' : 1, 'upper left' : 2, 'lower left' : 3, 'lower right' : 4 - loc2 : {1, 2, 3, 4}, optional - Corner of *bbox2* to draw the line. If None, defaults to *loc1*. - Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 + *loc2* is optional and defaults to *loc1*. **kwargs Patch properties for the line drawn. Valid arguments include: - %(Patch)s + %(Patch:kwdoc)s """ if "transform" in kwargs: raise ValueError("transform should not be set") kwargs["transform"] = IdentityTransform() - if 'fill' in kwargs: - super().__init__(**kwargs) - else: - fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs)) - super().__init__(fill=fill, **kwargs) + kwargs.setdefault( + "fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs))) + super().__init__(**kwargs) self.bbox1 = bbox1 self.bbox2 = bbox2 self.loc1 = loc1 @@ -293,7 +192,7 @@ def get_path(self): class BboxConnectorPatch(BboxConnector): - @docstring.dedent_interpd + @_docstring.interpd def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs): """ Connect two bboxes with a quadrilateral. @@ -305,21 +204,13 @@ def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs): Parameters ---------- - bbox1, bbox2 : `matplotlib.transforms.Bbox` + bbox1, bbox2 : `~matplotlib.transforms.Bbox` Bounding boxes to connect. - loc1a, loc2a : {1, 2, 3, 4} - Corners of *bbox1* and *bbox2* to draw the first line. - Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - loc1b, loc2b : {1, 2, 3, 4} - Corners of *bbox1* and *bbox2* to draw the second line. - Valid values are:: + loc1a, loc2a, loc1b, loc2b : {1, 2, 3, 4} + The first line connects corners *loc1a* of *bbox1* and *loc2a* of + *bbox2*; the second line connects corners *loc1b* of *bbox1* and + *loc2b* of *bbox2*. Valid values are:: 'upper right' : 1, 'upper left' : 2, @@ -329,7 +220,7 @@ def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs): **kwargs Patch properties for the line drawn: - %(Patch)s + %(Patch:kwdoc)s """ if "transform" in kwargs: raise ValueError("transform should not be set") @@ -346,17 +237,23 @@ def get_path(self): return Path(path_merged) -def _add_inset_axes(parent_axes, inset_axes): - """Helper function to add an inset axes and disable navigation in it""" - parent_axes.figure.add_axes(inset_axes) - inset_axes.set_navigate(False) +def _add_inset_axes(parent_axes, axes_class, axes_kwargs, axes_locator): + """Helper function to add an inset axes and disable navigation in it.""" + if axes_class is None: + axes_class = HostAxes + if axes_kwargs is None: + axes_kwargs = {} + fig = parent_axes.get_figure(root=False) + inset_axes = axes_class( + fig, parent_axes.get_position(), + **{"navigate": False, **axes_kwargs, "axes_locator": axes_locator}) + return fig.add_axes(inset_axes) -@docstring.dedent_interpd +@_docstring.interpd def inset_axes(parent_axes, width, height, loc='upper right', bbox_to_anchor=None, bbox_transform=None, - axes_class=None, - axes_kwargs=None, + axes_class=None, axes_kwargs=None, borderpad=0.5): """ Create an inset axes with a given width and height. @@ -364,7 +261,7 @@ def inset_axes(parent_axes, width, height, loc='upper right', Both sizes used can be specified either in inches or percentage. For example,:: - inset_axes(parent_axes, width='40%%', height='30%%', loc=3) + inset_axes(parent_axes, width='40%%', height='30%%', loc='lower left') creates in inset axes in the lower left corner of *parent_axes* which spans over 30%% in height and 40%% in width of the *parent_axes*. Since the usage @@ -401,24 +298,18 @@ def inset_axes(parent_axes, width, height, loc='upper right', the size in inches, e.g. *width=1.3*. If a string is provided, it is the size in relative units, e.g. *width='40%%'*. By default, i.e. if neither *bbox_to_anchor* nor *bbox_transform* are specified, those - are relative to the parent_axes. Otherwise they are to be understood + are relative to the parent_axes. Otherwise, they are to be understood relative to the bounding box provided via *bbox_to_anchor*. - loc : int or str, default: 1 - Location to place the inset axes. The valid locations are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - - bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional + loc : str, default: 'upper right' + Location to place the inset axes. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. + + bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional Bbox that the inset axes will be anchored to. If None, a tuple of (0, 0, 1, 1) is used if *bbox_transform* is set to *parent_axes.transAxes* or *parent_axes.figure.transFigure*. @@ -432,7 +323,7 @@ def inset_axes(parent_axes, width, height, loc='upper right', a *bbox_transform*. This might often be the axes transform *parent_axes.transAxes*. - bbox_transform : `matplotlib.transforms.Transform`, optional + bbox_transform : `~matplotlib.transforms.Transform`, optional Transformation for the bbox that contains the inset axes. If None, a `.transforms.IdentityTransform` is used. The value of *bbox_to_anchor* (or the return value of its get_points method) @@ -441,15 +332,14 @@ def inset_axes(parent_axes, width, height, loc='upper right', You may provide *bbox_to_anchor* in some normalized coordinate, and give an appropriate transform (e.g., *parent_axes.transAxes*). - axes_class : `matplotlib.axes.Axes` type, optional - If specified, the inset axes created will be created with this class's - constructor. + axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes` + The type of the newly created inset axes. axes_kwargs : dict, optional - Keyworded arguments to pass to the constructor of the inset axes. + Keyword arguments to pass to the constructor of the inset axes. Valid arguments include: - %(Axes)s + %(Axes:kwdoc)s borderpad : float, default: 0.5 Padding between inset axes and the bbox_to_anchor. @@ -462,52 +352,32 @@ def inset_axes(parent_axes, width, height, loc='upper right', Inset axes object created. """ - if axes_class is None: - axes_class = HostAxes - - if axes_kwargs is None: - inset_axes = axes_class(parent_axes.figure, parent_axes.get_position()) - else: - inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(), - **axes_kwargs) - - if bbox_transform in [parent_axes.transAxes, - parent_axes.figure.transFigure]: - if bbox_to_anchor is None: - cbook._warn_external("Using the axes or figure transform " - "requires a bounding box in the respective " - "coordinates. " - "Using bbox_to_anchor=(0, 0, 1, 1) now.") - bbox_to_anchor = (0, 0, 1, 1) - + if (bbox_transform in [parent_axes.transAxes, + parent_axes.get_figure(root=False).transFigure] + and bbox_to_anchor is None): + _api.warn_external("Using the axes or figure transform requires a " + "bounding box in the respective coordinates. " + "Using bbox_to_anchor=(0, 0, 1, 1) now.") + bbox_to_anchor = (0, 0, 1, 1) if bbox_to_anchor is None: bbox_to_anchor = parent_axes.bbox - if (isinstance(bbox_to_anchor, tuple) and (isinstance(width, str) or isinstance(height, str))): if len(bbox_to_anchor) != 4: raise ValueError("Using relative units for width or height " "requires to provide a 4-tuple or a " "`Bbox` instance to `bbox_to_anchor.") + return _add_inset_axes( + parent_axes, axes_class, axes_kwargs, + AnchoredSizeLocator( + bbox_to_anchor, width, height, loc=loc, + bbox_transform=bbox_transform, borderpad=borderpad)) - axes_locator = AnchoredSizeLocator(bbox_to_anchor, - width, height, - loc=loc, - bbox_transform=bbox_transform, - borderpad=borderpad) - - inset_axes.set_axes_locator(axes_locator) - - _add_inset_axes(parent_axes, inset_axes) - - return inset_axes - -@docstring.dedent_interpd +@_docstring.interpd def zoomed_inset_axes(parent_axes, zoom, loc='upper right', bbox_to_anchor=None, bbox_transform=None, - axes_class=None, - axes_kwargs=None, + axes_class=None, axes_kwargs=None, borderpad=0.5): """ Create an anchored inset axes by scaling a parent axes. For usage, also see @@ -515,29 +385,23 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', Parameters ---------- - parent_axes : `matplotlib.axes.Axes` + parent_axes : `~matplotlib.axes.Axes` Axes to place the inset axes. zoom : float - Scaling factor of the data axes. *zoom* > 1 will enlargen the + Scaling factor of the data axes. *zoom* > 1 will enlarge the coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the coordinates (i.e., "zoomed out"). - loc : int or str, default: 'upper right' - Location to place the inset axes. The valid locations are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4, - 'right' : 5, - 'center left' : 6, - 'center right' : 7, - 'lower center' : 8, - 'upper center' : 9, - 'center' : 10 - - bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional + loc : str, default: 'upper right' + Location to place the inset axes. Valid locations are + 'upper left', 'upper center', 'upper right', + 'center left', 'center', 'center right', + 'lower left', 'lower center', 'lower right'. + For backward compatibility, numeric values are accepted as well. + See the parameter *loc* of `.Legend` for details. + + bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional Bbox that the inset axes will be anchored to. If None, *parent_axes.bbox* is used. If a tuple, can be either [left, bottom, width, height], or [left, bottom]. @@ -548,7 +412,7 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', also specify a *bbox_transform*. This might often be the axes transform *parent_axes.transAxes*. - bbox_transform : `matplotlib.transforms.Transform`, optional + bbox_transform : `~matplotlib.transforms.Transform`, optional Transformation for the bbox that contains the inset axes. If None, a `.transforms.IdentityTransform` is used (i.e. pixel coordinates). This is useful when not providing any argument to @@ -559,15 +423,14 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', *bbox_to_anchor* will use *parent_axes.bbox*, the units of which are in display (pixel) coordinates. - axes_class : `matplotlib.axes.Axes` type, optional - If specified, the inset axes created will be created with this class's - constructor. + axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes` + The type of the newly created inset axes. axes_kwargs : dict, optional - Keyworded arguments to pass to the constructor of the inset axes. + Keyword arguments to pass to the constructor of the inset axes. Valid arguments include: - %(Axes)s + %(Axes:kwdoc)s borderpad : float, default: 0.5 Padding between inset axes and the bbox_to_anchor. @@ -580,27 +443,31 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', Inset axes object created. """ - if axes_class is None: - axes_class = HostAxes + return _add_inset_axes( + parent_axes, axes_class, axes_kwargs, + AnchoredZoomLocator( + parent_axes, zoom=zoom, loc=loc, + bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform, + borderpad=borderpad)) - if axes_kwargs is None: - inset_axes = axes_class(parent_axes.figure, parent_axes.get_position()) - else: - inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(), - **axes_kwargs) - axes_locator = AnchoredZoomLocator(parent_axes, zoom=zoom, loc=loc, - bbox_to_anchor=bbox_to_anchor, - bbox_transform=bbox_transform, - borderpad=borderpad) - inset_axes.set_axes_locator(axes_locator) +class _TransformedBboxWithCallback(TransformedBbox): + """ + Variant of `.TransformBbox` which calls *callback* before returning points. - _add_inset_axes(parent_axes, inset_axes) + Used by `.mark_inset` to unstale the parent axes' viewlim as needed. + """ - return inset_axes + def __init__(self, *args, callback, **kwargs): + super().__init__(*args, **kwargs) + self._callback = callback + def get_points(self): + self._callback() + return super().get_points() -@docstring.dedent_interpd + +@_docstring.interpd def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs): """ Draw a box to mark the location of an area represented by an inset axes. @@ -611,10 +478,10 @@ def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs): Parameters ---------- - parent_axes : `matplotlib.axes.Axes` + parent_axes : `~matplotlib.axes.Axes` Axes which contains the area of the inset axes. - inset_axes : `matplotlib.axes.Axes` + inset_axes : `~matplotlib.axes.Axes` The inset axes. loc1, loc2 : {1, 2, 3, 4} @@ -624,23 +491,22 @@ def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs): **kwargs Patch properties for the lines and box drawn: - %(Patch)s + %(Patch:kwdoc)s Returns ------- - pp : `matplotlib.patches.Patch` + pp : `~matplotlib.patches.Patch` The patch drawn to represent the area of the inset axes. - p1, p2 : `matplotlib.patches.Patch` + p1, p2 : `~matplotlib.patches.Patch` The patches connecting two corners of the inset axes and its area. """ - rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData) + rect = _TransformedBboxWithCallback( + inset_axes.viewLim, parent_axes.transData, + callback=parent_axes._unstale_viewLim) - if 'fill' in kwargs: - pp = BboxPatch(rect, **kwargs) - else: - fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs)) - pp = BboxPatch(rect, fill=fill, **kwargs) + kwargs.setdefault("fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs))) + pp = BboxPatch(rect, **kwargs) parent_axes.add_patch(pp) p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs) diff --git a/lib/mpl_toolkits/axes_grid1/meson.build b/lib/mpl_toolkits/axes_grid1/meson.build new file mode 100644 index 000000000000..7dd5c3861163 --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/meson.build @@ -0,0 +1,15 @@ +python_sources = [ + '__init__.py', + 'anchored_artists.py', + 'axes_divider.py', + 'axes_grid.py', + 'axes_rgb.py', + 'axes_size.py', + 'inset_locator.py', + 'mpl_axes.py', + 'parasite_axes.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axes_grid1') + +subdir('tests') diff --git a/lib/mpl_toolkits/axes_grid1/mpl_axes.py b/lib/mpl_toolkits/axes_grid1/mpl_axes.py index 3bb73770a07b..51c8748758cb 100644 --- a/lib/mpl_toolkits/axes_grid1/mpl_axes.py +++ b/lib/mpl_toolkits/axes_grid1/mpl_axes.py @@ -40,9 +40,14 @@ def __getitem__(self, k): def __call__(self, *v, **kwargs): return maxes.Axes.axis(self.axes, *v, **kwargs) - def _init_axis_artists(self, axes=None): - if axes is None: - axes = self + @property + def axis(self): + return self._axislines + + def clear(self): + # docstring inherited + super().clear() + # Init axis artists. self._axislines = self.AxisDict(self) self._axislines.update( bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]), @@ -50,14 +55,6 @@ def _init_axis_artists(self, axes=None): left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]), right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"])) - @property - def axis(self): - return self._axislines - - def cla(self): - super().cla() - self._init_axis_artists() - class SimpleAxisArtist(Artist): def __init__(self, axis, axisnum, spine): @@ -115,14 +112,11 @@ def toggle(self, all=None, ticks=None, ticklabels=None, label=None): if label is not None: _label = label - tickOn = "tick%dOn" % self._axisnum - labelOn = "label%dOn" % self._axisnum - if _ticks is not None: - tickparam = {tickOn: _ticks} + tickparam = {f"tick{self._axisnum}On": _ticks} self._axis.set_tick_params(**tickparam) if _ticklabels is not None: - tickparam = {labelOn: _ticklabels} + tickparam = {f"label{self._axisnum}On": _ticklabels} self._axis.set_tick_params(**tickparam) if _label is not None: diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index fc64e7c3f135..fbc6e8141272 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -1,40 +1,32 @@ -import functools - -from matplotlib import artist as martist, cbook, transforms as mtransforms -from matplotlib.axes import subplot_class_factory +from matplotlib import _api, cbook +import matplotlib.artist as martist +import matplotlib.transforms as mtransforms from matplotlib.transforms import Bbox from .mpl_axes import Axes -import numpy as np - class ParasiteAxesBase: - def get_images_artists(self): - artists = {a for a in self.get_children() if a.get_visible()} - images = {a for a in self.images if a.get_visible()} - - return list(images), list(artists - images) - - def __init__(self, parent_axes, **kwargs): + def __init__(self, parent_axes, aux_transform=None, + *, viewlim_mode=None, **kwargs): self._parent_axes = parent_axes + self.transAux = aux_transform + self.set_viewlim_mode(viewlim_mode) kwargs["frameon"] = False - super().__init__(parent_axes.figure, parent_axes._position, **kwargs) - - def cla(self): - super().cla() + super().__init__(parent_axes.get_figure(root=False), + parent_axes._position, **kwargs) + def clear(self): + super().clear() martist.setp(self.get_children(), visible=False) self._get_lines = self._parent_axes._get_lines + self._parent_axes.callbacks._connect_picklable( + "xlim_changed", self._sync_lims) + self._parent_axes.callbacks._connect_picklable( + "ylim_changed", self._sync_lims) - # In mpl's Axes, zorders of x- and y-axis are originally set - # within Axes.draw(). - if self._axisbelow: - self.xaxis.set_zorder(0.5) - self.yaxis.set_zorder(0.5) - else: - self.xaxis.set_zorder(2.5) - self.yaxis.set_zorder(2.5) + def get_axes_locator(self): + return self._parent_axes.get_axes_locator() def pick(self, mouseevent): # This most likely goes to Artist.pick (depending on axes_class given @@ -48,154 +40,44 @@ def pick(self, mouseevent): and self in mouseevent.inaxes.parasites): a.pick(mouseevent) - -@functools.lru_cache(None) -def parasite_axes_class_factory(axes_class=None): - if axes_class is None: - cbook.warn_deprecated( - "3.3", message="Support for passing None to " - "parasite_axes_class_factory is deprecated since %(since)s and " - "will be removed %(removal)s; explicitly pass the default Axes " - "class instead.") - axes_class = Axes - - return type("%sParasite" % axes_class.__name__, - (ParasiteAxesBase, axes_class), {}) - - -ParasiteAxes = parasite_axes_class_factory(Axes) - - -class ParasiteAxesAuxTransBase: - def __init__(self, parent_axes, aux_transform, viewlim_mode=None, - **kwargs): - self.transAux = aux_transform - self.set_viewlim_mode(viewlim_mode) - super().__init__(parent_axes, **kwargs) + # aux_transform support def _set_lim_and_transforms(self): - - self.transAxes = self._parent_axes.transAxes - - self.transData = \ - self.transAux + \ - self._parent_axes.transData - - self._xaxis_transform = mtransforms.blended_transform_factory( + if self.transAux is not None: + self.transAxes = self._parent_axes.transAxes + self.transData = self.transAux + self._parent_axes.transData + self._xaxis_transform = mtransforms.blended_transform_factory( self.transData, self.transAxes) - self._yaxis_transform = mtransforms.blended_transform_factory( + self._yaxis_transform = mtransforms.blended_transform_factory( self.transAxes, self.transData) + else: + super()._set_lim_and_transforms() def set_viewlim_mode(self, mode): - cbook._check_in_list([None, "equal", "transform"], mode=mode) + _api.check_in_list([None, "equal", "transform"], mode=mode) self._viewlim_mode = mode def get_viewlim_mode(self): return self._viewlim_mode - def update_viewlim(self): - viewlim = self._parent_axes.viewLim.frozen() + def _sync_lims(self, parent): + viewlim = parent.viewLim.frozen() mode = self.get_viewlim_mode() if mode is None: pass elif mode == "equal": - self.axes.viewLim.set(viewlim) + self.viewLim.set(viewlim) elif mode == "transform": - self.axes.viewLim.set( - viewlim.transformed(self.transAux.inverted())) + self.viewLim.set(viewlim.transformed(self.transAux.inverted())) else: - cbook._check_in_list([None, "equal", "transform"], mode=mode) - - def _pcolor(self, super_pcolor, *XYC, **kwargs): - if len(XYC) == 1: - C = XYC[0] - ny, nx = C.shape + _api.check_in_list([None, "equal", "transform"], mode=mode) - gx = np.arange(-0.5, nx) - gy = np.arange(-0.5, ny) + # end of aux_transform support - X, Y = np.meshgrid(gx, gy) - else: - X, Y, C = XYC - - if "transform" in kwargs: - mesh = super_pcolor(X, Y, C, **kwargs) - else: - orig_shape = X.shape - xyt = np.column_stack([X.flat, Y.flat]) - wxy = self.transAux.transform(xyt) - gx = wxy[:, 0].reshape(orig_shape) - gy = wxy[:, 1].reshape(orig_shape) - mesh = super_pcolor(gx, gy, C, **kwargs) - mesh.set_transform(self._parent_axes.transData) - return mesh - - def pcolormesh(self, *XYC, **kwargs): - return self._pcolor(super().pcolormesh, *XYC, **kwargs) - - def pcolor(self, *XYC, **kwargs): - return self._pcolor(super().pcolor, *XYC, **kwargs) - - def _contour(self, super_contour, *XYCL, **kwargs): - - if len(XYCL) <= 2: - C = XYCL[0] - ny, nx = C.shape - - gx = np.arange(0., nx) - gy = np.arange(0., ny) - - X, Y = np.meshgrid(gx, gy) - CL = XYCL - else: - X, Y = XYCL[:2] - CL = XYCL[2:] - - if "transform" in kwargs: - cont = super_contour(X, Y, *CL, **kwargs) - else: - orig_shape = X.shape - xyt = np.column_stack([X.flat, Y.flat]) - wxy = self.transAux.transform(xyt) - gx = wxy[:, 0].reshape(orig_shape) - gy = wxy[:, 1].reshape(orig_shape) - cont = super_contour(gx, gy, *CL, **kwargs) - for c in cont.collections: - c.set_transform(self._parent_axes.transData) - - return cont - - def contour(self, *XYCL, **kwargs): - return self._contour(super().contour, *XYCL, **kwargs) - - def contourf(self, *XYCL, **kwargs): - return self._contour(super().contourf, *XYCL, **kwargs) - - def apply_aspect(self, position=None): - self.update_viewlim() - super().apply_aspect() - - -@functools.lru_cache(None) -def parasite_axes_auxtrans_class_factory(axes_class=None): - if axes_class is None: - cbook.warn_deprecated( - "3.3", message="Support for passing None to " - "parasite_axes_auxtrans_class_factory is deprecated since " - "%(since)s and will be removed %(removal)s; explicitly pass the " - "default ParasiteAxes class instead.") - parasite_axes_class = ParasiteAxes - elif not issubclass(axes_class, ParasiteAxesBase): - parasite_axes_class = parasite_axes_class_factory(axes_class) - else: - parasite_axes_class = axes_class - return type("%sParasiteAuxTrans" % parasite_axes_class.__name__, - (ParasiteAxesAuxTransBase, parasite_axes_class), - {'name': 'parasite_axes'}) - - -ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes) +parasite_axes_class_factory = cbook._make_class_factory( + ParasiteAxesBase, "{}Parasite") +ParasiteAxes = parasite_axes_class_factory(Axes) class HostAxesBase: @@ -203,53 +85,67 @@ def __init__(self, *args, **kwargs): self.parasites = [] super().__init__(*args, **kwargs) - def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=ParasiteAxes): - parasite_axes_class = parasite_axes_auxtrans_class_factory(axes_class) - ax2 = parasite_axes_class(self, tr, viewlim_mode) + def get_aux_axes( + self, tr=None, viewlim_mode="equal", axes_class=None, **kwargs): + """ + Add a parasite axes to this host. + + Despite this method's name, this should actually be thought of as an + ``add_parasite_axes`` method. + + .. versionchanged:: 3.7 + Defaults to same base axes class as host axes. + + Parameters + ---------- + tr : `~matplotlib.transforms.Transform` or None, default: None + If a `.Transform`, the following relation will hold: + ``parasite.transData = tr + host.transData``. + If None, the parasite's and the host's ``transData`` are unrelated. + viewlim_mode : {"equal", "transform", None}, default: "equal" + How the parasite's view limits are set: directly equal to the + parent axes ("equal"), equal after application of *tr* + ("transform"), or independently (None). + axes_class : subclass type of `~matplotlib.axes.Axes`, optional + The `~.axes.Axes` subclass that is instantiated. If None, the base + class of the host axes is used. + **kwargs + Other parameters are forwarded to the parasite axes constructor. + """ + if axes_class is None: + axes_class = self._base_axes_class + parasite_axes_class = parasite_axes_class_factory(axes_class) + ax2 = parasite_axes_class( + self, tr, viewlim_mode=viewlim_mode, **kwargs) # note that ax2.transData == tr + ax1.transData # Anything you draw in ax2 will match the ticks and grids of ax1. self.parasites.append(ax2) ax2._remove_method = self.parasites.remove return ax2 - def _get_legend_handles(self, legend_handler_map=None): - all_handles = super()._get_legend_handles() - for ax in self.parasites: - all_handles.extend(ax._get_legend_handles(legend_handler_map)) - return all_handles - def draw(self, renderer): + orig_children_len = len(self._children) - orig_artists = list(self.artists) - orig_images = list(self.images) - - if hasattr(self, "get_axes_locator"): - locator = self.get_axes_locator() - if locator: - pos = locator(self, renderer) - self.set_position(pos, which="active") - self.apply_aspect(pos) - else: - self.apply_aspect() + locator = self.get_axes_locator() + if locator: + pos = locator(self, renderer) + self.set_position(pos, which="active") + self.apply_aspect(pos) else: self.apply_aspect() rect = self.get_position() - for ax in self.parasites: ax.apply_aspect(rect) - images, artists = ax.get_images_artists() - self.images.extend(images) - self.artists.extend(artists) + self._children.extend(ax.get_children()) super().draw(renderer) - self.artists = orig_artists - self.images = orig_images + del self._children[orig_children_len:] - def cla(self): + def clear(self): + super().clear() for ax in self.parasites: - ax.cla() - super().cla() + ax.clear() def pick(self, mouseevent): super().pick(mouseevent) @@ -265,26 +161,11 @@ def twinx(self, axes_class=None): The y-axis of self will have ticks on the left and the returned axes will have ticks on the right. """ - if axes_class is None: - axes_class = self._get_base_axes() - - parasite_axes_class = parasite_axes_class_factory(axes_class) - - ax2 = parasite_axes_class(self, sharex=self, frameon=False) - self.parasites.append(ax2) - ax2._remove_method = self._remove_twinx - + ax = self._add_twin_axes(axes_class, sharex=self) self.axis["right"].set_visible(False) - - ax2.axis["right"].set_visible(True) - ax2.axis["left", "top", "bottom"].set_visible(False) - - return ax2 - - def _remove_twinx(self, ax): - self.parasites.remove(ax) - self.axis["right"].set_visible(True) - self.axis["right"].toggle(ticklabels=False, label=False) + ax.axis["right"].set_visible(True) + ax.axis["left", "top", "bottom"].set_visible(False) + return ax def twiny(self, axes_class=None): """ @@ -293,26 +174,11 @@ def twiny(self, axes_class=None): The x-axis of self will have ticks on the bottom and the returned axes will have ticks on the top. """ - if axes_class is None: - axes_class = self._get_base_axes() - - parasite_axes_class = parasite_axes_class_factory(axes_class) - - ax2 = parasite_axes_class(self, sharey=self, frameon=False) - self.parasites.append(ax2) - ax2._remove_method = self._remove_twiny - + ax = self._add_twin_axes(axes_class, sharey=self) self.axis["top"].set_visible(False) - - ax2.axis["top"].set_visible(True) - ax2.axis["left", "right", "bottom"].set_visible(False) - - return ax2 - - def _remove_twiny(self, ax): - self.parasites.remove(ax) - self.axis["top"].set_visible(True) - self.axis["top"].toggle(ticklabels=False, label=False) + ax.axis["top"].set_visible(True) + ax.axis["left", "right", "bottom"].set_visible(False) + return ax def twin(self, aux_trans=None, axes_class=None): """ @@ -321,35 +187,39 @@ def twin(self, aux_trans=None, axes_class=None): While self will have ticks on the left and bottom axis, the returned axes will have ticks on the top and right axis. """ - if axes_class is None: - axes_class = self._get_base_axes() - - parasite_axes_auxtrans_class = \ - parasite_axes_auxtrans_class_factory(axes_class) - if aux_trans is None: - ax2 = parasite_axes_auxtrans_class( - self, mtransforms.IdentityTransform(), viewlim_mode="equal") - else: - ax2 = parasite_axes_auxtrans_class( - self, aux_trans, viewlim_mode="transform") - self.parasites.append(ax2) - ax2._remove_method = self.parasites.remove - + aux_trans = mtransforms.IdentityTransform() + ax = self._add_twin_axes( + axes_class, aux_transform=aux_trans, viewlim_mode="transform") self.axis["top", "right"].set_visible(False) + ax.axis["top", "right"].set_visible(True) + ax.axis["left", "bottom"].set_visible(False) + return ax - ax2.axis["top", "right"].set_visible(True) - ax2.axis["left", "bottom"].set_visible(False) - - def _remove_method(h): - self.parasites.remove(h) - self.axis["top", "right"].set_visible(True) - self.axis["top", "right"].toggle(ticklabels=False, label=False) - ax2._remove_method = _remove_method + def _add_twin_axes(self, axes_class, **kwargs): + """ + Helper for `.twinx`/`.twiny`/`.twin`. - return ax2 + *kwargs* are forwarded to the parasite axes constructor. + """ + if axes_class is None: + axes_class = self._base_axes_class + ax = parasite_axes_class_factory(axes_class)(self, **kwargs) + self.parasites.append(ax) + ax._remove_method = self._remove_any_twin + return ax - def get_tightbbox(self, renderer, call_axes_locator=True, + def _remove_any_twin(self, ax): + self.parasites.remove(ax) + restore = ["top", "right"] + if ax._sharex: + restore.remove("top") + if ax._sharey: + restore.remove("right") + self.axis[tuple(restore)].set_visible(True) + self.axis[tuple(restore)].toggle(ticklabels=False, label=False) + + def get_tightbbox(self, renderer=None, *, call_axes_locator=True, bbox_extra_artists=None): bbs = [ *[ax.get_tightbbox(renderer, call_axes_locator=call_axes_locator) @@ -360,31 +230,9 @@ def get_tightbbox(self, renderer, call_axes_locator=True, return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0]) -@functools.lru_cache(None) -def host_axes_class_factory(axes_class=None): - if axes_class is None: - cbook.warn_deprecated( - "3.3", message="Support for passing None to host_axes_class is " - "deprecated since %(since)s and will be removed %(removed)s; " - "explicitly pass the default Axes class instead.") - axes_class = Axes - - def _get_base_axes(self): - return axes_class - - return type("%sHostAxes" % axes_class.__name__, - (HostAxesBase, axes_class), - {'_get_base_axes': _get_base_axes}) - - -def host_subplot_class_factory(axes_class): - host_axes_class = host_axes_class_factory(axes_class) - subplot_host_class = subplot_class_factory(host_axes_class) - return subplot_host_class - - -HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) +host_axes_class_factory = host_subplot_class_factory = \ + cbook._make_class_factory(HostAxesBase, "{}HostAxes", "_base_axes_class") +HostAxes = SubplotHost = host_axes_class_factory(Axes) def host_axes(*args, axes_class=Axes, figure=None, **kwargs): @@ -393,12 +241,12 @@ def host_axes(*args, axes_class=Axes, figure=None, **kwargs): Parameters ---------- - figure : `matplotlib.figure.Figure` + figure : `~matplotlib.figure.Figure` Figure to which the axes will be added. Defaults to the current figure `.pyplot.gcf()`. *args, **kwargs - Will be passed on to the underlying ``Axes`` object creation. + Will be passed on to the underlying `~.axes.Axes` object creation. """ import matplotlib.pyplot as plt host_axes_class = host_axes_class_factory(axes_class) @@ -406,28 +254,7 @@ def host_axes(*args, axes_class=Axes, figure=None, **kwargs): figure = plt.gcf() ax = host_axes_class(figure, *args, **kwargs) figure.add_axes(ax) - plt.draw_if_interactive() return ax -def host_subplot(*args, axes_class=Axes, figure=None, **kwargs): - """ - Create a subplot that can act as a host to parasitic axes. - - Parameters - ---------- - figure : `matplotlib.figure.Figure` - Figure to which the subplot will be added. Defaults to the current - figure `.pyplot.gcf()`. - - *args, **kwargs - Will be passed on to the underlying ``Axes`` object creation. - """ - import matplotlib.pyplot as plt - host_subplot_class = host_subplot_class_factory(axes_class) - if figure is None: - figure = plt.gcf() - ax = host_subplot_class(figure, *args, **kwargs) - figure.add_subplot(ax) - plt.draw_if_interactive() - return ax +host_subplot = host_axes diff --git a/lib/mpl_toolkits/axes_grid1/tests/__init__.py b/lib/mpl_toolkits/axes_grid1/tests/__init__.py new file mode 100644 index 000000000000..ea4d8ed16a6a --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/tests/__init__.py @@ -0,0 +1,10 @@ +from pathlib import Path + + +# Check that the test directories exist +if not (Path(__file__).parent / "baseline_images").exists(): + raise OSError( + 'The baseline image directory does not exist. ' + 'This is most likely because the test data is not installed. ' + 'You may need to install matplotlib from source to get the ' + 'test data.') diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_artists.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_artists.png new file mode 100644 index 000000000000..8729ba90f148 Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_artists.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_locator_base_call.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_locator_base_call.png new file mode 100644 index 000000000000..31c63d7df718 Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_locator_base_call.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/fill_facecolor.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/fill_facecolor.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/fill_facecolor.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/fill_facecolor.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/image_grid.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/image_grid.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_each_left_label_mode_all.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_each_left_label_mode_all.png new file mode 100644 index 000000000000..f9a4524b5812 Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_each_left_label_mode_all.png differ diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_single_bottom_label_mode_1.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_single_bottom_label_mode_1.png new file mode 100644 index 000000000000..1a0f4cd1fc9a Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_single_bottom_label_mode_1.png differ diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png new file mode 100644 index 000000000000..9cb576faa49a Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_axes.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_axes.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_locator.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_locator.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/rgb_axes.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/rgb_axes.png new file mode 100644 index 000000000000..5cf6dc7e35c0 Binary files /dev/null and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/rgb_axes.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/zoomed_axes.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/zoomed_axes.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axes_grid1/zoomed_axes.png rename to lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/zoomed_axes.png diff --git a/lib/mpl_toolkits/axes_grid1/tests/conftest.py b/lib/mpl_toolkits/axes_grid1/tests/conftest.py new file mode 100644 index 000000000000..61c2de3e07ba --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/tests/conftest.py @@ -0,0 +1,2 @@ +from matplotlib.testing.conftest import (mpl_test_settings, # noqa + pytest_configure, pytest_unconfigure) diff --git a/lib/mpl_toolkits/axes_grid1/tests/meson.build b/lib/mpl_toolkits/axes_grid1/tests/meson.build new file mode 100644 index 000000000000..980bf173cc10 --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/tests/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_axes_grid1.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axes_grid1/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/axes_grid1/tests')) diff --git a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py new file mode 100644 index 000000000000..26f0aaa37de0 --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py @@ -0,0 +1,808 @@ +from itertools import product +import io +import platform + +import matplotlib as mpl +import matplotlib.pyplot as plt +import matplotlib.ticker as mticker +from matplotlib import cbook +from matplotlib.backend_bases import MouseEvent +from matplotlib.colors import LogNorm +from matplotlib.patches import Circle, Ellipse +from matplotlib.transforms import Affine2D, Bbox, TransformedBbox +from matplotlib.testing.decorators import ( + check_figures_equal, image_comparison, remove_ticks_and_titles) + +from mpl_toolkits.axes_grid1 import ( + axes_size as Size, + host_subplot, make_axes_locatable, + Grid, AxesGrid, ImageGrid) +from mpl_toolkits.axes_grid1.anchored_artists import ( + AnchoredAuxTransformBox, AnchoredDrawingArea, + AnchoredDirectionArrows, AnchoredSizeBar) +from mpl_toolkits.axes_grid1.axes_divider import ( + Divider, HBoxDivider, make_axes_area_auto_adjustable, SubplotDivider, + VBoxDivider) +from mpl_toolkits.axes_grid1.axes_rgb import RGBAxes +from mpl_toolkits.axes_grid1.inset_locator import ( + zoomed_inset_axes, mark_inset, inset_axes, BboxConnectorPatch) +from mpl_toolkits.axes_grid1.parasite_axes import HostAxes +import mpl_toolkits.axes_grid1.mpl_axes +import pytest + +import numpy as np +from numpy.testing import assert_array_equal, assert_array_almost_equal + + +def test_divider_append_axes(): + fig, ax = plt.subplots() + divider = make_axes_locatable(ax) + axs = { + "main": ax, + "top": divider.append_axes("top", 1.2, pad=0.1, sharex=ax), + "bottom": divider.append_axes("bottom", 1.2, pad=0.1, sharex=ax), + "left": divider.append_axes("left", 1.2, pad=0.1, sharey=ax), + "right": divider.append_axes("right", 1.2, pad=0.1, sharey=ax), + } + fig.canvas.draw() + bboxes = {k: axs[k].get_window_extent() for k in axs} + dpi = fig.dpi + assert bboxes["top"].height == pytest.approx(1.2 * dpi) + assert bboxes["bottom"].height == pytest.approx(1.2 * dpi) + assert bboxes["left"].width == pytest.approx(1.2 * dpi) + assert bboxes["right"].width == pytest.approx(1.2 * dpi) + assert bboxes["top"].y0 - bboxes["main"].y1 == pytest.approx(0.1 * dpi) + assert bboxes["main"].y0 - bboxes["bottom"].y1 == pytest.approx(0.1 * dpi) + assert bboxes["main"].x0 - bboxes["left"].x1 == pytest.approx(0.1 * dpi) + assert bboxes["right"].x0 - bboxes["main"].x1 == pytest.approx(0.1 * dpi) + assert bboxes["left"].y0 == bboxes["main"].y0 == bboxes["right"].y0 + assert bboxes["left"].y1 == bboxes["main"].y1 == bboxes["right"].y1 + assert bboxes["top"].x0 == bboxes["main"].x0 == bboxes["bottom"].x0 + assert bboxes["top"].x1 == bboxes["main"].x1 == bboxes["bottom"].x1 + + +# Update style when regenerating the test image +@image_comparison(['twin_axes_empty_and_removed'], extensions=["png"], tol=1, + style=('classic', '_classic_test_patch')) +def test_twin_axes_empty_and_removed(): + # Purely cosmetic font changes (avoid overlap) + mpl.rcParams.update( + {"font.size": 8, "xtick.labelsize": 8, "ytick.labelsize": 8}) + generators = ["twinx", "twiny", "twin"] + modifiers = ["", "host invisible", "twin removed", "twin invisible", + "twin removed\nhost invisible"] + # Unmodified host subplot at the beginning for reference + h = host_subplot(len(modifiers)+1, len(generators), 2) + h.text(0.5, 0.5, "host_subplot", + horizontalalignment="center", verticalalignment="center") + # Host subplots with various modifications (twin*, visibility) applied + for i, (mod, gen) in enumerate(product(modifiers, generators), + len(generators) + 1): + h = host_subplot(len(modifiers)+1, len(generators), i) + t = getattr(h, gen)() + if "twin invisible" in mod: + t.axis[:].set_visible(False) + if "twin removed" in mod: + t.remove() + if "host invisible" in mod: + h.axis[:].set_visible(False) + h.text(0.5, 0.5, gen + ("\n" + mod if mod else ""), + horizontalalignment="center", verticalalignment="center") + plt.subplots_adjust(wspace=0.5, hspace=1) + + +def test_twin_axes_both_with_units(): + host = host_subplot(111) + host.yaxis.axis_date() + host.plot([0, 1, 2], [0, 1, 2]) + twin = host.twinx() + twin.plot(["a", "b", "c"]) + assert host.get_yticklabels()[0].get_text() == "00:00:00" + assert twin.get_yticklabels()[0].get_text() == "a" + + +def test_axesgrid_colorbar_log_smoketest(): + fig = plt.figure() + grid = AxesGrid(fig, 111, # modified to be only subplot + nrows_ncols=(1, 1), + label_mode="L", + cbar_location="top", + cbar_mode="single", + ) + + Z = 10000 * np.random.rand(10, 10) + im = grid[0].imshow(Z, interpolation="nearest", norm=LogNorm()) + + grid.cbar_axes[0].colorbar(im) + + +def test_inset_colorbar_tight_layout_smoketest(): + fig, ax = plt.subplots(1, 1) + pts = ax.scatter([0, 1], [0, 1], c=[1, 5]) + + cax = inset_axes(ax, width="3%", height="70%") + plt.colorbar(pts, cax=cax) + + with pytest.warns(UserWarning, match="This figure includes Axes"): + # Will warn, but not raise an error + plt.tight_layout() + + +@image_comparison(['inset_locator.png'], style='default', remove_text=True) +def test_inset_locator(): + fig, ax = plt.subplots(figsize=[5, 4]) + + # prepare the demo image + # Z is a 15x15 array + Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy") + extent = (-3, 4, -4, 3) + Z2 = np.zeros((150, 150)) + ny, nx = Z.shape + Z2[30:30+ny, 30:30+nx] = Z + + ax.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") + + axins = zoomed_inset_axes(ax, zoom=6, loc='upper right') + axins.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") + axins.yaxis.get_major_locator().set_params(nbins=7) + axins.xaxis.get_major_locator().set_params(nbins=7) + # sub region of the original image + x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 + axins.set_xlim(x1, x2) + axins.set_ylim(y1, y2) + + plt.xticks(visible=False) + plt.yticks(visible=False) + + # draw a bbox of the region of the inset axes in the parent axes and + # connecting lines between the bbox and the inset axes area + mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5") + + asb = AnchoredSizeBar(ax.transData, + 0.5, + '0.5', + loc='lower center', + pad=0.1, borderpad=0.5, sep=5, + frameon=False) + ax.add_artist(asb) + + +@image_comparison(['inset_axes.png'], style='default', remove_text=True) +def test_inset_axes(): + fig, ax = plt.subplots(figsize=[5, 4]) + + # prepare the demo image + # Z is a 15x15 array + Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy") + extent = (-3, 4, -4, 3) + Z2 = np.zeros((150, 150)) + ny, nx = Z.shape + Z2[30:30+ny, 30:30+nx] = Z + + ax.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") + + # creating our inset axes with a bbox_transform parameter + axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1), + bbox_transform=ax.transAxes) + + axins.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") + axins.yaxis.get_major_locator().set_params(nbins=7) + axins.xaxis.get_major_locator().set_params(nbins=7) + # sub region of the original image + x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 + axins.set_xlim(x1, x2) + axins.set_ylim(y1, y2) + + plt.xticks(visible=False) + plt.yticks(visible=False) + + # draw a bbox of the region of the inset axes in the parent axes and + # connecting lines between the bbox and the inset axes area + mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5") + + asb = AnchoredSizeBar(ax.transData, + 0.5, + '0.5', + loc='lower center', + pad=0.1, borderpad=0.5, sep=5, + frameon=False) + ax.add_artist(asb) + + +def test_inset_axes_complete(): + dpi = 100 + figsize = (6, 5) + fig, ax = plt.subplots(figsize=figsize, dpi=dpi) + fig.subplots_adjust(.1, .1, .9, .9) + + ins = inset_axes(ax, width=2., height=2., borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, + [(0.9*figsize[0]-2.)/figsize[0], (0.9*figsize[1]-2.)/figsize[1], + 0.9, 0.9]) + + ins = inset_axes(ax, width="40%", height="30%", borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, [.9-.8*.4, .9-.8*.3, 0.9, 0.9]) + + ins = inset_axes(ax, width=1., height=1.2, bbox_to_anchor=(200, 100), + loc=3, borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, + [200/dpi/figsize[0], 100/dpi/figsize[1], + (200/dpi+1)/figsize[0], (100/dpi+1.2)/figsize[1]]) + + ins1 = inset_axes(ax, width="35%", height="60%", loc=3, borderpad=1) + ins2 = inset_axes(ax, width="100%", height="100%", + bbox_to_anchor=(0, 0, .35, .60), + bbox_transform=ax.transAxes, loc=3, borderpad=1) + fig.canvas.draw() + assert_array_equal(ins1.get_position().extents, + ins2.get_position().extents) + + with pytest.raises(ValueError): + ins = inset_axes(ax, width="40%", height="30%", + bbox_to_anchor=(0.4, 0.5)) + + with pytest.warns(UserWarning): + ins = inset_axes(ax, width="40%", height="30%", + bbox_transform=ax.transAxes) + + +def test_inset_axes_tight(): + # gh-26287 found that inset_axes raised with bbox_inches=tight + fig, ax = plt.subplots() + inset_axes(ax, width=1.3, height=0.9) + + f = io.BytesIO() + fig.savefig(f, bbox_inches="tight") + + +@image_comparison(['fill_facecolor.png'], remove_text=True, style='mpl20') +def test_fill_facecolor(): + fig, ax = plt.subplots(1, 5) + fig.set_size_inches(5, 5) + for i in range(1, 4): + ax[i].yaxis.set_visible(False) + ax[4].yaxis.tick_right() + bbox = Bbox.from_extents(0, 0.4, 1, 0.6) + + # fill with blue by setting 'fc' field + bbox1 = TransformedBbox(bbox, ax[0].transData) + bbox2 = TransformedBbox(bbox, ax[1].transData) + # set color to BboxConnectorPatch + p = BboxConnectorPatch( + bbox1, bbox2, loc1a=1, loc2a=2, loc1b=4, loc2b=3, + ec="r", fc="b") + p.set_clip_on(False) + ax[0].add_patch(p) + # set color to marked area + axins = zoomed_inset_axes(ax[0], 1, loc='upper right') + axins.set_xlim(0, 0.2) + axins.set_ylim(0, 0.2) + plt.gca().axes.xaxis.set_ticks([]) + plt.gca().axes.yaxis.set_ticks([]) + mark_inset(ax[0], axins, loc1=2, loc2=4, fc="b", ec="0.5") + + # fill with yellow by setting 'facecolor' field + bbox3 = TransformedBbox(bbox, ax[1].transData) + bbox4 = TransformedBbox(bbox, ax[2].transData) + # set color to BboxConnectorPatch + p = BboxConnectorPatch( + bbox3, bbox4, loc1a=1, loc2a=2, loc1b=4, loc2b=3, + ec="r", facecolor="y") + p.set_clip_on(False) + ax[1].add_patch(p) + # set color to marked area + axins = zoomed_inset_axes(ax[1], 1, loc='upper right') + axins.set_xlim(0, 0.2) + axins.set_ylim(0, 0.2) + plt.gca().axes.xaxis.set_ticks([]) + plt.gca().axes.yaxis.set_ticks([]) + mark_inset(ax[1], axins, loc1=2, loc2=4, facecolor="y", ec="0.5") + + # fill with green by setting 'color' field + bbox5 = TransformedBbox(bbox, ax[2].transData) + bbox6 = TransformedBbox(bbox, ax[3].transData) + # set color to BboxConnectorPatch + p = BboxConnectorPatch( + bbox5, bbox6, loc1a=1, loc2a=2, loc1b=4, loc2b=3, + ec="r", color="g") + p.set_clip_on(False) + ax[2].add_patch(p) + # set color to marked area + axins = zoomed_inset_axes(ax[2], 1, loc='upper right') + axins.set_xlim(0, 0.2) + axins.set_ylim(0, 0.2) + plt.gca().axes.xaxis.set_ticks([]) + plt.gca().axes.yaxis.set_ticks([]) + mark_inset(ax[2], axins, loc1=2, loc2=4, color="g", ec="0.5") + + # fill with green but color won't show if set fill to False + bbox7 = TransformedBbox(bbox, ax[3].transData) + bbox8 = TransformedBbox(bbox, ax[4].transData) + # BboxConnectorPatch won't show green + p = BboxConnectorPatch( + bbox7, bbox8, loc1a=1, loc2a=2, loc1b=4, loc2b=3, + ec="r", fc="g", fill=False) + p.set_clip_on(False) + ax[3].add_patch(p) + # marked area won't show green + axins = zoomed_inset_axes(ax[3], 1, loc='upper right') + axins.set_xlim(0, 0.2) + axins.set_ylim(0, 0.2) + axins.xaxis.set_ticks([]) + axins.yaxis.set_ticks([]) + mark_inset(ax[3], axins, loc1=2, loc2=4, fc="g", ec="0.5", fill=False) + + +# Update style when regenerating the test image +@image_comparison(['zoomed_axes.png', 'inverted_zoomed_axes.png'], + style=('classic', '_classic_test_patch'), + tol=0 if platform.machine() == 'x86_64' else 0.02) +def test_zooming_with_inverted_axes(): + fig, ax = plt.subplots() + ax.plot([1, 2, 3], [1, 2, 3]) + ax.axis([1, 3, 1, 3]) + inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right') + inset_ax.axis([1.1, 1.4, 1.1, 1.4]) + + fig, ax = plt.subplots() + ax.plot([1, 2, 3], [1, 2, 3]) + ax.axis([3, 1, 3, 1]) + inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right') + inset_ax.axis([1.4, 1.1, 1.4, 1.1]) + + +# Update style when regenerating the test image +@image_comparison(['anchored_direction_arrows.png'], + tol=0 if platform.machine() == 'x86_64' else 0.01, + style=('classic', '_classic_test_patch')) +def test_anchored_direction_arrows(): + fig, ax = plt.subplots() + ax.imshow(np.zeros((10, 10)), interpolation='nearest') + + simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y') + ax.add_artist(simple_arrow) + + +# Update style when regenerating the test image +@image_comparison(['anchored_direction_arrows_many_args.png'], + style=('classic', '_classic_test_patch')) +def test_anchored_direction_arrows_many_args(): + fig, ax = plt.subplots() + ax.imshow(np.ones((10, 10))) + + direction_arrows = AnchoredDirectionArrows( + ax.transAxes, 'A', 'B', loc='upper right', color='red', + aspect_ratio=-0.5, pad=0.6, borderpad=2, frameon=True, alpha=0.7, + sep_x=-0.06, sep_y=-0.08, back_length=0.1, head_width=9, + head_length=10, tail_width=5) + ax.add_artist(direction_arrows) + + +def test_axes_locatable_position(): + fig, ax = plt.subplots() + divider = make_axes_locatable(ax) + with mpl.rc_context({"figure.subplot.wspace": 0.02}): + cax = divider.append_axes('right', size='5%') + fig.canvas.draw() + assert np.isclose(cax.get_position(original=False).width, + 0.03621495327102808) + + +@image_comparison(['image_grid_each_left_label_mode_all.png'], style='mpl20', + savefig_kwarg={'bbox_inches': 'tight'}) +def test_image_grid_each_left_label_mode_all(): + imdata = np.arange(100).reshape((10, 10)) + + fig = plt.figure(1, (3, 3)) + grid = ImageGrid(fig, (1, 1, 1), nrows_ncols=(3, 2), axes_pad=(0.5, 0.3), + cbar_mode="each", cbar_location="left", cbar_size="15%", + label_mode="all") + # 3-tuple rect => SubplotDivider + assert isinstance(grid.get_divider(), SubplotDivider) + assert grid.get_axes_pad() == (0.5, 0.3) + assert grid.get_aspect() # True by default for ImageGrid + for ax, cax in zip(grid, grid.cbar_axes): + im = ax.imshow(imdata, interpolation='none') + cax.colorbar(im) + + +@image_comparison(['image_grid_single_bottom_label_mode_1.png'], style='mpl20', + savefig_kwarg={'bbox_inches': 'tight'}) +def test_image_grid_single_bottom(): + imdata = np.arange(100).reshape((10, 10)) + + fig = plt.figure(1, (2.5, 1.5)) + grid = ImageGrid(fig, (0, 0, 1, 1), nrows_ncols=(1, 3), + axes_pad=(0.2, 0.15), cbar_mode="single", cbar_pad=0.3, + cbar_location="bottom", cbar_size="10%", label_mode="1") + # 4-tuple rect => Divider, isinstance will give True for SubplotDivider + assert type(grid.get_divider()) is Divider + for i in range(3): + im = grid[i].imshow(imdata, interpolation='none') + grid.cbar_axes[0].colorbar(im) + + +def test_image_grid_label_mode_invalid(): + fig = plt.figure() + with pytest.raises(ValueError, match="'foo' is not a valid value for mode"): + ImageGrid(fig, (0, 0, 1, 1), (2, 1), label_mode="foo") + + +@image_comparison(['image_grid.png'], + remove_text=True, style='mpl20', + savefig_kwarg={'bbox_inches': 'tight'}) +def test_image_grid(): + # test that image grid works with bbox_inches=tight. + im = np.arange(100).reshape((10, 10)) + + fig = plt.figure(1, (4, 4)) + grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.1) + assert grid.get_axes_pad() == (0.1, 0.1) + for i in range(4): + grid[i].imshow(im, interpolation='nearest') + + +def test_gettightbbox(): + fig, ax = plt.subplots(figsize=(8, 6)) + + l, = ax.plot([1, 2, 3], [0, 1, 0]) + + ax_zoom = zoomed_inset_axes(ax, 4) + ax_zoom.plot([1, 2, 3], [0, 1, 0]) + + mark_inset(ax, ax_zoom, loc1=1, loc2=3, fc="none", ec='0.3') + + remove_ticks_and_titles(fig) + bbox = fig.get_tightbbox(fig.canvas.get_renderer()) + np.testing.assert_array_almost_equal(bbox.extents, + [-17.7, -13.9, 7.2, 5.4]) + + +def test_gettightbbox_parasite(): + fig = plt.figure() + + y0 = 0.3 + horiz = [Size.Scaled(1.0)] + vert = [Size.Scaled(1.0)] + ax0_div = Divider(fig, [0.1, y0, 0.8, 0.2], horiz, vert) + ax1_div = Divider(fig, [0.1, 0.5, 0.8, 0.4], horiz, vert) + + ax0 = fig.add_subplot( + xticks=[], yticks=[], axes_locator=ax0_div.new_locator(nx=0, ny=0)) + ax1 = fig.add_subplot( + axes_class=HostAxes, axes_locator=ax1_div.new_locator(nx=0, ny=0)) + aux_ax = ax1.get_aux_axes(Affine2D()) + + fig.canvas.draw() + rdr = fig.canvas.get_renderer() + assert rdr.get_canvas_width_height()[1] * y0 / fig.dpi == fig.get_tightbbox(rdr).y0 + + +@pytest.mark.parametrize("click_on", ["big", "small"]) +@pytest.mark.parametrize("big_on_axes,small_on_axes", [ + ("gca", "gca"), + ("host", "host"), + ("host", "parasite"), + ("parasite", "host"), + ("parasite", "parasite") +]) +def test_picking_callbacks_overlap(big_on_axes, small_on_axes, click_on): + """Test pick events on normal, host or parasite axes.""" + # Two rectangles are drawn and "clicked on", a small one and a big one + # enclosing the small one. The axis on which they are drawn as well as the + # rectangle that is clicked on are varied. + # In each case we expect that both rectangles are picked if we click on the + # small one and only the big one is picked if we click on the big one. + # Also tests picking on normal axes ("gca") as a control. + big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=5) + small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5) + # Machinery for "receiving" events + received_events = [] + def on_pick(event): + received_events.append(event) + plt.gcf().canvas.mpl_connect('pick_event', on_pick) + # Shortcut + rectangles_on_axes = (big_on_axes, small_on_axes) + # Axes setup + axes = {"gca": None, "host": None, "parasite": None} + if "gca" in rectangles_on_axes: + axes["gca"] = plt.gca() + if "host" in rectangles_on_axes or "parasite" in rectangles_on_axes: + axes["host"] = host_subplot(111) + axes["parasite"] = axes["host"].twin() + # Add rectangles to axes + axes[big_on_axes].add_patch(big) + axes[small_on_axes].add_patch(small) + # Simulate picking with click mouse event + if click_on == "big": + click_axes = axes[big_on_axes] + axes_coords = (0.3, 0.3) + else: + click_axes = axes[small_on_axes] + axes_coords = (0.5, 0.5) + # In reality mouse events never happen on parasite axes, only host axes + if click_axes is axes["parasite"]: + click_axes = axes["host"] + (x, y) = click_axes.transAxes.transform(axes_coords) + m = MouseEvent("button_press_event", click_axes.get_figure(root=True).canvas, x, y, + button=1) + click_axes.pick(m) + # Checks + expected_n_events = 2 if click_on == "small" else 1 + assert len(received_events) == expected_n_events + event_rects = [event.artist for event in received_events] + assert big in event_rects + if click_on == "small": + assert small in event_rects + + +@image_comparison(['anchored_artists.png'], remove_text=True, style='mpl20') +def test_anchored_artists(): + fig, ax = plt.subplots(figsize=(3, 3)) + ada = AnchoredDrawingArea(40, 20, 0, 0, loc='upper right', pad=0., + frameon=False) + p1 = Circle((10, 10), 10) + ada.drawing_area.add_artist(p1) + p2 = Circle((30, 10), 5, fc="r") + ada.drawing_area.add_artist(p2) + ax.add_artist(ada) + + box = AnchoredAuxTransformBox(ax.transData, loc='upper left') + el = Ellipse((0, 0), width=0.1, height=0.4, angle=30, color='cyan') + box.drawing_area.add_artist(el) + ax.add_artist(box) + + # This block used to test the AnchoredEllipse class, but that was removed. The block + # remains, though it duplicates the above ellipse, so that the test image doesn't + # need to be regenerated. + box = AnchoredAuxTransformBox(ax.transData, loc='lower left', frameon=True, + pad=0.5, borderpad=0.4) + el = Ellipse((0, 0), width=0.1, height=0.25, angle=-60) + box.drawing_area.add_artist(el) + ax.add_artist(box) + + asb = AnchoredSizeBar(ax.transData, 0.2, r"0.2 units", loc='lower right', + pad=0.3, borderpad=0.4, sep=4, fill_bar=True, + frameon=False, label_top=True, prop={'size': 20}, + size_vertical=0.05, color='green') + ax.add_artist(asb) + + +def test_hbox_divider(): + arr1 = np.arange(20).reshape((4, 5)) + arr2 = np.arange(20).reshape((5, 4)) + + fig, (ax1, ax2) = plt.subplots(1, 2) + ax1.imshow(arr1) + ax2.imshow(arr2) + + pad = 0.5 # inches. + divider = HBoxDivider( + fig, 111, # Position of combined axes. + horizontal=[Size.AxesX(ax1), Size.Fixed(pad), Size.AxesX(ax2)], + vertical=[Size.AxesY(ax1), Size.Scaled(1), Size.AxesY(ax2)]) + ax1.set_axes_locator(divider.new_locator(0)) + ax2.set_axes_locator(divider.new_locator(2)) + + fig.canvas.draw() + p1 = ax1.get_position() + p2 = ax2.get_position() + assert p1.height == p2.height + assert p2.width / p1.width == pytest.approx((4 / 5) ** 2) + + +def test_vbox_divider(): + arr1 = np.arange(20).reshape((4, 5)) + arr2 = np.arange(20).reshape((5, 4)) + + fig, (ax1, ax2) = plt.subplots(1, 2) + ax1.imshow(arr1) + ax2.imshow(arr2) + + pad = 0.5 # inches. + divider = VBoxDivider( + fig, 111, # Position of combined axes. + horizontal=[Size.AxesX(ax1), Size.Scaled(1), Size.AxesX(ax2)], + vertical=[Size.AxesY(ax1), Size.Fixed(pad), Size.AxesY(ax2)]) + ax1.set_axes_locator(divider.new_locator(0)) + ax2.set_axes_locator(divider.new_locator(2)) + + fig.canvas.draw() + p1 = ax1.get_position() + p2 = ax2.get_position() + assert p1.width == p2.width + assert p1.height / p2.height == pytest.approx((4 / 5) ** 2) + + +def test_axes_class_tuple(): + fig = plt.figure() + axes_class = (mpl_toolkits.axes_grid1.mpl_axes.Axes, {}) + gr = AxesGrid(fig, 111, nrows_ncols=(1, 1), axes_class=axes_class) + + +def test_grid_axes_lists(): + """Test Grid axes_all, axes_row and axes_column relationship.""" + fig = plt.figure() + grid = Grid(fig, 111, (2, 3), direction="row") + assert_array_equal(grid, grid.axes_all) + assert_array_equal(grid.axes_row, np.transpose(grid.axes_column)) + assert_array_equal(grid, np.ravel(grid.axes_row), "row") + assert grid.get_geometry() == (2, 3) + grid = Grid(fig, 111, (2, 3), direction="column") + assert_array_equal(grid, np.ravel(grid.axes_column), "column") + + +@pytest.mark.parametrize('direction', ('row', 'column')) +def test_grid_axes_position(direction): + """Test positioning of the axes in Grid.""" + fig = plt.figure() + grid = Grid(fig, 111, (2, 2), direction=direction) + loc = [ax.get_axes_locator() for ax in np.ravel(grid.axes_row)] + # Test nx. + assert loc[1].args[0] > loc[0].args[0] + assert loc[0].args[0] == loc[2].args[0] + assert loc[3].args[0] == loc[1].args[0] + # Test ny. + assert loc[2].args[1] < loc[0].args[1] + assert loc[0].args[1] == loc[1].args[1] + assert loc[3].args[1] == loc[2].args[1] + + +@pytest.mark.parametrize('rect, n_axes, error, message', ( + ((1, 1), None, TypeError, "Incorrect rect format"), + (111, -1, ValueError, "n_axes must be positive"), + (111, 7, ValueError, "n_axes must be positive"), +)) +def test_grid_errors(rect, n_axes, error, message): + fig = plt.figure() + with pytest.raises(error, match=message): + Grid(fig, rect, (2, 3), n_axes=n_axes) + + +@pytest.mark.parametrize('anchor, error, message', ( + (None, TypeError, "anchor must be str"), + ("CC", ValueError, "'CC' is not a valid value for anchor"), + ((1, 1, 1), TypeError, "anchor must be str"), +)) +def test_divider_errors(anchor, error, message): + fig = plt.figure() + with pytest.raises(error, match=message): + Divider(fig, [0, 0, 1, 1], [Size.Fixed(1)], [Size.Fixed(1)], + anchor=anchor) + + +@check_figures_equal() +def test_mark_inset_unstales_viewlim(fig_test, fig_ref): + inset, full = fig_test.subplots(1, 2) + full.plot([0, 5], [0, 5]) + inset.set(xlim=(1, 2), ylim=(1, 2)) + # Check that mark_inset unstales full's viewLim before drawing the marks. + mark_inset(full, inset, 1, 4) + + inset, full = fig_ref.subplots(1, 2) + full.plot([0, 5], [0, 5]) + inset.set(xlim=(1, 2), ylim=(1, 2)) + mark_inset(full, inset, 1, 4) + # Manually unstale the full's viewLim. + fig_ref.canvas.draw() + + +def test_auto_adjustable(): + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1]) + pad = 0.1 + make_axes_area_auto_adjustable(ax, pad=pad) + fig.canvas.draw() + tbb = ax.get_tightbbox() + assert tbb.x0 == pytest.approx(pad * fig.dpi) + assert tbb.x1 == pytest.approx(fig.bbox.width - pad * fig.dpi) + assert tbb.y0 == pytest.approx(pad * fig.dpi) + assert tbb.y1 == pytest.approx(fig.bbox.height - pad * fig.dpi) + + +# Update style when regenerating the test image +@image_comparison(['rgb_axes.png'], remove_text=True, + style=('classic', '_classic_test_patch')) +def test_rgb_axes(): + fig = plt.figure() + ax = RGBAxes(fig, (0.1, 0.1, 0.8, 0.8), pad=0.1) + rng = np.random.default_rng(19680801) + r = rng.random((5, 5)) + g = rng.random((5, 5)) + b = rng.random((5, 5)) + ax.imshow_rgb(r, g, b, interpolation='none') + + +# The original version of this test relied on mpl_toolkits's slightly different +# colorbar implementation; moving to matplotlib's own colorbar implementation +# caused the small image comparison error. +@image_comparison(['imagegrid_cbar_mode.png'], + remove_text=True, style='mpl20', tol=0.3) +def test_imagegrid_cbar_mode_edge(): + arr = np.arange(16).reshape((4, 4)) + + fig = plt.figure(figsize=(18, 9)) + + positions = (241, 242, 243, 244, 245, 246, 247, 248) + directions = ['row']*4 + ['column']*4 + cbar_locations = ['left', 'right', 'top', 'bottom']*2 + + for position, direction, location in zip( + positions, directions, cbar_locations): + grid = ImageGrid(fig, position, + nrows_ncols=(2, 2), + direction=direction, + cbar_location=location, + cbar_size='20%', + cbar_mode='edge') + ax1, ax2, ax3, ax4 = grid + + ax1.imshow(arr, cmap='nipy_spectral') + ax2.imshow(arr.T, cmap='hot') + ax3.imshow(np.hypot(arr, arr.T), cmap='jet') + ax4.imshow(np.arctan2(arr, arr.T), cmap='hsv') + + # In each row/column, the "first" colorbars must be overwritten by the + # "second" ones. To achieve this, clear out the axes first. + for ax in grid: + ax.cax.cla() + cb = ax.cax.colorbar(ax.images[0]) + + +def test_imagegrid(): + fig = plt.figure() + grid = ImageGrid(fig, 111, nrows_ncols=(1, 1)) + ax = grid[0] + im = ax.imshow([[1, 2]], norm=mpl.colors.LogNorm()) + cb = ax.cax.colorbar(im) + assert isinstance(cb.locator, mticker.LogLocator) + + +def test_removal(): + import matplotlib.pyplot as plt + import mpl_toolkits.axisartist as AA + fig = plt.figure() + ax = host_subplot(111, axes_class=AA.Axes, figure=fig) + col = ax.fill_between(range(5), 0, range(5)) + fig.canvas.draw() + col.remove() + fig.canvas.draw() + + +@image_comparison(['anchored_locator_base_call.png'], style="mpl20") +def test_anchored_locator_base_call(): + fig = plt.figure(figsize=(3, 3)) + fig1, fig2 = fig.subfigures(nrows=2, ncols=1) + + ax = fig1.subplots() + ax.set(aspect=1, xlim=(-15, 15), ylim=(-20, 5)) + ax.set(xticks=[], yticks=[]) + + Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy") + extent = (-3, 4, -4, 3) + + axins = zoomed_inset_axes(ax, zoom=2, loc="upper left") + axins.set(xticks=[], yticks=[]) + + axins.imshow(Z, extent=extent, origin="lower") + + +def test_grid_with_axes_class_not_overriding_axis(): + Grid(plt.figure(), 111, (2, 2), axes_class=mpl.axes.Axes) + RGBAxes(plt.figure(), 111, axes_class=mpl.axes.Axes) + + +def test_grid_n_axes(): + fig = plt.figure() + grid = Grid(fig, 111, (3, 3), n_axes=5) + assert len(fig.axes) == grid.n_axes == 5 diff --git a/lib/mpl_toolkits/axisartist/__init__.py b/lib/mpl_toolkits/axisartist/__init__.py index b99af4430c69..7b8d8c08ac22 100644 --- a/lib/mpl_toolkits/axisartist/__init__.py +++ b/lib/mpl_toolkits/axisartist/__init__.py @@ -1,15 +1,14 @@ -from .axislines import ( - Axes, AxesZero, AxisArtistHelper, AxisArtistHelperRectlinear, +from .axislines import Axes +from .axislines import ( # noqa: F401 + AxesZero, AxisArtistHelper, AxisArtistHelperRectlinear, GridHelperBase, GridHelperRectlinear, Subplot, SubplotZero) -from .axis_artist import AxisArtist, GridlinesCollection -from .grid_helper_curvelinear import GridHelperCurveLinear -from .floating_axes import FloatingAxes, FloatingSubplot +from .axis_artist import AxisArtist, GridlinesCollection # noqa: F401 +from .grid_helper_curvelinear import GridHelperCurveLinear # noqa: F401 +from .floating_axes import FloatingAxes, FloatingSubplot # noqa: F401 from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory, - parasite_axes_auxtrans_class_factory, subplot_class_factory) + host_axes_class_factory, parasite_axes_class_factory) ParasiteAxes = parasite_axes_class_factory(Axes) -ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes) HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) +SubplotHost = HostAxes diff --git a/lib/mpl_toolkits/axisartist/angle_helper.py b/lib/mpl_toolkits/axisartist/angle_helper.py index dac3f9f6b9f4..56b461e4a1d3 100644 --- a/lib/mpl_toolkits/axisartist/angle_helper.py +++ b/lib/mpl_toolkits/axisartist/angle_helper.py @@ -1,7 +1,7 @@ import numpy as np import math -from matplotlib import cbook +from matplotlib.transforms import Bbox from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple @@ -141,20 +141,10 @@ def select_step360(v1, v2, nv, include_last=True, threshold_factor=3600): class LocatorBase: - @cbook._rename_parameter("3.3", "den", "nbins") def __init__(self, nbins, include_last=True): self.nbins = nbins self._include_last = include_last - @cbook.deprecated("3.3", alternative="nbins") - @property - def den(self): - return self.nbins - - @den.setter - def den(self, v): - self.nbins = v - def set_params(self, nbins=None): if nbins is not None: self.nbins = int(nbins) @@ -295,7 +285,7 @@ def __call__(self, direction, factor, values): return r else: # factor > 3600. - return [r"$%s^{\circ}$" % (str(v),) for v in ss*values] + return [r"$%s^{\circ}$" % v for v in ss*values] class FormatterHMS(FormatterDMS): @@ -358,11 +348,12 @@ def __init__(self, nx, ny, self.lon_minmax = lon_minmax self.lat_minmax = lat_minmax - def __call__(self, transform_xy, x1, y1, x2, y2): + def _find_transformed_bbox(self, trans, bbox): # docstring inherited - x, y = np.meshgrid( - np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)) - lon, lat = transform_xy(np.ravel(x), np.ravel(y)) + grid = np.reshape(np.meshgrid(np.linspace(bbox.x0, bbox.x1, self.nx), + np.linspace(bbox.y0, bbox.y1, self.ny)), + (2, -1)).T + lon, lat = trans.transform(grid).T # iron out jumps, but algorithm should be improved. # This is just naive way of doing and my fail for some cases. @@ -378,11 +369,10 @@ def __call__(self, transform_xy, x1, y1, x2, y2): lat0 = np.nanmin(lat) lat -= 360. * ((lat - lat0) > 180.) - lon_min, lon_max = np.nanmin(lon), np.nanmax(lon) - lat_min, lat_max = np.nanmin(lat), np.nanmax(lat) - - lon_min, lon_max, lat_min, lat_max = \ - self._add_pad(lon_min, lon_max, lat_min, lat_max) + tbbox = Bbox.null() + tbbox.update_from_data_xy(np.column_stack([lon, lat])) + tbbox = tbbox.expanded(1 + 2 / self.nx, 1 + 2 / self.ny) + lon_min, lat_min, lon_max, lat_max = tbbox.extents # check cycle if self.lon_cycle: @@ -402,4 +392,4 @@ def __call__(self, transform_xy, x1, y1, x2, y2): max0 = self.lat_minmax[1] lat_max = min(max0, lat_max) - return lon_min, lon_max, lat_min, lat_max + return Bbox.from_extents(lon_min, lat_min, lon_max, lat_max) diff --git a/lib/mpl_toolkits/axisartist/axes_divider.py b/lib/mpl_toolkits/axisartist/axes_divider.py index 3b177838f896..d0392be782d9 100644 --- a/lib/mpl_toolkits/axisartist/axes_divider.py +++ b/lib/mpl_toolkits/axisartist/axes_divider.py @@ -1,2 +1,2 @@ -from mpl_toolkits.axes_grid1.axes_divider import ( - Divider, AxesLocator, SubplotDivider, AxesDivider, make_axes_locatable) +from mpl_toolkits.axes_grid1.axes_divider import ( # noqa + Divider, SubplotDivider, AxesDivider, make_axes_locatable) diff --git a/lib/mpl_toolkits/axisartist/axes_grid.py b/lib/mpl_toolkits/axisartist/axes_grid.py deleted file mode 100644 index 15c715b72896..000000000000 --- a/lib/mpl_toolkits/axisartist/axes_grid.py +++ /dev/null @@ -1,18 +0,0 @@ -import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig -from .axislines import Axes - - -class CbarAxes(axes_grid_orig.CbarAxesBase, Axes): - pass - - -class Grid(axes_grid_orig.Grid): - _defaultAxesClass = Axes - - -class ImageGrid(axes_grid_orig.ImageGrid): - _defaultAxesClass = Axes - _defaultCbarAxesClass = CbarAxes - - -AxesGrid = ImageGrid diff --git a/lib/mpl_toolkits/axisartist/axes_rgb.py b/lib/mpl_toolkits/axisartist/axes_rgb.py deleted file mode 100644 index 429843f6f4b9..000000000000 --- a/lib/mpl_toolkits/axisartist/axes_rgb.py +++ /dev/null @@ -1,7 +0,0 @@ -from mpl_toolkits.axes_grid1.axes_rgb import ( - make_rgb_axes, imshow_rgb, RGBAxes as _RGBAxes) -from .axislines import Axes - - -class RGBAxes(_RGBAxes): - _defaultAxesClass = Axes diff --git a/lib/mpl_toolkits/axisartist/axis_artist.py b/lib/mpl_toolkits/axisartist/axis_artist.py index 23e508c75e0b..9ba6f6075844 100644 --- a/lib/mpl_toolkits/axisartist/axis_artist.py +++ b/lib/mpl_toolkits/axisartist/axis_artist.py @@ -1,39 +1,24 @@ """ -axis_artist.py module provides axis-related artists. They are +The :mod:`.axis_artist` module implements custom artists to draw axis elements +(axis lines and labels, tick lines and labels, grid lines). -* axis line -* tick lines -* tick labels -* axis label -* grid lines +Axis lines and labels and tick lines and labels are managed by the `AxisArtist` +class; grid lines are managed by the `GridlinesCollection` class. -The main artist classes are `AxisArtist` and `GridlinesCollection`. While -`GridlinesCollection` is responsible for drawing grid lines, `AxisArtist` -is responsible for all other artists. `AxisArtist` has attributes that are -associated with each type of artists: +There is one `AxisArtist` per Axis; it can be accessed through +the ``axis`` dictionary of the parent Axes (which should be a +`~mpl_toolkits.axisartist.axislines.Axes`), e.g. ``ax.axis["bottom"]``. -* line: axis line -* major_ticks: major tick lines -* major_ticklabels: major tick labels -* minor_ticks: minor tick lines -* minor_ticklabels: minor tick labels -* label: axis label +Children of the AxisArtist are accessed as attributes: ``.line`` and ``.label`` +for the axis line and label, ``.major_ticks``, ``.major_ticklabels``, +``.minor_ticks``, ``.minor_ticklabels`` for the tick lines and labels (e.g. +``ax.axis["bottom"].line``). -Typically, the `AxisArtist` associated with an axes will be accessed with the -*axis* dictionary of the axes, i.e., the `AxisArtist` for the bottom axis is :: +Children properties (colors, fonts, line widths, etc.) can be set using +setters, e.g. :: - ax.axis["bottom"] - -where *ax* is an instance of `mpl_toolkits.axislines.Axes`. Thus, -``ax.axis["bottom"].line`` is an artist associated with the axis line, and -``ax.axis["bottom"].major_ticks`` is an artist associated with the major tick -lines. - -You can change the colors, fonts, line widths, etc. of these artists -by calling suitable set method. For example, to change the color of the major -ticks of the bottom axis to red, use :: - - ax.axis["bottom"].major_ticks.set_color("r") + # Make the major ticks of the bottom axis red. + ax.axis["bottom"].major_ticks.set_color("red") However, things like the locations of ticks, and their ticklabels need to be changed from the side of the grid_helper. @@ -58,11 +43,11 @@ ticklabel), which gives 0 for bottom axis. =================== ====== ======== ====== ======== -Parameter left bottom right top +Property left bottom right top =================== ====== ======== ====== ======== -ticklabels location left right right left +ticklabel location left right right left axislabel location left right right left -ticklabels angle 90 0 -90 180 +ticklabel angle 90 0 -90 180 axislabel angle 180 0 0 180 ticklabel va center baseline center baseline axislabel va center top center bottom @@ -73,7 +58,7 @@ Ticks are by default direct opposite side of the ticklabels. To make ticks to the same side of the ticklabels, :: - ax.axis["bottom"].major_ticks.set_ticks_out(True) + ax.axis["bottom"].major_ticks.set_tick_out(True) The following attributes can be customized (use the ``set_xxx`` methods): @@ -90,90 +75,22 @@ import numpy as np -from matplotlib import cbook, rcParams +import matplotlib as mpl +from matplotlib import _api, cbook import matplotlib.artist as martist +import matplotlib.colors as mcolors import matplotlib.text as mtext - from matplotlib.collections import LineCollection from matplotlib.lines import Line2D from matplotlib.patches import PathPatch from matplotlib.path import Path from matplotlib.transforms import ( - Affine2D, Bbox, IdentityTransform, ScaledTranslation, TransformedPath) + Affine2D, Bbox, IdentityTransform, ScaledTranslation) from .axisline_style import AxislineStyle -@cbook.deprecated("3.2", alternative="matplotlib.patches.PathPatch") -class BezierPath(Line2D): - - def __init__(self, path, *args, **kwargs): - """ - Parameters - ---------- - path : `~.path.Path` - The path to draw. - **kwargs - All remaining keyword arguments are passed to `.Line2D`. - """ - super().__init__([], [], *args, **kwargs) - self._path = path - self._invalid = False - - def recache(self): - self._transformed_path = TransformedPath( - self._path, self.get_transform()) - self._invalid = False - - def set_path(self, path): - self._path = path - self._invalid = True - - def draw(self, renderer): - if self._invalid: - self.recache() - - if not self._visible: - return - renderer.open_group('line2d', gid=self.get_gid()) - - gc = renderer.new_gc() - self._set_gc_clip(gc) - - gc.set_foreground(self._color) - gc.set_antialiased(self._antialiased) - gc.set_linewidth(self._linewidth) - gc.set_alpha(self._alpha) - if self.is_dashed(): - cap = self._dashcapstyle - join = self._dashjoinstyle - else: - cap = self._solidcapstyle - join = self._solidjoinstyle - gc.set_joinstyle(join) - gc.set_capstyle(cap) - gc.set_dashes(self._dashOffset, self._dashSeq) - - if self._lineStyles[self._linestyle] != '_draw_nothing': - tpath, affine = ( - self._transformed_path.get_transformed_path_and_affine()) - renderer.draw_path(gc, tpath, affine.frozen()) - - gc.restore() - renderer.close_group('line2d') - - class AttributeCopier: - @cbook.deprecated("3.2") - def __init__(self, ref_artist, klass=martist.Artist): - self._klass = klass - self._ref_artist = ref_artist - super().__init__() - - @cbook.deprecated("3.2") - def set_ref_artist(self, artist): - self._ref_artist = artist - def get_ref_artist(self): """ Return the underlying artist that actually defines some properties @@ -181,8 +98,7 @@ def get_ref_artist(self): """ raise RuntimeError("get_ref_artist must overridden") - @cbook._delete_parameter("3.2", "default_value") - def get_attribute_from_ref_artist(self, attr_name, default_value=None): + def get_attribute_from_ref_artist(self, attr_name): getter = methodcaller("get_" + attr_name) prop = getter(super()) return getter(self.get_ref_artist()) if prop == "auto" else prop @@ -190,13 +106,13 @@ def get_attribute_from_ref_artist(self, attr_name, default_value=None): class Ticks(AttributeCopier, Line2D): """ - Ticks are derived from Line2D, and note that ticks themselves + Ticks are derived from `.Line2D`, and note that ticks themselves are markers. Thus, you should use set_mec, set_mew, etc. To change the tick size (length), you need to use - set_ticksize. To change the direction of the ticks (ticks are + `set_ticksize`. To change the direction of the ticks (ticks are in opposite direction of ticklabels by default), use - set_tick_out(False). + ``set_tick_out(False)`` """ def __init__(self, ticksize, tick_out=False, *, axis=None, **kwargs): @@ -219,6 +135,14 @@ def get_ref_artist(self): # docstring inherited return self._axis.majorTicks[0].tick1line + def set_color(self, color): + # docstring inherited + # Unlike the base Line2D.set_color, this also supports "auto". + if not cbook._str_equal(color, "auto"): + mcolors._check_color_like(color=color) + self._color = color + self.stale = True + def get_color(self): return self.get_attribute_from_ref_artist("color") @@ -278,8 +202,8 @@ def draw(self, renderer): class LabelBase(mtext.Text): """ - A base class for AxisLabel and TickLabels. The position and angle - of the text are calculated by to offset_ref_angle, + A base class for `.AxisLabel` and `.TickLabels`. The position and + angle of the text are calculated by the offset_ref_angle, text_ref_angle, and offset_radius attributes. """ @@ -293,26 +217,16 @@ def __init__(self, *args, **kwargs): self.set_rotation_mode("anchor") self._text_follow_ref_angle = True - def _set_ref_angle(self, a): - self._ref_angle = a - - def _get_ref_angle(self): - return self._ref_angle - - def _get_text_ref_angle(self): + @property + def _text_ref_angle(self): if self._text_follow_ref_angle: - return self._get_ref_angle()+90 + return self._ref_angle + 90 else: - return 0 # self.get_ref_angle() - - def _get_offset_ref_angle(self): - return self._get_ref_angle() - - def _set_offset_radius(self, offset_radius): - self._offset_radius = offset_radius + return 0 - def _get_offset_radius(self): - return self._offset_radius + @property + def _offset_ref_angle(self): + return self._ref_angle _get_opposite_direction = {"left": "right", "right": "left", @@ -326,31 +240,30 @@ def draw(self, renderer): # save original and adjust some properties tr = self.get_transform() angle_orig = self.get_rotation() - text_ref_angle = self._get_text_ref_angle() - offset_ref_angle = self._get_offset_ref_angle() - theta = np.deg2rad(offset_ref_angle) - dd = self._get_offset_radius() + theta = np.deg2rad(self._offset_ref_angle) + dd = self._offset_radius dx, dy = dd * np.cos(theta), dd * np.sin(theta) self.set_transform(tr + Affine2D().translate(dx, dy)) - self.set_rotation(text_ref_angle+angle_orig) + self.set_rotation(self._text_ref_angle + angle_orig) super().draw(renderer) # restore original properties self.set_transform(tr) self.set_rotation(angle_orig) - def get_window_extent(self, renderer): + def get_window_extent(self, renderer=None): + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + # save original and adjust some properties tr = self.get_transform() angle_orig = self.get_rotation() - text_ref_angle = self._get_text_ref_angle() - offset_ref_angle = self._get_offset_ref_angle() - theta = np.deg2rad(offset_ref_angle) - dd = self._get_offset_radius() + theta = np.deg2rad(self._offset_ref_angle) + dd = self._offset_radius dx, dy = dd * np.cos(theta), dd * np.sin(theta) self.set_transform(tr + Affine2D().translate(dx, dy)) - self.set_rotation(text_ref_angle+angle_orig) + self.set_rotation(self._text_ref_angle + angle_orig) bbox = super().get_window_extent(renderer).frozen() # restore original properties self.set_transform(tr) @@ -361,17 +274,17 @@ def get_window_extent(self, renderer): class AxisLabel(AttributeCopier, LabelBase): """ - Axis Label. Derived from Text. The position of the text is updated + Axis label. Derived from `.Text`. The position of the text is updated in the fly, so changing text position has no effect. Otherwise, the - properties can be changed as a normal Text. + properties can be changed as a normal `.Text`. - To change the pad between ticklabels and axis label, use set_pad. + To change the pad between tick labels and axis label, use `set_pad`. """ def __init__(self, *args, axis_direction="bottom", axis=None, **kwargs): self._axis = axis self._pad = 5 - self._extra_pad = 0 + self._external_pad = 0 # in pixels LabelBase.__init__(self, *args, **kwargs) self.set_axis_direction(axis_direction) @@ -380,7 +293,12 @@ def set_pad(self, pad): Set the internal pad in points. The actual pad will be the sum of the internal pad and the - external pad (the latter is set automatically by the AxisArtist). + external pad (the latter is set automatically by the `.AxisArtist`). + + Parameters + ---------- + pad : float + The internal pad in points. """ self._pad = pad @@ -392,22 +310,15 @@ def get_pad(self): """ return self._pad - def _set_external_pad(self, p): - """Set external pad in pixels.""" - self._extra_pad = p - - def _get_external_pad(self): - """Return external pad in pixels.""" - return self._extra_pad - def get_ref_artist(self): # docstring inherited - return self._axis.get_label() + return self._axis.label def get_text(self): + # docstring inherited t = super().get_text() if t == "__from_axes__": - return self._axis.get_label().get_text() + return self._axis.label.get_text() return self._text _default_alignments = dict(left=("bottom", "center"), @@ -416,7 +327,14 @@ def get_text(self): top=("bottom", "center")) def set_default_alignment(self, d): - va, ha = cbook._check_getitem(self._default_alignments, d=d) + """ + Set the default alignment. See `set_axis_direction` for details. + + Parameters + ---------- + d : {"left", "bottom", "right", "top"} + """ + va, ha = _api.check_getitem(self._default_alignments, d=d) self.set_va(va) self.set_ha(ha) @@ -426,7 +344,14 @@ def set_default_alignment(self, d): top=180) def set_default_angle(self, d): - self.set_rotation(cbook._check_getitem(self._default_angles, d=d)) + """ + Set the default angle. See `set_axis_direction` for details. + + Parameters + ---------- + d : {"left", "bottom", "right", "top"} + """ + self.set_rotation(_api.check_getitem(self._default_angles, d=d)) def set_axis_direction(self, d): """ @@ -434,7 +359,7 @@ def set_axis_direction(self, d): according to the matplotlib convention. ===================== ========== ========= ========== ========== - property left bottom right top + Property left bottom right top ===================== ========== ========= ========== ========== axislabel angle 180 0 0 180 axislabel va center top center bottom @@ -444,6 +369,10 @@ def set_axis_direction(self, d): Note that the text angles are actually relative to (90 + angle of the direction to the ticklabel), which gives 0 for bottom axis. + + Parameters + ---------- + d : {"left", "bottom", "right", "top"} """ self.set_default_alignment(d) self.set_default_angle(d) @@ -455,19 +384,19 @@ def draw(self, renderer): if not self.get_visible(): return - pad = renderer.points_to_pixels(self.get_pad()) - r = self._get_external_pad() + pad - self._set_offset_radius(r) + self._offset_radius = \ + self._external_pad + renderer.points_to_pixels(self.get_pad()) super().draw(renderer) - def get_window_extent(self, renderer): + def get_window_extent(self, renderer=None): + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() if not self.get_visible(): return - pad = renderer.points_to_pixels(self.get_pad()) - r = self._get_external_pad() + pad - self._set_offset_radius(r) + r = self._external_pad + renderer.points_to_pixels(self.get_pad()) + self._offset_radius = r bb = super().get_window_extent(renderer) @@ -476,14 +405,14 @@ def get_window_extent(self, renderer): class TickLabels(AxisLabel): # mtext.Text """ - Tick Labels. While derived from Text, this single artist draws all - ticklabels. As in AxisLabel, the position of the text is updated + Tick labels. While derived from `.Text`, this single artist draws all + ticklabels. As in `.AxisLabel`, the position of the text is updated in the fly, so changing text position has no effect. Otherwise, - the properties can be changed as a normal Text. Unlike the - ticklabels of the mainline matplotlib, properties of single - ticklabel alone cannot modified. + the properties can be changed as a normal `.Text`. Unlike the + ticklabels of the mainline Matplotlib, properties of a single + ticklabel alone cannot be modified. - To change the pad between ticks and ticklabels, use set_pad. + To change the pad between ticks and ticklabels, use `~.AxisLabel.set_pad`. """ def __init__(self, *, axis_direction="bottom", **kwargs): @@ -498,14 +427,14 @@ def get_ref_artist(self): def set_axis_direction(self, label_direction): """ Adjust the text angle and text alignment of ticklabels - according to the matplotlib convention. + according to the Matplotlib convention. The *label_direction* must be one of [left, right, bottom, top]. ===================== ========== ========= ========== ========== - property left bottom right top + Property left bottom right top ===================== ========== ========= ========== ========== - ticklabels angle 90 0 -90 180 + ticklabel angle 90 0 -90 180 ticklabel va center baseline center baseline ticklabel ha right center right center ===================== ========== ========= ========== ========== @@ -513,6 +442,11 @@ def set_axis_direction(self, label_direction): Note that the text angles are actually relative to (90 + angle of the direction to the ticklabel), which gives 0 for bottom axis. + + Parameters + ---------- + label_direction : {"left", "bottom", "right", "top"} + """ self.set_default_alignment(label_direction) self.set_default_angle(label_direction) @@ -590,20 +524,19 @@ def _get_ticklabels_offsets(self, renderer, label_direction): def draw(self, renderer): if not self.get_visible(): - self._axislabel_pad = self._get_external_pad() + self._axislabel_pad = self._external_pad return r, total_width = self._get_ticklabels_offsets(renderer, self._axis_direction) - pad = (self._get_external_pad() - + renderer.points_to_pixels(self.get_pad())) - self._set_offset_radius(r+pad) + pad = self._external_pad + renderer.points_to_pixels(self.get_pad()) + self._offset_radius = r + pad for (x, y), a, l in self._locs_angles_labels: if not l.strip(): continue - self._set_ref_angle(a) # + add_angle + self._ref_angle = a self.set_x(x) self.set_y(y) self.set_text(l) @@ -615,10 +548,12 @@ def draw(self, renderer): def set_locs_angles_labels(self, locs_angles_labels): self._locs_angles_labels = locs_angles_labels - def get_window_extents(self, renderer): + def get_window_extents(self, renderer=None): + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() if not self.get_visible(): - self._axislabel_pad = self._get_external_pad() + self._axislabel_pad = self._external_pad return [] bboxes = [] @@ -626,12 +561,11 @@ def get_window_extents(self, renderer): r, total_width = self._get_ticklabels_offsets(renderer, self._axis_direction) - pad = self._get_external_pad() + \ - renderer.points_to_pixels(self.get_pad()) - self._set_offset_radius(r+pad) + pad = self._external_pad + renderer.points_to_pixels(self.get_pad()) + self._offset_radius = r + pad for (x, y), a, l in self._locs_angles_labels: - self._set_ref_angle(a) # + add_angle + self._ref_angle = a self.set_x(x) self.set_y(y) self.set_text(l) @@ -654,8 +588,9 @@ def get_texts_widths_heights_descents(self, renderer): if not label.strip(): continue clean_line, ismath = self._preprocess_math(label) - whd = renderer.get_text_width_height_descent( - clean_line, self._fontproperties, ismath=ismath) + whd = mtext._get_text_metrics_with_cache( + renderer, clean_line, self._fontproperties, ismath=ismath, + dpi=self.get_figure(root=True).dpi) whd_list.append(whd) return whd_list @@ -663,10 +598,16 @@ def get_texts_widths_heights_descents(self, renderer): class GridlinesCollection(LineCollection): def __init__(self, *args, which="major", axis="both", **kwargs): """ + Collection of grid lines. + Parameters ---------- which : {"major", "minor"} + Which grid to consider. axis : {"both", "x", "y"} + Which axis to consider. + *args, **kwargs + Passed to `.LineCollection`. """ self._which = which self._axis = axis @@ -674,32 +615,50 @@ def __init__(self, *args, which="major", axis="both", **kwargs): self.set_grid_helper(None) def set_which(self, which): + """ + Select major or minor grid lines. + + Parameters + ---------- + which : {"major", "minor"} + """ self._which = which def set_axis(self, axis): + """ + Select axis. + + Parameters + ---------- + axis : {"both", "x", "y"} + """ self._axis = axis def set_grid_helper(self, grid_helper): + """ + Set grid helper. + + Parameters + ---------- + grid_helper : `.GridHelperBase` subclass + """ self._grid_helper = grid_helper def draw(self, renderer): if self._grid_helper is not None: self._grid_helper.update_lim(self.axes) gl = self._grid_helper.get_gridlines(self._which, self._axis) - if gl: - self.set_segments([np.transpose(l) for l in gl]) - else: - self.set_segments([]) + self.set_segments([np.transpose(l) for l in gl]) super().draw(renderer) class AxisArtist(martist.Artist): """ An artist which draws axis (a line along which the n-th axes coord - is constant) line, ticks, ticklabels, and axis label. + is constant) line, ticks, tick labels, and axis label. """ - zorder = ZORDER = 2.5 # ZORDER is a backcompat alias. + zorder = 2.5 @property def LABELPAD(self): @@ -720,7 +679,7 @@ def __init__(self, axes, axes : `mpl_toolkits.axisartist.axislines.Axes` helper : `~mpl_toolkits.axisartist.axislines.AxisArtistHelper` """ - #axes is also used to follow the axis attribute (tick color, etc). + # axes is also used to follow the axis attribute (tick color, etc). super().__init__(**kwargs) @@ -733,7 +692,7 @@ def __init__(self, axes, self.offset_transform = ScaledTranslation( *offset, Affine2D().scale(1 / 72) # points to inches. - + self.axes.figure.dpi_scale_trans) + + self.axes.get_figure(root=False).dpi_scale_trans) if axis_direction in ["left", "right"]: self.axis = axes.yaxis @@ -753,27 +712,22 @@ def __init__(self, axes, self._axislabel_add_angle = 0. self.set_axis_direction(axis_direction) - @cbook.deprecated("3.3") - @property - def dpi_transform(self): - return Affine2D().scale(1 / 72) + self.axes.figure.dpi_scale_trans - # axis direction def set_axis_direction(self, axis_direction): """ - Adjust the direction, text angle, text alignment of - ticklabels, labels following the matplotlib convention for - the rectangle axes. + Adjust the direction, text angle, and text alignment of tick labels + and axis labels following the Matplotlib convention for the rectangle + axes. The *axis_direction* must be one of [left, right, bottom, top]. ===================== ========== ========= ========== ========== - property left bottom right top + Property left bottom right top ===================== ========== ========= ========== ========== - ticklabels location "-" "+" "+" "-" - axislabel location "-" "+" "+" "-" - ticklabels angle 90 0 -90 180 + ticklabel direction "-" "+" "+" "-" + axislabel direction "-" "+" "+" "-" + ticklabel angle 90 0 -90 180 ticklabel va center baseline center baseline ticklabel ha right center right center axislabel angle 180 0 0 180 @@ -785,6 +739,10 @@ def set_axis_direction(self, axis_direction): the increasing coordinate. Also, the text angles are actually relative to (90 + angle of the direction to the ticklabel), which gives 0 for bottom axis. + + Parameters + ---------- + axis_direction : {"left", "bottom", "right", "top"} """ self.major_ticklabels.set_axis_direction(axis_direction) self.label.set_axis_direction(axis_direction) @@ -798,16 +756,16 @@ def set_axis_direction(self, axis_direction): def set_ticklabel_direction(self, tick_direction): r""" - Adjust the direction of the ticklabel. + Adjust the direction of the tick labels. - Note that the *label_direction*\s '+' and '-' are relative to the + Note that the *tick_direction*\s '+' and '-' are relative to the direction of the increasing coordinate. Parameters ---------- tick_direction : {"+", "-"} """ - self._ticklabel_add_angle = cbook._check_getitem( + self._ticklabel_add_angle = _api.check_getitem( {"+": 0, "-": 180}, tick_direction=tick_direction) def invert_ticklabel_direction(self): @@ -817,16 +775,16 @@ def invert_ticklabel_direction(self): def set_axislabel_direction(self, label_direction): r""" - Adjust the direction of the axislabel. + Adjust the direction of the axis label. Note that the *label_direction*\s '+' and '-' are relative to the direction of the increasing coordinate. Parameters ---------- - tick_direction : {"+", "-"} + label_direction : {"+", "-"} """ - self._axislabel_add_angle = cbook._check_getitem( + self._axislabel_add_angle = _api.check_getitem( {"+": 0, "-": 180}, label_direction=label_direction) def get_transform(self): @@ -857,6 +815,7 @@ def set_axisline_style(self, axisline_style=None, **kwargs): Examples -------- The following two commands are equal: + >>> set_axisline_style("->,size=1.5") >>> set_axisline_style("->", size=1.5) """ @@ -885,11 +844,11 @@ def _init_line(self): if axisline_style is None: self.line = PathPatch( self._axis_artist_helper.get_line(self.axes), - color=rcParams['axes.edgecolor'], + color=mpl.rcParams['axes.edgecolor'], fill=False, - linewidth=rcParams['axes.linewidth'], - capstyle=rcParams['lines.solid_capstyle'], - joinstyle=rcParams['lines.solid_joinstyle'], + linewidth=mpl.rcParams['axes.linewidth'], + capstyle=mpl.rcParams['lines.solid_capstyle'], + joinstyle=mpl.rcParams['lines.solid_joinstyle'], transform=tran) else: self.line = axisline_style(self, transform=tran) @@ -908,31 +867,33 @@ def _init_ticks(self, **kwargs): self.major_ticks = Ticks( kwargs.get( - "major_tick_size", rcParams[f"{axis_name}tick.major.size"]), + "major_tick_size", + mpl.rcParams[f"{axis_name}tick.major.size"]), axis=self.axis, transform=trans) self.minor_ticks = Ticks( kwargs.get( - "minor_tick_size", rcParams[f"{axis_name}tick.minor.size"]), + "minor_tick_size", + mpl.rcParams[f"{axis_name}tick.minor.size"]), axis=self.axis, transform=trans) - size = rcParams[f"{axis_name}tick.labelsize"] + size = mpl.rcParams[f"{axis_name}tick.labelsize"] self.major_ticklabels = TickLabels( axis=self.axis, axis_direction=self._axis_direction, - figure=self.axes.figure, + figure=self.axes.get_figure(root=False), transform=trans, fontsize=size, pad=kwargs.get( - "major_tick_pad", rcParams[f"{axis_name}tick.major.pad"]), + "major_tick_pad", mpl.rcParams[f"{axis_name}tick.major.pad"]), ) self.minor_ticklabels = TickLabels( axis=self.axis, axis_direction=self._axis_direction, - figure=self.axes.figure, + figure=self.axes.get_figure(root=False), transform=trans, fontsize=size, pad=kwargs.get( - "minor_tick_pad", rcParams[f"{axis_name}tick.minor.pad"]), + "minor_tick_pad", mpl.rcParams[f"{axis_name}tick.minor.pad"]), ) def _get_tick_info(self, tick_iter): @@ -957,96 +918,45 @@ def _get_tick_info(self, tick_iter): return ticks_loc_angle, ticklabels_loc_angle_label - def _update_ticks(self, renderer): + def _update_ticks(self, renderer=None): # set extra pad for major and minor ticklabels: use ticksize of # majorticks even for minor ticks. not clear what is best. + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + dpi_cor = renderer.points_to_pixels(1.) if self.major_ticks.get_visible() and self.major_ticks.get_tick_out(): - self.major_ticklabels._set_external_pad( - self.major_ticks._ticksize * dpi_cor) - self.minor_ticklabels._set_external_pad( - self.major_ticks._ticksize * dpi_cor) + ticklabel_pad = self.major_ticks._ticksize * dpi_cor + self.major_ticklabels._external_pad = ticklabel_pad + self.minor_ticklabels._external_pad = ticklabel_pad else: - self.major_ticklabels._set_external_pad(0) - self.minor_ticklabels._set_external_pad(0) + self.major_ticklabels._external_pad = 0 + self.minor_ticklabels._external_pad = 0 majortick_iter, minortick_iter = \ self._axis_artist_helper.get_tick_iterators(self.axes) tick_loc_angle, ticklabel_loc_angle_label = \ self._get_tick_info(majortick_iter) - self.major_ticks.set_locs_angles(tick_loc_angle) self.major_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - # minor ticks tick_loc_angle, ticklabel_loc_angle_label = \ self._get_tick_info(minortick_iter) - self.minor_ticks.set_locs_angles(tick_loc_angle) self.minor_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - return self.major_ticklabels.get_window_extents(renderer) - def _draw_ticks(self, renderer): - - extents = self._update_ticks(renderer) - - self.major_ticks.draw(renderer) - self.major_ticklabels.draw(renderer) - - self.minor_ticks.draw(renderer) - self.minor_ticklabels.draw(renderer) - - if (self.major_ticklabels.get_visible() - or self.minor_ticklabels.get_visible()): - self._draw_offsetText(renderer) - - return extents - - def _draw_ticks2(self, renderer): - # set extra pad for major and minor ticklabels: use ticksize of - # majorticks even for minor ticks. not clear what is best. - - dpi_cor = renderer.points_to_pixels(1.) - if self.major_ticks.get_visible() and self.major_ticks.get_tick_out(): - self.major_ticklabels._set_external_pad( - self.major_ticks._ticksize * dpi_cor) - self.minor_ticklabels._set_external_pad( - self.major_ticks._ticksize * dpi_cor) - else: - self.major_ticklabels._set_external_pad(0) - self.minor_ticklabels._set_external_pad(0) - - majortick_iter, minortick_iter = \ - self._axis_artist_helper.get_tick_iterators(self.axes) - - tick_loc_angle, ticklabel_loc_angle_label = \ - self._get_tick_info(majortick_iter) - - self.major_ticks.set_locs_angles(tick_loc_angle) - self.major_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - + self._update_ticks(renderer) self.major_ticks.draw(renderer) self.major_ticklabels.draw(renderer) - - # minor ticks - tick_loc_angle, ticklabel_loc_angle_label = \ - self._get_tick_info(minortick_iter) - - self.minor_ticks.set_locs_angles(tick_loc_angle) - self.minor_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - self.minor_ticks.draw(renderer) self.minor_ticklabels.draw(renderer) - if (self.major_ticklabels.get_visible() or self.minor_ticklabels.get_visible()): self._draw_offsetText(renderer) - return self.major_ticklabels.get_window_extents(renderer) - _offsetText_pos = dict(left=(0, 1, "bottom", "right"), right=(1, 1, "bottom", "left"), bottom=(1, 0, "top", "right"), @@ -1058,7 +968,7 @@ def _init_offsetText(self, direction): "", xy=(x, y), xycoords="axes fraction", xytext=(0, 0), textcoords="offset points", - color=rcParams['xtick.color'], + color=mpl.rcParams['xtick.color'], horizontalalignment=ha, verticalalignment=va, ) self.offsetText.set_transform(IdentityTransform()) @@ -1082,13 +992,13 @@ def _init_label(self, **kwargs): self.label = AxisLabel( 0, 0, "__from_axes__", color="auto", - fontsize=kwargs.get("labelsize", rcParams['axes.labelsize']), - fontweight=rcParams['axes.labelweight'], + fontsize=kwargs.get("labelsize", mpl.rcParams['axes.labelsize']), + fontweight=mpl.rcParams['axes.labelweight'], axis=self.axis, transform=tr, axis_direction=self._axis_direction, ) - self.label.set_figure(self.axes.figure) + self.label.set_figure(self.axes.get_figure(root=False)) labelpad = kwargs.get("labelpad", 5) self.label.set_pad(labelpad) @@ -1108,7 +1018,7 @@ def _update_label(self, renderer): axislabel_pad = max(self.major_ticklabels._axislabel_pad, self.minor_ticklabels._axislabel_pad) - self.label._set_external_pad(axislabel_pad) + self.label._external_pad = axislabel_pad xy, angle_tangent = \ self._axis_artist_helper.get_axislabel_pos_angle(self.axes) @@ -1118,57 +1028,34 @@ def _update_label(self, renderer): angle_label = angle_tangent - 90 x, y = xy - self.label._set_ref_angle(angle_label+self._axislabel_add_angle) + self.label._ref_angle = angle_label + self._axislabel_add_angle self.label.set(x=x, y=y) def _draw_label(self, renderer): self._update_label(renderer) self.label.draw(renderer) - def _draw_label2(self, renderer): - if not self.label.get_visible(): - return - - if self._ticklabel_add_angle != self._axislabel_add_angle: - if ((self.major_ticks.get_visible() - and not self.major_ticks.get_tick_out()) - or (self.minor_ticks.get_visible() - and not self.major_ticks.get_tick_out())): - axislabel_pad = self.major_ticks._ticksize - else: - axislabel_pad = 0 - else: - axislabel_pad = max(self.major_ticklabels._axislabel_pad, - self.minor_ticklabels._axislabel_pad) - - self.label._set_external_pad(axislabel_pad) - - xy, angle_tangent = \ - self._axis_artist_helper.get_axislabel_pos_angle(self.axes) - if xy is None: - return - - angle_label = angle_tangent - 90 - - x, y = xy - self.label._set_ref_angle(angle_label+self._axislabel_add_angle) - self.label.set(x=x, y=y) - self.label.draw(renderer) - def set_label(self, s): + # docstring inherited self.label.set_text(s) - def get_tightbbox(self, renderer): + def get_tightbbox(self, renderer=None): if not self.get_visible(): return self._axis_artist_helper.update_lim(self.axes) self._update_ticks(renderer) self._update_label(renderer) + + self.line.set_path(self._axis_artist_helper.get_line(self.axes)) + if self.get_axisline_style() is not None: + self.line.set_line_mutation_scale(self.major_ticklabels.get_size()) + bb = [ *self.major_ticklabels.get_window_extents(renderer), *self.minor_ticklabels.get_window_extents(renderer), self.label.get_window_extent(renderer), self.offsetText.get_window_extent(renderer), + self.line.get_window_extent(renderer), ] bb = [b for b in bb if b and (b.width != 0 or b.height != 0)] if bb: @@ -1202,7 +1089,7 @@ def toggle(self, all=None, ticks=None, ticklabels=None, label=None): To turn all on but (axis) label off :: - axis.toggle(all=True, label=False)) + axis.toggle(all=True, label=False) """ if all: diff --git a/lib/mpl_toolkits/axisartist/axisline_style.py b/lib/mpl_toolkits/axisartist/axisline_style.py index 80f3ce58eb48..ac89603e0844 100644 --- a/lib/mpl_toolkits/axisartist/axisline_style.py +++ b/lib/mpl_toolkits/axisartist/axisline_style.py @@ -1,17 +1,19 @@ +""" +Provides classes to style the axis lines. +""" import math import numpy as np +import matplotlib as mpl from matplotlib.patches import _Style, FancyArrowPatch -from matplotlib.transforms import IdentityTransform from matplotlib.path import Path +from matplotlib.transforms import IdentityTransform class _FancyAxislineStyle: class SimpleArrow(FancyArrowPatch): - """ - The artist class that will be returned for SimpleArrow style. - """ + """The artist class that will be returned for SimpleArrow style.""" _ARROW_STYLE = "->" def __init__(self, axis_artist, line_path, transform, @@ -56,10 +58,10 @@ def set_path(self, path): def draw(self, renderer): """ Draw the axis line. - 1) transform the path to the display coordinate. - 2) extend the path to make a room for arrow - 3) update the path of the FancyArrowPatch. - 4) draw + 1) Transform the path to the display coordinate. + 2) Extend the path to make a room for arrow. + 3) Update the path of the FancyArrowPatch. + 4) Draw. """ path_in_disp = self._line_transform.transform_path(self._line_path) mutation_size = self.get_mutation_scale() # line_mutation_scale() @@ -68,18 +70,31 @@ def draw(self, renderer): self._path_original = extended_path FancyArrowPatch.draw(self, renderer) + def get_window_extent(self, renderer=None): + + path_in_disp = self._line_transform.transform_path(self._line_path) + mutation_size = self.get_mutation_scale() # line_mutation_scale() + extended_path = self._extend_path(path_in_disp, + mutation_size=mutation_size) + self._path_original = extended_path + return FancyArrowPatch.get_window_extent(self, renderer) + class FilledArrow(SimpleArrow): - """ - The artist class that will be returned for SimpleArrow style. - """ + """The artist class that will be returned for FilledArrow style.""" _ARROW_STYLE = "-|>" + def __init__(self, axis_artist, line_path, transform, + line_mutation_scale, facecolor): + super().__init__(axis_artist, line_path, transform, + line_mutation_scale) + self.set_facecolor(facecolor) + class AxislineStyle(_Style): """ A container class which defines style classes for AxisArtists. - An instance of any axisline style class is an callable object, + An instance of any axisline style class is a callable object, whose call signature is :: __call__(self, axis_artist, path, transform) @@ -144,6 +159,34 @@ def new_line(self, axis_artist, transform): _style_list["->"] = SimpleArrow class FilledArrow(SimpleArrow): + """ + An arrow with a filled head. + """ + ArrowAxisClass = _FancyAxislineStyle.FilledArrow + def __init__(self, size=1, facecolor=None): + """ + Parameters + ---------- + size : float + Size of the arrow as a fraction of the ticklabel size. + facecolor : :mpltype:`color`, default: :rc:`axes.edgecolor` + Fill color. + + .. versionadded:: 3.7 + """ + + facecolor = mpl._val_or_rc(facecolor, 'axes.edgecolor') + self.size = size + self._facecolor = facecolor + super().__init__(size=size) + + def new_line(self, axis_artist, transform): + linepath = Path([(0, 0), (0, 1)]) + axisline = self.ArrowAxisClass(axis_artist, linepath, transform, + line_mutation_scale=self.size, + facecolor=self._facecolor) + return axisline + _style_list["-|>"] = FilledArrow diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index db89bd434eb8..c921ea597cb4 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -20,8 +20,8 @@ instance responsible to draw left y-axis. The default Axes.axis contains "bottom", "left", "top" and "right". -AxisArtist can be considered as a container artist and -has following children artists which will draw ticks, labels, etc. +AxisArtist can be considered as a container artist and has the following +children artists which will draw ticks, labels, etc. * line * major_ticks, major_ticklabels @@ -41,333 +41,263 @@ import numpy as np -from matplotlib import cbook, rcParams +import matplotlib as mpl +from matplotlib import _api import matplotlib.axes as maxes from matplotlib.path import Path +from matplotlib.transforms import Bbox + from mpl_toolkits.axes_grid1 import mpl_axes -from .axisline_style import AxislineStyle +from .axisline_style import AxislineStyle # noqa from .axis_artist import AxisArtist, GridlinesCollection -class AxisArtistHelper: +class _AxisArtistHelperBase: """ - AxisArtistHelper should define - following method with given APIs. Note that the first axes argument - will be axes attribute of the caller artist.:: - + Base class for axis helper. - # LINE (spinal line?) + Subclasses should define the methods listed below. The *axes* + argument will be the ``.axes`` attribute of the caller artist. :: - def get_line(self, axes): - # path : Path - return path + # Construct the spine. def get_line_transform(self, axes): - # ... - # trans : transform - return trans + return transform - # LABEL + def get_line(self, axes): + return path - def get_label_pos(self, axes): - # x, y : position - return (x, y), trans + # Construct the label. + def get_axislabel_transform(self, axes): + return transform - def get_label_offset_transform(self, - axes, - pad_points, fontprops, renderer, - bboxes, - ): - # va : vertical alignment - # ha : horizontal alignment - # a : angle - return trans, va, ha, a + def get_axislabel_pos_angle(self, axes): + return (x, y), angle - # TICK + # Construct the ticks. def get_tick_transform(self, axes): - return trans + return transform def get_tick_iterators(self, axes): - # iter : iterable object that yields (c, angle, l) where - # c, angle, l is position, tick angle, and label - + # A pair of iterables (one for major ticks, one for minor ticks) + # that yield (tick_position, tick_angle, tick_label). return iter_major, iter_minor """ - class _Base: - """Base class for axis helper.""" - def __init__(self): - self.delta1, self.delta2 = 0.00001, 0.00001 + def __init__(self, nth_coord): + self.nth_coord = nth_coord - def update_lim(self, axes): - pass + def update_lim(self, axes): + pass - class Fixed(_Base): - """Helper class for a fixed (in the axes coordinate) axis.""" + def get_nth_coord(self): + return self.nth_coord - _default_passthru_pt = dict(left=(0, 0), - right=(1, 0), - bottom=(0, 0), - top=(0, 1)) + def _to_xy(self, values, const): + """ + Create a (*values.shape, 2)-shape array representing (x, y) pairs. - def __init__(self, loc, nth_coord=None): - """ - nth_coord = along which coordinate value varies - in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - cbook._check_in_list(["left", "right", "bottom", "top"], loc=loc) - self._loc = loc + The other coordinate is filled with the constant *const*. - if nth_coord is None: - if loc in ["left", "right"]: - nth_coord = 1 - elif loc in ["bottom", "top"]: - nth_coord = 0 + Example:: - self.nth_coord = nth_coord + >>> self.nth_coord = 0 + >>> self._to_xy([1, 2, 3], const=0) + array([[1, 0], + [2, 0], + [3, 0]]) + """ + if self.nth_coord == 0: + return np.stack(np.broadcast_arrays(values, const), axis=-1) + elif self.nth_coord == 1: + return np.stack(np.broadcast_arrays(const, values), axis=-1) + else: + raise ValueError("Unexpected nth_coord") - super().__init__() - self.passthru_pt = self._default_passthru_pt[loc] +class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): + """Helper class for a fixed (in the axes coordinate) axis.""" - _verts = np.array([[0., 0.], - [1., 1.]]) - fixed_coord = 1 - nth_coord - _verts[:, fixed_coord] = self.passthru_pt[fixed_coord] + def __init__(self, loc): + """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" + super().__init__(_api.check_getitem( + {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) + self._loc = loc + self._pos = {"bottom": 0, "top": 1, "left": 0, "right": 1}[loc] + # axis line in transAxes + self._path = Path(self._to_xy((0, 1), const=self._pos)) - # axis line in transAxes - self._path = Path(_verts) + # LINE - def get_nth_coord(self): - return self.nth_coord + def get_line(self, axes): + return self._path - # LINE + def get_line_transform(self, axes): + return axes.transAxes - def get_line(self, axes): - return self._path + # LABEL - def get_line_transform(self, axes): - return axes.transAxes + def get_axislabel_transform(self, axes): + return axes.transAxes - # LABEL + def get_axislabel_pos_angle(self, axes): + """ + Return the label reference position in transAxes. - def get_axislabel_transform(self, axes): - return axes.transAxes + get_label_transform() returns a transform of (transAxes+offset) + """ + return dict(left=((0., 0.5), 90), # (position, angle_tangent) + right=((1., 0.5), 90), + bottom=((0.5, 0.), 0), + top=((0.5, 1.), 0))[self._loc] - def get_axislabel_pos_angle(self, axes): - """ - Return the label reference position in transAxes. + # TICK - get_label_transform() returns a transform of (transAxes+offset) - """ - return dict(left=((0., 0.5), 90), # (position, angle_tangent) - right=((1., 0.5), 90), - bottom=((0.5, 0.), 0), - top=((0.5, 1.), 0))[self._loc] + def get_tick_transform(self, axes): + return [axes.get_xaxis_transform(), axes.get_yaxis_transform()][self.nth_coord] - # TICK - def get_tick_transform(self, axes): - return [axes.get_xaxis_transform(), - axes.get_yaxis_transform()][self.nth_coord] +class _FloatingAxisArtistHelperBase(_AxisArtistHelperBase): + def __init__(self, nth_coord, value): + self._value = value + super().__init__(nth_coord) - class Floating(_Base): + def get_line(self, axes): + raise RuntimeError("get_line method should be defined by the derived class") - def __init__(self, nth_coord, value): - self.nth_coord = nth_coord - self._value = value - super().__init__() - def get_nth_coord(self): - return self.nth_coord +class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): - def get_line(self, axes): - raise RuntimeError( - "get_line method should be defined by the derived class") + def __init__(self, axes, loc): + super().__init__(loc) + self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] + # TICK -class AxisArtistHelperRectlinear: + def get_tick_iterators(self, axes): + """tick_loc, tick_angle, tick_label""" + angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] - class Fixed(AxisArtistHelper.Fixed): + major = self.axis.major + major_locs = major.locator() + major_labels = major.formatter.format_ticks(major_locs) - def __init__(self, axes, loc, nth_coord=None): - """ - nth_coord = along which coordinate value varies - in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - super().__init__(loc, nth_coord) - self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] + minor = self.axis.minor + minor_locs = minor.locator() + minor_labels = minor.formatter.format_ticks(minor_locs) - # TICK + tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label""" + def _f(locs, labels): + for loc, label in zip(locs, labels): + c = self._to_xy(loc, const=self._pos) + # check if the tick point is inside axes + c2 = tick_to_axes.transform(c) + if mpl.transforms._interval_contains_close((0, 1), c2[self.nth_coord]): + yield c, angle_normal, angle_tangent, label - loc = self._loc + return _f(major_locs, major_labels), _f(minor_locs, minor_labels) - if loc in ["bottom", "top"]: - angle_normal, angle_tangent = 90, 0 - else: - angle_normal, angle_tangent = 0, 90 - major = self.axis.major - majorLocs = major.locator() - majorLabels = major.formatter.format_ticks(majorLocs) +class FloatingAxisArtistHelperRectilinear(_FloatingAxisArtistHelperBase): - minor = self.axis.minor - minorLocs = minor.locator() - minorLabels = minor.formatter.format_ticks(minorLocs) + def __init__(self, axes, nth_coord, + passingthrough_point, axis_direction="bottom"): + super().__init__(nth_coord, passingthrough_point) + self._axis_direction = axis_direction + self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes + def get_line(self, axes): + fixed_coord = 1 - self.nth_coord + data_to_axes = axes.transData - axes.transAxes + p = data_to_axes.transform([self._value, self._value]) + return Path(self._to_xy((0, 1), const=p[fixed_coord])) - def _f(locs, labels): - for x, l in zip(locs, labels): + def get_line_transform(self, axes): + return axes.transAxes - c = list(self.passthru_pt) # copy - c[self.nth_coord] = x + def get_axislabel_transform(self, axes): + return axes.transAxes - # check if the tick point is inside axes - c2 = tick_to_axes.transform(c) - if (0 - self.delta1 - <= c2[self.nth_coord] - <= 1 + self.delta2): - yield c, angle_normal, angle_tangent, l - - return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels) + def get_axislabel_pos_angle(self, axes): + """ + Return the label reference position in transAxes. - class Floating(AxisArtistHelper.Floating): - def __init__(self, axes, nth_coord, - passingthrough_point, axis_direction="bottom"): - super().__init__(nth_coord, passingthrough_point) - self._axis_direction = axis_direction - self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] + get_label_transform() returns a transform of (transAxes+offset) + """ + angle = [0, 90][self.nth_coord] + fixed_coord = 1 - self.nth_coord + data_to_axes = axes.transData - axes.transAxes + p = data_to_axes.transform([self._value, self._value]) + verts = self._to_xy(0.5, const=p[fixed_coord]) + return (verts, angle) if 0 <= verts[fixed_coord] <= 1 else (None, None) - def get_line(self, axes): - _verts = np.array([[0., 0.], - [1., 1.]]) + def get_tick_transform(self, axes): + return axes.transData - fixed_coord = 1 - self.nth_coord - data_to_axes = axes.transData - axes.transAxes - p = data_to_axes.transform([self._value, self._value]) - _verts[:, fixed_coord] = p[fixed_coord] + def get_tick_iterators(self, axes): + """tick_loc, tick_angle, tick_label""" + angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] - return Path(_verts) + major = self.axis.major + major_locs = major.locator() + major_labels = major.formatter.format_ticks(major_locs) - def get_line_transform(self, axes): - return axes.transAxes + minor = self.axis.minor + minor_locs = minor.locator() + minor_labels = minor.formatter.format_ticks(minor_locs) - def get_axislabel_transform(self, axes): - return axes.transAxes + data_to_axes = axes.transData - axes.transAxes - def get_axislabel_pos_angle(self, axes): - """ - Return the label reference position in transAxes. - - get_label_transform() returns a transform of (transAxes+offset) - """ - angle = [0, 90][self.nth_coord] - _verts = [0.5, 0.5] - fixed_coord = 1 - self.nth_coord - data_to_axes = axes.transData - axes.transAxes - p = data_to_axes.transform([self._value, self._value]) - _verts[fixed_coord] = p[fixed_coord] - if 0 <= _verts[fixed_coord] <= 1: - return _verts, angle - else: - return None, None - - def get_tick_transform(self, axes): - return axes.transData - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label""" - if self.nth_coord == 0: - angle_normal, angle_tangent = 90, 0 - else: - angle_normal, angle_tangent = 0, 90 + def _f(locs, labels): + for loc, label in zip(locs, labels): + c = self._to_xy(loc, const=self._value) + c1, c2 = data_to_axes.transform(c) + if 0 <= c1 <= 1 and 0 <= c2 <= 1: + yield c, angle_normal, angle_tangent, label - major = self.axis.major - majorLocs = major.locator() - majorLabels = major.formatter.format_ticks(majorLocs) + return _f(major_locs, major_labels), _f(minor_locs, minor_labels) - minor = self.axis.minor - minorLocs = minor.locator() - minorLabels = minor.formatter.format_ticks(minorLocs) - data_to_axes = axes.transData - axes.transAxes +class AxisArtistHelper: # Backcompat. + Fixed = _FixedAxisArtistHelperBase + Floating = _FloatingAxisArtistHelperBase - def _f(locs, labels): - for x, l in zip(locs, labels): - c = [self._value, self._value] - c[self.nth_coord] = x - c1, c2 = data_to_axes.transform(c) - if (0 <= c1 <= 1 and 0 <= c2 <= 1 - and 0 - self.delta1 - <= [c1, c2][self.nth_coord] - <= 1 + self.delta2): - yield c, angle_normal, angle_tangent, l - return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels) +class AxisArtistHelperRectlinear: # Backcompat. + Fixed = FixedAxisArtistHelperRectilinear + Floating = FloatingAxisArtistHelperRectilinear class GridHelperBase: def __init__(self): - self._force_update = True self._old_limits = None super().__init__() def update_lim(self, axes): x1, x2 = axes.get_xlim() y1, y2 = axes.get_ylim() - - if self._force_update or self._old_limits != (x1, x2, y1, y2): - self._update(x1, x2, y1, y2) - self._force_update = False + if self._old_limits != (x1, x2, y1, y2): + self._update_grid(Bbox.from_extents(x1, y1, x2, y2)) self._old_limits = (x1, x2, y1, y2) - def _update(self, x1, x2, y1, y2): - pass - - def invalidate(self): - self._force_update = True - - def valid(self): - return not self._force_update + def _update_grid(self, bbox): + """Cache relevant computations when the axes limits have changed.""" def get_gridlines(self, which, axis): """ Return list of grid lines as a list of paths (list of points). - *which* : "major" or "minor" - *axis* : "both", "x" or "y" + Parameters + ---------- + which : {"both", "major", "minor"} + axis : {"both", "x", "y"} """ return [] - def new_gridlines(self, ax): - """ - Create and return a new GridlineCollection instance. - - *which* : "major" or "minor" - *axis* : "both", "x" or "y" - - """ - gridlines = GridlinesCollection(None, transform=ax.transData, - colors=rcParams['grid.color'], - linestyles=rcParams['grid.linestyle'], - linewidths=rcParams['grid.linewidth']) - ax._set_artist_props(gridlines) - gridlines.set_grid_helper(self) - - ax.axes._set_artist_props(gridlines) - # gridlines.set_clip_path(self.axes.patch) - # set_clip_path need to be deferred after Axes.cla is completed. - # It is done inside the cla. - - return gridlines - class GridHelperRectlinear(GridHelperBase): @@ -375,43 +305,26 @@ def __init__(self, axes): super().__init__() self.axes = axes - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None, - ): - + def new_fixed_axis( + self, loc, *, axis_direction=None, offset=None, axes=None + ): if axes is None: - cbook._warn_external( + _api.warn_external( "'new_fixed_axis' explicitly requires the axes keyword.") axes = self.axes - - _helper = AxisArtistHelperRectlinear.Fixed(axes, loc, nth_coord) - if axis_direction is None: axis_direction = loc - axisline = AxisArtist(axes, _helper, offset=offset, - axis_direction=axis_direction, - ) - - return axisline - - def new_floating_axis(self, nth_coord, value, - axis_direction="bottom", - axes=None, - ): + return AxisArtist(axes, FixedAxisArtistHelperRectilinear(axes, loc), + offset=offset, axis_direction=axis_direction) + def new_floating_axis(self, nth_coord, value, axis_direction="bottom", axes=None): if axes is None: - cbook._warn_external( + _api.warn_external( "'new_floating_axis' explicitly requires the axes keyword.") axes = self.axes - - _helper = AxisArtistHelperRectlinear.Floating( + helper = FloatingAxisArtistHelperRectilinear( axes, nth_coord, value, axis_direction) - - axisline = AxisArtist(axes, _helper) - + axisline = AxisArtist(axes, helper, axis_direction=axis_direction) axisline.line.set_clip_on(True) axisline.line.set_clip_box(axisline.axes.bbox) return axisline @@ -420,45 +333,41 @@ def get_gridlines(self, which="major", axis="both"): """ Return list of gridline coordinates in data coordinates. - *which* : "major" or "minor" - *axis* : "both", "x" or "y" + Parameters + ---------- + which : {"both", "major", "minor"} + axis : {"both", "x", "y"} """ + _api.check_in_list(["both", "major", "minor"], which=which) + _api.check_in_list(["both", "x", "y"], axis=axis) gridlines = [] - if axis in ["both", "x"]: + if axis in ("both", "x"): locs = [] y1, y2 = self.axes.get_ylim() - if which in ["both", "major"]: + if which in ("both", "major"): locs.extend(self.axes.xaxis.major.locator()) - if which in ["both", "minor"]: + if which in ("both", "minor"): locs.extend(self.axes.xaxis.minor.locator()) + gridlines.extend([[x, x], [y1, y2]] for x in locs) - for x in locs: - gridlines.append([[x, x], [y1, y2]]) - - if axis in ["both", "y"]: + if axis in ("both", "y"): x1, x2 = self.axes.get_xlim() locs = [] - if self.axes.yaxis._gridOnMajor: + if self.axes.yaxis._major_tick_kw["gridOn"]: locs.extend(self.axes.yaxis.major.locator()) - if self.axes.yaxis._gridOnMinor: + if self.axes.yaxis._minor_tick_kw["gridOn"]: locs.extend(self.axes.yaxis.minor.locator()) - - for y in locs: - gridlines.append([[x1, x2], [y, y]]) + gridlines.extend([[x1, x2], [y, y]] for y in locs) return gridlines class Axes(maxes.Axes): - def __call__(self, *args, **kwargs): - return maxes.Axes.axis(self.axes, *args, **kwargs) - def __init__(self, *args, grid_helper=None, **kwargs): self._axisline_on = True - self._grid_helper = (grid_helper if grid_helper - else GridHelperRectlinear(self)) + self._grid_helper = grid_helper if grid_helper else GridHelperRectlinear(self) super().__init__(*args, **kwargs) self.toggle_axisline(True) @@ -467,84 +376,66 @@ def toggle_axisline(self, b=None): b = not self._axisline_on if b: self._axisline_on = True - for s in self.spines.values(): - s.set_visible(False) + self.spines[:].set_visible(False) self.xaxis.set_visible(False) self.yaxis.set_visible(False) else: self._axisline_on = False - for s in self.spines.values(): - s.set_visible(True) + self.spines[:].set_visible(True) self.xaxis.set_visible(True) self.yaxis.set_visible(True) - def _init_axis_artists(self, axes=None): - if axes is None: - axes = self - - self._axislines = mpl_axes.Axes.AxisDict(self) - new_fixed_axis = self.get_grid_helper().new_fixed_axis - for loc in ["bottom", "top", "left", "right"]: - self._axislines[loc] = new_fixed_axis(loc=loc, axes=axes, - axis_direction=loc) - - for axisline in [self._axislines["top"], self._axislines["right"]]: - axisline.label.set_visible(False) - axisline.major_ticklabels.set_visible(False) - axisline.minor_ticklabels.set_visible(False) - @property def axis(self): return self._axislines - def new_gridlines(self, grid_helper=None): - """ - Create and return a new GridlineCollection instance. - - *which* : "major" or "minor" - *axis* : "both", "x" or "y" - - """ - if grid_helper is None: - grid_helper = self.get_grid_helper() - - gridlines = grid_helper.new_gridlines(self) - return gridlines + def clear(self): + # docstring inherited - def _init_gridlines(self, grid_helper=None): - # It is done inside the cla. - self.gridlines = self.new_gridlines(grid_helper) + # Init gridlines before clear() as clear() calls grid(). + self.gridlines = gridlines = GridlinesCollection( + [], + colors=mpl.rcParams['grid.color'], + linestyles=mpl.rcParams['grid.linestyle'], + linewidths=mpl.rcParams['grid.linewidth']) + self._set_artist_props(gridlines) + gridlines.set_grid_helper(self.get_grid_helper()) - def cla(self): - # gridlines need to b created before cla() since cla calls grid() - self._init_gridlines() - super().cla() + super().clear() - # the clip_path should be set after Axes.cla() since that's - # when a patch is created. - self.gridlines.set_clip_path(self.axes.patch) + # clip_path is set after Axes.clear(): that's when a patch is created. + gridlines.set_clip_path(self.axes.patch) - self._init_axis_artists() + # Init axis artists. + self._axislines = mpl_axes.Axes.AxisDict(self) + new_fixed_axis = self.get_grid_helper().new_fixed_axis + self._axislines.update({ + loc: new_fixed_axis(loc=loc, axes=self, axis_direction=loc) + for loc in ["bottom", "top", "left", "right"]}) + for axisline in [self._axislines["top"], self._axislines["right"]]: + axisline.label.set_visible(False) + axisline.major_ticklabels.set_visible(False) + axisline.minor_ticklabels.set_visible(False) def get_grid_helper(self): return self._grid_helper - def grid(self, b=None, which='major', axis="both", **kwargs): + def grid(self, visible=None, which='major', axis="both", **kwargs): """ Toggle the gridlines, and optionally set the properties of the lines. """ - # their are some discrepancy between the behavior of grid in - # axes_grid and the original mpl's grid, because axes_grid - # explicitly set the visibility of the gridlines. - super().grid(b, which=which, axis=axis, **kwargs) + # There are some discrepancies in the behavior of grid() between + # axes_grid and Matplotlib, because axes_grid explicitly sets the + # visibility of the gridlines. + super().grid(visible, which=which, axis=axis, **kwargs) if not self._axisline_on: return - if b is None: - b = (self.axes.xaxis._gridOnMinor - or self.axes.xaxis._gridOnMajor - or self.axes.yaxis._gridOnMinor - or self.axes.yaxis._gridOnMajor) - self.gridlines.set(which=which, axis=axis, visible=b) + if visible is None: + visible = (self.axes.xaxis._minor_tick_kw["gridOn"] + or self.axes.xaxis._major_tick_kw["gridOn"] + or self.axes.yaxis._minor_tick_kw["gridOn"] + or self.axes.yaxis._major_tick_kw["gridOn"]) + self.gridlines.set(which=which, axis=axis, visible=visible) self.gridlines.set(**kwargs) def get_children(self): @@ -555,53 +446,29 @@ def get_children(self): children.extend(super().get_children()) return children - def invalidate_grid_helper(self): - self._grid_helper.invalidate() - def new_fixed_axis(self, loc, offset=None): - gh = self.get_grid_helper() - axis = gh.new_fixed_axis(loc, - nth_coord=None, - axis_direction=None, - offset=offset, - axes=self, - ) - return axis + return self.get_grid_helper().new_fixed_axis(loc, offset=offset, axes=self) def new_floating_axis(self, nth_coord, value, axis_direction="bottom"): - gh = self.get_grid_helper() - axis = gh.new_floating_axis(nth_coord, value, - axis_direction=axis_direction, - axes=self) - return axis - - -Subplot = maxes.subplot_class_factory(Axes) + return self.get_grid_helper().new_floating_axis( + nth_coord, value, axis_direction=axis_direction, axes=self) class AxesZero(Axes): - def _init_axis_artists(self): - super()._init_axis_artists() - - new_floating_axis = self._grid_helper.new_floating_axis - xaxis_zero = new_floating_axis(nth_coord=0, - value=0., - axis_direction="bottom", - axes=self) - - xaxis_zero.line.set_clip_path(self.patch) - xaxis_zero.set_visible(False) - self._axislines["xzero"] = xaxis_zero - - yaxis_zero = new_floating_axis(nth_coord=1, - value=0., - axis_direction="left", - axes=self) - - yaxis_zero.line.set_clip_path(self.patch) - yaxis_zero.set_visible(False) - self._axislines["yzero"] = yaxis_zero - - -SubplotZero = maxes.subplot_class_factory(AxesZero) + def clear(self): + super().clear() + new_floating_axis = self.get_grid_helper().new_floating_axis + self._axislines.update( + xzero=new_floating_axis( + nth_coord=0, value=0., axis_direction="bottom", axes=self), + yzero=new_floating_axis( + nth_coord=1, value=0., axis_direction="left", axes=self), + ) + for k in ["xzero", "yzero"]: + self._axislines[k].line.set_clip_path(self.patch) + self._axislines[k].set_visible(False) + + +Subplot = Axes +SubplotZero = AxesZero diff --git a/lib/mpl_toolkits/axisartist/clip_path.py b/lib/mpl_toolkits/axisartist/clip_path.py deleted file mode 100644 index f12c463b7771..000000000000 --- a/lib/mpl_toolkits/axisartist/clip_path.py +++ /dev/null @@ -1,118 +0,0 @@ -import numpy as np -from math import degrees -from matplotlib import cbook -import math - - -def atan2(dy, dx): - if dx == 0 and dy == 0: - cbook._warn_external("dx and dy are 0") - return 0 - else: - return math.atan2(dy, dx) - - -# FIXME : The current algorithm seems to return incorrect angle when the line -# ends at the boundary. -def clip(xlines, ylines, x0, clip="right", xdir=True, ydir=True): - - clipped_xlines = [] - clipped_ylines = [] - - _pos_angles = [] - - xsign = 1 if xdir else -1 - ysign = 1 if ydir else -1 - - for x, y in zip(xlines, ylines): - - if clip in ["up", "right"]: - b = (x < x0).astype("i") - db = b[1:] - b[:-1] - else: - b = (x > x0).astype("i") - db = b[1:] - b[:-1] - - if b[0]: - ns = 0 - else: - ns = -1 - segx, segy = [], [] - for (i,) in np.argwhere(db): - c = db[i] - if c == -1: - dx = (x0 - x[i]) - dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i])) - y0 = y[i] + dy - clipped_xlines.append(np.concatenate([segx, x[ns:i+1], [x0]])) - clipped_ylines.append(np.concatenate([segy, y[ns:i+1], [y0]])) - ns = -1 - segx, segy = [], [] - - if dx == 0. and dy == 0: - dx = x[i+1] - x[i] - dy = y[i+1] - y[i] - - a = degrees(atan2(ysign*dy, xsign*dx)) - _pos_angles.append((x0, y0, a)) - - elif c == 1: - dx = (x0 - x[i]) - dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i])) - y0 = y[i] + dy - segx, segy = [x0], [y0] - ns = i+1 - - if dx == 0. and dy == 0: - dx = x[i+1] - x[i] - dy = y[i+1] - y[i] - - a = degrees(atan2(ysign*dy, xsign*dx)) - _pos_angles.append((x0, y0, a)) - - if ns != -1: - clipped_xlines.append(np.concatenate([segx, x[ns:]])) - clipped_ylines.append(np.concatenate([segy, y[ns:]])) - - return clipped_xlines, clipped_ylines, _pos_angles - - -def clip_line_to_rect(xline, yline, bbox): - - x0, y0, x1, y1 = bbox.extents - - xdir = x1 > x0 - ydir = y1 > y0 - - if x1 > x0: - lx1, ly1, c_right_ = clip([xline], [yline], x1, - clip="right", xdir=xdir, ydir=ydir) - lx2, ly2, c_left_ = clip(lx1, ly1, x0, - clip="left", xdir=xdir, ydir=ydir) - else: - lx1, ly1, c_right_ = clip([xline], [yline], x0, - clip="right", xdir=xdir, ydir=ydir) - lx2, ly2, c_left_ = clip(lx1, ly1, x1, - clip="left", xdir=xdir, ydir=ydir) - - if y1 > y0: - ly3, lx3, c_top_ = clip(ly2, lx2, y1, - clip="right", xdir=ydir, ydir=xdir) - ly4, lx4, c_bottom_ = clip(ly3, lx3, y0, - clip="left", xdir=ydir, ydir=xdir) - else: - ly3, lx3, c_top_ = clip(ly2, lx2, y0, - clip="right", xdir=ydir, ydir=xdir) - ly4, lx4, c_bottom_ = clip(ly3, lx3, y1, - clip="left", xdir=ydir, ydir=xdir) - - c_left = [((x, y), (a + 90) % 180 - 90) for x, y, a in c_left_ - if bbox.containsy(y)] - c_bottom = [((x, y), (90 - a) % 180) for y, x, a in c_bottom_ - if bbox.containsx(x)] - c_right = [((x, y), (a + 90) % 180 + 90) for x, y, a in c_right_ - if bbox.containsy(y)] - c_top = [((x, y), (90 - a) % 180 + 180) for y, x, a in c_top_ - if bbox.containsx(x)] - - return list(zip(lx4, ly4)), [c_left, c_bottom, c_right, c_top] diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index e1958939c7d5..a533b2a9c427 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -9,12 +9,12 @@ import numpy as np +import matplotlib as mpl +from matplotlib import _api, cbook import matplotlib.patches as mpatches from matplotlib.path import Path -import matplotlib.axes as maxes - +from matplotlib.transforms import Bbox from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory - from . import axislines, grid_helper_curvelinear from .axis_artist import AxisArtist from .grid_finder import ExtremeFinderSimple @@ -32,7 +32,10 @@ def __init__(self, grid_helper, side, nth_coord_ticks=None): nth_coord = along which coordinate value varies. nth_coord = 0 -> x axis, nth_coord = 1 -> y axis """ - value, nth_coord = grid_helper.get_data_boundary(side) + lon1, lon2, lat1, lat2 = grid_helper.grid_finder.extreme_finder(*[None] * 5) + value, nth_coord = _api.check_getitem( + dict(left=(lon1, 0), right=(lon2, 0), bottom=(lat1, 1), top=(lat2, 1)), + side=side) super().__init__(grid_helper, nth_coord, value, axis_direction=side) if nth_coord_ticks is None: nth_coord_ticks = nth_coord @@ -44,91 +47,52 @@ def __init__(self, grid_helper, side, nth_coord_ticks=None): def update_lim(self, axes): self.grid_helper.update_lim(axes) - self.grid_info = self.grid_helper.grid_info + self._grid_info = self.grid_helper._grid_info def get_tick_iterators(self, axes): """tick_loc, tick_angle, tick_label, (optionally) tick_label""" grid_finder = self.grid_helper.grid_finder - lat_levs, lat_n, lat_factor = self.grid_info["lat_info"] - lon_levs, lon_n, lon_factor = self.grid_info["lon_info"] - - lon_levs, lat_levs = np.asarray(lon_levs), np.asarray(lat_levs) - if lat_factor is not None: - yy0 = lat_levs / lat_factor - dy = 0.001 / lat_factor - else: - yy0 = lat_levs - dy = 0.001 - - if lon_factor is not None: - xx0 = lon_levs / lon_factor - dx = 0.001 / lon_factor - else: - xx0 = lon_levs - dx = 0.001 - - extremes = self.grid_helper._extremes + lat_levs, lat_n, lat_factor = self._grid_info["lat_info"] + yy0 = lat_levs / lat_factor + + lon_levs, lon_n, lon_factor = self._grid_info["lon_info"] + xx0 = lon_levs / lon_factor + + extremes = self.grid_helper.grid_finder.extreme_finder(*[None] * 5) xmin, xmax = sorted(extremes[:2]) ymin, ymax = sorted(extremes[2:]) - def transform_xy(x, y): - x1, y1 = grid_finder.transform_xy(x, y) - x2, y2 = axes.transData.transform(np.array([x1, y1]).T).T - return x2, y2 + def trf_xy(x, y): + trf = grid_finder.get_transform() + axes.transData + return trf.transform(np.column_stack(np.broadcast_arrays(x, y))).T if self.nth_coord == 0: mask = (ymin <= yy0) & (yy0 <= ymax) - yy0 = yy0[mask] - xx0 = np.full_like(yy0, self.value) - xx1, yy1 = transform_xy(xx0, yy0) - - xx00 = xx0.astype(float, copy=True) - xx00[xx0 + dx > xmax] -= dx - xx1a, yy1a = transform_xy(xx00, yy0) - xx1b, yy1b = transform_xy(xx00 + dx, yy0) - - yy00 = yy0.astype(float, copy=True) - yy00[yy0 + dy > ymax] -= dy - xx2a, yy2a = transform_xy(xx0, yy00) - xx2b, yy2b = transform_xy(xx0, yy00 + dy) - - labels = self.grid_info["lat_labels"] - labels = [l for l, m in zip(labels, mask) if m] + (xx1, yy1), angle_normal, angle_tangent = \ + grid_helper_curvelinear._value_and_jac_angle( + trf_xy, self.value, yy0[mask], (xmin, xmax), (ymin, ymax)) + labels = self._grid_info["lat_labels"] elif self.nth_coord == 1: mask = (xmin <= xx0) & (xx0 <= xmax) - xx0 = xx0[mask] - yy0 = np.full_like(xx0, self.value) - xx1, yy1 = transform_xy(xx0, yy0) - - yy00 = yy0.astype(float, copy=True) - yy00[yy0 + dy > ymax] -= dy - xx1a, yy1a = transform_xy(xx0, yy00) - xx1b, yy1b = transform_xy(xx0, yy00 + dy) - - xx00 = xx0.astype(float, copy=True) - xx00[xx0 + dx > xmax] -= dx - xx2a, yy2a = transform_xy(xx00, yy0) - xx2b, yy2b = transform_xy(xx00 + dx, yy0) + (xx1, yy1), angle_tangent, angle_normal = \ + grid_helper_curvelinear._value_and_jac_angle( + trf_xy, xx0[mask], self.value, (xmin, xmax), (ymin, ymax)) + labels = self._grid_info["lon_labels"] - labels = self.grid_info["lon_labels"] - labels = [l for l, m in zip(labels, mask) if m] + labels = [l for l, m in zip(labels, mask) if m] + tick_to_axes = self.get_tick_transform(axes) - axes.transAxes + in_01 = functools.partial( + mpl.transforms._interval_contains_close, (0, 1)) def f1(): - dd = np.arctan2(yy1b - yy1a, xx1b - xx1a) # angle normal - dd2 = np.arctan2(yy2b - yy2a, xx2b - xx2a) # angle tangent - mm = (yy1b - yy1a == 0) & (xx1b - xx1a == 0) # mask not defined dd - dd[mm] = dd2[mm] + np.pi / 2 - - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels): + for x, y, normal, tangent, lab \ + in zip(xx1, yy1, angle_normal, angle_tangent, labels): c2 = tick_to_axes.transform((x, y)) - delta = 0.00001 - if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta: - d1, d2 = np.rad2deg([d, d2]) - yield [x, y], d1, d2, lab + if in_01(c2[0]) and in_01(c2[1]): + yield [x, y], *np.rad2deg([normal, tangent]), lab return f1(), iter([]) @@ -138,8 +102,7 @@ def get_line(self, axes): right=("lon_lines0", 1), bottom=("lat_lines0", 0), top=("lat_lines0", 1))[self._side] - xx, yy = self.grid_info[k][v] - return Path(np.column_stack([xx, yy])) + return Path(self._grid_info[k][v]) class ExtremeFinderFixed(ExtremeFinderSimple): @@ -154,11 +117,12 @@ def __init__(self, extremes): extremes : (float, float, float, float) The bounding box that this helper always returns. """ - self._extremes = extremes + x0, x1, y0, y1 = extremes + self._tbbox = Bbox.from_extents(x0, y0, x1, y1) - def __call__(self, transform_xy, x1, y1, x2, y2): + def _find_transformed_bbox(self, trans, bbox): # docstring inherited - return self._extremes + return self._tbbox class GridHelperCurveLinear(grid_helper_curvelinear.GridHelperCurveLinear): @@ -169,39 +133,24 @@ def __init__(self, aux_trans, extremes, tick_formatter1=None, tick_formatter2=None): # docstring inherited - self._extremes = extremes - extreme_finder = ExtremeFinderFixed(extremes) super().__init__(aux_trans, - extreme_finder, + extreme_finder=ExtremeFinderFixed(extremes), grid_locator1=grid_locator1, grid_locator2=grid_locator2, tick_formatter1=tick_formatter1, tick_formatter2=tick_formatter2) - def get_data_boundary(self, side): - """ - Return v=0, nth=1. - """ - lon1, lon2, lat1, lat2 = self._extremes - return dict(left=(lon1, 0), - right=(lon2, 0), - bottom=(lat1, 1), - top=(lat2, 1))[side] - - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): + def new_fixed_axis( + self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): if axes is None: axes = self.axes if axis_direction is None: axis_direction = loc # This is not the same as the FixedAxisArtistHelper class used by # grid_helper_curvelinear.GridHelperCurveLinear.new_fixed_axis! - _helper = FixedAxisArtistHelper( + helper = FixedAxisArtistHelper( self, loc, nth_coord_ticks=nth_coord) - axisline = AxisArtist(axes, _helper, axis_direction=axis_direction) + axisline = AxisArtist(axes, helper, axis_direction=axis_direction) # Perhaps should be moved to the base class? axisline.line.set_clip_on(True) axisline.line.set_clip_box(axisline.axes.bbox) @@ -209,74 +158,57 @@ def new_fixed_axis(self, loc, # new_floating_axis will inherit the grid_helper's extremes. - # def new_floating_axis(self, nth_coord, - # value, - # axes=None, - # axis_direction="bottom" - # ): - + # def new_floating_axis(self, nth_coord, value, axes=None, axis_direction="bottom"): # axis = super(GridHelperCurveLinear, # self).new_floating_axis(nth_coord, # value, axes=axes, # axis_direction=axis_direction) - # # set extreme values of the axis helper # if nth_coord == 1: # axis.get_helper().set_extremes(*self._extremes[:2]) # elif nth_coord == 0: # axis.get_helper().set_extremes(*self._extremes[2:]) - # return axis - def _update_grid(self, x1, y1, x2, y2): - if self.grid_info is None: - self.grid_info = dict() + def _update_grid(self, bbox): + if self._grid_info is None: + self._grid_info = dict() - grid_info = self.grid_info + grid_info = self._grid_info grid_finder = self.grid_finder - extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy, - x1, y1, x2, y2) + tbbox = grid_finder.extreme_finder._find_transformed_bbox( + grid_finder.get_transform().inverted(), bbox) + + lon_min, lat_min, lon_max, lat_max = tbbox.extents + grid_info["extremes"] = tbbox - lon_min, lon_max = sorted(extremes[:2]) - lat_min, lat_max = sorted(extremes[2:]) - lon_levs, lon_n, lon_factor = \ - grid_finder.grid_locator1(lon_min, lon_max) - lat_levs, lat_n, lat_factor = \ - grid_finder.grid_locator2(lat_min, lat_max) - grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max # extremes + lon_levs, lon_n, lon_factor = grid_finder.grid_locator1(lon_min, lon_max) + lon_levs = np.asarray(lon_levs) + lat_levs, lat_n, lat_factor = grid_finder.grid_locator2(lat_min, lat_max) + lat_levs = np.asarray(lat_levs) grid_info["lon_info"] = lon_levs, lon_n, lon_factor grid_info["lat_info"] = lat_levs, lat_n, lat_factor - grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom", - lon_factor, - lon_levs) + grid_info["lon_labels"] = grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs) + grid_info["lat_labels"] = grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs) - grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom", - lat_factor, - lat_levs) - - if lon_factor is None: - lon_values = np.asarray(lon_levs[:lon_n]) - else: - lon_values = np.asarray(lon_levs[:lon_n]/lon_factor) - if lat_factor is None: - lat_values = np.asarray(lat_levs[:lat_n]) - else: - lat_values = np.asarray(lat_levs[:lat_n]/lat_factor) + lon_values = lon_levs[:lon_n] / lon_factor + lat_values = lat_levs[:lat_n] / lat_factor lon_lines, lat_lines = grid_finder._get_raw_grid_lines( lon_values[(lon_min < lon_values) & (lon_values < lon_max)], lat_values[(lat_min < lat_values) & (lat_values < lat_max)], - lon_min, lon_max, lat_min, lat_max) + tbbox) grid_info["lon_lines"] = lon_lines grid_info["lat_lines"] = lat_lines lon_lines, lat_lines = grid_finder._get_raw_grid_lines( - # lon_min, lon_max, lat_min, lat_max) - extremes[:2], extremes[2:], *extremes) + tbbox.intervalx, tbbox.intervaly, tbbox) grid_info["lon_lines0"] = lon_lines grid_info["lat_lines0"] = lat_lines @@ -284,89 +216,49 @@ def _update_grid(self, x1, y1, x2, y2): def get_gridlines(self, which="major", axis="both"): grid_lines = [] if axis in ["both", "x"]: - grid_lines.extend(self.grid_info["lon_lines"]) + grid_lines.extend(map(np.transpose, self._grid_info["lon_lines"])) if axis in ["both", "y"]: - grid_lines.extend(self.grid_info["lat_lines"]) + grid_lines.extend(map(np.transpose, self._grid_info["lat_lines"])) return grid_lines - def get_boundary(self): - """ - Return (N, 2) array of (x, y) coordinate of the boundary. - """ - x0, x1, y0, y1 = self._extremes - tr = self._aux_trans - - xx = np.linspace(x0, x1, 100) - yy0 = np.full_like(xx, y0) - yy1 = np.full_like(xx, y1) - yy = np.linspace(y0, y1, 100) - xx0 = np.full_like(yy, x0) - xx1 = np.full_like(yy, x1) - - xxx = np.concatenate([xx[:-1], xx1[:-1], xx[-1:0:-1], xx0]) - yyy = np.concatenate([yy0[:-1], yy[:-1], yy1[:-1], yy[::-1]]) - t = tr.transform(np.array([xxx, yyy]).transpose()) - - return t - class FloatingAxesBase: - def __init__(self, *args, **kwargs): - grid_helper = kwargs.get("grid_helper", None) - if grid_helper is None: - raise ValueError("FloatingAxes requires grid_helper argument") - if not hasattr(grid_helper, "get_boundary"): - raise ValueError("grid_helper must implement get_boundary method") - - self._axes_class_floating.__init__(self, *args, **kwargs) - + def __init__(self, *args, grid_helper, **kwargs): + _api.check_isinstance(GridHelperCurveLinear, grid_helper=grid_helper) + super().__init__(*args, grid_helper=grid_helper, **kwargs) self.set_aspect(1.) - self.adjust_axes_lim() def _gen_axes_patch(self): # docstring inherited - grid_helper = self.get_grid_helper() - t = grid_helper.get_boundary() - return mpatches.Polygon(t) - - def cla(self): - self._axes_class_floating.cla(self) - # HostAxes.cla(self) - self.patch.set_transform(self.transData) - - patch = self._axes_class_floating._gen_axes_patch(self) - patch.set_figure(self.figure) - patch.set_visible(False) - patch.set_transform(self.transAxes) - - self.patch.set_clip_path(patch) - self.gridlines.set_clip_path(patch) - - self._original_patch = patch + x0, x1, y0, y1 = self.get_grid_helper().grid_finder.extreme_finder(*[None] * 5) + patch = mpatches.Polygon([(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) + patch.get_path()._interpolation_steps = 100 + return patch + + def clear(self): + super().clear() + self.patch.set_transform( + self.get_grid_helper().grid_finder.get_transform() + + self.transData) + # The original patch is not in the draw tree; it is only used for + # clipping purposes. + orig_patch = super()._gen_axes_patch() + orig_patch.set_figure(self.get_figure(root=False)) + orig_patch.set_transform(self.transAxes) + self.patch.set_clip_path(orig_patch) + self.gridlines.set_clip_path(orig_patch) + self.adjust_axes_lim() def adjust_axes_lim(self): - grid_helper = self.get_grid_helper() - t = grid_helper.get_boundary() - x, y = t[:, 0], t[:, 1] - - xmin, xmax = min(x), max(x) - ymin, ymax = min(y), max(y) - - dx = (xmax-xmin) / 100 - dy = (ymax-ymin) / 100 - - self.set_xlim(xmin-dx, xmax+dx) - self.set_ylim(ymin-dy, ymax+dy) - - -@functools.lru_cache(None) -def floatingaxes_class_factory(axes_class): - return type("Floating %s" % axes_class.__name__, - (FloatingAxesBase, axes_class), - {'_axes_class_floating': axes_class}) + bbox = self.patch.get_path().get_extents( + # First transform to pixel coords, then to parent data coords. + self.patch.get_transform() - self.transData) + bbox = bbox.expanded(1.02, 1.02) + self.set_xlim(bbox.xmin, bbox.xmax) + self.set_ylim(bbox.ymin, bbox.ymax) -FloatingAxes = floatingaxes_class_factory( - host_axes_class_factory(axislines.Axes)) -FloatingSubplot = maxes.subplot_class_factory(FloatingAxes) +floatingaxes_class_factory = cbook._make_class_factory(FloatingAxesBase, "Floating{}") +FloatingAxes = floatingaxes_class_factory(host_axes_class_factory(axislines.Axes)) +FloatingSubplot = FloatingAxes diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index 8835f48957c9..e51d4912c732 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -1,19 +1,46 @@ import numpy as np -from matplotlib import cbook, ticker as mticker +from matplotlib import ticker as mticker, _api from matplotlib.transforms import Bbox, Transform -from .clip_path import clip_line_to_rect -def _deprecate_factor_none(factor): - # After the deprecation period, calls to _deprecate_factor_none can just be - # removed. - if factor is None: - cbook.warn_deprecated( - "3.2", message="factor=None is deprecated since %(since)s and " - "support will be removed %(removal)s; use/return factor=1 instead") - factor = 1 - return factor +def _find_line_box_crossings(xys, bbox): + """ + Find the points where a polyline crosses a bbox, and the crossing angles. + + Parameters + ---------- + xys : (N, 2) array + The polyline coordinates. + bbox : `.Bbox` + The bounding box. + + Returns + ------- + list of ((float, float), float) + Four separate lists of crossings, for the left, right, bottom, and top + sides of the bbox, respectively. For each list, the entries are the + ``((x, y), ccw_angle_in_degrees)`` of the crossing, where an angle of 0 + means that the polyline is moving to the right at the crossing point. + + The entries are computed by linearly interpolating at each crossing + between the nearest points on either side of the bbox edges. + """ + crossings = [] + dxys = xys[1:] - xys[:-1] + for sl in [slice(None), slice(None, None, -1)]: + us, vs = xys.T[sl] # "this" coord, "other" coord + dus, dvs = dxys.T[sl] + umin, vmin = bbox.min[sl] + umax, vmax = bbox.max[sl] + for u0, inside in [(umin, us > umin), (umax, us < umax)]: + cross = [] + idxs, = (inside[:-1] ^ inside[1:]).nonzero() + vv = vs[idxs] + (u0 - us[idxs]) * dvs[idxs] / dus[idxs] + crossings.append([ + ((u0, v)[sl], np.degrees(np.arctan2(*dxy[::-1]))) # ((x, y), theta) + for v, dxy in zip(vv, dxys[idxs]) if vmin <= v <= vmax]) + return crossings class ExtremeFinderSimple: @@ -46,23 +73,65 @@ def __call__(self, transform_xy, x1, y1, x2, y2): extremal coordinates; then adding some padding to take into account the finite sampling. - As each sampling step covers a relative range of *1/nx* or *1/ny*, + As each sampling step covers a relative range of ``1/nx`` or ``1/ny``, the padding is computed by expanding the span covered by the extremal coordinates by these fractions. """ - x, y = np.meshgrid( - np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)) - xt, yt = transform_xy(np.ravel(x), np.ravel(y)) - return self._add_pad(xt.min(), xt.max(), yt.min(), yt.max()) + tbbox = self._find_transformed_bbox( + _User2DTransform(transform_xy, None), Bbox.from_extents(x1, y1, x2, y2)) + return tbbox.x0, tbbox.x1, tbbox.y0, tbbox.y1 + + def _find_transformed_bbox(self, trans, bbox): + """ + Compute an approximation of the bounding box obtained by applying + *trans* to *bbox*. + + See ``__call__`` for details; this method performs similar + calculations, but using a different representation of the arguments and + return value. + """ + grid = np.reshape(np.meshgrid(np.linspace(bbox.x0, bbox.x1, self.nx), + np.linspace(bbox.y0, bbox.y1, self.ny)), + (2, -1)).T + tbbox = Bbox.null() + tbbox.update_from_data_xy(trans.transform(grid)) + return tbbox.expanded(1 + 2 / self.nx, 1 + 2 / self.ny) + + +class _User2DTransform(Transform): + """A transform defined by two user-set functions.""" + + input_dims = output_dims = 2 + + def __init__(self, forward, backward): + """ + Parameters + ---------- + forward, backward : callable + The forward and backward transforms, taking ``x`` and ``y`` as + separate arguments and returning ``(tr_x, tr_y)``. + """ + # The normal Matplotlib convention would be to take and return an + # (N, 2) array but axisartist uses the transposed version. + super().__init__() + self._forward = forward + self._backward = backward + + def transform_non_affine(self, values): + # docstring inherited + return np.transpose(self._forward(*np.transpose(values))) - def _add_pad(self, x_min, x_max, y_min, y_max): - """Perform the padding mentioned in `__call__`.""" - dx = (x_max - x_min) / self.nx - dy = (y_max - y_min) / self.ny - return x_min - dx, x_max + dx, y_min - dy, y_max + dy + def inverted(self): + # docstring inherited + return type(self)(self._backward, self._forward) class GridFinder: + """ + Internal helper for `~.grid_helper_curvelinear.GridHelperCurveLinear`, with + the same constructor parameters; should not be directly instantiated. + """ + def __init__(self, transform, extreme_finder=None, @@ -70,14 +139,6 @@ def __init__(self, grid_locator2=None, tick_formatter1=None, tick_formatter2=None): - """ - transform : transform from the image coordinate (which will be - the transData of the axes to the world coordinate. - - or transform = (transform_xy, inv_transform_xy) - - locator1, locator2 : grid locator for 1st and 2nd axis. - """ if extreme_finder is None: extreme_finder = ExtremeFinderSimple(20, 20) if grid_locator1 is None: @@ -93,7 +154,20 @@ def __init__(self, self.grid_locator2 = grid_locator2 self.tick_formatter1 = tick_formatter1 self.tick_formatter2 = tick_formatter2 - self.update_transform(transform) + self.set_transform(transform) + + def _format_ticks(self, idx, direction, factor, levels): + """ + Helper to support both standard formatters (inheriting from + `.mticker.Formatter`) and axisartist-specific ones; should be called instead of + directly calling ``self.tick_formatter1`` and ``self.tick_formatter2``. This + method should be considered as a temporary workaround which will be removed in + the future at the same time as axisartist-specific formatters. + """ + fmt = _api.check_getitem( + {1: self.tick_formatter1, 2: self.tick_formatter2}, idx=idx) + return (fmt.format_ticks(levels) if isinstance(fmt, mticker.Formatter) + else fmt(direction, factor, levels)) def get_grid_info(self, x1, y1, x2, y2): """ @@ -101,134 +175,86 @@ def get_grid_info(self, x1, y1, x2, y2): rough number of grids in each direction. """ - extremes = self.extreme_finder(self.inv_transform_xy, x1, y1, x2, y2) - - # min & max rage of lat (or lon) for each grid line will be drawn. - # i.e., gridline of lon=0 will be drawn from lat_min to lat_max. - - lon_min, lon_max, lat_min, lat_max = extremes - lon_levs, lon_n, lon_factor = self.grid_locator1(lon_min, lon_max) - lat_levs, lat_n, lat_factor = self.grid_locator2(lat_min, lat_max) - - lon_values = lon_levs[:lon_n] / _deprecate_factor_none(lon_factor) - lat_values = lat_levs[:lat_n] / _deprecate_factor_none(lat_factor) - - lon_lines, lat_lines = self._get_raw_grid_lines(lon_values, - lat_values, - lon_min, lon_max, - lat_min, lat_max) - - ddx = (x2-x1)*1.e-10 - ddy = (y2-y1)*1.e-10 - bb = Bbox.from_extents(x1-ddx, y1-ddy, x2+ddx, y2+ddy) - - grid_info = { - "extremes": extremes, - "lon_lines": lon_lines, - "lat_lines": lat_lines, - "lon": self._clip_grid_lines_and_find_ticks( - lon_lines, lon_values, lon_levs, bb), - "lat": self._clip_grid_lines_and_find_ticks( - lat_lines, lat_values, lat_levs, bb), - } - - tck_labels = grid_info["lon"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lon"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter1( - direction, lon_factor, levs) - - tck_labels = grid_info["lat"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lat"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter2( - direction, lat_factor, levs) + bbox = Bbox.from_extents(x1, y1, x2, y2) + tbbox = self.extreme_finder._find_transformed_bbox( + self.get_transform().inverted(), bbox) + + lon_levs, lon_n, lon_factor = self.grid_locator1(*tbbox.intervalx) + lat_levs, lat_n, lat_factor = self.grid_locator2(*tbbox.intervaly) + + lon_values = np.asarray(lon_levs[:lon_n]) / lon_factor + lat_values = np.asarray(lat_levs[:lat_n]) / lat_factor + + lon_lines, lat_lines = self._get_raw_grid_lines(lon_values, lat_values, tbbox) + + bbox_expanded = bbox.expanded(1 + 2e-10, 1 + 2e-10) + grid_info = {"extremes": tbbox} # "lon", "lat" keys filled below. + + for idx, lon_or_lat, levs, factor, values, lines in [ + (1, "lon", lon_levs, lon_factor, lon_values, lon_lines), + (2, "lat", lat_levs, lat_factor, lat_values, lat_lines), + ]: + grid_info[lon_or_lat] = gi = { + "lines": lines, + "ticks": {"left": [], "right": [], "bottom": [], "top": []}, + } + for xys, v, level in zip(lines, values, levs): + all_crossings = _find_line_box_crossings(xys, bbox_expanded) + for side, crossings in zip( + ["left", "right", "bottom", "top"], all_crossings): + for crossing in crossings: + gi["ticks"][side].append({"level": level, "loc": crossing}) + for side in gi["ticks"]: + levs = [tick["level"] for tick in gi["ticks"][side]] + labels = self._format_ticks(idx, side, factor, levs) + for tick, label in zip(gi["ticks"][side], labels): + tick["label"] = label return grid_info - def _get_raw_grid_lines(self, - lon_values, lat_values, - lon_min, lon_max, lat_min, lat_max): - - lons_i = np.linspace(lon_min, lon_max, 100) # for interpolation - lats_i = np.linspace(lat_min, lat_max, 100) - - lon_lines = [self.transform_xy(np.full_like(lats_i, lon), lats_i) + def _get_raw_grid_lines(self, lon_values, lat_values, bbox): + trans = self.get_transform() + lons = np.linspace(bbox.x0, bbox.x1, 100) # for interpolation + lats = np.linspace(bbox.y0, bbox.y1, 100) + lon_lines = [trans.transform(np.column_stack([np.full_like(lats, lon), lats])) for lon in lon_values] - lat_lines = [self.transform_xy(lons_i, np.full_like(lons_i, lat)) + lat_lines = [trans.transform(np.column_stack([lons, np.full_like(lons, lat)])) for lat in lat_values] - return lon_lines, lat_lines - def _clip_grid_lines_and_find_ticks(self, lines, values, levs, bb): - gi = { - "values": [], - "levels": [], - "tick_levels": dict(left=[], bottom=[], right=[], top=[]), - "tick_locs": dict(left=[], bottom=[], right=[], top=[]), - "lines": [], - } - - tck_levels = gi["tick_levels"] - tck_locs = gi["tick_locs"] - for (lx, ly), v, lev in zip(lines, values, levs): - xy, tcks = clip_line_to_rect(lx, ly, bb) - if not xy: - continue - gi["levels"].append(v) - gi["lines"].append(xy) - - for tck, direction in zip(tcks, - ["left", "bottom", "right", "top"]): - for t in tck: - tck_levels[direction].append(lev) - tck_locs[direction].append(t) - - return gi - - def update_transform(self, aux_trans): + def set_transform(self, aux_trans): if isinstance(aux_trans, Transform): - def transform_xy(x, y): - ll1 = np.column_stack([x, y]) - ll2 = aux_trans.transform(ll1) - lon, lat = ll2[:, 0], ll2[:, 1] - return lon, lat - - def inv_transform_xy(x, y): - ll1 = np.column_stack([x, y]) - ll2 = aux_trans.inverted().transform(ll1) - lon, lat = ll2[:, 0], ll2[:, 1] - return lon, lat - + self._aux_transform = aux_trans + elif len(aux_trans) == 2 and all(map(callable, aux_trans)): + self._aux_transform = _User2DTransform(*aux_trans) else: - transform_xy, inv_transform_xy = aux_trans + raise TypeError("'aux_trans' must be either a Transform " + "instance or a pair of callables") + + def get_transform(self): + return self._aux_transform + + update_transform = set_transform # backcompat alias. - self.transform_xy = transform_xy - self.inv_transform_xy = inv_transform_xy + @_api.deprecated("3.11", alternative="grid_finder.get_transform()") + def transform_xy(self, x, y): + return self._aux_transform.transform(np.column_stack([x, y])).T - def update(self, **kw): - for k in kw: + @_api.deprecated("3.11", alternative="grid_finder.get_transform().inverted()") + def inv_transform_xy(self, x, y): + return self._aux_transform.inverted().transform( + np.column_stack([x, y])).T + + def update(self, **kwargs): + for k, v in kwargs.items(): if k in ["extreme_finder", "grid_locator1", "grid_locator2", "tick_formatter1", "tick_formatter2"]: - setattr(self, k, kw[k]) + setattr(self, k, v) else: - raise ValueError("Unknown update property '%s'" % k) - - -@cbook.deprecated("3.2") -class GridFinderBase(GridFinder): - def __init__(self, - extreme_finder, - grid_locator1=None, - grid_locator2=None, - tick_formatter1=None, - tick_formatter2=None): - super().__init__((None, None), extreme_finder, - grid_locator1, grid_locator2, - tick_formatter1, tick_formatter2) + raise ValueError(f"Unknown update property {k!r}") class MaxNLocator(mticker.MaxNLocator): @@ -241,31 +267,20 @@ def __init__(self, nbins=10, steps=None, super().__init__(nbins, steps=steps, integer=integer, symmetric=symmetric, prune=prune) self.create_dummy_axis() - self._factor = 1 def __call__(self, v1, v2): - self.set_bounds(v1 * self._factor, v2 * self._factor) - locs = super().__call__() - return np.array(locs), len(locs), self._factor - - @cbook.deprecated("3.3") - def set_factor(self, f): - self._factor = _deprecate_factor_none(f) + locs = super().tick_values(v1, v2) + return np.array(locs), len(locs), 1 # 1: factor (see angle_helper) class FixedLocator: def __init__(self, locs): self._locs = locs - self._factor = 1 def __call__(self, v1, v2): - v1, v2 = sorted([v1 * self._factor, v2 * self._factor]) + v1, v2 = sorted([v1, v2]) locs = np.array([l for l in self._locs if v1 <= l <= v2]) - return locs, len(locs), self._factor - - @cbook.deprecated("3.3") - def set_factor(self, f): - self._factor = _deprecate_factor_none(f) + return locs, len(locs), 1 # 1: factor (see angle_helper) # Tick Formatter diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index 58457e3b0e24..1e27b3f571f3 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -1,18 +1,92 @@ """ An experimental support for curvilinear grid. """ -from itertools import chain + +import functools import numpy as np +import matplotlib as mpl from matplotlib.path import Path -from matplotlib.transforms import Affine2D, IdentityTransform -from .axislines import AxisArtistHelper, GridHelperBase +from matplotlib.transforms import Affine2D, Bbox, IdentityTransform +from .axislines import ( + _FixedAxisArtistHelperBase, _FloatingAxisArtistHelperBase, GridHelperBase) from .axis_artist import AxisArtist -from .grid_finder import GridFinder, _deprecate_factor_none +from .grid_finder import GridFinder + +def _value_and_jac_angle(func, xs, ys, xlim, ylim): + """ + Parameters + ---------- + func : callable + A function that transforms the coordinates of a point (x, y) to a new coordinate + system (u, v), and which can also take x and y as arrays of shape *shape* and + returns (u, v) as a ``(2, shape)`` array. + xs, ys : array-likes + Points where *func* and its derivatives will be evaluated. + xlim, ylim : pairs of floats + (min, max) beyond which *func* should not be evaluated. + + Returns + ------- + val + Value of *func* at each point of ``(xs, ys)``. + thetas_dx + Angles (in radians) defined by the (u, v) components of the numerically + differentiated df/dx vector, at each point of ``(xs, ys)``. If needed, the + differentiation step size is increased until at least one component of df/dx + is nonzero, under the constraint of not going out of the *xlims*, *ylims* + bounds. If the gridline at a point is actually null (and the angle is thus not + well defined), the derivatives are evaluated after taking a small step along y; + this ensures e.g. that the tick at r=0 on a radial axis of a polar plot is + parallel with the ticks at r!=0. + thetas_dy + Like *thetas_dx*, but for df/dy. + """ -class FixedAxisArtistHelper(AxisArtistHelper.Fixed): + shape = np.broadcast_shapes(np.shape(xs), np.shape(ys)) + val = func(xs, ys) + + # Take finite difference steps towards the furthest bound; the step size will be the + # min of epsilon and the distance to that bound. + eps0 = np.finfo(float).eps ** (1/2) # cf. scipy.optimize.approx_fprime + + def calc_eps(vals, lim): + lo, hi = sorted(lim) + dlo = vals - lo + dhi = hi - vals + eps_max = np.maximum(dlo, dhi) + eps = np.where(dhi >= dlo, 1, -1) * np.minimum(eps0, eps_max) + return eps, eps_max + + xeps, xeps_max = calc_eps(xs, xlim) + yeps, yeps_max = calc_eps(ys, ylim) + + def calc_thetas(dfunc, ps, eps_p0, eps_max, eps_q): + thetas_dp = np.full(shape, np.nan) + missing = np.full(shape, True) + eps_p = eps_p0 + for it, eps_q in enumerate([0, eps_q]): + while missing.any() and (abs(eps_p) < eps_max).any(): + if it == 0 and (eps_p > 1).any(): + break # Degenerate derivative, move a bit along the other coord. + eps_p = np.minimum(eps_p, eps_max) + df_x, df_y = (dfunc(eps_p, eps_q) - dfunc(0, eps_q)) / eps_p + good = missing & ((df_x != 0) | (df_y != 0)) + thetas_dp[good] = np.arctan2(df_y, df_x)[good] + missing &= ~good + eps_p *= 2 + return thetas_dp + + thetas_dx = calc_thetas(lambda eps_p, eps_q: func(xs + eps_p, ys + eps_q), + xs, xeps, xeps_max, yeps) + thetas_dy = calc_thetas(lambda eps_p, eps_q: func(xs + eps_q, ys + eps_p), + ys, yeps, yeps_max, xeps) + return (val, thetas_dx, thetas_dy) + + +class FixedAxisArtistHelper(_FixedAxisArtistHelperBase): """ Helper class for a fixed axis. """ @@ -31,63 +105,46 @@ def __init__(self, grid_helper, side, nth_coord_ticks=None): self.nth_coord_ticks = nth_coord_ticks self.side = side - self._limits_inverted = False def update_lim(self, axes): self.grid_helper.update_lim(axes) - if self.nth_coord == 0: - xy1, xy2 = axes.get_ylim() - else: - xy1, xy2 = axes.get_xlim() - - if xy1 > xy2: - self._limits_inverted = True - else: - self._limits_inverted = False - - def change_tick_coord(self, coord_number=None): - if coord_number is None: - self.nth_coord_ticks = 1 - self.nth_coord_ticks - elif coord_number in [0, 1]: - self.nth_coord_ticks = coord_number - else: - raise Exception("wrong coord number") - def get_tick_transform(self, axes): return axes.transData def get_tick_iterators(self, axes): """tick_loc, tick_angle, tick_label""" - - g = self.grid_helper - - if self._limits_inverted: + v1, v2 = axes.get_ylim() if self.nth_coord == 0 else axes.get_xlim() + if v1 > v2: # Inverted limits. side = {"left": "right", "right": "left", "top": "bottom", "bottom": "top"}[self.side] else: side = self.side - ti1 = g.get_tick_iterator(self.nth_coord_ticks, side) - ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True) + angle_tangent = dict(left=90, right=90, bottom=0, top=0)[side] + + def iter_major(): + for nth_coord, show_labels in [ + (self.nth_coord_ticks, True), (1 - self.nth_coord_ticks, False)]: + gi = self.grid_helper._grid_info[["lon", "lat"][nth_coord]] + for tick in gi["ticks"][side]: + yield (*tick["loc"], angle_tangent, + (tick["label"] if show_labels else "")) - return chain(ti1, ti2), iter([]) + return iter_major(), iter([]) -class FloatingAxisArtistHelper(AxisArtistHelper.Floating): +class FloatingAxisArtistHelper(_FloatingAxisArtistHelperBase): def __init__(self, grid_helper, nth_coord, value, axis_direction=None): """ nth_coord = along which coordinate value varies. nth_coord = 0 -> x axis, nth_coord = 1 -> y axis """ - super().__init__(nth_coord, value) self.value = value self.grid_helper = grid_helper self._extremes = -np.inf, np.inf - - self._get_line_path = None # a method that returns a Path. self._line_num_points = 100 # number of points to create a line def set_extremes(self, e1, e2): @@ -103,10 +160,10 @@ def update_lim(self, axes): x1, x2 = axes.get_xlim() y1, y2 = axes.get_ylim() grid_finder = self.grid_helper.grid_finder - extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy, - x1, y1, x2, y2) + tbbox = grid_finder.extreme_finder._find_transformed_bbox( + grid_finder.get_transform().inverted(), Bbox.from_extents(x1, y1, x2, y2)) - lon_min, lon_max, lat_min, lat_max = extremes + lon_min, lat_min, lon_max, lat_max = tbbox.extents e_min, e_max = self._extremes # ranges of other coordinates if self.nth_coord == 0: lat_min = max(e_min, lat_min) @@ -115,60 +172,51 @@ def update_lim(self, axes): lon_min = max(e_min, lon_min) lon_max = min(e_max, lon_max) - lon_levs, lon_n, lon_factor = \ - grid_finder.grid_locator1(lon_min, lon_max) - lat_levs, lat_n, lat_factor = \ - grid_finder.grid_locator2(lat_min, lat_max) + lon_levs, lon_n, lon_factor = grid_finder.grid_locator1(lon_min, lon_max) + lat_levs, lat_n, lat_factor = grid_finder.grid_locator2(lat_min, lat_max) if self.nth_coord == 0: - xx0 = np.full(self._line_num_points, self.value, type(self.value)) - yy0 = np.linspace(lat_min, lat_max, self._line_num_points) - xx, yy = grid_finder.transform_xy(xx0, yy0) + xys = grid_finder.get_transform().transform(np.column_stack([ + np.full(self._line_num_points, self.value), + np.linspace(lat_min, lat_max, self._line_num_points), + ])) elif self.nth_coord == 1: - xx0 = np.linspace(lon_min, lon_max, self._line_num_points) - yy0 = np.full(self._line_num_points, self.value, type(self.value)) - xx, yy = grid_finder.transform_xy(xx0, yy0) - - self.grid_info = { - "extremes": (lon_min, lon_max, lat_min, lat_max), - "lon_info": (lon_levs, lon_n, _deprecate_factor_none(lon_factor)), - "lat_info": (lat_levs, lat_n, _deprecate_factor_none(lat_factor)), - "lon_labels": grid_finder.tick_formatter1( - "bottom", _deprecate_factor_none(lon_factor), lon_levs), - "lat_labels": grid_finder.tick_formatter2( - "bottom", _deprecate_factor_none(lat_factor), lat_levs), - "line_xy": (xx, yy), + xys = grid_finder.get_transform().transform(np.column_stack([ + np.linspace(lon_min, lon_max, self._line_num_points), + np.full(self._line_num_points, self.value), + ])) + + self._grid_info = { + "extremes": Bbox.from_extents(lon_min, lat_min, lon_max, lat_max), + "lon_info": (lon_levs, lon_n, np.asarray(lon_factor)), + "lat_info": (lat_levs, lat_n, np.asarray(lat_factor)), + "lon_labels": grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs), + "lat_labels": grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs), + "line_xy": xys, } def get_axislabel_transform(self, axes): return Affine2D() # axes.transData def get_axislabel_pos_angle(self, axes): + def trf_xy(x, y): + trf = self.grid_helper.grid_finder.get_transform() + axes.transData + return trf.transform([x, y]).T - extremes = self.grid_info["extremes"] - + xmin, ymin, xmax, ymax = self._grid_info["extremes"].extents if self.nth_coord == 0: xx0 = self.value - yy0 = (extremes[2] + extremes[3]) / 2 - dxx = 0 - dyy = abs(extremes[2] - extremes[3]) / 1000 + yy0 = (ymin + ymax) / 2 elif self.nth_coord == 1: - xx0 = (extremes[0] + extremes[1]) / 2 + xx0 = (xmin + xmax) / 2 yy0 = self.value - dxx = abs(extremes[0] - extremes[1]) / 1000 - dyy = 0 - - grid_finder = self.grid_helper.grid_finder - (xx1,), (yy1,) = grid_finder.transform_xy([xx0], [yy0]) - - data_to_axes = axes.transData - axes.transAxes - p = data_to_axes.transform([xx1, yy1]) - + xy1, angle_dx, angle_dy = _value_and_jac_angle( + trf_xy, xx0, yy0, (xmin, xmax), (ymin, ymax)) + p = axes.transAxes.inverted().transform(xy1) if 0 <= p[0] <= 1 and 0 <= p[1] <= 1: - xx1c, yy1c = axes.transData.transform([xx1, yy1]) - (xx2,), (yy2,) = grid_finder.transform_xy([xx0 + dxx], [yy0 + dyy]) - xx2c, yy2c = axes.transData.transform([xx2, yy2]) - return (xx1c, yy1c), np.rad2deg(np.arctan2(yy2c-yy1c, xx2c-xx1c)) + return xy1, np.rad2deg([angle_dy, angle_dx][self.nth_coord]) else: return None, None @@ -178,106 +226,54 @@ def get_tick_transform(self, axes): def get_tick_iterators(self, axes): """tick_loc, tick_angle, tick_label, (optionally) tick_label""" - grid_finder = self.grid_helper.grid_finder - - lat_levs, lat_n, lat_factor = self.grid_info["lat_info"] - lat_levs = np.asarray(lat_levs) - yy0 = lat_levs / _deprecate_factor_none(lat_factor) - dy = 0.01 / _deprecate_factor_none(lat_factor) - - lon_levs, lon_n, lon_factor = self.grid_info["lon_info"] - lon_levs = np.asarray(lon_levs) - xx0 = lon_levs / _deprecate_factor_none(lon_factor) - dx = 0.01 / _deprecate_factor_none(lon_factor) + lat_levs, lat_n, lat_factor = self._grid_info["lat_info"] + yy0 = lat_levs / lat_factor - if None in self._extremes: - e0, e1 = self._extremes - else: - e0, e1 = sorted(self._extremes) - if e0 is None: - e0 = -np.inf - if e1 is None: - e1 = np.inf + lon_levs, lon_n, lon_factor = self._grid_info["lon_info"] + xx0 = lon_levs / lon_factor - if self.nth_coord == 0: - mask = (e0 <= yy0) & (yy0 <= e1) - #xx0, yy0 = xx0[mask], yy0[mask] - yy0 = yy0[mask] - elif self.nth_coord == 1: - mask = (e0 <= xx0) & (xx0 <= e1) - #xx0, yy0 = xx0[mask], yy0[mask] - xx0 = xx0[mask] + e0, e1 = self._extremes - def transform_xy(x, y): - x1, y1 = grid_finder.transform_xy(x, y) - x2y2 = axes.transData.transform(np.array([x1, y1]).transpose()) - x2, y2 = x2y2.transpose() - return x2, y2 + def trf_xy(x, y): + trf = self.grid_helper.grid_finder.get_transform() + axes.transData + return trf.transform(np.column_stack(np.broadcast_arrays(x, y))).T # find angles if self.nth_coord == 0: - xx0 = np.full_like(yy0, self.value) - - xx1, yy1 = transform_xy(xx0, yy0) - - xx00 = xx0.copy() - xx00[xx0 + dx > e1] -= dx - xx1a, yy1a = transform_xy(xx00, yy0) - xx1b, yy1b = transform_xy(xx00+dx, yy0) - - xx2a, yy2a = transform_xy(xx0, yy0) - xx2b, yy2b = transform_xy(xx0, yy0+dy) - - labels = self.grid_info["lat_labels"] - labels = [l for l, m in zip(labels, mask) if m] + mask = (e0 <= yy0) & (yy0 <= e1) + (xx1, yy1), angle_normal, angle_tangent = _value_and_jac_angle( + trf_xy, self.value, yy0[mask], (-np.inf, np.inf), (e0, e1)) + labels = self._grid_info["lat_labels"] elif self.nth_coord == 1: - yy0 = np.full_like(xx0, self.value) - - xx1, yy1 = transform_xy(xx0, yy0) - - xx1a, yy1a = transform_xy(xx0, yy0) - xx1b, yy1b = transform_xy(xx0, yy0+dy) - - xx00 = xx0.copy() - xx00[xx0 + dx > e1] -= dx - xx2a, yy2a = transform_xy(xx00, yy0) - xx2b, yy2b = transform_xy(xx00+dx, yy0) - - labels = self.grid_info["lon_labels"] - labels = [l for l, m in zip(labels, mask) if m] - - def f1(): - dd = np.arctan2(yy1b-yy1a, xx1b-xx1a) # angle normal - dd2 = np.arctan2(yy2b-yy2a, xx2b-xx2a) # angle tangent - mm = (yy1b == yy1a) & (xx1b == xx1a) # mask where dd not defined - dd[mm] = dd2[mm] + np.pi / 2 - - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels): + mask = (e0 <= xx0) & (xx0 <= e1) + (xx1, yy1), angle_tangent, angle_normal = _value_and_jac_angle( + trf_xy, xx0[mask], self.value, (-np.inf, np.inf), (e0, e1)) + labels = self._grid_info["lon_labels"] + + labels = [l for l, m in zip(labels, mask) if m] + tick_to_axes = self.get_tick_transform(axes) - axes.transAxes + in_01 = functools.partial( + mpl.transforms._interval_contains_close, (0, 1)) + + def iter_major(): + for x, y, normal, tangent, lab \ + in zip(xx1, yy1, angle_normal, angle_tangent, labels): c2 = tick_to_axes.transform((x, y)) - delta = 0.00001 - if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta: - d1, d2 = np.rad2deg([d, d2]) - yield [x, y], d1, d2, lab + if in_01(c2[0]) and in_01(c2[1]): + yield [x, y], *np.rad2deg([normal, tangent]), lab - return f1(), iter([]) + return iter_major(), iter([]) def get_line_transform(self, axes): return axes.transData def get_line(self, axes): self.update_lim(axes) - x, y = self.grid_info["line_xy"] - - if self._get_line_path is None: - return Path(np.column_stack([x, y])) - else: - return self._get_line_path(axes, x, y) + return Path(self._grid_info["line_xy"]) class GridHelperCurveLinear(GridHelperBase): - def __init__(self, aux_trans, extreme_finder=None, grid_locator1=None, @@ -285,19 +281,27 @@ def __init__(self, aux_trans, tick_formatter1=None, tick_formatter2=None): """ - aux_trans : a transform from the source (curved) coordinate to - target (rectilinear) coordinate. An instance of MPL's Transform - (inverse transform should be defined) or a tuple of two callable - objects which defines the transform and its inverse. The callables - need take two arguments of array of source coordinates and - should return two target coordinates. - - e.g., ``x2, y2 = trans(x1, y1)`` + Parameters + ---------- + aux_trans : `.Transform` or tuple[Callable, Callable] + The transform from curved coordinates to rectilinear coordinate: + either a `.Transform` instance (which provides also its inverse), + or a pair of callables ``(trans, inv_trans)`` that define the + transform and its inverse. The callables should have signature:: + + x_rect, y_rect = trans(x_curved, y_curved) + x_curved, y_curved = inv_trans(x_rect, y_rect) + + extreme_finder + + grid_locator1, grid_locator2 + Grid locators for each axis. + + tick_formatter1, tick_formatter2 + Tick formatters for each axis. """ super().__init__() - self.grid_info = None - self._old_values = None - self._aux_trans = aux_trans + self._grid_info = None self.grid_finder = GridFinder(aux_trans, extreme_finder, grid_locator1, @@ -305,94 +309,44 @@ def __init__(self, aux_trans, tick_formatter1, tick_formatter2) - def update_grid_finder(self, aux_trans=None, **kw): + def update_grid_finder(self, aux_trans=None, **kwargs): if aux_trans is not None: self.grid_finder.update_transform(aux_trans) - self.grid_finder.update(**kw) - self.invalidate() - - def _update(self, x1, x2, y1, y2): - """bbox in 0-based image coordinates""" - # update wcsgrid - if self.valid() and self._old_values == (x1, x2, y1, y2): - return - self._update_grid(x1, y1, x2, y2) - self._old_values = (x1, x2, y1, y2) - self._force_update = False - - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): + self.grid_finder.update(**kwargs) + self._old_limits = None # Force revalidation. + + def new_fixed_axis( + self, loc, *, axis_direction=None, offset=None, axes=None, nth_coord=None + ): if axes is None: axes = self.axes if axis_direction is None: axis_direction = loc - _helper = FixedAxisArtistHelper(self, loc, nth_coord_ticks=nth_coord) - axisline = AxisArtist(axes, _helper, axis_direction=axis_direction) + helper = FixedAxisArtistHelper(self, loc, nth_coord_ticks=nth_coord) + axisline = AxisArtist(axes, helper, axis_direction=axis_direction) # Why is clip not set on axisline, unlike in new_floating_axis or in # the floating_axig.GridHelperCurveLinear subclass? return axisline - def new_floating_axis(self, nth_coord, - value, - axes=None, - axis_direction="bottom" - ): - + def new_floating_axis(self, nth_coord, value, axes=None, axis_direction="bottom"): if axes is None: axes = self.axes - - _helper = FloatingAxisArtistHelper( + helper = FloatingAxisArtistHelper( self, nth_coord, value, axis_direction) - - axisline = AxisArtist(axes, _helper) - - # _helper = FloatingAxisArtistHelper(self, nth_coord, - # value, - # label_direction=label_direction, - # ) - - # axisline = AxisArtistFloating(axes, _helper, - # axis_direction=axis_direction) + axisline = AxisArtist(axes, helper) axisline.line.set_clip_on(True) axisline.line.set_clip_box(axisline.axes.bbox) # axisline.major_ticklabels.set_visible(True) # axisline.minor_ticklabels.set_visible(False) - return axisline - def _update_grid(self, x1, y1, x2, y2): - self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2) + def _update_grid(self, bbox): + self._grid_info = self.grid_finder.get_grid_info(*bbox.extents) def get_gridlines(self, which="major", axis="both"): grid_lines = [] if axis in ["both", "x"]: - for gl in self.grid_info["lon"]["lines"]: - grid_lines.extend(gl) + grid_lines.extend([gl.T for gl in self._grid_info["lon"]["lines"]]) if axis in ["both", "y"]: - for gl in self.grid_info["lat"]["lines"]: - grid_lines.extend(gl) + grid_lines.extend([gl.T for gl in self._grid_info["lat"]["lines"]]) return grid_lines - - def get_tick_iterator(self, nth_coord, axis_side, minor=False): - - # axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side] - angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] - # angle = [0, 90, 180, 270][axisnr] - lon_or_lat = ["lon", "lat"][nth_coord] - if not minor: # major ticks - for (xy, a), l in zip( - self.grid_info[lon_or_lat]["tick_locs"][axis_side], - self.grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a - yield xy, angle_normal, angle_tangent, l - else: - for (xy, a), l in zip( - self.grid_info[lon_or_lat]["tick_locs"][axis_side], - self.grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a - yield xy, angle_normal, angle_tangent, "" - # for xy, a, l in self.grid_info[lon_or_lat]["ticks"][axis_side]: - # yield xy, a, "" diff --git a/lib/mpl_toolkits/axisartist/meson.build b/lib/mpl_toolkits/axisartist/meson.build new file mode 100644 index 000000000000..6d95cf0dfdcd --- /dev/null +++ b/lib/mpl_toolkits/axisartist/meson.build @@ -0,0 +1,16 @@ +python_sources = [ + '__init__.py', + 'angle_helper.py', + 'axes_divider.py', + 'axis_artist.py', + 'axislines.py', + 'axisline_style.py', + 'floating_axes.py', + 'grid_finder.py', + 'grid_helper_curvelinear.py', + 'parasite_axes.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axisartist') + +subdir('tests') diff --git a/lib/mpl_toolkits/axisartist/parasite_axes.py b/lib/mpl_toolkits/axisartist/parasite_axes.py index fad7caa59b7a..4ebd6acc03be 100644 --- a/lib/mpl_toolkits/axisartist/parasite_axes.py +++ b/lib/mpl_toolkits/axisartist/parasite_axes.py @@ -1,10 +1,7 @@ from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory, - parasite_axes_auxtrans_class_factory, subplot_class_factory) + host_axes_class_factory, parasite_axes_class_factory) from .axislines import Axes ParasiteAxes = parasite_axes_class_factory(Axes) -ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes) -HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) +HostAxes = SubplotHost = host_axes_class_factory(Axes) diff --git a/lib/mpl_toolkits/axisartist/tests/__init__.py b/lib/mpl_toolkits/axisartist/tests/__init__.py new file mode 100644 index 000000000000..ea4d8ed16a6a --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/__init__.py @@ -0,0 +1,10 @@ +from pathlib import Path + + +# Check that the test directories exist +if not (Path(__file__).parent / "baseline_images").exists(): + raise OSError( + 'The baseline image directory does not exist. ' + 'This is most likely because the test data is not installed. ' + 'You may need to install matplotlib from source to get the ' + 'test data.') diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_labelbase.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_labelbase.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_labelbase.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_labelbase.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticklabels.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_ticklabels.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticklabels.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_ticklabels.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticks.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_ticks.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticks.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axis_artist/axis_artist_ticks.png diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/ParasiteAxesAuxTrans_meshplot.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/ParasiteAxesAuxTrans_meshplot.png new file mode 100644 index 000000000000..ffff4806d18e Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/ParasiteAxesAuxTrans_meshplot.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/Subplot.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/Subplot.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/Subplot.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/Subplot.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/SubplotZero.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/SubplotZero.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/SubplotZero.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/SubplotZero.png diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style.png new file mode 100644 index 000000000000..31c194bd8af6 Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style.png differ diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_size_color.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_size_color.png new file mode 100644 index 000000000000..046928cba3c7 Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_size_color.png differ diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_tight.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_tight.png new file mode 100644 index 000000000000..3b2b80f1f678 Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_tight.png differ diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/subplotzero_ylabel.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/subplotzero_ylabel.png new file mode 100644 index 000000000000..9dc9e4a1540d Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/subplotzero_ylabel.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear3.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_floating_axes/curvelinear3.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear3.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_floating_axes/curvelinear3.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear4.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_floating_axes/curvelinear4.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear4.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_floating_axes/curvelinear4.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/axis_direction.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/axis_direction.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/axis_direction.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/axis_direction.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/custom_transform.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/custom_transform.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/custom_transform.png rename to lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/custom_transform.png diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png new file mode 100644 index 000000000000..8909355e9af8 Binary files /dev/null and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png differ diff --git a/lib/mpl_toolkits/axisartist/tests/conftest.py b/lib/mpl_toolkits/axisartist/tests/conftest.py new file mode 100644 index 000000000000..61c2de3e07ba --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/conftest.py @@ -0,0 +1,2 @@ +from matplotlib.testing.conftest import (mpl_test_settings, # noqa + pytest_configure, pytest_unconfigure) diff --git a/lib/mpl_toolkits/axisartist/tests/meson.build b/lib/mpl_toolkits/axisartist/tests/meson.build new file mode 100644 index 000000000000..634c8190a037 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/meson.build @@ -0,0 +1,17 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_angle_helper.py', + 'test_axis_artist.py', + 'test_axislines.py', + 'test_floating_axes.py', + 'test_grid_finder.py', + 'test_grid_helper_curvelinear.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axisartist/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/axisartist/tests')) diff --git a/lib/mpl_toolkits/tests/test_axisartist_angle_helper.py b/lib/mpl_toolkits/axisartist/tests/test_angle_helper.py similarity index 100% rename from lib/mpl_toolkits/tests/test_axisartist_angle_helper.py rename to lib/mpl_toolkits/axisartist/tests/test_angle_helper.py diff --git a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py new file mode 100644 index 000000000000..d44a61b6dd4a --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py @@ -0,0 +1,99 @@ +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import image_comparison + +from mpl_toolkits.axisartist import AxisArtistHelperRectlinear +from mpl_toolkits.axisartist.axis_artist import (AxisArtist, AxisLabel, + LabelBase, Ticks, TickLabels) + + +@image_comparison(['axis_artist_ticks.png'], style='default') +def test_ticks(): + fig, ax = plt.subplots() + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + locs_angles = [((i / 10, 0.0), i * 30) for i in range(-1, 12)] + + ticks_in = Ticks(ticksize=10, axis=ax.xaxis) + ticks_in.set_locs_angles(locs_angles) + ax.add_artist(ticks_in) + + ticks_out = Ticks(ticksize=10, tick_out=True, color='C3', axis=ax.xaxis) + ticks_out.set_locs_angles(locs_angles) + ax.add_artist(ticks_out) + + +@image_comparison(['axis_artist_labelbase.png'], style='default') +def test_labelbase(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig, ax = plt.subplots() + + ax.plot([0.5], [0.5], "o") + + label = LabelBase(0.5, 0.5, "Test") + label._ref_angle = -90 + label._offset_radius = 50 + label.set_rotation(-90) + label.set(ha="center", va="top") + ax.add_artist(label) + + +@image_comparison(['axis_artist_ticklabels.png'], style='default') +def test_ticklabels(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig, ax = plt.subplots() + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + ax.plot([0.2, 0.4], [0.5, 0.5], "o") + + ticks = Ticks(ticksize=10, axis=ax.xaxis) + ax.add_artist(ticks) + locs_angles_labels = [((0.2, 0.5), -90, "0.2"), + ((0.4, 0.5), -120, "0.4")] + tick_locs_angles = [(xy, a + 180) for xy, a, l in locs_angles_labels] + ticks.set_locs_angles(tick_locs_angles) + + ticklabels = TickLabels(axis_direction="left") + ticklabels._locs_angles_labels = locs_angles_labels + ticklabels.set_pad(10) + ax.add_artist(ticklabels) + + ax.plot([0.5], [0.5], "s") + axislabel = AxisLabel(0.5, 0.5, "Test") + axislabel._offset_radius = 20 + axislabel._ref_angle = 0 + axislabel.set_axis_direction("bottom") + ax.add_artist(axislabel) + + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + + +@image_comparison(['axis_artist.png'], style='default') +def test_axis_artist(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig, ax = plt.subplots() + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + for loc in ('left', 'right', 'bottom'): + helper = AxisArtistHelperRectlinear.Fixed(ax, loc=loc) + axisline = AxisArtist(ax, helper, offset=None, axis_direction=loc) + ax.add_artist(axisline) + + # Settings for bottom AxisArtist. + axisline.set_label("TTT") + axisline.major_ticks.set_tick_out(False) + axisline.label.set_pad(5) + + ax.set_ylabel("Test") diff --git a/lib/mpl_toolkits/axisartist/tests/test_axislines.py b/lib/mpl_toolkits/axisartist/tests/test_axislines.py new file mode 100644 index 000000000000..8bc3707421b6 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/test_axislines.py @@ -0,0 +1,145 @@ +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import image_comparison +from matplotlib.transforms import IdentityTransform + +from mpl_toolkits.axisartist.axislines import AxesZero, SubplotZero, Subplot +from mpl_toolkits.axisartist import Axes, SubplotHost + + +@image_comparison(['SubplotZero.png'], style='default') +def test_SubplotZero(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig = plt.figure() + + ax = SubplotZero(fig, 1, 1, 1) + fig.add_subplot(ax) + + ax.axis["xzero"].set_visible(True) + ax.axis["xzero"].label.set_text("Axis Zero") + + for n in ["top", "right"]: + ax.axis[n].set_visible(False) + + xx = np.arange(0, 2 * np.pi, 0.01) + ax.plot(xx, np.sin(xx)) + ax.set_ylabel("Test") + + +@image_comparison(['Subplot.png'], style='default') +def test_Subplot(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig = plt.figure() + + ax = Subplot(fig, 1, 1, 1) + fig.add_subplot(ax) + + xx = np.arange(0, 2 * np.pi, 0.01) + ax.plot(xx, np.sin(xx)) + ax.set_ylabel("Test") + + ax.axis["top"].major_ticks.set_tick_out(True) + ax.axis["bottom"].major_ticks.set_tick_out(True) + + ax.axis["bottom"].set_label("Tk0") + + +def test_Axes(): + fig = plt.figure() + ax = Axes(fig, [0.15, 0.1, 0.65, 0.8]) + fig.add_axes(ax) + ax.plot([1, 2, 3], [0, 1, 2]) + ax.set_xscale('log') + fig.canvas.draw() + + +@image_comparison(['ParasiteAxesAuxTrans_meshplot.png'], + remove_text=True, style='default', tol=0.075) +def test_ParasiteAxesAuxTrans(): + data = np.ones((6, 6)) + data[2, 2] = 2 + data[0, :] = 0 + data[-2, :] = 0 + data[:, 0] = 0 + data[:, -2] = 0 + x = np.arange(6) + y = np.arange(6) + xx, yy = np.meshgrid(x, y) + + funcnames = ['pcolor', 'pcolormesh', 'contourf'] + + fig = plt.figure() + for i, name in enumerate(funcnames): + + ax1 = SubplotHost(fig, 1, 3, i+1) + fig.add_subplot(ax1) + + ax2 = ax1.get_aux_axes(IdentityTransform(), viewlim_mode=None) + if name.startswith('pcolor'): + getattr(ax2, name)(xx, yy, data[:-1, :-1]) + else: + getattr(ax2, name)(xx, yy, data) + ax1.set_xlim((0, 5)) + ax1.set_ylim((0, 5)) + + ax2.contour(xx, yy, data, colors='k') + + +@image_comparison(['axisline_style.png'], remove_text=True, style='mpl20') +def test_axisline_style(): + fig = plt.figure(figsize=(2, 2)) + ax = fig.add_subplot(axes_class=AxesZero) + ax.axis["xzero"].set_axisline_style("-|>") + ax.axis["xzero"].set_visible(True) + ax.axis["yzero"].set_axisline_style("->") + ax.axis["yzero"].set_visible(True) + + for direction in ("left", "right", "bottom", "top"): + ax.axis[direction].set_visible(False) + + +@image_comparison(['axisline_style_size_color.png'], remove_text=True, + style='mpl20') +def test_axisline_style_size_color(): + fig = plt.figure(figsize=(2, 2)) + ax = fig.add_subplot(axes_class=AxesZero) + ax.axis["xzero"].set_axisline_style("-|>", size=2.0, facecolor='r') + ax.axis["xzero"].set_visible(True) + ax.axis["yzero"].set_axisline_style("->, size=1.5") + ax.axis["yzero"].set_visible(True) + + for direction in ("left", "right", "bottom", "top"): + ax.axis[direction].set_visible(False) + + +@image_comparison(['axisline_style_tight.png'], remove_text=True, + style='mpl20') +def test_axisline_style_tight(): + fig = plt.figure(figsize=(2, 2), layout='tight') + ax = fig.add_subplot(axes_class=AxesZero) + ax.axis["xzero"].set_axisline_style("-|>", size=5, facecolor='g') + ax.axis["xzero"].set_visible(True) + ax.axis["yzero"].set_axisline_style("->, size=8") + ax.axis["yzero"].set_visible(True) + + for direction in ("left", "right", "bottom", "top"): + ax.axis[direction].set_visible(False) + + +@image_comparison(['subplotzero_ylabel.png'], style='mpl20') +def test_subplotzero_ylabel(): + fig = plt.figure() + ax = fig.add_subplot(111, axes_class=SubplotZero) + + ax.set(xlim=(-3, 7), ylim=(-3, 7), xlabel="x", ylabel="y") + + zero_axis = ax.axis["xzero", "yzero"] + zero_axis.set_visible(True) # they are hidden by default + + ax.axis["left", "right", "bottom", "top"].set_visible(False) + + zero_axis.set_axisline_style("->") diff --git a/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py new file mode 100644 index 000000000000..feb667af013e --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py @@ -0,0 +1,143 @@ +import numpy as np + +import pytest + +import matplotlib.pyplot as plt +import matplotlib.projections as mprojections +import matplotlib.transforms as mtransforms +from matplotlib.testing.decorators import image_comparison +from mpl_toolkits.axisartist.axislines import Subplot +from mpl_toolkits.axisartist.floating_axes import ( + FloatingAxes, GridHelperCurveLinear) +from mpl_toolkits.axisartist.grid_finder import FixedLocator +from mpl_toolkits.axisartist import angle_helper + + +def test_subplot(): + fig = plt.figure(figsize=(5, 5)) + ax = Subplot(fig, 111) + fig.add_subplot(ax) + + +# Rather high tolerance to allow ongoing work with floating axes internals; +# remove when image is regenerated. +@image_comparison(['curvelinear3.png'], style='default', tol=5) +def test_curvelinear3(): + fig = plt.figure(figsize=(5, 5)) + + tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + + mprojections.PolarAxes.PolarTransform()) + grid_helper = GridHelperCurveLinear( + tr, + extremes=(0, 360, 10, 3), + grid_locator1=angle_helper.LocatorDMS(15), + grid_locator2=FixedLocator([2, 4, 6, 8, 10]), + tick_formatter1=angle_helper.FormatterDMS(), + tick_formatter2=None) + ax1 = fig.add_subplot(axes_class=FloatingAxes, grid_helper=grid_helper) + + r_scale = 10 + tr2 = mtransforms.Affine2D().scale(1, 1 / r_scale) + tr + grid_helper2 = GridHelperCurveLinear( + tr2, + extremes=(0, 360, 10 * r_scale, 3 * r_scale), + grid_locator2=FixedLocator([30, 60, 90])) + + ax1.axis["right"] = axis = grid_helper2.new_fixed_axis("right", axes=ax1) + + ax1.axis["left"].label.set_text("Test 1") + ax1.axis["right"].label.set_text("Test 2") + ax1.axis["left", "right"].set_visible(False) + + axis = grid_helper.new_floating_axis(1, 7, axes=ax1, + axis_direction="bottom") + ax1.axis["z"] = axis + axis.toggle(all=True, label=True) + axis.label.set_text("z = ?") + axis.label.set_visible(True) + axis.line.set_color("0.5") + + ax2 = ax1.get_aux_axes(tr) + + xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] + ax2.scatter(xx, yy) + l, = ax2.plot(xx, yy, "k-") + l.set_clip_path(ax1.patch) + + +# Rather high tolerance to allow ongoing work with floating axes internals; +# remove when image is regenerated. +@image_comparison(['curvelinear4.png'], style='default', tol=0.9) +def test_curvelinear4(): + # Remove this line when this test image is regenerated. + plt.rcParams['text.kerning_factor'] = 6 + + fig = plt.figure(figsize=(5, 5)) + + tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + + mprojections.PolarAxes.PolarTransform()) + grid_helper = GridHelperCurveLinear( + tr, + extremes=(120, 30, 10, 0), + grid_locator1=angle_helper.LocatorDMS(5), + grid_locator2=FixedLocator([2, 4, 6, 8, 10]), + tick_formatter1=angle_helper.FormatterDMS(), + tick_formatter2=None) + ax1 = fig.add_subplot(axes_class=FloatingAxes, grid_helper=grid_helper) + ax1.clear() # Check that clear() also restores the correct limits on ax1. + + ax1.axis["left"].label.set_text("Test 1") + ax1.axis["right"].label.set_text("Test 2") + ax1.axis["top"].set_visible(False) + + axis = grid_helper.new_floating_axis(1, 70, axes=ax1, + axis_direction="bottom") + ax1.axis["z"] = axis + axis.toggle(all=True, label=True) + axis.label.set_axis_direction("top") + axis.label.set_text("z = ?") + axis.label.set_visible(True) + axis.line.set_color("0.5") + + ax2 = ax1.get_aux_axes(tr) + + xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] + ax2.scatter(xx, yy) + l, = ax2.plot(xx, yy, "k-") + l.set_clip_path(ax1.patch) + + +def test_axis_direction(): + # Check that axis direction is propagated on a floating axis + fig = plt.figure() + ax = Subplot(fig, 111) + fig.add_subplot(ax) + ax.axis['y'] = ax.new_floating_axis(nth_coord=1, value=0, + axis_direction='left') + assert ax.axis['y']._axis_direction == 'left' + + +def test_transform_with_zero_derivatives(): + # The transform is really a 45° rotation + # tr(x, y) = x-y, x+y; inv_tr(u, v) = (u+v)/2, (u-v)/2 + # with an additional x->exp(-x**-2) on each coordinate. + # Therefore all ticks should be at +/-45°, even the one at zero where the + # transform derivatives are zero. + + # at x=0, exp(-x**-2)=0; div-by-zero can be ignored. + @np.errstate(divide="ignore") + def tr(x, y): + return np.exp(-x**-2) - np.exp(-y**-2), np.exp(-x**-2) + np.exp(-y**-2) + + def inv_tr(u, v): + return (-np.log((u+v)/2))**(1/2), (-np.log((v-u)/2))**(1/2) + + fig = plt.figure() + ax = fig.add_subplot( + axes_class=FloatingAxes, grid_helper=GridHelperCurveLinear( + (tr, inv_tr), extremes=(0, 10, 0, 10))) + fig.canvas.draw() + + for k in ax.axis: + for l, a in ax.axis[k].major_ticks.locs_angles: + assert a % 90 == pytest.approx(45, abs=1e-3) diff --git a/lib/mpl_toolkits/axisartist/tests/test_grid_finder.py b/lib/mpl_toolkits/axisartist/tests/test_grid_finder.py new file mode 100644 index 000000000000..6b397675ee10 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/test_grid_finder.py @@ -0,0 +1,34 @@ +import numpy as np +import pytest + +from matplotlib.transforms import Bbox +from mpl_toolkits.axisartist.grid_finder import ( + _find_line_box_crossings, FormatterPrettyPrint, MaxNLocator) + + +def test_find_line_box_crossings(): + x = np.array([-3, -2, -1, 0., 1, 2, 3, 2, 1, 0, -1, -2, -3, 5]) + y = np.arange(len(x)) + bbox = Bbox.from_extents(-2, 3, 2, 12.5) + left, right, bottom, top = _find_line_box_crossings( + np.column_stack([x, y]), bbox) + ((lx0, ly0), la0), ((lx1, ly1), la1), = left + ((rx0, ry0), ra0), ((rx1, ry1), ra1), = right + ((bx0, by0), ba0), = bottom + ((tx0, ty0), ta0), = top + assert (lx0, ly0, la0) == (-2, 11, 135) + assert (lx1, ly1, la1) == pytest.approx((-2., 12.125, 7.125016)) + assert (rx0, ry0, ra0) == (2, 5, 45) + assert (rx1, ry1, ra1) == (2, 7, 135) + assert (bx0, by0, ba0) == (0, 3, 45) + assert (tx0, ty0, ta0) == pytest.approx((1., 12.5, 7.125016)) + + +def test_pretty_print_format(): + locator = MaxNLocator() + locs, nloc, factor = locator(0, 100) + + fmt = FormatterPrettyPrint() + + assert fmt("left", None, locs) == \ + [r'$\mathdefault{%d}$' % (l, ) for l in locs] diff --git a/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py new file mode 100644 index 000000000000..7d6554782fe6 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py @@ -0,0 +1,205 @@ +import numpy as np + +import matplotlib.pyplot as plt +from matplotlib.path import Path +from matplotlib.projections import PolarAxes +from matplotlib.ticker import FuncFormatter +from matplotlib.transforms import Affine2D, Transform +from matplotlib.testing.decorators import image_comparison + +from mpl_toolkits.axisartist import SubplotHost +from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory +from mpl_toolkits.axisartist import angle_helper +from mpl_toolkits.axisartist.axislines import Axes +from mpl_toolkits.axisartist.grid_helper_curvelinear import \ + GridHelperCurveLinear + + +@image_comparison(['custom_transform.png'], style='default', tol=0.2) +def test_custom_transform(): + class MyTransform(Transform): + input_dims = output_dims = 2 + + def __init__(self, resolution): + """ + Resolution is the number of steps to interpolate between each input + line segment to approximate its path in transformed space. + """ + Transform.__init__(self) + self._resolution = resolution + + def transform(self, ll): + x, y = ll.T + return np.column_stack([x, y - x]) + + transform_non_affine = transform + + def transform_path(self, path): + ipath = path.interpolated(self._resolution) + return Path(self.transform(ipath.vertices), ipath.codes) + + transform_path_non_affine = transform_path + + def inverted(self): + return MyTransformInv(self._resolution) + + class MyTransformInv(Transform): + input_dims = output_dims = 2 + + def __init__(self, resolution): + Transform.__init__(self) + self._resolution = resolution + + def transform(self, ll): + x, y = ll.T + return np.column_stack([x, y + x]) + + def inverted(self): + return MyTransform(self._resolution) + + fig = plt.figure() + + SubplotHost = host_axes_class_factory(Axes) + + tr = MyTransform(1) + grid_helper = GridHelperCurveLinear(tr) + ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) + fig.add_subplot(ax1) + + ax2 = ax1.get_aux_axes(tr, viewlim_mode="equal") + ax2.plot([3, 6], [5.0, 10.]) + + ax1.set_aspect(1.) + ax1.set_xlim(0, 10) + ax1.set_ylim(0, 10) + + ax1.grid(True) + + +@image_comparison(['polar_box.png'], style='default', tol=0.04) +def test_polar_box(): + fig = plt.figure(figsize=(5, 5)) + + # PolarAxes.PolarTransform takes radian. However, we want our coordinate + # system in degree + tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() + + # polar projection, which involves cycle, and also has limits in + # its coordinates, needs a special method to find the extremes + # (min, max of the coordinate within the view). + extreme_finder = angle_helper.ExtremeFinderCycle(20, 20, + lon_cycle=360, + lat_cycle=None, + lon_minmax=None, + lat_minmax=(0, np.inf)) + + grid_helper = GridHelperCurveLinear( + tr, + extreme_finder=extreme_finder, + grid_locator1=angle_helper.LocatorDMS(12), + tick_formatter1=angle_helper.FormatterDMS(), + tick_formatter2=FuncFormatter(lambda x, p: "eight" if x == 8 else f"{int(x)}"), + ) + + ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) + + ax1.axis["right"].major_ticklabels.set_visible(True) + ax1.axis["top"].major_ticklabels.set_visible(True) + + # let right axis shows ticklabels for 1st coordinate (angle) + ax1.axis["right"].get_helper().nth_coord_ticks = 0 + # let bottom axis shows ticklabels for 2nd coordinate (radius) + ax1.axis["bottom"].get_helper().nth_coord_ticks = 1 + + fig.add_subplot(ax1) + + ax1.axis["lat"] = axis = grid_helper.new_floating_axis(0, 45, axes=ax1) + axis.label.set_text("Test") + axis.label.set_visible(True) + axis.get_helper().set_extremes(2, 12) + + ax1.axis["lon"] = axis = grid_helper.new_floating_axis(1, 6, axes=ax1) + axis.label.set_text("Test 2") + axis.get_helper().set_extremes(-180, 90) + + # A parasite axes with given transform + ax2 = ax1.get_aux_axes(tr, viewlim_mode="equal") + assert ax2.transData == tr + ax1.transData + # Anything you draw in ax2 will match the ticks and grids of ax1. + ax2.plot(np.linspace(0, 30, 50), np.linspace(10, 10, 50)) + + ax1.set_aspect(1.) + ax1.set_xlim(-5, 12) + ax1.set_ylim(-5, 10) + + ax1.grid(True) + + +# Remove tol & kerning_factor when this test image is regenerated. +@image_comparison(['axis_direction.png'], style='default', tol=0.13) +def test_axis_direction(): + plt.rcParams['text.kerning_factor'] = 6 + + fig = plt.figure(figsize=(5, 5)) + + # PolarAxes.PolarTransform takes radian. However, we want our coordinate + # system in degree + tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() + + # polar projection, which involves cycle, and also has limits in + # its coordinates, needs a special method to find the extremes + # (min, max of the coordinate within the view). + + # 20, 20 : number of sampling points along x, y direction + extreme_finder = angle_helper.ExtremeFinderCycle(20, 20, + lon_cycle=360, + lat_cycle=None, + lon_minmax=None, + lat_minmax=(0, np.inf), + ) + + grid_locator1 = angle_helper.LocatorDMS(12) + tick_formatter1 = angle_helper.FormatterDMS() + + grid_helper = GridHelperCurveLinear(tr, + extreme_finder=extreme_finder, + grid_locator1=grid_locator1, + tick_formatter1=tick_formatter1) + + ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) + + for axis in ax1.axis.values(): + axis.set_visible(False) + + fig.add_subplot(ax1) + + ax1.axis["lat1"] = axis = grid_helper.new_floating_axis( + 0, 130, + axes=ax1, axis_direction="left") + axis.label.set_text("Test") + axis.label.set_visible(True) + axis.get_helper().set_extremes(0.001, 10) + + ax1.axis["lat2"] = axis = grid_helper.new_floating_axis( + 0, 50, + axes=ax1, axis_direction="right") + axis.label.set_text("Test") + axis.label.set_visible(True) + axis.get_helper().set_extremes(0.001, 10) + + ax1.axis["lon"] = axis = grid_helper.new_floating_axis( + 1, 10, + axes=ax1, axis_direction="bottom") + axis.label.set_text("Test 2") + axis.get_helper().set_extremes(50, 130) + axis.major_ticklabels.set_axis_direction("top") + axis.label.set_axis_direction("top") + + grid_helper.grid_finder.grid_locator1.set_params(nbins=5) + grid_helper.grid_finder.grid_locator2.set_params(nbins=5) + + ax1.set_aspect(1.) + ax1.set_xlim(-8, 8) + ax1.set_ylim(-4, 12) + + ax1.grid(True) diff --git a/lib/mpl_toolkits/meson.build b/lib/mpl_toolkits/meson.build new file mode 100644 index 000000000000..290cd6139f94 --- /dev/null +++ b/lib/mpl_toolkits/meson.build @@ -0,0 +1,3 @@ +subdir('axes_grid1') +subdir('axisartist') +subdir('mplot3d') diff --git a/lib/mpl_toolkits/mplot3d/__init__.py b/lib/mpl_toolkits/mplot3d/__init__.py index 30e682aea018..a089fbd6b70e 100644 --- a/lib/mpl_toolkits/mplot3d/__init__.py +++ b/lib/mpl_toolkits/mplot3d/__init__.py @@ -1 +1,3 @@ from .axes3d import Axes3D + +__all__ = ['Axes3D'] diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f37be535ea10..483fd09be163 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -11,11 +11,13 @@ import numpy as np +from contextlib import contextmanager + from matplotlib import ( - artist, colors as mcolors, lines, text as mtext, path as mpath) + _api, artist, cbook, colors as mcolors, lines, text as mtext, + path as mpath, rcParams) from matplotlib.collections import ( - LineCollection, PolyCollection, PatchCollection, PathCollection) -from matplotlib.colors import Normalize + Collection, LineCollection, PolyCollection, PatchCollection, PathCollection) from matplotlib.patches import Patch from . import proj3d @@ -49,13 +51,12 @@ def get_dir_vector(zdir): - 'y': equivalent to (0, 1, 0) - 'z': equivalent to (0, 0, 1) - *None*: equivalent to (0, 0, 0) - - an iterable (x, y, z) is returned unchanged. + - an iterable (x, y, z) is converted to an array Returns ------- - x, y, z : array-like - The direction vector. This is either a numpy.array or *zdir* itself if - *zdir* is already a length-3 iterable. + x, y, z : array + The direction vector. """ if zdir == 'x': return np.array((1, 0, 0)) @@ -66,82 +67,214 @@ def get_dir_vector(zdir): elif zdir is None: return np.array((0, 0, 0)) elif np.iterable(zdir) and len(zdir) == 3: - return zdir + return np.array(zdir) else: raise ValueError("'x', 'y', 'z', None or vector of length 3 expected") +def _viewlim_mask(xs, ys, zs, axes): + """ + Return the mask of the points outside the axes view limits. + + Parameters + ---------- + xs, ys, zs : array-like + The points to mask. + axes : Axes3D + The axes to use for the view limits. + + Returns + ------- + mask : np.array + The mask of the points as a bool array. + """ + mask = np.logical_or.reduce((xs < axes.xy_viewLim.xmin, + xs > axes.xy_viewLim.xmax, + ys < axes.xy_viewLim.ymin, + ys > axes.xy_viewLim.ymax, + zs < axes.zz_viewLim.xmin, + zs > axes.zz_viewLim.xmax)) + return mask + + class Text3D(mtext.Text): """ Text object with 3D position and direction. Parameters ---------- - x, y, z + x, y, z : float The position of the text. text : str The text string to display. zdir : {'x', 'y', 'z', None, 3-tuple} The direction of the text. See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide text outside the axes view limits. + + .. versionadded:: 3.10 Other Parameters ---------------- **kwargs All other parameters are passed on to `~matplotlib.text.Text`. - """ + """ - def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs): + def __init__(self, x=0, y=0, z=0, text='', zdir='z', axlim_clip=False, + **kwargs): mtext.Text.__init__(self, x, y, text, **kwargs) - self.set_3d_properties(z, zdir) + self.set_3d_properties(z, zdir, axlim_clip) + + def get_position_3d(self): + """Return the (x, y, z) position of the text.""" + return self._x, self._y, self._z + + def set_position_3d(self, xyz, zdir=None): + """ + Set the (*x*, *y*, *z*) position of the text. + + Parameters + ---------- + xyz : (float, float, float) + The position in 3D space. + zdir : {'x', 'y', 'z', None, 3-tuple} + The direction of the text. If unspecified, the *zdir* will not be + changed. See `.get_dir_vector` for a description of the values. + """ + super().set_position(xyz[:2]) + self.set_z(xyz[2]) + if zdir is not None: + self._dir_vec = get_dir_vector(zdir) + + def set_z(self, z): + """ + Set the *z* position of the text. - def set_3d_properties(self, z=0, zdir='z'): - x, y = self.get_position() - self._position3d = np.array((x, y, z)) + Parameters + ---------- + z : float + """ + self._z = z + self.stale = True + + def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): + """ + Set the *z* position and direction of the text. + + Parameters + ---------- + z : float + The z-position in 3D space. + zdir : {'x', 'y', 'z', 3-tuple} + The direction of the text. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide text outside the axes view limits. + + .. versionadded:: 3.10 + """ + self._z = z self._dir_vec = get_dir_vector(zdir) + self._axlim_clip = axlim_clip self.stale = True @artist.allow_rasterization def draw(self, renderer): - proj = proj3d.proj_trans_points( - [self._position3d, self._position3d + self._dir_vec], renderer.M) + if self._axlim_clip: + mask = _viewlim_mask(self._x, self._y, self._z, self.axes) + pos3d = np.ma.array([self._x, self._y, self._z], + mask=mask, dtype=float).filled(np.nan) + else: + pos3d = np.array([self._x, self._y, self._z], dtype=float) + + proj = proj3d._proj_trans_points([pos3d, pos3d + self._dir_vec], self.axes.M) dx = proj[0][1] - proj[0][0] dy = proj[1][1] - proj[1][0] angle = math.degrees(math.atan2(dy, dx)) - self.set_position((proj[0][0], proj[1][0])) - self.set_rotation(_norm_text_angle(angle)) - mtext.Text.draw(self, renderer) + with cbook._setattr_cm(self, _x=proj[0][0], _y=proj[1][0], + _rotation=_norm_text_angle(angle)): + mtext.Text.draw(self, renderer) self.stale = False - def get_tightbbox(self, renderer): + def get_tightbbox(self, renderer=None): # Overwriting the 2d Text behavior which is not valid for 3d. # For now, just return None to exclude from layout calculation. return None -def text_2d_to_3d(obj, z=0, zdir='z'): - """Convert a Text to a Text3D object.""" +def text_2d_to_3d(obj, z=0, zdir='z', axlim_clip=False): + """ + Convert a `.Text` to a `.Text3D` object. + + Parameters + ---------- + z : float + The z-position in 3D space. + zdir : {'x', 'y', 'z', 3-tuple} + The direction of the text. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide text outside the axes view limits. + + .. versionadded:: 3.10 + """ obj.__class__ = Text3D - obj.set_3d_properties(z, zdir) + obj.set_3d_properties(z, zdir, axlim_clip) class Line3D(lines.Line2D): """ 3D line object. + + .. note:: Use `get_data_3d` to obtain the data associated with the line. + `~.Line2D.get_data`, `~.Line2D.get_xdata`, and `~.Line2D.get_ydata` return + the x- and y-coordinates of the projected 2D-line, not the x- and y-data of + the 3D-line. Similarly, use `set_data_3d` to set the data, not + `~.Line2D.set_data`, `~.Line2D.set_xdata`, and `~.Line2D.set_ydata`. """ - def __init__(self, xs, ys, zs, *args, **kwargs): + def __init__(self, xs, ys, zs, *args, axlim_clip=False, **kwargs): """ - Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`. + + Parameters + ---------- + xs : array-like + The x-data to be plotted. + ys : array-like + The y-data to be plotted. + zs : array-like + The z-data to be plotted. + *args, **kwargs + Additional arguments are passed to `~matplotlib.lines.Line2D`. """ super().__init__([], [], *args, **kwargs) - self._verts3d = xs, ys, zs + self.set_data_3d(xs, ys, zs) + self._axlim_clip = axlim_clip + + def set_3d_properties(self, zs=0, zdir='z', axlim_clip=False): + """ + Set the *z* position and direction of the line. - def set_3d_properties(self, zs=0, zdir='z'): + Parameters + ---------- + zs : float or array of floats + The location along the *zdir* axis in 3D space to position the + line. + zdir : {'x', 'y', 'z'} + Plane to plot line orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide lines with an endpoint outside the axes view limits. + + .. versionadded:: 3.10 + """ xs = self.get_xdata() ys = self.get_ydata() - zs = np.broadcast_to(zs, xs.shape) + zs = cbook._to_unmasked_float_array(zs).ravel() + zs = np.broadcast_to(zs, len(xs)) self._verts3d = juggle_axes(xs, ys, zs, zdir) + self._axlim_clip = axlim_clip self.stale = True def set_data_3d(self, *args): @@ -162,9 +295,11 @@ def set_data_3d(self, *args): Accepts x, y, z arguments or a single array-like (x, y, z) """ if len(args) == 1: - self._verts3d = args[0] - else: - self._verts3d = args + args = args[0] + for name, xyz in zip('xyz', args): + if not np.iterable(xyz): + raise RuntimeError(f'{name} must be a sequence') + self._verts3d = args self.stale = True def get_data_3d(self): @@ -180,18 +315,42 @@ def get_data_3d(self): @artist.allow_rasterization def draw(self, renderer): - xs3d, ys3d, zs3d = self._verts3d - xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) + if self._axlim_clip: + mask = np.broadcast_to( + _viewlim_mask(*self._verts3d, self.axes), + (len(self._verts3d), *self._verts3d[0].shape) + ) + xs3d, ys3d, zs3d = np.ma.array(self._verts3d, + dtype=float, mask=mask).filled(np.nan) + else: + xs3d, ys3d, zs3d = self._verts3d + xs, ys, zs, tis = proj3d._proj_transform_clip(xs3d, ys3d, zs3d, + self.axes.M, + self.axes._focal_length) self.set_data(xs, ys) super().draw(renderer) self.stale = False -def line_2d_to_3d(line, zs=0, zdir='z'): - """Convert a 2D line to 3D.""" +def line_2d_to_3d(line, zs=0, zdir='z', axlim_clip=False): + """ + Convert a `.Line2D` to a `.Line3D` object. + + Parameters + ---------- + zs : float + The location along the *zdir* axis in 3D space to position the line. + zdir : {'x', 'y', 'z'} + Plane to plot line orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide lines with an endpoint outside the axes view limits. + + .. versionadded:: 3.10 + """ line.__class__ = Line3D - line.set_3d_properties(zs, zdir) + line.set_3d_properties(zs, zdir, axlim_clip) def _path_to_3d_segment(path, zs=0, zdir='z'): @@ -248,10 +407,67 @@ def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): return list(segments), list(codes) +class Collection3D(Collection): + """A collection of 3D paths.""" + + def do_3d_projection(self): + """Project the points according to renderer matrix.""" + vs_list = [vs for vs, _ in self._3dverts_codes] + if self._axlim_clip: + vs_list = [np.ma.array(vs, mask=np.broadcast_to( + _viewlim_mask(*vs.T, self.axes), vs.shape)) + for vs in vs_list] + xyzs_list = [proj3d.proj_transform(*vs.T, self.axes.M) for vs in vs_list] + self._paths = [mpath.Path(np.ma.column_stack([xs, ys]), cs) + for (xs, ys, _), (_, cs) in zip(xyzs_list, self._3dverts_codes)] + zs = np.concatenate([zs for _, _, zs in xyzs_list]) + return zs.min() if len(zs) else 1e9 + + +def collection_2d_to_3d(col, zs=0, zdir='z', axlim_clip=False): + """Convert a `.Collection` to a `.Collection3D` object.""" + zs = np.broadcast_to(zs, len(col.get_paths())) + col._3dverts_codes = [ + (np.column_stack(juggle_axes( + *np.column_stack([p.vertices, np.broadcast_to(z, len(p.vertices))]).T, + zdir)), + p.codes) + for p, z in zip(col.get_paths(), zs)] + col.__class__ = cbook._make_class_factory(Collection3D, "{}3D")(type(col)) + col._axlim_clip = axlim_clip + + class Line3DCollection(LineCollection): """ A collection of 3D lines. """ + def __init__(self, lines, axlim_clip=False, **kwargs): + super().__init__(lines, **kwargs) + self._axlim_clip = axlim_clip + """ + Parameters + ---------- + lines : list of (N, 3) array-like + A sequence ``[line0, line1, ...]`` where each line is a (N, 3)-shape + array-like containing points:: line0 = [(x0, y0, z0), (x1, y1, z1), ...] + Each line can contain a different number of points. + linewidths : float or list of float, default: :rc:`lines.linewidth` + The width of each line in points. + colors : :mpltype:`color` or list of color, default: :rc:`lines.color` + A sequence of RGBA tuples (e.g., arbitrary color strings, etc, not + allowed). + antialiaseds : bool or list of bool, default: :rc:`lines.antialiased` + Whether to use antialiasing for each line. + facecolors : :mpltype:`color` or list of :mpltype:`color`, default: 'none' + When setting *facecolors*, each line is interpreted as a boundary + for an area, implicitly closing the path from the last point to the + first point. The enclosed area is filled with *facecolor*. + In order to manually specify what should count as the "interior" of + each line, please use `.PathCollection` instead, where the + "interior" can be specified by appropriate usage of + `~.path.Path.CLOSEPOLY`. + **kwargs : Forwarded to `.Collection`. + """ def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" @@ -265,34 +481,45 @@ def set_segments(self, segments): self._segments3d = segments super().set_segments([]) - def do_3d_projection(self, renderer): + def do_3d_projection(self): """ Project the points according to renderer matrix. """ - xyslist = [ - proj3d.proj_trans_points(points, renderer.M) for points in - self._segments3d] - segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist] + segments = np.asanyarray(self._segments3d) + + mask = False + if np.ma.isMA(segments): + mask = segments.mask + + if self._axlim_clip: + viewlim_mask = _viewlim_mask(segments[..., 0], + segments[..., 1], + segments[..., 2], + self.axes) + if np.any(viewlim_mask): + # broadcast mask to 3D + viewlim_mask = np.broadcast_to(viewlim_mask[..., np.newaxis], + (*viewlim_mask.shape, 3)) + mask = mask | viewlim_mask + xyzs = np.ma.array(proj3d._proj_transform_vectors(segments, self.axes.M), + mask=mask) + segments_2d = xyzs[..., 0:2] LineCollection.set_segments(self, segments_2d) # FIXME - minz = 1e9 - for xs, ys, zs in xyslist: - minz = min(minz, min(zs)) + if len(xyzs) > 0: + minz = min(xyzs[..., 2].min(), 1e9) + else: + minz = np.nan return minz - @artist.allow_rasterization - def draw(self, renderer, project=False): - if project: - self.do_3d_projection(renderer) - super().draw(renderer) - -def line_collection_2d_to_3d(col, zs=0, zdir='z'): - """Convert a LineCollection to a Line3DCollection object.""" +def line_collection_2d_to_3d(col, zs=0, zdir='z', axlim_clip=False): + """Convert a `.LineCollection` to a `.Line3DCollection` object.""" segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir) col.__class__ = Line3DCollection col.set_segments(segments3d) + col._axlim_clip = axlim_clip class Patch3D(Patch): @@ -300,29 +527,68 @@ class Patch3D(Patch): 3D patch object. """ - def __init__(self, *args, zs=(), zdir='z', **kwargs): + def __init__(self, *args, zs=(), zdir='z', axlim_clip=False, **kwargs): + """ + Parameters + ---------- + verts : + zs : float + The location along the *zdir* axis in 3D space to position the + patch. + zdir : {'x', 'y', 'z'} + Plane to plot patch orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + """ super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) + self.set_3d_properties(zs, zdir, axlim_clip) - def set_3d_properties(self, verts, zs=0, zdir='z'): + def set_3d_properties(self, verts, zs=0, zdir='z', axlim_clip=False): + """ + Set the *z* position and direction of the patch. + + Parameters + ---------- + verts : + zs : float + The location along the *zdir* axis in 3D space to position the + patch. + zdir : {'x', 'y', 'z'} + Plane to plot patch orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + """ zs = np.broadcast_to(zs, len(verts)) self._segment3d = [juggle_axes(x, y, z, zdir) for ((x, y), z) in zip(verts, zs)] - self._facecolor3d = Patch.get_facecolor(self) + self._axlim_clip = axlim_clip def get_path(self): + # docstring inherited + # self._path2d is not initialized until do_3d_projection + if not hasattr(self, '_path2d'): + self.axes.M = self.axes.get_proj() + self.do_3d_projection() return self._path2d - def get_facecolor(self): - return self._facecolor2d - - def do_3d_projection(self, renderer): + def do_3d_projection(self): s = self._segment3d - xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - self._path2d = mpath.Path(np.column_stack([vxs, vys])) - # FIXME: coloring - self._facecolor2d = self._facecolor3d + if self._axlim_clip: + mask = _viewlim_mask(*zip(*s), self.axes) + xs, ys, zs = np.ma.array(zip(*s), + dtype=float, mask=mask).filled(np.nan) + else: + xs, ys, zs = zip(*s) + vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, + self.axes.M, + self.axes._focal_length) + self._path2d = mpath.Path(np.ma.column_stack([vxs, vys])) return min(vzs) @@ -331,22 +597,60 @@ class PathPatch3D(Patch3D): 3D PathPatch object. """ - def __init__(self, path, *, zs=(), zdir='z', **kwargs): + def __init__(self, path, *, zs=(), zdir='z', axlim_clip=False, **kwargs): + """ + Parameters + ---------- + path : + zs : float + The location along the *zdir* axis in 3D space to position the + path patch. + zdir : {'x', 'y', 'z', 3-tuple} + Plane to plot path patch orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide path patches with a point outside the axes view limits. + + .. versionadded:: 3.10 + """ # Not super().__init__! Patch.__init__(self, **kwargs) - self.set_3d_properties(path, zs, zdir) + self.set_3d_properties(path, zs, zdir, axlim_clip) + + def set_3d_properties(self, path, zs=0, zdir='z', axlim_clip=False): + """ + Set the *z* position and direction of the path patch. - def set_3d_properties(self, path, zs=0, zdir='z'): - Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir) + Parameters + ---------- + path : + zs : float + The location along the *zdir* axis in 3D space to position the + path patch. + zdir : {'x', 'y', 'z', 3-tuple} + Plane to plot path patch orthogonal to. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide path patches with a point outside the axes view limits. + + .. versionadded:: 3.10 + """ + Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir, + axlim_clip=axlim_clip) self._code3d = path.codes - def do_3d_projection(self, renderer): + def do_3d_projection(self): s = self._segment3d - xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d) - # FIXME: coloring - self._facecolor2d = self._facecolor3d + if self._axlim_clip: + mask = _viewlim_mask(*zip(*s), self.axes) + xs, ys, zs = np.ma.array(zip(*s), + dtype=float, mask=mask).filled(np.nan) + else: + xs, ys, zs = zip(*s) + vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, + self.axes.M, + self.axes._focal_length) + self._path2d = mpath.Path(np.ma.column_stack([vxs, vys]), self._code3d) return min(vzs) @@ -355,21 +659,18 @@ def _get_patch_verts(patch): trans = patch.get_patch_transform() path = patch.get_path() polygons = path.to_polygons(trans) - if len(polygons): - return polygons[0] - else: - return [] + return polygons[0] if len(polygons) else np.array([]) -def patch_2d_to_3d(patch, z=0, zdir='z'): - """Convert a Patch to a Patch3D object.""" +def patch_2d_to_3d(patch, z=0, zdir='z', axlim_clip=False): + """Convert a `.Patch` to a `.Patch3D` object.""" verts = _get_patch_verts(patch) patch.__class__ = Patch3D - patch.set_3d_properties(verts, z, zdir) + patch.set_3d_properties(verts, z, zdir, axlim_clip) def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'): - """Convert a PathPatch to a PathPatch3D object.""" + """Convert a `.PathPatch` to a `.PathPatch3D` object.""" path = pathpatch.get_path() trans = pathpatch.get_patch_transform() @@ -383,7 +684,16 @@ class Patch3DCollection(PatchCollection): A collection of 3D patches. """ - def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): + def __init__( + self, + *args, + zs=0, + zdir="z", + depthshade=None, + depthshade_minalpha=None, + axlim_clip=False, + **kwargs + ): """ Create a collection of flat 3D patches with its normal vector pointed in *zdir* direction, and located at *zs* on the *zdir* @@ -394,21 +704,74 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): :class:`~matplotlib.collections.PatchCollection`. In addition, keywords *zs=0* and *zdir='z'* are available. - Also, the keyword argument "depthshade" is available to + The keyword argument *depthshade* is available to indicate whether or not to shade the patches in order to give the appearance of depth (default is *True*). This is typically desired in scatter plots. + + *depthshade_minalpha* sets the minimum alpha value applied by + depth-shading. """ + if depthshade is None: + depthshade = rcParams['axes3d.depthshade'] + if depthshade_minalpha is None: + depthshade_minalpha = rcParams['axes3d.depthshade_minalpha'] self._depthshade = depthshade + self._depthshade_minalpha = depthshade_minalpha super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) + self.set_3d_properties(zs, zdir, axlim_clip) + + def get_depthshade(self): + return self._depthshade + + def set_depthshade( + self, + depthshade, + depthshade_minalpha=None, + ): + """ + Set whether depth shading is performed on collection members. + + Parameters + ---------- + depthshade : bool + Whether to shade the patches in order to give the appearance of + depth. + depthshade_minalpha : float, default: None + Sets the minimum alpha value used by depth-shading. + If None, use the value from rcParams['axes3d.depthshade_minalpha']. + + .. versionadded:: 3.11 + """ + if depthshade_minalpha is None: + depthshade_minalpha = rcParams['axes3d.depthshade_minalpha'] + self._depthshade = depthshade + self._depthshade_minalpha = depthshade_minalpha + self.stale = True def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True - def set_3d_properties(self, zs, zdir): + def set_3d_properties(self, zs, zdir, axlim_clip=False): + """ + Set the *z* positions and direction of the patches. + + Parameters + ---------- + zs : float or array of floats + The location or locations to place the patches in the collection + along the *zdir* axis. + zdir : {'x', 'y', 'z'} + Plane to plot patches orthogonal to. + All patches must have the same direction. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + """ # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() @@ -419,37 +782,95 @@ def set_3d_properties(self, zs, zdir): xs = [] ys = [] self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) - self._facecolor3d = self.get_facecolor() - self._edgecolor3d = self.get_edgecolor() + self._z_markers_idx = slice(-1) + self._vzs = None + self._axlim_clip = axlim_clip self.stale = True - def do_3d_projection(self, renderer): - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - - fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else - self._facecolor3d) - fcs = mcolors.to_rgba_array(fcs, self._alpha) - self.set_facecolors(fcs) - - ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else - self._edgecolor3d) - ecs = mcolors.to_rgba_array(ecs, self._alpha) - self.set_edgecolors(ecs) - PatchCollection.set_offsets(self, np.column_stack([vxs, vys])) + def do_3d_projection(self): + if self._axlim_clip: + mask = _viewlim_mask(*self._offsets3d, self.axes) + xs, ys, zs = np.ma.array(self._offsets3d, mask=mask) + else: + xs, ys, zs = self._offsets3d + vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, + self.axes.M, + self.axes._focal_length) + self._vzs = vzs + if np.ma.isMA(vxs): + super().set_offsets(np.ma.column_stack([vxs, vys])) + else: + super().set_offsets(np.column_stack([vxs, vys])) if vzs.size > 0: return min(vzs) else: return np.nan + def _maybe_depth_shade_and_sort_colors(self, color_array): + color_array = ( + _zalpha( + color_array, + self._vzs, + min_alpha=self._depthshade_minalpha, + ) + if self._vzs is not None and self._depthshade + else color_array + ) + if len(color_array) > 1: + color_array = color_array[self._z_markers_idx] + return mcolors.to_rgba_array(color_array, self._alpha) + + def get_facecolor(self): + return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) + + def get_edgecolor(self): + # We need this check here to make sure we do not double-apply the depth + # based alpha shading when the edge color is "face" which means the + # edge colour should be identical to the face colour. + if cbook._str_equal(self._edgecolors, 'face'): + return self.get_facecolor() + return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) + + +def _get_data_scale(X, Y, Z): + """ + Estimate the scale of the 3D data for use in depth shading + + Parameters + ---------- + X, Y, Z : masked arrays + The data to estimate the scale of. + """ + # Account for empty datasets. Assume that X Y and Z have the same number + # of elements. + if not np.ma.count(X): + return 0 + + # Estimate the scale using the RSS of the ranges of the dimensions + # Note that we don't use np.ma.ptp() because we otherwise get a build + # warning about handing empty arrays. + ptp_x = X.max() - X.min() + ptp_y = Y.max() - Y.min() + ptp_z = Z.max() - Z.min() + return np.sqrt(ptp_x ** 2 + ptp_y ** 2 + ptp_z ** 2) + class Path3DCollection(PathCollection): """ A collection of 3D paths. """ - def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): + def __init__( + self, + *args, + zs=0, + zdir="z", + depthshade=None, + depthshade_minalpha=None, + axlim_clip=False, + **kwargs + ): """ Create a collection of flat 3D paths with its normal vector pointed in *zdir* direction, and located at *zs* on the *zdir* @@ -460,21 +881,53 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): :class:`~matplotlib.collections.PathCollection`. In addition, keywords *zs=0* and *zdir='z'* are available. - Also, the keyword argument "depthshade" is available to + Also, the keyword argument *depthshade* is available to indicate whether or not to shade the patches in order to give the appearance of depth (default is *True*). This is typically desired in scatter plots. + + *depthshade_minalpha* sets the minimum alpha value applied by + depth-shading. """ + if depthshade is None: + depthshade = rcParams['axes3d.depthshade'] + if depthshade_minalpha is None: + depthshade_minalpha = rcParams['axes3d.depthshade_minalpha'] self._depthshade = depthshade + self._depthshade_minalpha = depthshade_minalpha + self._in_draw = False super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) + self.set_3d_properties(zs, zdir, axlim_clip) + self._offset_zordered = None + + def draw(self, renderer): + with self._use_zordered_offset(): + with cbook._setattr_cm(self, _in_draw=True): + super().draw(renderer) def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True - def set_3d_properties(self, zs, zdir): + def set_3d_properties(self, zs, zdir, axlim_clip=False): + """ + Set the *z* positions and direction of the paths. + + Parameters + ---------- + zs : float or array of floats + The location or locations to place the paths in the collection + along the *zdir* axis. + zdir : {'x', 'y', 'z'} + Plane to plot paths orthogonal to. + All paths must have the same direction. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide paths with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + """ # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() @@ -484,80 +937,208 @@ def set_3d_properties(self, zs, zdir): else: xs = [] ys = [] + self._zdir = zdir self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) - self._facecolor3d = self.get_facecolor() - self._edgecolor3d = self.get_edgecolor() - self._sizes3d = self.get_sizes() - self._linewidth3d = self.get_linewidth() + # In the base draw methods we access the attributes directly which + # means we cannot resolve the shuffling in the getter methods like + # we do for the edge and face colors. + # + # This means we need to carry around a cache of the unsorted sizes and + # widths (postfixed with 3d) and in `do_3d_projection` set the + # depth-sorted version of that data into the private state used by the + # base collection class in its draw method. + # + # Grab the current sizes and linewidths to preserve them. + self._sizes3d = self._sizes + self._linewidths3d = np.array(self._linewidths) + xs, ys, zs = self._offsets3d + + # Sort the points based on z coordinates + # Performance optimization: Create a sorted index array and reorder + # points and point properties according to the index array + self._z_markers_idx = slice(-1) + self._vzs = None + + self._axlim_clip = axlim_clip self.stale = True - def do_3d_projection(self, renderer): - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + def set_sizes(self, sizes, dpi=72.0): + super().set_sizes(sizes, dpi) + if not self._in_draw: + self._sizes3d = sizes + + def set_linewidth(self, lw): + super().set_linewidth(lw) + if not self._in_draw: + self._linewidths3d = np.array(self._linewidths) - fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else - self._facecolor3d) - ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else - self._edgecolor3d) - sizes = self._sizes3d - lws = self._linewidth3d + def get_depthshade(self): + return self._depthshade + + def set_depthshade( + self, + depthshade, + depthshade_minalpha=None, + ): + """ + Set whether depth shading is performed on collection members. + + Parameters + ---------- + depthshade : bool + Whether to shade the patches in order to give the appearance of + depth. + depthshade_minalpha : float + Sets the minimum alpha value used by depth-shading. + .. versionadded:: 3.11 + """ + if depthshade_minalpha is None: + depthshade_minalpha = rcParams['axes3d.depthshade_minalpha'] + self._depthshade = depthshade + self._depthshade_minalpha = depthshade_minalpha + self.stale = True + + def do_3d_projection(self): + mask = False + for xyz in self._offsets3d: + if np.ma.isMA(xyz): + mask = mask | xyz.mask + if self._axlim_clip: + mask = mask | _viewlim_mask(*self._offsets3d, self.axes) + mask = np.broadcast_to(mask, + (len(self._offsets3d), *self._offsets3d[0].shape)) + xyzs = np.ma.array(self._offsets3d, mask=mask) + else: + xyzs = self._offsets3d + vxs, vys, vzs, vis = proj3d._proj_transform_clip(*xyzs, + self.axes.M, + self.axes._focal_length) + self._data_scale = _get_data_scale(vxs, vys, vzs) # Sort the points based on z coordinates # Performance optimization: Create a sorted index array and reorder # points and point properties according to the index array - z_markers_idx = np.argsort(vzs)[::-1] + z_markers_idx = self._z_markers_idx = np.ma.argsort(vzs)[::-1] + self._vzs = vzs + + # we have to special case the sizes because of code in collections.py + # as the draw method does + # self.set_sizes(self._sizes, self.figure.dpi) + # so we cannot rely on doing the sorting on the way out via get_* + + if len(self._sizes3d) > 1: + self._sizes = self._sizes3d[z_markers_idx] + + if len(self._linewidths3d) > 1: + self._linewidths = self._linewidths3d[z_markers_idx] + + PathCollection.set_offsets(self, np.ma.column_stack((vxs, vys))) # Re-order items vzs = vzs[z_markers_idx] vxs = vxs[z_markers_idx] vys = vys[z_markers_idx] - if len(fcs) > 1: - fcs = fcs[z_markers_idx] - if len(ecs) > 1: - ecs = ecs[z_markers_idx] - if len(sizes) > 1: - sizes = sizes[z_markers_idx] - if len(lws) > 1: - lws = lws[z_markers_idx] - vps = np.column_stack((vxs, vys)) - - fcs = mcolors.to_rgba_array(fcs, self._alpha) - ecs = mcolors.to_rgba_array(ecs, self._alpha) - - self.set_edgecolors(ecs) - self.set_facecolors(fcs) - self.set_sizes(sizes) - self.set_linewidth(lws) - - PathCollection.set_offsets(self, vps) + + # Store ordered offset for drawing purpose + self._offset_zordered = np.ma.column_stack((vxs, vys)) return np.min(vzs) if vzs.size else np.nan + @contextmanager + def _use_zordered_offset(self): + if self._offset_zordered is None: + # Do nothing + yield + else: + # Swap offset with z-ordered offset + old_offset = self._offsets + super().set_offsets(self._offset_zordered) + try: + yield + finally: + self._offsets = old_offset + + def _maybe_depth_shade_and_sort_colors(self, color_array): + # Adjust the color_array alpha values if point depths are defined + # and depth shading is active + if self._vzs is not None and self._depthshade: + color_array = _zalpha( + color_array, + self._vzs, + min_alpha=self._depthshade_minalpha, + _data_scale=self._data_scale, + ) + + # Adjust the order of the color_array using the _z_markers_idx, + # which has been sorted by z-depth + if len(color_array) > 1: + color_array = color_array[self._z_markers_idx] + + return mcolors.to_rgba_array(color_array) -def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): + def get_facecolor(self): + return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) + + def get_edgecolor(self): + # We need this check here to make sure we do not double-apply the depth + # based alpha shading when the edge color is "face" which means the + # edge colour should be identical to the face colour. + if cbook._str_equal(self._edgecolors, 'face'): + return self.get_facecolor() + return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) + + +def patch_collection_2d_to_3d( + col, + zs=0, + zdir="z", + depthshade=None, + axlim_clip=False, + *args, + depthshade_minalpha=None, +): """ - Convert a :class:`~matplotlib.collections.PatchCollection` into a - :class:`Patch3DCollection` object - (or a :class:`~matplotlib.collections.PathCollection` into a - :class:`Path3DCollection` object). + Convert a `.PatchCollection` into a `.Patch3DCollection` object + (or a `.PathCollection` into a `.Path3DCollection` object). Parameters ---------- - za + col : `~matplotlib.collections.PatchCollection` or \ +`~matplotlib.collections.PathCollection` + The collection to convert. + zs : float or array of floats The location or locations to place the patches in the collection along the *zdir* axis. Default: 0. - zdir + zdir : {'x', 'y', 'z'} The axis in which to place the patches. Default: "z". - depthshade - Whether to shade the patches to give a sense of depth. Default: *True*. + See `.get_dir_vector` for a description of the values. + depthshade : bool, default: None + Whether to shade the patches to give a sense of depth. + If None, use the value from rcParams['axes3d.depthshade']. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + depthshade_minalpha : float, default: None + Sets the minimum alpha value used by depth-shading. + If None, use the value from rcParams['axes3d.depthshade_minalpha']. + + .. versionadded:: 3.11 """ if isinstance(col, PathCollection): col.__class__ = Path3DCollection + col._offset_zordered = None elif isinstance(col, PatchCollection): col.__class__ = Patch3DCollection + if depthshade is None: + depthshade = rcParams['axes3d.depthshade'] + if depthshade_minalpha is None: + depthshade_minalpha = rcParams['axes3d.depthshade_minalpha'] col._depthshade = depthshade - col.set_3d_properties(zs, zdir) + col._depthshade_minalpha = depthshade_minalpha + col._in_draw = False + col.set_3d_properties(zs, zdir, axlim_clip) class Poly3DCollection(PolyCollection): @@ -581,16 +1162,34 @@ class Poly3DCollection(PolyCollection): triangulation and thus generates consistent surfaces. """ - def __init__(self, verts, *args, zsort='average', **kwargs): + def __init__(self, verts, *args, zsort='average', shade=False, + lightsource=None, axlim_clip=False, **kwargs): """ Parameters ---------- - verts : list of array-like Nx3 - Each element describes a polygon as a sequence of ``N_i`` points - ``(x, y, z)``. + verts : list of (N, 3) array-like + The sequence of polygons [*verts0*, *verts1*, ...] where each + element *verts_i* defines the vertices of polygon *i* as a 2D + array-like of shape (N, 3). zsort : {'average', 'min', 'max'}, default: 'average' The calculation method for the z-order. See `~.Poly3DCollection.set_zsort` for details. + shade : bool, default: False + Whether to shade *facecolors* and *edgecolors*. When activating + *shade*, *facecolors* and/or *edgecolors* must be provided. + + .. versionadded:: 3.7 + + lightsource : `~matplotlib.colors.LightSource`, optional + The lightsource to use when *shade* is True. + + .. versionadded:: 3.7 + + axlim_clip : bool, default: False + Whether to hide polygons with a vertex outside the view limits. + + .. versionadded:: 3.10 + *args, **kwargs All other parameters are forwarded to `.PolyCollection`. @@ -599,9 +1198,33 @@ def __init__(self, verts, *args, zsort='average', **kwargs): Note that this class does a bit of magic with the _facecolors and _edgecolors properties. """ + if shade: + normals = _generate_normals(verts) + facecolors = kwargs.get('facecolors', None) + if facecolors is not None: + kwargs['facecolors'] = _shade_colors( + facecolors, normals, lightsource + ) + + edgecolors = kwargs.get('edgecolors', None) + if edgecolors is not None: + kwargs['edgecolors'] = _shade_colors( + edgecolors, normals, lightsource + ) + if facecolors is None and edgecolors is None: + raise ValueError( + "You must provide facecolors, edgecolors, or both for " + "shade to work.") super().__init__(verts, *args, **kwargs) + if isinstance(verts, np.ndarray): + if verts.ndim != 3: + raise ValueError('verts must be a list of (N, 3) array-like') + else: + if any(len(np.shape(vert)) != 2 for vert in verts): + raise ValueError('verts must be a list of (N, 3) array-like') self.set_zsort(zsort) self._codes3d = None + self._axlim_clip = axlim_clip _zsort_functions = { 'average': np.average, @@ -623,21 +1246,56 @@ def set_zsort(self, zsort): self._sort_zpos = None self.stale = True + @_api.deprecated("3.10") def get_vector(self, segments3d): - """Optimize points for projection.""" - if len(segments3d): - xs, ys, zs = np.row_stack(segments3d).T - else: # row_stack can't stack zero arrays. - xs, ys, zs = [], [], [] - ones = np.ones(len(xs)) - self._vec = np.array([xs, ys, zs, ones]) + return self._get_vector(segments3d) - indices = [0, *np.cumsum([len(segment) for segment in segments3d])] - self._segslices = [*map(slice, indices[:-1], indices[1:])] + def _get_vector(self, segments3d): + """ + Optimize points for projection. + + Parameters + ---------- + segments3d : NumPy array or list of NumPy arrays + List of vertices of the boundary of every segment. If all paths are + of equal length and this argument is a NumPy array, then it should + be of shape (num_faces, num_vertices, 3). + """ + if isinstance(segments3d, np.ndarray): + _api.check_shape((None, None, 3), segments3d=segments3d) + if isinstance(segments3d, np.ma.MaskedArray): + self._faces = segments3d.data + self._invalid_vertices = segments3d.mask.any(axis=-1) + else: + self._faces = segments3d + self._invalid_vertices = False + else: + # Turn the potentially ragged list into a numpy array for later speedups + # If it is ragged, set the unused vertices per face as invalid + num_faces = len(segments3d) + num_verts = np.fromiter(map(len, segments3d), dtype=np.intp) + max_verts = num_verts.max(initial=0) + segments = np.empty((num_faces, max_verts, 3)) + for i, face in enumerate(segments3d): + segments[i, :len(face)] = face + self._faces = segments + self._invalid_vertices = np.arange(max_verts) >= num_verts[:, None] def set_verts(self, verts, closed=True): - """Set 3D vertices.""" - self.get_vector(verts) + """ + Set 3D vertices. + + Parameters + ---------- + verts : list of (N, 3) array-like + The sequence of polygons [*verts0*, *verts1*, ...] where each + element *verts_i* defines the vertices of polygon *i* as a 2D + array-like of shape (N, 3). + closed : bool, default: True + Whether the polygon should be closed by adding a CLOSEPOLY + connection at the end. + """ + self._get_vector(verts) # 2D verts will be updated at draw time super().set_verts([], False) self._closed = closed @@ -650,14 +1308,14 @@ def set_verts_and_codes(self, verts, codes): # and set our own codes instead. self._codes3d = codes - def set_3d_properties(self): + def set_3d_properties(self, axlim_clip=False): # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() self._sort_zpos = None self.set_zsort('average') - self._facecolors3d = PolyCollection.get_facecolor(self) - self._edgecolors3d = PolyCollection.get_edgecolor(self) + self._facecolor3d = PolyCollection.get_facecolor(self) + self._edgecolor3d = PolyCollection.get_edgecolor(self) self._alpha3d = PolyCollection.get_alpha(self) self.stale = True @@ -666,105 +1324,176 @@ def set_sort_zpos(self, val): self._sort_zpos = val self.stale = True - def do_3d_projection(self, renderer): + def do_3d_projection(self): """ Perform the 3D projection for this object. """ - # FIXME: This may no longer be needed? if self._A is not None: + # force update of color mapping because we re-order them + # below. If we do not do this here, the 2D draw will call + # this, but we will never port the color mapped values back + # to the 3D versions. + # + # We hold the 3D versions in a fixed order (the order the user + # passed in) and sort the 2D version by view depth. self.update_scalarmappable() - self._facecolors3d = self._facecolors - - txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M) - xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] + if self._face_is_mapped: + self._facecolor3d = self._facecolors + if self._edge_is_mapped: + self._edgecolor3d = self._edgecolors + + needs_masking = np.any(self._invalid_vertices) + num_faces = len(self._faces) + mask = self._invalid_vertices + + # Some faces might contain masked vertices, so we want to ignore any + # errors that those might cause + with np.errstate(invalid='ignore', divide='ignore'): + pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) + + if self._axlim_clip: + viewlim_mask = _viewlim_mask(self._faces[..., 0], self._faces[..., 1], + self._faces[..., 2], self.axes) + if np.any(viewlim_mask): + needs_masking = True + mask = mask | viewlim_mask + + pzs = pfaces[..., 2] + if needs_masking: + pzs = np.ma.MaskedArray(pzs, mask=mask) # This extra fuss is to re-order face / edge colors - cface = self._facecolors3d - cedge = self._edgecolors3d - if len(cface) != len(xyzlist): - cface = cface.repeat(len(xyzlist), axis=0) - if len(cedge) != len(xyzlist): + cface = self._facecolor3d + cedge = self._edgecolor3d + if len(cface) != num_faces: + cface = cface.repeat(num_faces, axis=0) + if len(cedge) != num_faces: if len(cedge) == 0: cedge = cface else: - cedge = cedge.repeat(len(xyzlist), axis=0) - - # sort by depth (furthest drawn first) - z_segments_2d = sorted( - ((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx) - for idx, ((xs, ys, zs), fc, ec) - in enumerate(zip(xyzlist, cface, cedge))), - key=lambda x: x[0], reverse=True) + cedge = cedge.repeat(num_faces, axis=0) - zzs, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \ - zip(*z_segments_2d) - - if self._codes3d is not None: - codes = [self._codes3d[idx] for idx in idxs] - PolyCollection.set_verts_and_codes(self, segments_2d, codes) + if len(pzs) > 0: + face_z = self._zsortfunc(pzs, axis=-1) else: - PolyCollection.set_verts(self, segments_2d, self._closed) + face_z = pzs + if needs_masking: + face_z = face_z.data + face_order = np.argsort(face_z, axis=-1)[::-1] - if len(self._edgecolors3d) != len(cface): - self._edgecolors2d = self._edgecolors3d + if len(pfaces) > 0: + faces_2d = pfaces[face_order, :, :2] + else: + faces_2d = pfaces + if self._codes3d is not None and len(self._codes3d) > 0: + if needs_masking: + segment_mask = ~mask[face_order, :] + faces_2d = [face[mask, :] for face, mask + in zip(faces_2d, segment_mask)] + codes = [self._codes3d[idx] for idx in face_order] + PolyCollection.set_verts_and_codes(self, faces_2d, codes) + else: + if needs_masking and len(faces_2d) > 0: + invalid_vertices_2d = np.broadcast_to( + mask[face_order, :, None], + faces_2d.shape) + faces_2d = np.ma.MaskedArray( + faces_2d, mask=invalid_vertices_2d) + PolyCollection.set_verts(self, faces_2d, self._closed) + + if len(cface) > 0: + self._facecolors2d = cface[face_order] + else: + self._facecolors2d = cface + if len(self._edgecolor3d) == len(cface) and len(cedge) > 0: + self._edgecolors2d = cedge[face_order] + else: + self._edgecolors2d = self._edgecolor3d # Return zorder value if self._sort_zpos is not None: zvec = np.array([[0], [0], [self._sort_zpos], [1]]) - ztrans = proj3d._proj_transform_vec(zvec, renderer.M) + ztrans = proj3d._proj_transform_vec(zvec, self.axes.M) return ztrans[2][0] - elif tzs.size > 0: + elif pzs.size > 0: # FIXME: Some results still don't look quite right. # In particular, examine contourf3d_demo2.py # with az = -54 and elev = -45. - return np.min(tzs) + return np.min(pzs) else: return np.nan def set_facecolor(self, colors): + # docstring inherited super().set_facecolor(colors) - self._facecolors3d = PolyCollection.get_facecolor(self) + self._facecolor3d = PolyCollection.get_facecolor(self) def set_edgecolor(self, colors): + # docstring inherited super().set_edgecolor(colors) - self._edgecolors3d = PolyCollection.get_edgecolor(self) + self._edgecolor3d = PolyCollection.get_edgecolor(self) def set_alpha(self, alpha): # docstring inherited artist.Artist.set_alpha(self, alpha) try: - self._facecolors3d = mcolors.to_rgba_array( - self._facecolors3d, self._alpha) + self._facecolor3d = mcolors.to_rgba_array( + self._facecolor3d, self._alpha) except (AttributeError, TypeError, IndexError): pass try: self._edgecolors = mcolors.to_rgba_array( - self._edgecolors3d, self._alpha) + self._edgecolor3d, self._alpha) except (AttributeError, TypeError, IndexError): pass self.stale = True def get_facecolor(self): - return self._facecolors2d + # docstring inherited + # self._facecolors2d is not initialized until do_3d_projection + if not hasattr(self, '_facecolors2d'): + self.axes.M = self.axes.get_proj() + self.do_3d_projection() + return np.asarray(self._facecolors2d) def get_edgecolor(self): - return self._edgecolors2d + # docstring inherited + # self._edgecolors2d is not initialized until do_3d_projection + if not hasattr(self, '_edgecolors2d'): + self.axes.M = self.axes.get_proj() + self.do_3d_projection() + return np.asarray(self._edgecolors2d) -def poly_collection_2d_to_3d(col, zs=0, zdir='z'): - """Convert a PolyCollection to a Poly3DCollection object.""" +def poly_collection_2d_to_3d(col, zs=0, zdir='z', axlim_clip=False): + """ + Convert a `.PolyCollection` into a `.Poly3DCollection` object. + + Parameters + ---------- + col : `~matplotlib.collections.PolyCollection` + The collection to convert. + zs : float or array of floats + The location or locations to place the polygons in the collection along + the *zdir* axis. Default: 0. + zdir : {'x', 'y', 'z'} + The axis in which to place the patches. Default: 'z'. + See `.get_dir_vector` for a description of the values. + """ segments_3d, codes = _paths_to_3d_segments_with_codes( col.get_paths(), zs, zdir) col.__class__ = Poly3DCollection col.set_verts_and_codes(segments_3d, codes) col.set_3d_properties() + col._axlim_clip = axlim_clip def juggle_axes(xs, ys, zs, zdir): """ - Reorder coordinates so that 2D xs, ys can be plotted in the plane - orthogonal to zdir. zdir is normally x, y or z. However, if zdir - starts with a '-' it is interpreted as a compensation for rotate_axes. + Reorder coordinates so that 2D *xs*, *ys* can be plotted in the plane + orthogonal to *zdir*. *zdir* is normally 'x', 'y' or 'z'. However, if + *zdir* starts with a '-' it is interpreted as a compensation for + `rotate_axes`. """ if zdir == 'x': return zs, xs, ys @@ -778,40 +1507,164 @@ def juggle_axes(xs, ys, zs, zdir): def rotate_axes(xs, ys, zs, zdir): """ - Reorder coordinates so that the axes are rotated with zdir along + Reorder coordinates so that the axes are rotated with *zdir* along the original z axis. Prepending the axis with a '-' does the - inverse transform, so zdir can be x, -x, y, -y, z or -z + inverse transform, so *zdir* can be 'x', '-x', 'y', '-y', 'z' or '-z'. """ - if zdir == 'x': + if zdir in ('x', '-y'): return ys, zs, xs - elif zdir == '-x': + elif zdir in ('-x', 'y'): return zs, xs, ys - - elif zdir == 'y': - return zs, xs, ys - elif zdir == '-y': - return ys, zs, xs - else: return xs, ys, zs -def _get_colors(c, num): - """Stretch the color argument to provide the required number *num*.""" - return np.broadcast_to( - mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0], - (num, 4)) +def _zalpha( + colors, + zs, + min_alpha=0.3, + _data_scale=None, +): + """Modify the alpha values of the color list according to z-depth.""" - -def _zalpha(colors, zs): - """Modify the alphas of the color list according to depth.""" - # FIXME: This only works well if the points for *zs* are well-spaced - # in all three dimensions. Otherwise, at certain orientations, - # the min and max zs are very close together. - # Should really normalize against the viewing depth. - if len(zs) == 0: + if len(colors) == 0 or len(zs) == 0: return np.zeros((0, 4)) - norm = Normalize(min(zs), max(zs)) - sats = 1 - norm(zs) * 0.7 + + # Alpha values beyond the range 0-1 inclusive make no sense, so clip them + min_alpha = np.clip(min_alpha, 0, 1) + + if _data_scale is None or _data_scale == 0: + # Don't scale the alpha values since we have no valid data scale for reference + sats = np.ones_like(zs) + + else: + # Deeper points have an increasingly transparent appearance + sats = np.clip(1 - (zs - np.min(zs)) / _data_scale, min_alpha, 1) + rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4)) + + # Change the alpha values of the colors using the generated alpha multipliers return np.column_stack([rgba[:, :3], rgba[:, 3] * sats]) + + +def _all_points_on_plane(xs, ys, zs, atol=1e-8): + """ + Check if all points are on the same plane. Note that NaN values are + ignored. + + Parameters + ---------- + xs, ys, zs : array-like + The x, y, and z coordinates of the points. + atol : float, default: 1e-8 + The tolerance for the equality check. + """ + xs, ys, zs = np.asarray(xs), np.asarray(ys), np.asarray(zs) + points = np.column_stack([xs, ys, zs]) + points = points[~np.isnan(points).any(axis=1)] + # Check for the case where we have less than 3 unique points + points = np.unique(points, axis=0) + if len(points) <= 3: + return True + # Calculate the vectors from the first point to all other points + vs = (points - points[0])[1:] + vs = vs / np.linalg.norm(vs, axis=1)[:, np.newaxis] + # Filter out parallel vectors + vs = np.unique(vs, axis=0) + if len(vs) <= 2: + return True + # Filter out parallel and antiparallel vectors to the first vector + cross_norms = np.linalg.norm(np.cross(vs[0], vs[1:]), axis=1) + zero_cross_norms = np.where(np.isclose(cross_norms, 0, atol=atol))[0] + 1 + vs = np.delete(vs, zero_cross_norms, axis=0) + if len(vs) <= 2: + return True + # Calculate the normal vector from the first three points + n = np.cross(vs[0], vs[1]) + n = n / np.linalg.norm(n) + # If the dot product of the normal vector and all other vectors is zero, + # all points are on the same plane + dots = np.dot(n, vs.transpose()) + return np.allclose(dots, 0, atol=atol) + + +def _generate_normals(polygons): + """ + Compute the normals of a list of polygons, one normal per polygon. + + Normals point towards the viewer for a face with its vertices in + counterclockwise order, following the right hand rule. + + Uses three points equally spaced around the polygon. This method assumes + that the points are in a plane. Otherwise, more than one shade is required, + which is not supported. + + Parameters + ---------- + polygons : list of (M_i, 3) array-like, or (..., M, 3) array-like + A sequence of polygons to compute normals for, which can have + varying numbers of vertices. If the polygons all have the same + number of vertices and array is passed, then the operation will + be vectorized. + + Returns + ------- + normals : (..., 3) array + A normal vector estimated for the polygon. + """ + if isinstance(polygons, np.ndarray): + # optimization: polygons all have the same number of points, so can + # vectorize + n = polygons.shape[-2] + i1, i2, i3 = 0, n//3, 2*n//3 + v1 = polygons[..., i1, :] - polygons[..., i2, :] + v2 = polygons[..., i2, :] - polygons[..., i3, :] + else: + # The subtraction doesn't vectorize because polygons is jagged. + v1 = np.empty((len(polygons), 3)) + v2 = np.empty((len(polygons), 3)) + for poly_i, ps in enumerate(polygons): + n = len(ps) + ps = np.asarray(ps) + i1, i2, i3 = 0, n//3, 2*n//3 + v1[poly_i, :] = ps[i1, :] - ps[i2, :] + v2[poly_i, :] = ps[i2, :] - ps[i3, :] + return np.cross(v1, v2) + + +def _shade_colors(color, normals, lightsource=None): + """ + Shade *color* using normal vectors given by *normals*, + assuming a *lightsource* (using default position if not given). + *color* can also be an array of the same length as *normals*. + """ + if lightsource is None: + # chosen for backwards-compatibility + lightsource = mcolors.LightSource(azdeg=225, altdeg=19.4712) + + with np.errstate(invalid="ignore"): + shade = ((normals / np.linalg.norm(normals, axis=1, keepdims=True)) + @ lightsource.direction) + mask = ~np.isnan(shade) + + if mask.any(): + # convert dot product to allowed shading fractions + in_norm = mcolors.Normalize(-1, 1) + out_norm = mcolors.Normalize(0.3, 1).inverse + + def norm(x): + return out_norm(in_norm(x)) + + shade[~mask] = 0 + + color = mcolors.to_rgba_array(color) + # shape of color should be (M, 4) (where M is number of faces) + # shape of shade should be (M,) + # colors should have final shape of (M, 4) + alpha = color[:, 3] + colors = norm(shade)[:, np.newaxis] * color + colors[:, 3] = alpha + else: + colors = np.asanyarray(color).copy() + + return colors diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index b11bdbdccfb3..55b204022fb9 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -11,76 +11,113 @@ """ from collections import defaultdict -from functools import reduce -from itertools import compress +import itertools import math import textwrap +import warnings import numpy as np -from matplotlib import artist -import matplotlib.axes as maxes -import matplotlib.cbook as cbook +import matplotlib as mpl +from matplotlib import _api, cbook, _docstring, _preprocess_data +import matplotlib.artist as martist import matplotlib.collections as mcoll import matplotlib.colors as mcolors -import matplotlib.docstring as docstring -import matplotlib.scale as mscale +import matplotlib.image as mimage +import matplotlib.lines as mlines +import matplotlib.patches as mpatches import matplotlib.container as mcontainer import matplotlib.transforms as mtransforms -from matplotlib.axes import Axes, rcParams +from matplotlib.axes import Axes from matplotlib.axes._base import _axis_method_wrapper, _process_plot_format from matplotlib.transforms import Bbox -from matplotlib.tri.triangulation import Triangulation +from matplotlib.tri._triangulation import Triangulation from . import art3d from . import proj3d from . import axis3d -@cbook.deprecated("3.2", alternative="Bbox.unit()") -def unit_bbox(): - box = Bbox(np.array([[0, 0], [1, 1]])) - return box - - -@cbook._define_aliases({ - "xlim3d": ["xlim"], "ylim3d": ["ylim"], "zlim3d": ["zlim"]}) +@_docstring.interpd +@_api.define_aliases({ + "xlim": ["xlim3d"], "ylim": ["ylim3d"], "zlim": ["zlim3d"]}) class Axes3D(Axes): """ - 3D axes object. + 3D Axes object. + + .. note:: + + As a user, you do not instantiate Axes directly, but use Axes creation + methods instead; e.g. from `.pyplot` or `.Figure`: + `~.pyplot.subplots`, `~.pyplot.subplot_mosaic` or `.Figure.add_axes`. """ name = '3d' - _shared_z_axes = cbook.Grouper() + + _axis_names = ("x", "y", "z") + Axes._shared_axes["z"] = cbook.Grouper() + Axes._shared_axes["view"] = cbook.Grouper() def __init__( - self, fig, rect=None, *args, - azim=-60, elev=30, sharez=None, proj_type='persp', - box_aspect=None, - **kwargs): + self, fig, rect=None, *args, + elev=30, azim=-60, roll=0, shareview=None, sharez=None, + proj_type='persp', focal_length=None, + box_aspect=None, + computed_zorder=True, + **kwargs, + ): """ Parameters ---------- fig : Figure The parent figure. - rect : (float, float, float, float) - The ``(left, bottom, width, height)`` axes position. - azim : float, default: -60 - Azimuthal viewing angle. + rect : tuple (left, bottom, width, height), default: None. + The ``(left, bottom, width, height)`` Axes position. elev : float, default: 30 - Elevation viewing angle. + The elevation angle in degrees rotates the camera above and below + the x-y plane, with a positive angle corresponding to a location + above the plane. + azim : float, default: -60 + The azimuthal angle in degrees rotates the camera about the z axis, + with a positive angle corresponding to a right-handed rotation. In + other words, a positive azimuth rotates the camera about the origin + from its location along the +x axis towards the +y axis. + roll : float, default: 0 + The roll angle in degrees rotates the camera about the viewing + axis. A positive angle spins the camera clockwise, causing the + scene to rotate counter-clockwise. + shareview : Axes3D, optional + Other Axes to share view angles with. Note that it is not possible + to unshare axes. sharez : Axes3D, optional - Other axes to share z-limits with. + Other Axes to share z-limits with. Note that it is not possible to + unshare axes. proj_type : {'persp', 'ortho'} The projection type, default 'persp'. + focal_length : float, default: None + For a projection type of 'persp', the focal length of the virtual + camera. Must be > 0. If None, defaults to 1. + For a projection type of 'ortho', must be set to either None + or infinity (numpy.inf). If None, defaults to infinity. + The focal length can be computed from a desired Field Of View via + the equation: focal_length = 1/tan(FOV/2) + box_aspect : 3-tuple of floats, default: None + Changes the physical dimensions of the Axes3D, such that the ratio + of the axis lengths in display units is x:y:z. + If None, defaults to 4:4:3 + computed_zorder : bool, default: True + If True, the draw order is computed based on the average position + of the `.Artist`\\s along the view direction. + Set to False if you want to manually control the order in which + Artists are drawn on top of each other using their *zorder* + attribute. This can be used for fine-tuning if the automatic order + does not produce the desired result. Note however, that a manual + zorder will only be correct for a limited view angle. If the figure + is rotated by the user, it will look wrong from certain angles. + **kwargs Other optional keyword arguments: - %(Axes3D)s - - Notes - ----- - .. versionadded:: 1.2.1 - The *sharez* parameter. + %(Axes3D:kwdoc)s """ if rect is None: @@ -88,22 +125,37 @@ def __init__( self.initial_azim = azim self.initial_elev = elev - self.set_proj_type(proj_type) + self.initial_roll = roll + self.set_proj_type(proj_type, focal_length) + self.computed_zorder = computed_zorder self.xy_viewLim = Bbox.unit() self.zz_viewLim = Bbox.unit() - self.xy_dataLim = Bbox.unit() + xymargin = 0.05 * 10/11 # match mpl3.8 appearance + self.xy_dataLim = Bbox([[xymargin, xymargin], + [1 - xymargin, 1 - xymargin]]) + # z-limits are encoded in the x-component of the Bbox, y is un-used self.zz_dataLim = Bbox.unit() # inhibit autoscale_view until the axes are defined # they can't be defined until Axes.__init__ has been called - self.view_init(self.initial_elev, self.initial_azim) + self.view_init(self.initial_elev, self.initial_azim, self.initial_roll) self._sharez = sharez if sharez is not None: - self._shared_z_axes.join(self, sharez) + self._shared_axes["z"].join(self, sharez) self._adjustable = 'datalim' + self._shareview = shareview + if shareview is not None: + self._shared_axes["view"].join(self, shareview) + + if kwargs.pop('auto_add_to_figure', False): + raise AttributeError( + 'auto_add_to_figure is no longer supported for Axes3D. ' + 'Use fig.add_axes(ax) instead.' + ) + super().__init__( fig, rect, frameon=True, box_aspect=box_aspect, *args, **kwargs ) @@ -112,23 +164,22 @@ def __init__( # Enable drawing of axes by Axes3D class self.set_axis_on() self.M = None + self.invM = None + + self._view_margin = 1/48 # default value to match mpl3.8 + self.autoscale_view() # func used to format z -- fall back on major formatters self.fmt_zdata = None - if self.zaxis is not None: - self._zcid = self.zaxis.callbacks.connect( - 'units finalize', lambda: self._on_units_changed(scalez=True)) - else: - self._zcid = None - self.mouse_init() - self.figure.canvas.mpl_connect( - 'motion_notify_event', self._on_move), - self.figure.canvas.mpl_connect( - 'button_press_event', self._button_press), - self.figure.canvas.mpl_connect( - 'button_release_event', self._button_release), + fig = self.get_figure(root=True) + fig.canvas.callbacks._connect_picklable( + 'motion_notify_event', self._on_move) + fig.canvas.callbacks._connect_picklable( + 'button_press_event', self._button_press) + fig.canvas.callbacks._connect_picklable( + 'button_release_event', self._button_release) self.set_top_view() self.patch.set_linewidth(0) @@ -136,12 +187,9 @@ def __init__( pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)]) self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0] - self.figure.add_axes(self) - # mplot3d currently manages its own spines and needs these turned off # for bounding box calculations - for k in self.spines.keys(): - self.spines[k].set_visible(False) + self.spines[:].set_visible(False) def set_axis_off(self): self._axis3don = False @@ -153,59 +201,28 @@ def set_axis_on(self): def convert_zunits(self, z): """ - For artists in an axes, if the zaxis has units support, + For artists in an Axes, if the zaxis has units support, convert *z* using zaxis unit type - - .. versionadded:: 1.2.1 - """ return self.zaxis.convert_units(z) - def _process_unit_info(self, xdata=None, ydata=None, zdata=None, - kwargs=None): - """Update the axis instances based on unit *kwargs* if given.""" - super()._process_unit_info(xdata=xdata, ydata=ydata, kwargs=kwargs) - - if self.xaxis is None or self.yaxis is None or self.zaxis is None: - return - - if zdata is not None: - # we only need to update if there is nothing set yet. - if not self.zaxis.have_units(): - self.zaxis.update_units(xdata) - - # process kwargs 2nd since these will override default units - if kwargs is not None: - zunits = kwargs.pop('zunits', self.zaxis.units) - if zunits != self.zaxis.units: - self.zaxis.set_units(zunits) - # If the units being set imply a different converter, - # we need to update. - if zdata is not None: - self.zaxis.update_units(zdata) - def set_top_view(self): # this happens to be the right view for the viewing coordinates # moved up and to the left slightly to fit labels and axes - xdwl = 0.95 / self.dist - xdw = 0.9 / self.dist - ydwl = 0.95 / self.dist - ydw = 0.9 / self.dist - # This is purposely using the 2D Axes's set_xlim and set_ylim, - # because we are trying to place our viewing pane. - super().set_xlim(-xdwl, xdw, auto=None) - super().set_ylim(-ydwl, ydw, auto=None) + xdwl = 0.95 / self._dist + xdw = 0.9 / self._dist + ydwl = 0.95 / self._dist + ydw = 0.9 / self._dist + # Set the viewing pane. + self.viewLim.intervalx = (-xdwl, xdw) + self.viewLim.intervaly = (-ydwl, ydw) + self.stale = True def _init_axis(self): - """Init 3D axes; overrides creation of regular X/Y axes.""" - self.xaxis = axis3d.XAxis('x', self.xy_viewLim.intervalx, - self.xy_dataLim.intervalx, self) - self.yaxis = axis3d.YAxis('y', self.xy_viewLim.intervaly, - self.xy_dataLim.intervaly, self) - self.zaxis = axis3d.ZAxis('z', self.zz_viewLim.intervalx, - self.zz_dataLim.intervalx, self) - for ax in self.xaxis, self.yaxis, self.zaxis: - ax.init3d() + """Init 3D Axes; overrides creation of regular X/Y Axes.""" + self.xaxis = axis3d.XAxis(self) + self.yaxis = axis3d.YAxis(self) + self.zaxis = axis3d.ZAxis(self) def get_zaxis(self): """Return the ``ZAxis`` (`~.axis3d.Axis`) instance.""" @@ -214,27 +231,10 @@ def get_zaxis(self): get_zgridlines = _axis_method_wrapper("zaxis", "get_gridlines") get_zticklines = _axis_method_wrapper("zaxis", "get_ticklines") - @cbook.deprecated("3.1", alternative="xaxis", pending=True) - @property - def w_xaxis(self): - return self.xaxis - - @cbook.deprecated("3.1", alternative="yaxis", pending=True) - @property - def w_yaxis(self): - return self.yaxis - - @cbook.deprecated("3.1", alternative="zaxis", pending=True) - @property - def w_zaxis(self): - return self.zaxis - - def _get_axis_list(self): - return super()._get_axis_list() + (self.zaxis, ) - - def unit_cube(self, vals=None): - minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims() - return [(minx, miny, minz), + def _transformed_cube(self, vals): + """Return cube with limits from *vals* transformed by self.M.""" + minx, maxx, miny, maxy, minz, maxz = vals + xyzs = [(minx, miny, minz), (maxx, miny, minz), (maxx, maxy, minz), (minx, maxy, minz), @@ -242,57 +242,28 @@ def unit_cube(self, vals=None): (maxx, miny, maxz), (maxx, maxy, maxz), (minx, maxy, maxz)] - - def tunit_cube(self, vals=None, M=None): - if M is None: - M = self.M - xyzs = self.unit_cube(vals) - tcube = proj3d.proj_points(xyzs, M) - return tcube - - def tunit_edges(self, vals=None, M=None): - tc = self.tunit_cube(vals, M) - edges = [(tc[0], tc[1]), - (tc[1], tc[2]), - (tc[2], tc[3]), - (tc[3], tc[0]), - - (tc[0], tc[4]), - (tc[1], tc[5]), - (tc[2], tc[6]), - (tc[3], tc[7]), - - (tc[4], tc[5]), - (tc[5], tc[6]), - (tc[6], tc[7]), - (tc[7], tc[4])] - return edges + return proj3d._proj_points(xyzs, self.M) def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): """ Set the aspect ratios. - Axes 3D does not current support any aspect but 'auto' which fills - the axes with the data limits. - - To simulate having equal aspect in data space, set the ratio - of your data limits to match the value of `~.get_box_aspect`. - To control box aspect ratios use `~.Axes3D.set_box_aspect`. - Parameters ---------- - aspect : {'auto'} + aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'} Possible values: ========= ================================================== value description ========= ================================================== 'auto' automatic; fill the position rectangle with data. + 'equal' adapt all the axes to have equal aspect ratios. + 'equalxy' adapt the x and y axes to have equal aspect ratios. + 'equalxz' adapt the x and z axes to have equal aspect ratios. + 'equalyz' adapt the y and z axes to have equal aspect ratios. ========= ================================================== - adjustable : None - Currently ignored by Axes3D - + adjustable : None or {'box', 'datalim'}, optional If not *None*, this defines which parameter will be adjusted to meet the required aspect. See `.set_adjustable` for further details. @@ -300,7 +271,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): anchor : None or str or 2-tuple of float, optional If not *None*, this defines where the Axes will be drawn if there is extra space due to aspect constraints. The most common way to - to specify the anchor are abbreviations of cardinal directions: + specify the anchor are abbreviations of cardinal directions: ===== ===================== value description @@ -312,7 +283,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): etc. ===== ===================== - See `.set_anchor` for further details. + See `~.Axes.set_anchor` for further details. share : bool, default: False If ``True``, apply the settings to all shared Axes. @@ -321,52 +292,73 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): -------- mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect """ - if aspect != 'auto': - raise NotImplementedError( - "Axes3D currently only supports the aspect argument " - f"'auto'. You passed in {aspect!r}." - ) - - if share: - axes = {*self._shared_x_axes.get_siblings(self), - *self._shared_y_axes.get_siblings(self), - *self._shared_z_axes.get_siblings(self), - } - else: - axes = {self} - - for ax in axes: - ax._aspect = aspect - ax.stale = True - - if anchor is not None: - self.set_anchor(anchor, share=share) + _api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'), + aspect=aspect) + super().set_aspect( + aspect='auto', adjustable=adjustable, anchor=anchor, share=share) + self._aspect = aspect + + if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): + ax_indices = self._equal_aspect_axis_indices(aspect) + + view_intervals = np.array([self.xaxis.get_view_interval(), + self.yaxis.get_view_interval(), + self.zaxis.get_view_interval()]) + ptp = np.ptp(view_intervals, axis=1) + if self._adjustable == 'datalim': + mean = np.mean(view_intervals, axis=1) + scale = max(ptp[ax_indices] / self._box_aspect[ax_indices]) + deltas = scale * self._box_aspect + + for i, set_lim in enumerate((self.set_xlim3d, + self.set_ylim3d, + self.set_zlim3d)): + if i in ax_indices: + set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2., + auto=True, view_margin=None) + else: # 'box' + # Change the box aspect such that the ratio of the length of + # the unmodified axis to the length of the diagonal + # perpendicular to it remains unchanged. + box_aspect = np.array(self._box_aspect) + box_aspect[ax_indices] = ptp[ax_indices] + remaining_ax_indices = {0, 1, 2}.difference(ax_indices) + if remaining_ax_indices: + remaining = remaining_ax_indices.pop() + old_diag = np.linalg.norm(self._box_aspect[ax_indices]) + new_diag = np.linalg.norm(box_aspect[ax_indices]) + box_aspect[remaining] *= new_diag / old_diag + self.set_box_aspect(box_aspect) + + def _equal_aspect_axis_indices(self, aspect): + """ + Get the indices for which of the x, y, z axes are constrained to have + equal aspect ratios. - def set_anchor(self, anchor, share=False): - # docstring inherited - if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2): - raise ValueError('anchor must be among %s' % - ', '.join(mtransforms.Bbox.coefs)) - if share: - axes = {*self._shared_x_axes.get_siblings(self), - *self._shared_y_axes.get_siblings(self), - *self._shared_z_axes.get_siblings(self), - } - else: - axes = {self} - for ax in axes: - ax._anchor = anchor - ax.stale = True + Parameters + ---------- + aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'} + See descriptions in docstring for `.set_aspect()`. + """ + ax_indices = [] # aspect == 'auto' + if aspect == 'equal': + ax_indices = [0, 1, 2] + elif aspect == 'equalxy': + ax_indices = [0, 1] + elif aspect == 'equalxz': + ax_indices = [0, 2] + elif aspect == 'equalyz': + ax_indices = [1, 2] + return ax_indices def set_box_aspect(self, aspect, *, zoom=1): """ - Set the axes box aspect. + Set the Axes box aspect. The box aspect is the ratio of height to width in display units for each face of the box when viewed perpendicular to - that face. This is not to be confused with the data aspect - (which for Axes3D is always 'auto'). The default ratios are - 4:4:3 (x:y:z). + that face. This is not to be confused with the data aspect (see + `~.Axes3D.set_aspect`). The default ratios are 4:4:3 (x:y:z). To simulate having equal aspect in data space, set the box aspect to match your data range in each dimension. @@ -378,26 +370,26 @@ def set_box_aspect(self, aspect, *, zoom=1): aspect : 3-tuple of floats or None Changes the physical dimensions of the Axes3D, such that the ratio of the axis lengths in display units is x:y:z. + If None, defaults to (4, 4, 3). - If None, defaults to 4:4:3 - - zoom : float - Control overall size of the Axes3D in the figure. + zoom : float, default: 1 + Control overall size of the Axes3D in the figure. Must be > 0. """ + if zoom <= 0: + raise ValueError(f'Argument zoom = {zoom} must be > 0') + if aspect is None: aspect = np.asarray((4, 4, 3), dtype=float) else: - orig_aspect = aspect aspect = np.asarray(aspect, dtype=float) - if aspect.shape != (3,): - raise ValueError( - "You must pass a 3-tuple that can be cast to floats. " - f"You passed {orig_aspect!r}" - ) - # default scale tuned to match the mpl32 appearance. - aspect *= 1.8294640721620434 * zoom / np.linalg.norm(aspect) - - self._box_aspect = aspect + _api.check_shape((3,), aspect=aspect) + # The scale 1.8294640721620434 is tuned to match the mpl3.2 appearance. + # The 25/24 factor is to compensate for the change in automargin + # behavior in mpl3.9. This comes from the padding of 1/48 on both sides + # of the axes in mpl3.8. + aspect *= 1.8294640721620434 * 25/24 * zoom / np.linalg.norm(aspect) + + self._box_aspect = self._roll_to_vertical(aspect, reverse=True) self.stale = True def apply_aspect(self, position=None): @@ -407,15 +399,22 @@ def apply_aspect(self, position=None): # in the superclass, we would go through and actually deal with axis # scales and box/datalim. Those are all irrelevant - all we need to do # is make sure our coordinate system is square. - figW, figH = self.get_figure().get_size_inches() - fig_aspect = figH / figW + trans = self.get_figure().transSubfigure + bb = mtransforms.Bbox.unit().transformed(trans) + # this is the physical aspect of the panel (or figure): + fig_aspect = bb.height / bb.width + box_aspect = 1 pb = position.frozen() pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) self._set_position(pb1.anchored(self.get_anchor(), pb), 'active') - @artist.allow_rasterization + @martist.allow_rasterization def draw(self, renderer): + if not self.get_visible(): + return + self._unstale_viewLim() + # draw the background patch self.patch.draw(renderer) self._frameon = False @@ -424,168 +423,115 @@ def draw(self, renderer): # this is duplicated from `axes._base._AxesBase.draw` # but must be called before any of the artist are drawn as # it adjusts the view limits and the size of the bounding box - # of the axes + # of the Axes locator = self.get_axes_locator() - if locator: - pos = locator(self, renderer) - self.apply_aspect(pos) - else: - self.apply_aspect() + self.apply_aspect(locator(self, renderer) if locator else None) # add the projection matrix to the renderer self.M = self.get_proj() - renderer.M = self.M - renderer.vvec = self.vvec - renderer.eye = self.eye - renderer.get_axis_position = self.get_axis_position - - # Calculate projection of collections and patches and zorder them. - # Make sure they are drawn above the grids. - zorder_offset = max(axis.get_zorder() - for axis in self._get_axis_list()) + 1 - for i, col in enumerate( - sorted(self.collections, - key=lambda col: col.do_3d_projection(renderer), - reverse=True)): - col.zorder = zorder_offset + i - for i, patch in enumerate( - sorted(self.patches, - key=lambda patch: patch.do_3d_projection(renderer), - reverse=True)): - patch.zorder = zorder_offset + i + self.invM = np.linalg.inv(self.M) + + collections_and_patches = ( + artist for artist in self._children + if isinstance(artist, (mcoll.Collection, mpatches.Patch)) + and artist.get_visible()) + if self.computed_zorder: + # Calculate projection of collections and patches and zorder + # them. Make sure they are drawn above the grids. + zorder_offset = max(axis.get_zorder() + for axis in self._axis_map.values()) + 1 + collection_zorder = patch_zorder = zorder_offset + + for artist in sorted(collections_and_patches, + key=lambda artist: artist.do_3d_projection(), + reverse=True): + if isinstance(artist, mcoll.Collection): + artist.zorder = collection_zorder + collection_zorder += 1 + elif isinstance(artist, mpatches.Patch): + artist.zorder = patch_zorder + patch_zorder += 1 + else: + for artist in collections_and_patches: + artist.do_3d_projection() if self._axis3don: # Draw panes first - for axis in self._get_axis_list(): + for axis in self._axis_map.values(): axis.draw_pane(renderer) - # Then axes - for axis in self._get_axis_list(): + # Then gridlines + for axis in self._axis_map.values(): + axis.draw_grid(renderer) + # Then axes, labels, text, and ticks + for axis in self._axis_map.values(): axis.draw(renderer) # Then rest super().draw(renderer) def get_axis_position(self): - vals = self.get_w_lims() - tc = self.tunit_cube(vals, self.M) + tc = self._transformed_cube(self.get_w_lims()) xhigh = tc[1][2] > tc[2][2] yhigh = tc[3][2] > tc[2][2] zhigh = tc[0][2] > tc[2][2] return xhigh, yhigh, zhigh - def _on_units_changed(self, scalex=False, scaley=False, scalez=False): - """ - Callback for processing changes to axis units. - - Currently forces updates of data limits and view limits. - """ - self.relim() - self.autoscale_view(scalex=scalex, scaley=scaley, scalez=scalez) - def update_datalim(self, xys, **kwargs): - pass - - def get_autoscale_on(self): - """ - Get whether autoscaling is applied for all axes on plot commands - - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. """ - return super().get_autoscale_on() and self.get_autoscalez_on() - - def get_autoscalez_on(self): - """ - Get whether autoscaling for the z-axis is applied on plot commands - - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. + Not implemented in `~mpl_toolkits.mplot3d.axes3d.Axes3D`. """ - return self._autoscaleZon - - def set_autoscale_on(self, b): - """ - Set whether autoscaling is applied on plot commands + pass - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. + get_autoscalez_on = _axis_method_wrapper("zaxis", "_get_autoscale_on") + set_autoscalez_on = _axis_method_wrapper("zaxis", "_set_autoscale_on") - Parameters - ---------- - b : bool + def get_zmargin(self): """ - super().set_autoscale_on(b) - self.set_autoscalez_on(b) + Retrieve autoscaling margin of the z-axis. - def set_autoscalez_on(self, b): - """ - Set whether autoscaling for the z-axis is applied on plot commands + .. versionadded:: 3.9 - .. versionadded:: 1.1.0 + Returns + ------- + zmargin : float - Parameters - ---------- - b : bool + See Also + -------- + mpl_toolkits.mplot3d.axes3d.Axes3D.set_zmargin """ - self._autoscaleZon = b + return self._zmargin def set_zmargin(self, m): """ Set padding of Z data limits prior to autoscaling. - *m* times the data interval will be added to each - end of that interval before it is used in autoscaling. + *m* times the data interval will be added to each end of that interval + before it is used in autoscaling. If *m* is negative, this will clip + the data range instead of expanding it. - accepts: float in range 0 to 1 + For example, if your data is in the range [0, 2], a margin of 0.1 will + result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range + of [0.2, 1.8]. - .. versionadded:: 1.1.0 + Parameters + ---------- + m : float greater than -0.5 """ - if m < 0 or m > 1: - raise ValueError("margin must be in range 0 to 1") + if m <= -0.5: + raise ValueError("margin must be greater than -0.5") self._zmargin = m + self._request_autoscale_view("z") self.stale = True def margins(self, *margins, x=None, y=None, z=None, tight=True): """ - Convenience method to set or retrieve autoscaling margins. - - Call signatures:: - - margins() - - returns xmargin, ymargin, zmargin - - :: - - margins(margin) - - margins(xmargin, ymargin, zmargin) - - margins(x=xmargin, y=ymargin, z=zmargin) + Set or retrieve autoscaling margins. - margins(..., tight=False) - - All forms above set the xmargin, ymargin and zmargin - parameters. All keyword parameters are optional. A single - positional argument specifies xmargin, ymargin and zmargin. - Passing both positional and keyword arguments for xmargin, - ymargin, and/or zmargin is invalid. - - The *tight* parameter - is passed to :meth:`autoscale_view`, which is executed after - a margin is changed; the default here is *True*, on the - assumption that when margins are specified, no additional - padding to match tick marks is usually desired. Setting - *tight* to *None* will preserve the previous setting. - - Specifying any margin changes only the autoscaling; for example, - if *xmargin* is not None, then *xmargin* times the X data - interval will be added to each end of that interval before - it is used in autoscaling. - - .. versionadded:: 1.1.0 + See `.Axes.margins` for full documentation. Because this function + applies to 3D Axes, it also takes a *z* argument, and returns + ``(xmargin, ymargin, zmargin)``. """ - if margins and x is not None and y is not None and z is not None: + if margins and (x is not None or y is not None or z is not None): raise TypeError('Cannot pass both positional and keyword ' 'arguments for x, y, and/or z.') elif len(margins) == 1: @@ -598,7 +544,7 @@ def margins(self, *margins, x=None, y=None, z=None, tight=True): if x is None and y is None and z is None: if tight is not True: - cbook._warn_external(f'ignoring tight={tight!r} in get mode') + _api.warn_external(f'ignoring tight={tight!r} in get mode') return self._xmargin, self._ymargin, self._zmargin if x is not None: @@ -616,12 +562,10 @@ def margins(self, *margins, x=None, y=None, z=None, tight=True): def autoscale(self, enable=True, axis='both', tight=None): """ Convenience method for simple axis view autoscaling. - See :meth:`matplotlib.axes.Axes.autoscale` for full explanation. - Note that this function behaves the same, but for all - three axes. Therefore, 'z' can be passed for *axis*, - and 'both' applies to all three axes. - .. versionadded:: 1.1.0 + See `.Axes.autoscale` for full documentation. Because this function + applies to 3D Axes, *axis* can also be set to 'z', and setting *axis* + to 'both' autoscales all three axes. """ if enable is None: scalex = True @@ -629,61 +573,65 @@ def autoscale(self, enable=True, axis='both', tight=None): scalez = True else: if axis in ['x', 'both']: - self._autoscaleXon = scalex = bool(enable) + self.set_autoscalex_on(enable) + scalex = self.get_autoscalex_on() else: scalex = False if axis in ['y', 'both']: - self._autoscaleYon = scaley = bool(enable) + self.set_autoscaley_on(enable) + scaley = self.get_autoscaley_on() else: scaley = False if axis in ['z', 'both']: - self._autoscaleZon = scalez = bool(enable) + self.set_autoscalez_on(enable) + scalez = self.get_autoscalez_on() else: scalez = False - self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley, - scalez=scalez) + if scalex: + self._request_autoscale_view("x", tight=tight) + if scaley: + self._request_autoscale_view("y", tight=tight) + if scalez: + self._request_autoscale_view("z", tight=tight) def auto_scale_xyz(self, X, Y, Z=None, had_data=None): # This updates the bounding boxes as to keep a record as to what the # minimum sized rectangular volume holds the data. - X = np.reshape(X, -1) - Y = np.reshape(Y, -1) - self.xy_dataLim.update_from_data_xy( - np.column_stack([X, Y]), not had_data) + if np.shape(X) == np.shape(Y): + self.xy_dataLim.update_from_data_xy( + np.column_stack([np.ravel(X), np.ravel(Y)]), not had_data) + else: + self.xy_dataLim.update_from_data_x(X, not had_data) + self.xy_dataLim.update_from_data_y(Y, not had_data) if Z is not None: - Z = np.reshape(Z, -1) - self.zz_dataLim.update_from_data_xy( - np.column_stack([Z, Z]), not had_data) + self.zz_dataLim.update_from_data_x(Z, not had_data) # Let autoscale_view figure out how to use this data. self.autoscale_view() - def autoscale_view(self, tight=None, scalex=True, scaley=True, - scalez=True): + def autoscale_view(self, tight=None, + scalex=True, scaley=True, scalez=True): """ Autoscale the view limits using the data limits. - See :meth:`matplotlib.axes.Axes.autoscale_view` for documentation. - Note that this function applies to the 3D axes, and as such - adds the *scalez* to the function arguments. - - .. versionchanged:: 1.1.0 - Function signature was changed to better match the 2D version. - *tight* is now explicitly a kwarg and placed first. - .. versionchanged:: 1.2.1 - This is now fully functional. + See `.Axes.autoscale_view` for full documentation. Because this + function applies to 3D Axes, it also takes a *scalez* argument. """ # This method looks at the rectangular volume (see above) # of data and decides how to scale the view portal to fit it. if tight is None: - # if image data only just use the datalim - _tight = self._tight or ( - len(self.images) > 0 - and len(self.lines) == len(self.patches) == 0) + _tight = self._tight + if not _tight: + # if image data only just use the datalim + for artist in self._children: + if isinstance(artist, mimage.AxesImage): + _tight = True + elif isinstance(artist, (mlines.Line2D, mpatches.Patch)): + _tight = False + break else: _tight = self._tight = bool(tight) - if scalex and self._autoscaleXon: - self._shared_x_axes.clean() + if scalex and self.get_autoscalex_on(): x0, x1 = self.xy_dataLim.intervalx xlocator = self.xaxis.get_major_locator() x0, x1 = xlocator.nonsingular(x0, x1) @@ -693,10 +641,9 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, x1 += delta if not _tight: x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1) + self.set_xbound(x0, x1, self._view_margin) - if scaley and self._autoscaleYon: - self._shared_y_axes.clean() + if scaley and self.get_autoscaley_on(): y0, y1 = self.xy_dataLim.intervaly ylocator = self.yaxis.get_major_locator() y0, y1 = ylocator.nonsingular(y0, y1) @@ -706,10 +653,9 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, y1 += delta if not _tight: y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1) + self.set_ybound(y0, y1, self._view_margin) - if scalez and self._autoscaleZon: - self._shared_z_axes.clean() + if scalez and self.get_autoscalez_on(): z0, z1 = self.zz_dataLim.intervalx zlocator = self.zaxis.get_major_locator() z0, z1 = zlocator.nonsingular(z0, z1) @@ -719,7 +665,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, z1 += delta if not _tight: z0, z1 = zlocator.view_limits(z0, z1) - self.set_zbound(z0, z1) + self.set_zbound(z0, z1, self._view_margin) def get_w_lims(self): """Get 3D world limits.""" @@ -728,220 +674,385 @@ def get_w_lims(self): minz, maxz = self.get_zlim3d() return minx, maxx, miny, maxy, minz, maxz - def set_xlim3d(self, left=None, right=None, emit=True, auto=False, - *, xmin=None, xmax=None): - """ - Set 3D x limits. - - See :meth:`matplotlib.axes.Axes.set_xlim` for full documentation. - """ - if right is None and np.iterable(left): - left, right = left - if xmin is not None: - if left is not None: - raise TypeError('Cannot pass both `xmin` and `left`') - left = xmin - if xmax is not None: - if right is not None: - raise TypeError('Cannot pass both `xmax` and `right`') - right = xmax - - self._process_unit_info(xdata=(left, right)) - left = self._validate_converted_limits(left, self.convert_xunits) - right = self._validate_converted_limits(right, self.convert_xunits) - - old_left, old_right = self.get_xlim() - if left is None: - left = old_left - if right is None: - right = old_right - - if left == right: - cbook._warn_external( - f"Attempting to set identical left == right == {left} results " - f"in singular transformations; automatically expanding.") - reverse = left > right - left, right = self.xaxis.get_major_locator().nonsingular(left, right) - left, right = self.xaxis.limit_range_for_scale(left, right) - # cast to bool to avoid bad interaction between python 3.8 and np.bool_ - left, right = sorted([left, right], reverse=bool(reverse)) - self.xy_viewLim.intervalx = (left, right) - - if auto is not None: - self._autoscaleXon = bool(auto) - - if emit: - self.callbacks.process('xlim_changed', self) - # Call all of the other x-axes that are shared with this one - for other in self._shared_x_axes.get_siblings(self): - if other is not self: - other.set_xlim(self.xy_viewLim.intervalx, - emit=False, auto=auto) - if other.figure != self.figure: - other.figure.canvas.draw_idle() - self.stale = True - return left, right - - def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, - *, ymin=None, ymax=None): - """ - Set 3D y limits. - - See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation. - """ - if top is None and np.iterable(bottom): - bottom, top = bottom - if ymin is not None: - if bottom is not None: - raise TypeError('Cannot pass both `ymin` and `bottom`') - bottom = ymin - if ymax is not None: - if top is not None: - raise TypeError('Cannot pass both `ymax` and `top`') - top = ymax - - self._process_unit_info(ydata=(bottom, top)) - bottom = self._validate_converted_limits(bottom, self.convert_yunits) - top = self._validate_converted_limits(top, self.convert_yunits) - - old_bottom, old_top = self.get_ylim() - if bottom is None: - bottom = old_bottom - if top is None: - top = old_top - - if bottom == top: - cbook._warn_external( - f"Attempting to set identical bottom == top == {bottom} " - f"results in singular transformations; automatically " - f"expanding.") - swapped = bottom > top - bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top) - bottom, top = self.yaxis.limit_range_for_scale(bottom, top) - if swapped: - bottom, top = top, bottom - self.xy_viewLim.intervaly = (bottom, top) - - if auto is not None: - self._autoscaleYon = bool(auto) - - if emit: - self.callbacks.process('ylim_changed', self) - # Call all of the other y-axes that are shared with this one - for other in self._shared_y_axes.get_siblings(self): - if other is not self: - other.set_ylim(self.xy_viewLim.intervaly, - emit=False, auto=auto) - if other.figure != self.figure: - other.figure.canvas.draw_idle() - self.stale = True - return bottom, top - - def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False, - *, zmin=None, zmax=None): - """ - Set 3D z limits. - - See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation - """ - if top is None and np.iterable(bottom): - bottom, top = bottom - if zmin is not None: - if bottom is not None: - raise TypeError('Cannot pass both `zmin` and `bottom`') - bottom = zmin - if zmax is not None: - if top is not None: - raise TypeError('Cannot pass both `zmax` and `top`') - top = zmax - - self._process_unit_info(zdata=(bottom, top)) - bottom = self._validate_converted_limits(bottom, self.convert_zunits) - top = self._validate_converted_limits(top, self.convert_zunits) - - old_bottom, old_top = self.get_zlim() - if bottom is None: - bottom = old_bottom - if top is None: - top = old_top - - if bottom == top: - cbook._warn_external( - f"Attempting to set identical bottom == top == {bottom} " - f"results in singular transformations; automatically " - f"expanding.") - swapped = bottom > top - bottom, top = self.zaxis.get_major_locator().nonsingular(bottom, top) - bottom, top = self.zaxis.limit_range_for_scale(bottom, top) - if swapped: - bottom, top = top, bottom - self.zz_viewLim.intervalx = (bottom, top) - - if auto is not None: - self._autoscaleZon = bool(auto) - - if emit: - self.callbacks.process('zlim_changed', self) - # Call all of the other y-axes that are shared with this one - for other in self._shared_z_axes.get_siblings(self): - if other is not self: - other.set_zlim(self.zz_viewLim.intervalx, - emit=False, auto=auto) - if other.figure != self.figure: - other.figure.canvas.draw_idle() - self.stale = True - return bottom, top + def _set_bound3d(self, get_bound, set_lim, axis_inverted, + lower=None, upper=None, view_margin=None): + """ + Set 3D axis bounds. + """ + if upper is None and np.iterable(lower): + lower, upper = lower - def get_xlim3d(self): - return tuple(self.xy_viewLim.intervalx) - get_xlim3d.__doc__ = maxes.Axes.get_xlim.__doc__ - if get_xlim3d.__doc__ is not None: - get_xlim3d.__doc__ += """ - .. versionchanged:: 1.1.0 - This function now correctly refers to the 3D x-limits + old_lower, old_upper = get_bound() + if lower is None: + lower = old_lower + if upper is None: + upper = old_upper + + set_lim(sorted((lower, upper), reverse=bool(axis_inverted())), + auto=None, view_margin=view_margin) + + def set_xbound(self, lower=None, upper=None, view_margin=None): """ + Set the lower and upper numerical bounds of the x-axis. - def get_ylim3d(self): - return tuple(self.xy_viewLim.intervaly) - get_ylim3d.__doc__ = maxes.Axes.get_ylim.__doc__ - if get_ylim3d.__doc__ is not None: - get_ylim3d.__doc__ += """ - .. versionchanged:: 1.1.0 - This function now correctly refers to the 3D y-limits. + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscalex_on()`). + + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_xlim`. + + See Also + -------- + get_xbound + get_xlim, set_xlim + invert_xaxis, xaxis_inverted """ + self._set_bound3d(self.get_xbound, self.set_xlim, self.xaxis_inverted, + lower, upper, view_margin) - def get_zlim3d(self): - """Get 3D z limits.""" - return tuple(self.zz_viewLim.intervalx) + def set_ybound(self, lower=None, upper=None, view_margin=None): + """ + Set the lower and upper numerical bounds of the y-axis. - def get_zscale(self): + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscaley_on()`). + + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_ylim`. + + See Also + -------- + get_ybound + get_ylim, set_ylim + invert_yaxis, yaxis_inverted """ - Return the zaxis scale string %s + self._set_bound3d(self.get_ybound, self.set_ylim, self.yaxis_inverted, + lower, upper, view_margin) - """ % (", ".join(mscale.get_scale_names())) - return self.zaxis.get_scale() + def set_zbound(self, lower=None, upper=None, view_margin=None): + """ + Set the lower and upper numerical bounds of the z-axis. + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscaley_on()`). - # We need to slightly redefine these to pass scalez=False - # to their calls of autoscale_view. + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_zlim`. - def set_xscale(self, value, **kwargs): - self.xaxis._set_scale(value, **kwargs) - self.autoscale_view(scaley=False, scalez=False) - self._update_transScale() - self.stale = True + See Also + -------- + get_zbound + get_zlim, set_zlim + invert_zaxis, zaxis_inverted + """ + self._set_bound3d(self.get_zbound, self.set_zlim, self.zaxis_inverted, + lower, upper, view_margin) - def set_yscale(self, value, **kwargs): - self.yaxis._set_scale(value, **kwargs) - self.autoscale_view(scalex=False, scalez=False) - self._update_transScale() - self.stale = True + def _set_lim3d(self, axis, lower=None, upper=None, *, emit=True, + auto=False, view_margin=None, axmin=None, axmax=None): + """ + Set 3D axis limits. + """ + if upper is None: + if np.iterable(lower): + lower, upper = lower + elif axmax is None: + upper = axis.get_view_interval()[1] + if lower is None and axmin is None: + lower = axis.get_view_interval()[0] + if axmin is not None: + if lower is not None: + raise TypeError("Cannot pass both 'lower' and 'min'") + lower = axmin + if axmax is not None: + if upper is not None: + raise TypeError("Cannot pass both 'upper' and 'max'") + upper = axmax + if np.isinf(lower) or np.isinf(upper): + raise ValueError(f"Axis limits {lower}, {upper} cannot be infinite") + if view_margin is None: + if mpl.rcParams['axes3d.automargin']: + view_margin = self._view_margin + else: + view_margin = 0 + delta = (upper - lower) * view_margin + lower -= delta + upper += delta + return axis._set_lim(lower, upper, emit=emit, auto=auto) + + def set_xlim(self, left=None, right=None, *, emit=True, auto=False, + view_margin=None, xmin=None, xmax=None): + """ + Set the 3D x-axis view limits. - def set_zscale(self, value, **kwargs): - self.zaxis._set_scale(value, **kwargs) - self.autoscale_view(scalex=False, scaley=False) - self._update_transScale() - self.stale = True + Parameters + ---------- + left : float, optional + The left xlim in data coordinates. Passing *None* leaves the + limit unchanged. + + The left and right xlims may also be passed as the tuple + (*left*, *right*) as the first positional argument (or as + the *left* keyword argument). + + .. ACCEPTS: (left: float, right: float) + + right : float, optional + The right xlim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the x-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + xmin, xmax : float, optional + They are equivalent to left and right respectively, and it is an + error to pass both *xmin* and *left* or *xmax* and *right*. + + Returns + ------- + left, right : (float, float) + The new x-axis limits in data coordinates. + + See Also + -------- + get_xlim + set_xbound, get_xbound + invert_xaxis, xaxis_inverted + + Notes + ----- + The *left* value may be greater than the *right* value, in which + case the x-axis values will decrease from *left* to *right*. + + Examples + -------- + >>> set_xlim(left, right) + >>> set_xlim((left, right)) + >>> left, right = set_xlim(left, right) + One limit may be left unchanged. + + >>> set_xlim(right=right_lim) + + Limits may be passed in reverse order to flip the direction of + the x-axis. For example, suppose ``x`` represents depth of the + ocean in m. The x-axis limits might be set like the following + so 5000 m depth is at the left of the plot and the surface, + 0 m, is at the right. + + >>> set_xlim(5000, 0) + """ + return self._set_lim3d(self.xaxis, left, right, emit=emit, auto=auto, + view_margin=view_margin, axmin=xmin, axmax=xmax) + + def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, + view_margin=None, ymin=None, ymax=None): + """ + Set the 3D y-axis view limits. + + Parameters + ---------- + bottom : float, optional + The bottom ylim in data coordinates. Passing *None* leaves the + limit unchanged. + + The bottom and top ylims may also be passed as the tuple + (*bottom*, *top*) as the first positional argument (or as + the *bottom* keyword argument). + + .. ACCEPTS: (bottom: float, top: float) + + top : float, optional + The top ylim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the y-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + ymin, ymax : float, optional + They are equivalent to bottom and top respectively, and it is an + error to pass both *ymin* and *bottom* or *ymax* and *top*. + + Returns + ------- + bottom, top : (float, float) + The new y-axis limits in data coordinates. + + See Also + -------- + get_ylim + set_ybound, get_ybound + invert_yaxis, yaxis_inverted + + Notes + ----- + The *bottom* value may be greater than the *top* value, in which + case the y-axis values will decrease from *bottom* to *top*. + + Examples + -------- + >>> set_ylim(bottom, top) + >>> set_ylim((bottom, top)) + >>> bottom, top = set_ylim(bottom, top) + + One limit may be left unchanged. + + >>> set_ylim(top=top_lim) + + Limits may be passed in reverse order to flip the direction of + the y-axis. For example, suppose ``y`` represents depth of the + ocean in m. The y-axis limits might be set like the following + so 5000 m depth is at the bottom of the plot and the surface, + 0 m, is at the top. + + >>> set_ylim(5000, 0) + """ + return self._set_lim3d(self.yaxis, bottom, top, emit=emit, auto=auto, + view_margin=view_margin, axmin=ymin, axmax=ymax) + + def set_zlim(self, bottom=None, top=None, *, emit=True, auto=False, + view_margin=None, zmin=None, zmax=None): + """ + Set the 3D z-axis view limits. + + Parameters + ---------- + bottom : float, optional + The bottom zlim in data coordinates. Passing *None* leaves the + limit unchanged. + + The bottom and top zlims may also be passed as the tuple + (*bottom*, *top*) as the first positional argument (or as + the *bottom* keyword argument). + + .. ACCEPTS: (bottom: float, top: float) + + top : float, optional + The top zlim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the z-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + zmin, zmax : float, optional + They are equivalent to bottom and top respectively, and it is an + error to pass both *zmin* and *bottom* or *zmax* and *top*. + + Returns + ------- + bottom, top : (float, float) + The new z-axis limits in data coordinates. + + See Also + -------- + get_zlim + set_zbound, get_zbound + invert_zaxis, zaxis_inverted + + Notes + ----- + The *bottom* value may be greater than the *top* value, in which + case the z-axis values will decrease from *bottom* to *top*. + + Examples + -------- + >>> set_zlim(bottom, top) + >>> set_zlim((bottom, top)) + >>> bottom, top = set_zlim(bottom, top) + + One limit may be left unchanged. + + >>> set_zlim(top=top_lim) + + Limits may be passed in reverse order to flip the direction of + the z-axis. For example, suppose ``z`` represents depth of the + ocean in m. The z-axis limits might be set like the following + so 5000 m depth is at the bottom of the plot and the surface, + 0 m, is at the top. + + >>> set_zlim(5000, 0) + """ + return self._set_lim3d(self.zaxis, bottom, top, emit=emit, auto=auto, + view_margin=view_margin, axmin=zmin, axmax=zmax) + + set_xlim3d = set_xlim + set_ylim3d = set_ylim + set_zlim3d = set_zlim + + def get_xlim(self): + # docstring inherited + return tuple(self.xy_viewLim.intervalx) + + def get_ylim(self): + # docstring inherited + return tuple(self.xy_viewLim.intervaly) + + def get_zlim(self): + """ + Return the 3D z-axis view limits. + + Returns + ------- + left, right : (float, float) + The current z-axis limits in data coordinates. + + See Also + -------- + set_zlim + set_zbound, get_zbound + invert_zaxis, zaxis_inverted + + Notes + ----- + The z-axis may be inverted, in which case the *left* value will + be greater than the *right* value. + """ + return tuple(self.zz_viewLim.intervalx) + + get_zscale = _axis_method_wrapper("zaxis", "get_scale") + + # Redefine all three methods to overwrite their docstrings. + set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") + set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") + set_zscale = _axis_method_wrapper("zaxis", "_set_axes_scale") set_xscale.__doc__, set_yscale.__doc__, set_zscale.__doc__ = map( """ Set the {}-axis scale. @@ -949,7 +1060,7 @@ def set_zscale(self, value, **kwargs): Parameters ---------- value : {{"linear"}} - The axis scale type to apply. 3D axes currently only support + The axis scale type to apply. 3D Axes currently only support linear scales; other scales yield nonsensical results. **kwargs @@ -964,7 +1075,7 @@ def set_zscale(self, value, **kwargs): get_zminorticklabels = _axis_method_wrapper("zaxis", "get_minorticklabels") get_zticklabels = _axis_method_wrapper("zaxis", "get_ticklabels") set_zticklabels = _axis_method_wrapper( - "zaxis", "_set_ticklabels", + "zaxis", "set_ticklabels", doc_sub={"Axis.set_ticks": "Axes3D.set_zticks"}) zaxis_date = _axis_method_wrapper("zaxis", "axis_date") @@ -973,189 +1084,323 @@ def set_zscale(self, value, **kwargs): Notes ----- - This function is merely provided for completeness, but 3d axes do not + This function is merely provided for completeness, but 3D Axes do not support dates for ticks, and so this may not work as expected. """) def clabel(self, *args, **kwargs): - """Currently not implemented for 3D axes, and returns *None*.""" + """Currently not implemented for 3D Axes, and returns *None*.""" return None - def view_init(self, elev=None, azim=None): + def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", + share=False): """ - Set the elevation and azimuth of the axes in degrees (not radians). + Set the elevation and azimuth of the Axes in degrees (not radians). - This can be used to rotate the axes programmatically. + This can be used to rotate the Axes programmatically. - 'elev' stores the elevation angle in the z plane (in degrees). - 'azim' stores the azimuth angle in the (x, y) plane (in degrees). + To look normal to the primary planes, the following elevation and + azimuth angles can be used. A roll angle of 0, 90, 180, or 270 deg + will rotate these views while keeping the axes at right angles. - if 'elev' or 'azim' are None (default), then the initial value - is used which was specified in the :class:`Axes3D` constructor. + ========== ==== ==== + view plane elev azim + ========== ==== ==== + XY 90 -90 + XZ 0 -90 + YZ 0 0 + -XY -90 90 + -XZ 0 90 + -YZ 0 180 + ========== ==== ==== + + Parameters + ---------- + elev : float, default: None + The elevation angle in degrees rotates the camera above the plane + pierced by the vertical axis, with a positive angle corresponding + to a location above that plane. For example, with the default + vertical axis of 'z', the elevation defines the angle of the camera + location above the x-y plane. + If None, then the initial value as specified in the `Axes3D` + constructor is used. + azim : float, default: None + The azimuthal angle in degrees rotates the camera about the + vertical axis, with a positive angle corresponding to a + right-handed rotation. For example, with the default vertical axis + of 'z', a positive azimuth rotates the camera about the origin from + its location along the +x axis towards the +y axis. + If None, then the initial value as specified in the `Axes3D` + constructor is used. + roll : float, default: None + The roll angle in degrees rotates the camera about the viewing + axis. A positive angle spins the camera clockwise, causing the + scene to rotate counter-clockwise. + If None, then the initial value as specified in the `Axes3D` + constructor is used. + vertical_axis : {"z", "x", "y"}, default: "z" + The axis to align vertically. *azim* rotates about this axis. + share : bool, default: False + If ``True``, apply the settings to all Axes with shared views. """ - self.dist = 10 + self._dist = 10 # The camera distance from origin. Behaves like zoom if elev is None: - self.elev = self.initial_elev - else: - self.elev = elev - + elev = self.initial_elev if azim is None: - self.azim = self.initial_azim + azim = self.initial_azim + if roll is None: + roll = self.initial_roll + vertical_axis = _api.check_getitem( + {name: idx for idx, name in enumerate(self._axis_names)}, + vertical_axis=vertical_axis, + ) + + if share: + axes = {sibling for sibling + in self._shared_axes['view'].get_siblings(self)} else: - self.azim = azim + axes = [self] - def set_proj_type(self, proj_type): + for ax in axes: + ax.elev = elev + ax.azim = azim + ax.roll = roll + ax._vertical_axis = vertical_axis + + def set_proj_type(self, proj_type, focal_length=None): """ Set the projection type. Parameters ---------- proj_type : {'persp', 'ortho'} + The projection type. + focal_length : float, default: None + For a projection type of 'persp', the focal length of the virtual + camera. Must be > 0. If None, defaults to 1. + The focal length can be computed from a desired Field Of View via + the equation: focal_length = 1/tan(FOV/2) + """ + _api.check_in_list(['persp', 'ortho'], proj_type=proj_type) + if proj_type == 'persp': + if focal_length is None: + focal_length = 1 + elif focal_length <= 0: + raise ValueError(f"focal_length = {focal_length} must be " + "greater than 0") + self._focal_length = focal_length + else: # 'ortho': + if focal_length not in (None, np.inf): + raise ValueError(f"focal_length = {focal_length} must be " + f"None for proj_type = {proj_type}") + self._focal_length = np.inf + + def _roll_to_vertical( + self, arr: "np.typing.ArrayLike", reverse: bool = False + ) -> np.ndarray: """ - self._projection = cbook._check_getitem({ - 'persp': proj3d.persp_transformation, - 'ortho': proj3d.ortho_transformation, - }, proj_type=proj_type) + Roll arrays to match the different vertical axis. + + Parameters + ---------- + arr : ArrayLike + Array to roll. + reverse : bool, default: False + Reverse the direction of the roll. + """ + if reverse: + return np.roll(arr, (self._vertical_axis - 2) * -1) + else: + return np.roll(arr, (self._vertical_axis - 2)) def get_proj(self): """Create the projection matrix from the current viewing position.""" - # elev stores the elevation angle in the z plane - # azim stores the azimuth angle in the x,y plane - # - # dist is the distance of the eye viewing point from the object - # point. - - relev, razim = np.pi * self.elev/180, np.pi * self.azim/180 - - xmin, xmax = self.get_xlim3d() - ymin, ymax = self.get_ylim3d() - zmin, zmax = self.get_zlim3d() - - # transform to uniform world coordinates 0-1, 0-1, 0-1 - worldM = proj3d.world_transformation(xmin, xmax, - ymin, ymax, - zmin, zmax, - pb_aspect=self._box_aspect) - - # look into the middle of the new coordinates - R = self._box_aspect / 2 - - xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist - yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist - zp = R[2] + np.sin(relev) * self.dist - E = np.array((xp, yp, zp)) - - self.eye = E - self.vvec = R - E - self.vvec = self.vvec / np.linalg.norm(self.vvec) - - if abs(relev) > np.pi/2: - # upside down - V = np.array((0, 0, -1)) - else: - V = np.array((0, 0, 1)) - zfront, zback = -self.dist, self.dist - viewM = proj3d.view_transformation(E, R, V) - projM = self._projection(zfront, zback) + # Transform to uniform world coordinates 0-1, 0-1, 0-1 + box_aspect = self._roll_to_vertical(self._box_aspect) + worldM = proj3d.world_transformation( + *self.get_xlim3d(), + *self.get_ylim3d(), + *self.get_zlim3d(), + pb_aspect=box_aspect, + ) + + # Look into the middle of the world coordinates: + R = 0.5 * box_aspect + + # elev: elevation angle in the z plane. + # azim: azimuth angle in the xy plane. + # Coordinates for a point that rotates around the box of data. + # p0, p1 corresponds to rotating the box only around the vertical axis. + # p2 corresponds to rotating the box only around the horizontal axis. + elev_rad = np.deg2rad(self.elev) + azim_rad = np.deg2rad(self.azim) + p0 = np.cos(elev_rad) * np.cos(azim_rad) + p1 = np.cos(elev_rad) * np.sin(azim_rad) + p2 = np.sin(elev_rad) + + # When changing vertical axis the coordinates changes as well. + # Roll the values to get the same behaviour as the default: + ps = self._roll_to_vertical([p0, p1, p2]) + + # The coordinates for the eye viewing point. The eye is looking + # towards the middle of the box of data from a distance: + eye = R + self._dist * ps + + # Calculate the viewing axes for the eye position + u, v, w = self._calc_view_axes(eye) + self._view_u = u # _view_u is towards the right of the screen + self._view_v = v # _view_v is towards the top of the screen + self._view_w = w # _view_w is out of the screen + + # Generate the view and projection transformation matrices + if self._focal_length == np.inf: + # Orthographic projection + viewM = proj3d._view_transformation_uvw(u, v, w, eye) + projM = proj3d._ortho_transformation(-self._dist, self._dist) + else: + # Perspective projection + # Scale the eye dist to compensate for the focal length zoom effect + eye_focal = R + self._dist * ps * self._focal_length + viewM = proj3d._view_transformation_uvw(u, v, w, eye_focal) + projM = proj3d._persp_transformation(-self._dist, + self._dist, + self._focal_length) + + # Combine all the transformation matrices to get the final projection M0 = np.dot(viewM, worldM) M = np.dot(projM, M0) return M - def mouse_init(self, rotate_btn=1, zoom_btn=3): + def mouse_init(self, rotate_btn=1, pan_btn=2, zoom_btn=3): """ Set the mouse buttons for 3D rotation and zooming. Parameters ---------- rotate_btn : int or list of int, default: 1 - The mouse button or buttons to use for 3D rotation of the axes. + The mouse button or buttons to use for 3D rotation of the Axes. + pan_btn : int or list of int, default: 2 + The mouse button or buttons to use to pan the 3D Axes. zoom_btn : int or list of int, default: 3 - The mouse button or buttons to use to zoom the 3D axes. + The mouse button or buttons to use to zoom the 3D Axes. """ self.button_pressed = None # coerce scalars into array-like, then convert into # a regular list to avoid comparisons against None # which breaks in recent versions of numpy. self._rotate_btn = np.atleast_1d(rotate_btn).tolist() + self._pan_btn = np.atleast_1d(pan_btn).tolist() self._zoom_btn = np.atleast_1d(zoom_btn).tolist() def disable_mouse_rotation(self): - """Disable mouse buttons for 3D rotation and zooming.""" - self.mouse_init(rotate_btn=[], zoom_btn=[]) + """Disable mouse buttons for 3D rotation, panning, and zooming.""" + self.mouse_init(rotate_btn=[], pan_btn=[], zoom_btn=[]) def can_zoom(self): - """ - Return *True* if this axes supports the zoom box button functionality. - - 3D axes objects do not use the zoom box button. - """ - return False + # doc-string inherited + return True def can_pan(self): + # doc-string inherited + return True + + def sharez(self, other): """ - Return *True* if this axes supports the pan/zoom button functionality. + Share the z-axis with *other*. - 3D axes objects do not use the pan/zoom button. + This is equivalent to passing ``sharez=other`` when constructing the + Axes, and cannot be used if the z-axis is already being shared with + another Axes. Note that it is not possible to unshare axes. + """ + _api.check_isinstance(Axes3D, other=other) + if self._sharez is not None and other is not self._sharez: + raise ValueError("z-axis is already shared") + self._shared_axes["z"].join(self, other) + self._sharez = other + self.zaxis.major = other.zaxis.major # Ticker instances holding + self.zaxis.minor = other.zaxis.minor # locator and formatter. + z0, z1 = other.get_zlim() + self.set_zlim(z0, z1, emit=False, auto=other.get_autoscalez_on()) + self.zaxis._scale = other.zaxis._scale + + def shareview(self, other): """ - return False + Share the view angles with *other*. - def cla(self): + This is equivalent to passing ``shareview=other`` when constructing the + Axes, and cannot be used if the view angles are already being shared + with another Axes. Note that it is not possible to unshare axes. + """ + _api.check_isinstance(Axes3D, other=other) + if self._shareview is not None and other is not self._shareview: + raise ValueError("view angles are already shared") + self._shared_axes["view"].join(self, other) + self._shareview = other + vertical_axis = self._axis_names[other._vertical_axis] + self.view_init(elev=other.elev, azim=other.azim, roll=other.roll, + vertical_axis=vertical_axis, share=True) + + def clear(self): # docstring inherited. - - super().cla() - self.zaxis.cla() - - if self._sharez is not None: - self.zaxis.major = self._sharez.zaxis.major - self.zaxis.minor = self._sharez.zaxis.minor - z0, z1 = self._sharez.get_zlim() - self.set_zlim(z0, z1, emit=False, auto=None) - self.zaxis._set_scale(self._sharez.zaxis.get_scale()) + super().clear() + if self._focal_length == np.inf: + self._zmargin = mpl.rcParams['axes.zmargin'] else: - self.zaxis._set_scale('linear') - try: - self.set_zlim(0, 1) - except TypeError: - pass + self._zmargin = 0. - self._autoscaleZon = True - self._zmargin = 0 + xymargin = 0.05 * 10/11 # match mpl3.8 appearance + self.xy_dataLim = Bbox([[xymargin, xymargin], + [1 - xymargin, 1 - xymargin]]) + # z-limits are encoded in the x-component of the Bbox, y is un-used + self.zz_dataLim = Bbox.unit() + self._view_margin = 1/48 # default value to match mpl3.8 + self.autoscale_view() - self.grid(rcParams['axes3d.grid']) + self.grid(mpl.rcParams['axes3d.grid']) def _button_press(self, event): if event.inaxes == self: self.button_pressed = event.button - self.sx, self.sy = event.xdata, event.ydata - toolbar = getattr(self.figure.canvas, "toolbar") + self._sx, self._sy = event.xdata, event.ydata + toolbar = self.get_figure(root=True).canvas.toolbar if toolbar and toolbar._nav_stack() is None: - self.figure.canvas.toolbar.push_current() + toolbar.push_current() + if toolbar: + toolbar.set_message(toolbar._mouse_event_to_message(event)) def _button_release(self, event): self.button_pressed = None - toolbar = getattr(self.figure.canvas, "toolbar") + toolbar = self.get_figure(root=True).canvas.toolbar + # backend_bases.release_zoom and backend_bases.release_pan call + # push_current, so check the navigation mode so we don't call it twice + if toolbar and self.get_navigate_mode() is None: + toolbar.push_current() if toolbar: - self.figure.canvas.toolbar.push_current() + toolbar.set_message(toolbar._mouse_event_to_message(event)) def _get_view(self): # docstring inherited - return (self.get_xlim(), self.get_ylim(), self.get_zlim(), - self.elev, self.azim) + return { + "xlim": self.get_xlim(), "autoscalex_on": self.get_autoscalex_on(), + "ylim": self.get_ylim(), "autoscaley_on": self.get_autoscaley_on(), + "zlim": self.get_zlim(), "autoscalez_on": self.get_autoscalez_on(), + }, (self.elev, self.azim, self.roll) def _set_view(self, view): # docstring inherited - xlim, ylim, zlim, elev, azim = view - self.set(xlim=xlim, ylim=ylim, zlim=zlim) + props, (elev, azim, roll) = view + self.set(**props) self.elev = elev self.azim = azim + self.roll = roll def format_zdata(self, z): """ Return *z* string formatted. This function will use the - :attr:`fmt_zdata` attribute if it is callable, else will fall + :attr:`!fmt_zdata` attribute if it is callable, else will fall back on the zaxis major formatter """ try: @@ -1165,64 +1410,164 @@ def format_zdata(self, z): val = func(z) return val - def format_coord(self, xd, yd): + def format_coord(self, xv, yv, renderer=None): """ - Given the 2D view coordinates attempt to guess a 3D coordinate. - Looks for the nearest edge to the point and then assumes that - the point is at the same z location as the nearest point on the edge. + Return a string giving the current view rotation angles, or the x, y, z + coordinates of the point on the nearest axis pane underneath the mouse + cursor, depending on the mouse button pressed. """ - - if self.M is None: - return '' + coords = '' if self.button_pressed in self._rotate_btn: - return 'azimuth={:.0f} deg, elevation={:.0f} deg '.format( - self.azim, self.elev) - # ignore xd and yd and display angles instead - - # nearest edge - p0, p1 = min(self.tunit_edges(), - key=lambda edge: proj3d._line2d_seg_dist( - edge[0], edge[1], (xd, yd))) - - # scale the z value to match - x0, y0, z0 = p0 - x1, y1, z1 = p1 - d0 = np.hypot(x0-xd, y0-yd) - d1 = np.hypot(x1-xd, y1-yd) - dt = d0+d1 - z = d1/dt * z0 + d0/dt * z1 - - x, y, z = proj3d.inv_transform(xd, yd, z, self.M) - - xs = self.format_xdata(x) - ys = self.format_ydata(y) - zs = self.format_zdata(z) - return 'x=%s, y=%s, z=%s' % (xs, ys, zs) + # ignore xv and yv and display angles instead + coords = self._rotation_coords() + + elif self.M is not None: + coords = self._location_coords(xv, yv, renderer) + + return coords + + def _rotation_coords(self): + """ + Return the rotation angles as a string. + """ + norm_elev = art3d._norm_angle(self.elev) + norm_azim = art3d._norm_angle(self.azim) + norm_roll = art3d._norm_angle(self.roll) + coords = (f"elevation={norm_elev:.0f}\N{DEGREE SIGN}, " + f"azimuth={norm_azim:.0f}\N{DEGREE SIGN}, " + f"roll={norm_roll:.0f}\N{DEGREE SIGN}" + ).replace("-", "\N{MINUS SIGN}") + return coords + + def _location_coords(self, xv, yv, renderer): + """ + Return the location on the axis pane underneath the cursor as a string. + """ + p1, pane_idx = self._calc_coord(xv, yv, renderer) + xs = self.format_xdata(p1[0]) + ys = self.format_ydata(p1[1]) + zs = self.format_zdata(p1[2]) + if pane_idx == 0: + coords = f'x pane={xs}, y={ys}, z={zs}' + elif pane_idx == 1: + coords = f'x={xs}, y pane={ys}, z={zs}' + elif pane_idx == 2: + coords = f'x={xs}, y={ys}, z pane={zs}' + return coords + + def _get_camera_loc(self): + """ + Returns the current camera location in data coordinates. + """ + cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() + c = np.array([cx, cy, cz]) + r = np.array([dx, dy, dz]) + + if self._focal_length == np.inf: # orthographic projection + focal_length = 1e9 # large enough to be effectively infinite + else: # perspective projection + focal_length = self._focal_length + eye = c + self._view_w * self._dist * r / self._box_aspect * focal_length + return eye + + def _calc_coord(self, xv, yv, renderer=None): + """ + Given the 2D view coordinates, find the point on the nearest axis pane + that lies directly below those coordinates. Returns a 3D point in data + coordinates. + """ + if self._focal_length == np.inf: # orthographic projection + zv = 1 + else: # perspective projection + zv = -1 / self._focal_length + + # Convert point on view plane to data coordinates + p1 = np.array(proj3d.inv_transform(xv, yv, zv, self.invM)).ravel() + + # Get the vector from the camera to the point on the view plane + vec = self._get_camera_loc() - p1 + + # Get the pane locations for each of the axes + pane_locs = [] + for axis in self._axis_map.values(): + xys, loc = axis.active_pane() + pane_locs.append(loc) + + # Find the distance to the nearest pane by projecting the view vector + scales = np.zeros(3) + for i in range(3): + if vec[i] == 0: + scales[i] = np.inf + else: + scales[i] = (p1[i] - pane_locs[i]) / vec[i] + pane_idx = np.argmin(abs(scales)) + scale = scales[pane_idx] + + # Calculate the point on the closest pane + p2 = p1 - scale*vec + return p2, pane_idx + + def _arcball(self, x: float, y: float) -> np.ndarray: + """ + Convert a point (x, y) to a point on a virtual trackball. + + This is Ken Shoemake's arcball (a sphere), modified + to soften the abrupt edge (optionally). + See: Ken Shoemake, "ARCBALL: A user interface for specifying + three-dimensional rotation using a mouse." in + Proceedings of Graphics Interface '92, 1992, pp. 151-156, + https://doi.org/10.20380/GI1992.18 + The smoothing of the edge is inspired by Gavin Bell's arcball + (a sphere combined with a hyperbola), but here, the sphere + is combined with a section of a cylinder, so it has finite support. + """ + s = mpl.rcParams['axes3d.trackballsize'] / 2 + b = mpl.rcParams['axes3d.trackballborder'] / s + x /= s + y /= s + r2 = x*x + y*y + r = np.sqrt(r2) + ra = 1 + b + a = b * (1 + b/2) + ri = 2/(ra + 1/ra) + if r < ri: + p = np.array([np.sqrt(1 - r2), x, y]) + elif r < ra: + dr = ra - r + p = np.array([a - np.sqrt((a + dr) * (a - dr)), x, y]) + p /= np.linalg.norm(p) + else: + p = np.array([0, x/r, y/r]) + return p def _on_move(self, event): """ Mouse moving. - By default, button-1 rotates and button-3 zooms; these buttons can be - modified via `mouse_init`. + By default, button-1 rotates, button-2 pans, and button-3 zooms; + these buttons can be modified via `mouse_init`. """ if not self.button_pressed: return + if self.get_navigate_mode() is not None: + # we don't want to rotate if we are zooming/panning + # from the toolbar + return + if self.M is None: return x, y = event.xdata, event.ydata # In case the mouse is out of bounds. - if x is None: + if x is None or event.inaxes != self: return - dx, dy = x - self.sx, y - self.sy + dx, dy = x - self._sx, y - self._sy w = self._pseudo_w h = self._pseudo_h - self.sx, self.sy = x, y # Rotation if self.button_pressed in self._rotate_btn: @@ -1230,32 +1575,246 @@ def _on_move(self, event): # get the x and y pixel coords if dx == 0 and dy == 0: return - self.elev = art3d._norm_angle(self.elev - (dy/h)*180) - self.azim = art3d._norm_angle(self.azim - (dx/w)*180) - self.get_proj() + + style = mpl.rcParams['axes3d.mouserotationstyle'] + if style == 'azel': + roll = np.deg2rad(self.roll) + delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll) + dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) + elev = self.elev + delev + azim = self.azim + dazim + roll = self.roll + else: + q = _Quaternion.from_cardan_angles( + *np.deg2rad((self.elev, self.azim, self.roll))) + + if style == 'trackball': + k = np.array([0, -dy/h, dx/w]) + nk = np.linalg.norm(k) + th = nk / mpl.rcParams['axes3d.trackballsize'] + dq = _Quaternion(np.cos(th), k*np.sin(th)/nk) + else: # 'sphere', 'arcball' + current_vec = self._arcball(self._sx/w, self._sy/h) + new_vec = self._arcball(x/w, y/h) + if style == 'sphere': + dq = _Quaternion.rotate_from_to(current_vec, new_vec) + else: # 'arcball' + dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) + + q = dq * q + elev, azim, roll = np.rad2deg(q.as_cardan_angles()) + + # update view + vertical_axis = self._axis_names[self._vertical_axis] + self.view_init( + elev=elev, + azim=azim, + roll=roll, + vertical_axis=vertical_axis, + share=True, + ) self.stale = True - self.figure.canvas.draw_idle() -# elif self.button_pressed == 2: - # pan view - # project xv, yv, zv -> xw, yw, zw - # pan -# pass + # Pan + elif self.button_pressed in self._pan_btn: + # Start the pan event with pixel coordinates + px, py = self.transData.transform([self._sx, self._sy]) + self.start_pan(px, py, 2) + # pan view (takes pixel coordinate input) + self.drag_pan(2, None, event.x, event.y) + self.end_pan() # Zoom elif self.button_pressed in self._zoom_btn: - # zoom view - # hmmm..this needs some help from clipping.... - minx, maxx, miny, maxy, minz, maxz = self.get_w_lims() - df = 1-((h - dy)/h) - dx = (maxx-minx)*df - dy = (maxy-miny)*df - dz = (maxz-minz)*df - self.set_xlim3d(minx - dx, maxx + dx) - self.set_ylim3d(miny - dy, maxy + dy) - self.set_zlim3d(minz - dz, maxz + dz) - self.get_proj() - self.figure.canvas.draw_idle() + # zoom view (dragging down zooms in) + scale = h/(h - dy) + self._scale_axis_limits(scale, scale, scale) + + # Store the event coordinates for the next time through. + self._sx, self._sy = x, y + # Always request a draw update at the end of interaction + self.get_figure(root=True).canvas.draw_idle() + + def drag_pan(self, button, key, x, y): + # docstring inherited + + # Get the coordinates from the move event + p = self._pan_start + (xdata, ydata), (xdata_start, ydata_start) = p.trans_inverse.transform( + [(x, y), (p.x, p.y)]) + self._sx, self._sy = xdata, ydata + # Calling start_pan() to set the x/y of this event as the starting + # move location for the next event + self.start_pan(x, y, button) + du, dv = xdata - xdata_start, ydata - ydata_start + dw = 0 + if key == 'x': + dv = 0 + elif key == 'y': + du = 0 + if du == 0 and dv == 0: + return + + # Transform the pan from the view axes to the data axes + R = np.array([self._view_u, self._view_v, self._view_w]) + R = -R / self._box_aspect * self._dist + duvw_projected = R.T @ np.array([du, dv, dw]) + + # Calculate pan distance + minx, maxx, miny, maxy, minz, maxz = self.get_w_lims() + dx = (maxx - minx) * duvw_projected[0] + dy = (maxy - miny) * duvw_projected[1] + dz = (maxz - minz) * duvw_projected[2] + + # Set the new axis limits + self.set_xlim3d(minx + dx, maxx + dx, auto=None) + self.set_ylim3d(miny + dy, maxy + dy, auto=None) + self.set_zlim3d(minz + dz, maxz + dz, auto=None) + + def _calc_view_axes(self, eye): + """ + Get the unit vectors for the viewing axes in data coordinates. + `u` is towards the right of the screen + `v` is towards the top of the screen + `w` is out of the screen + """ + elev_rad = np.deg2rad(art3d._norm_angle(self.elev)) + roll_rad = np.deg2rad(art3d._norm_angle(self.roll)) + + # Look into the middle of the world coordinates + R = 0.5 * self._roll_to_vertical(self._box_aspect) + + # Define which axis should be vertical. A negative value + # indicates the plot is upside down and therefore the values + # have been reversed: + V = np.zeros(3) + V[self._vertical_axis] = -1 if abs(elev_rad) > np.pi/2 else 1 + + u, v, w = proj3d._view_axes(eye, R, V, roll_rad) + return u, v, w + + def _set_view_from_bbox(self, bbox, direction='in', + mode=None, twinx=False, twiny=False): + """ + Zoom in or out of the bounding box. + + Will center the view in the center of the bounding box, and zoom by + the ratio of the size of the bounding box to the size of the Axes3D. + """ + (start_x, start_y, stop_x, stop_y) = bbox + if mode == 'x': + start_y = self.bbox.min[1] + stop_y = self.bbox.max[1] + elif mode == 'y': + start_x = self.bbox.min[0] + stop_x = self.bbox.max[0] + + # Clip to bounding box limits + start_x, stop_x = np.clip(sorted([start_x, stop_x]), + self.bbox.min[0], self.bbox.max[0]) + start_y, stop_y = np.clip(sorted([start_y, stop_y]), + self.bbox.min[1], self.bbox.max[1]) + + # Move the center of the view to the center of the bbox + zoom_center_x = (start_x + stop_x)/2 + zoom_center_y = (start_y + stop_y)/2 + + ax_center_x = (self.bbox.max[0] + self.bbox.min[0])/2 + ax_center_y = (self.bbox.max[1] + self.bbox.min[1])/2 + + self.start_pan(zoom_center_x, zoom_center_y, 2) + self.drag_pan(2, None, ax_center_x, ax_center_y) + self.end_pan() + + # Calculate zoom level + dx = abs(start_x - stop_x) + dy = abs(start_y - stop_y) + scale_u = dx / (self.bbox.max[0] - self.bbox.min[0]) + scale_v = dy / (self.bbox.max[1] - self.bbox.min[1]) + + # Keep aspect ratios equal + scale = max(scale_u, scale_v) + + # Zoom out + if direction == 'out': + scale = 1 / scale + + self._zoom_data_limits(scale, scale, scale) + + def _zoom_data_limits(self, scale_u, scale_v, scale_w): + """ + Zoom in or out of a 3D plot. + + Will scale the data limits by the scale factors. These will be + transformed to the x, y, z data axes based on the current view angles. + A scale factor > 1 zooms out and a scale factor < 1 zooms in. + + For an Axes that has had its aspect ratio set to 'equal', 'equalxy', + 'equalyz', or 'equalxz', the relevant axes are constrained to zoom + equally. + + Parameters + ---------- + scale_u : float + Scale factor for the u view axis (view screen horizontal). + scale_v : float + Scale factor for the v view axis (view screen vertical). + scale_w : float + Scale factor for the w view axis (view screen depth). + """ + scale = np.array([scale_u, scale_v, scale_w]) + + # Only perform frame conversion if unequal scale factors + if not np.allclose(scale, scale_u): + # Convert the scale factors from the view frame to the data frame + R = np.array([self._view_u, self._view_v, self._view_w]) + S = scale * np.eye(3) + scale = np.linalg.norm(R.T @ S, axis=1) + + # Set the constrained scale factors to the factor closest to 1 + if self._aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): + ax_idxs = self._equal_aspect_axis_indices(self._aspect) + min_ax_idxs = np.argmin(np.abs(scale[ax_idxs] - 1)) + scale[ax_idxs] = scale[ax_idxs][min_ax_idxs] + + self._scale_axis_limits(scale[0], scale[1], scale[2]) + + def _scale_axis_limits(self, scale_x, scale_y, scale_z): + """ + Keeping the center of the x, y, and z data axes fixed, scale their + limits by scale factors. A scale factor > 1 zooms out and a scale + factor < 1 zooms in. + + Parameters + ---------- + scale_x : float + Scale factor for the x data axis. + scale_y : float + Scale factor for the y data axis. + scale_z : float + Scale factor for the z data axis. + """ + # Get the axis centers and ranges + cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() + + # Set the scaled axis limits + self.set_xlim3d(cx - dx*scale_x/2, cx + dx*scale_x/2, auto=None) + self.set_ylim3d(cy - dy*scale_y/2, cy + dy*scale_y/2, auto=None) + self.set_zlim3d(cz - dz*scale_z/2, cz + dz*scale_z/2, auto=None) + + def _get_w_centers_ranges(self): + """Get 3D world centers and axis ranges.""" + # Calculate center of axis limits + minx, maxx, miny, maxy, minz, maxz = self.get_w_lims() + cx = (maxx + minx)/2 + cy = (maxy + miny)/2 + cz = (maxz + minz)/2 + + # Calculate range of axis limits + dx = (maxx - minx) + dy = (maxy - miny) + dz = (maxz - minz) + return cx, cy, cz, dx, dy, dz def set_zlabel(self, zlabel, fontdict=None, labelpad=None, **kwargs): """ @@ -1268,97 +1827,51 @@ def set_zlabel(self, zlabel, fontdict=None, labelpad=None, **kwargs): def get_zlabel(self): """ Get the z-label text string. - - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. """ - label = self.zaxis.get_label() + label = self.zaxis.label return label.get_text() # Axes rectangle characteristics - def get_frame_on(self): - """Get whether the 3D axes panels are drawn.""" - return self._frameon - - def set_frame_on(self, b): - """ - Set whether the 3D axes panels are drawn. - - Parameters - ---------- - b : bool - """ - self._frameon = bool(b) - self.stale = True + # The frame_on methods are not available for 3D axes. + # Python will raise a TypeError if they are called. + get_frame_on = None + set_frame_on = None - def grid(self, b=True, **kwargs): + def grid(self, visible=True, **kwargs): """ Set / unset 3D grid. .. note:: Currently, this function does not behave the same as - :meth:`matplotlib.axes.Axes.grid`, but it is intended to - eventually support that behavior. - - .. versionadded:: 1.1.0 + `.axes.Axes.grid`, but it is intended to eventually support that + behavior. """ # TODO: Operate on each axes separately if len(kwargs): - b = True - self._draw_grid = b + visible = True + self._draw_grid = visible self.stale = True - def locator_params(self, axis='both', tight=None, **kwargs): - """ - Convenience method for controlling tick locators. - - See :meth:`matplotlib.axes.Axes.locator_params` for full - documentation. Note that this is for Axes3D objects, - therefore, setting *axis* to 'both' will result in the - parameters being set for all three axes. Also, *axis* - can also take a value of 'z' to apply parameters to the - z axis. - - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. - """ - _x = axis in ['x', 'both'] - _y = axis in ['y', 'both'] - _z = axis in ['z', 'both'] - if _x: - self.xaxis.get_major_locator().set_params(**kwargs) - if _y: - self.yaxis.get_major_locator().set_params(**kwargs) - if _z: - self.zaxis.get_major_locator().set_params(**kwargs) - self.autoscale_view(tight=tight, scalex=_x, scaley=_y, scalez=_z) - def tick_params(self, axis='both', **kwargs): """ Convenience method for changing the appearance of ticks and tick labels. - See :meth:`matplotlib.axes.Axes.tick_params` for more complete - documentation. - - The only difference is that setting *axis* to 'both' will - mean that the settings are applied to all three axes. Also, - the *axis* parameter also accepts a value of 'z', which - would mean to apply to only the z-axis. + See `.Axes.tick_params` for full documentation. Because this function + applies to 3D Axes, *axis* can also be set to 'z', and setting *axis* + to 'both' autoscales all three axes. Also, because of how Axes3D objects are drawn very differently - from regular 2D axes, some of these settings may have + from regular 2D Axes, some of these settings may have ambiguous meaning. For simplicity, the 'z' axis will accept settings as if it was like the 'y' axis. .. note:: Axes3D currently ignores some of these settings. - - .. versionadded:: 1.1.0 """ - cbook._check_in_list(['x', 'y', 'z', 'both'], axis=axis) + _api.check_in_list(['x', 'y', 'z', 'both'], axis=axis) if axis in ['x', 'y', 'both']: super().tick_params(axis, **kwargs) if axis in ['z', 'both']: @@ -1373,71 +1886,84 @@ def tick_params(self, axis='both', **kwargs): def invert_zaxis(self): """ - Invert the z-axis. + [*Discouraged*] Invert the z-axis. + + .. admonition:: Discouraged + + The use of this method is discouraged. + Use `.Axes3D.set_zinverted` instead. - .. versionadded:: 1.1.0 - This function was added, but not tested. Please report any bugs. + See Also + -------- + get_zinverted + get_zlim, set_zlim + get_zbound, set_zbound """ bottom, top = self.get_zlim() self.set_zlim(top, bottom, auto=None) - def zaxis_inverted(self): - """ - Returns True if the z-axis is inverted. + set_zinverted = _axis_method_wrapper("zaxis", "set_inverted") + get_zinverted = _axis_method_wrapper("zaxis", "get_inverted") + zaxis_inverted = _axis_method_wrapper("zaxis", "get_inverted") + if zaxis_inverted.__doc__: + zaxis_inverted.__doc__ = ("[*Discouraged*] " + zaxis_inverted.__doc__ + + textwrap.dedent(""" - .. versionadded:: 1.1.0 - """ - bottom, top = self.get_zlim() - return top < bottom + .. admonition:: Discouraged + + The use of this method is discouraged. + Use `.Axes3D.get_zinverted` instead. + """)) def get_zbound(self): """ Return the lower and upper z-axis bounds, in increasing order. - .. versionadded:: 1.1.0 + See Also + -------- + set_zbound + get_zlim, set_zlim + invert_zaxis, zaxis_inverted """ - bottom, top = self.get_zlim() - if bottom < top: - return bottom, top + lower, upper = self.get_zlim() + if lower < upper: + return lower, upper else: - return top, bottom - - def set_zbound(self, lower=None, upper=None): - """ - Set the lower and upper numerical bounds of the z-axis. - - This method will honor axes inversion regardless of parameter order. - It will not change the autoscaling setting (`.get_autoscalez_on()`). - - .. versionadded:: 1.1.0 - """ - if upper is None and np.iterable(lower): - lower, upper = lower - - old_lower, old_upper = self.get_zbound() - if lower is None: - lower = old_lower - if upper is None: - upper = old_upper - - self.set_zlim(sorted((lower, upper), - reverse=bool(self.zaxis_inverted())), - auto=None) + return upper, lower - def text(self, x, y, z, s, zdir=None, **kwargs): + def text(self, x, y, z, s, zdir=None, *, axlim_clip=False, **kwargs): """ - Add text to the plot. kwargs will be passed on to Axes.text, - except for the *zdir* keyword, which sets the direction to be - used as the z direction. + Add the text *s* to the 3D Axes at location *x*, *y*, *z* in data coordinates. + + Parameters + ---------- + x, y, z : float + The position to place the text. + s : str + The text. + zdir : {'x', 'y', 'z', 3-tuple}, optional + The direction to be used as the z-direction. Default: 'z'. + See `.get_dir_vector` for a description of the values. + axlim_clip : bool, default: False + Whether to hide text that is outside the axes view limits. + + .. versionadded:: 3.10 + **kwargs + Other arguments are forwarded to `matplotlib.axes.Axes.text`. + + Returns + ------- + `.Text3D` + The created `.Text3D` instance. """ text = super().text(x, y, s, **kwargs) - art3d.text_2d_to_3d(text, z, zdir) + art3d.text_2d_to_3d(text, z, zdir, axlim_clip) return text text3D = text text2D = Axes.text - def plot(self, xs, ys, *args, zdir='z', **kwargs): + def plot(self, xs, ys, *args, zdir='z', axlim_clip=False, **kwargs): """ Plot 2D or 3D data. @@ -1451,7 +1977,11 @@ def plot(self, xs, ys, *args, zdir='z', **kwargs): z coordinates of vertices; either one for all points or one for each point. zdir : {'x', 'y', 'z'}, default: 'z' - When plotting 2D data, the direction to use as z ('x', 'y' or 'z'). + When plotting 2D data, the direction to use as z. + axlim_clip : bool, default: False + Whether to hide data that is outside the axes view limits. + + .. versionadded:: 3.10 **kwargs Other arguments are forwarded to `matplotlib.axes.Axes.plot`. """ @@ -1463,16 +1993,15 @@ def plot(self, xs, ys, *args, zdir='z', **kwargs): if args and not isinstance(args[0], str): zs, *args = args if 'zs' in kwargs: - raise TypeError("plot() for multiple values for argument 'z'") + raise TypeError("plot() for multiple values for argument 'zs'") else: zs = kwargs.pop('zs', 0) - # Match length - zs = np.broadcast_to(zs, np.shape(xs)) + xs, ys, zs = cbook._broadcast_with_masks(xs, ys, zs) lines = super().plot(xs, ys, *args, **kwargs) for line in lines: - art3d.line_2d_to_3d(line, zs=zs, zdir=zdir) + art3d.line_2d_to_3d(line, zs=zs, zdir=zdir, axlim_clip=axlim_clip) xs, ys, zs = art3d.juggle_axes(xs, ys, zs, zdir) self.auto_scale_xyz(xs, ys, zs, had_data) @@ -1480,13 +2009,142 @@ def plot(self, xs, ys, *args, zdir='z', **kwargs): plot3D = plot - def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, - vmax=None, lightsource=None, **kwargs): + def fill_between(self, x1, y1, z1, x2, y2, z2, *, + where=None, mode='auto', facecolors=None, shade=None, + axlim_clip=False, **kwargs): + """ + Fill the area between two 3D curves. + + The curves are defined by the points (*x1*, *y1*, *z1*) and + (*x2*, *y2*, *z2*). This creates one or multiple quadrangle + polygons that are filled. All points must be the same length N, or a + single value to be used for all points. + + Parameters + ---------- + x1, y1, z1 : float or 1D array-like + x, y, and z coordinates of vertices for 1st line. + + x2, y2, z2 : float or 1D array-like + x, y, and z coordinates of vertices for 2nd line. + + where : array of bool (length N), optional + Define *where* to exclude some regions from being filled. The + filled regions are defined by the coordinates ``pts[where]``, + for all x, y, and z pts. More precisely, fill between ``pts[i]`` + and ``pts[i+1]`` if ``where[i] and where[i+1]``. Note that this + definition implies that an isolated *True* value between two + *False* values in *where* will not result in filling. Both sides of + the *True* position remain unfilled due to the adjacent *False* + values. + + mode : {'quad', 'polygon', 'auto'}, default: 'auto' + The fill mode. One of: + + - 'quad': A separate quadrilateral polygon is created for each + pair of subsequent points in the two lines. + - 'polygon': The two lines are connected to form a single polygon. + This is faster and can render more cleanly for simple shapes + (e.g. for filling between two lines that lie within a plane). + - 'auto': If the points all lie on the same 3D plane, 'polygon' is + used. Otherwise, 'quad' is used. + + facecolors : list of :mpltype:`color`, default: None + Colors of each individual patch, or a single color to be used for + all patches. + + shade : bool, default: None + Whether to shade the facecolors. If *None*, then defaults to *True* + for 'quad' mode and *False* for 'polygon' mode. + + axlim_clip : bool, default: False + Whether to hide data that is outside the axes view limits. + + .. versionadded:: 3.10 + + **kwargs + All other keyword arguments are passed on to `.Poly3DCollection`. + + Returns + ------- + `.Poly3DCollection` + A `.Poly3DCollection` containing the plotted polygons. + + """ + _api.check_in_list(['auto', 'quad', 'polygon'], mode=mode) + + had_data = self.has_data() + x1, y1, z1, x2, y2, z2 = cbook._broadcast_with_masks(x1, y1, z1, x2, y2, z2) + + if facecolors is None: + facecolors = [self._get_patches_for_fill.get_next_color()] + facecolors = list(mcolors.to_rgba_array(facecolors)) + + if where is None: + where = True + else: + where = np.asarray(where, dtype=bool) + if where.size != x1.size: + raise ValueError(f"where size ({where.size}) does not match " + f"size ({x1.size})") + where = where & ~np.isnan(x1) # NaNs were broadcast in _broadcast_with_masks + + if mode == 'auto': + if art3d._all_points_on_plane(np.concatenate((x1[where], x2[where])), + np.concatenate((y1[where], y2[where])), + np.concatenate((z1[where], z2[where])), + atol=1e-12): + mode = 'polygon' + else: + mode = 'quad' + + if shade is None: + if mode == 'quad': + shade = True + else: + shade = False + + polys = [] + for idx0, idx1 in cbook.contiguous_regions(where): + x1i = x1[idx0:idx1] + y1i = y1[idx0:idx1] + z1i = z1[idx0:idx1] + x2i = x2[idx0:idx1] + y2i = y2[idx0:idx1] + z2i = z2[idx0:idx1] + + if not len(x1i): + continue + + if mode == 'quad': + # Preallocate the array for the region's vertices, and fill it in + n_polys_i = len(x1i) - 1 + polys_i = np.empty((n_polys_i, 4, 3)) + polys_i[:, 0, :] = np.column_stack((x1i[:-1], y1i[:-1], z1i[:-1])) + polys_i[:, 1, :] = np.column_stack((x1i[1:], y1i[1:], z1i[1:])) + polys_i[:, 2, :] = np.column_stack((x2i[1:], y2i[1:], z2i[1:])) + polys_i[:, 3, :] = np.column_stack((x2i[:-1], y2i[:-1], z2i[:-1])) + polys = polys + [*polys_i] + elif mode == 'polygon': + line1 = np.column_stack((x1i, y1i, z1i)) + line2 = np.column_stack((x2i[::-1], y2i[::-1], z2i[::-1])) + poly = np.concatenate((line1, line2), axis=0) + polys.append(poly) + + polyc = art3d.Poly3DCollection(polys, facecolors=facecolors, shade=shade, + axlim_clip=axlim_clip, **kwargs) + self.add_collection(polyc) + + self.auto_scale_xyz([x1, x2], [y1, y2], [z1, z2], had_data) + return polyc + + def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, + vmax=None, lightsource=None, axlim_clip=False, **kwargs): """ Create a surface plot. - By default it will be colored in shades of a solid color, but it also - supports color mapping by supplying the *cmap* argument. + By default, it will be colored in shades of a solid color, but it also + supports colormapping by supplying the *cmap* argument. .. note:: @@ -1508,7 +2166,7 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, Parameters ---------- - X, Y, Z : 2d arrays + X, Y, Z : 2D arrays Data values. rcount, ccount : int @@ -1516,8 +2174,6 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, data is larger, it will be downsampled (by slicing) to these numbers of points. Defaults to 50. - .. versionadded:: 2.0 - rstride, cstride : int Downsampling stride in each direction. These arguments are mutually exclusive with *rcount* and *ccount*. If only one of @@ -1526,42 +2182,43 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, 'classic' mode uses a default of ``rstride = cstride = 10`` instead of the new default of ``rcount = ccount = 50``. - color : color-like + color : :mpltype:`color` Color of the surface patches. - cmap : Colormap + cmap : Colormap, optional Colormap of the surface patches. - facecolors : array-like of colors. + facecolors : list of :mpltype:`color` Colors of each individual patch. - norm : Normalize + norm : `~matplotlib.colors.Normalize`, optional Normalization for the colormap. - vmin, vmax : float + vmin, vmax : float, optional Bounds for the normalization. shade : bool, default: True Whether to shade the facecolors. Shading is always disabled when *cmap* is specified. - lightsource : `~matplotlib.colors.LightSource` + lightsource : `~matplotlib.colors.LightSource`, optional The lightsource to use when *shade* is True. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + **kwargs - Other arguments are forwarded to `.Poly3DCollection`. + Other keyword arguments are forwarded to `.Poly3DCollection`. """ had_data = self.has_data() if Z.ndim != 2: raise ValueError("Argument Z must be 2-dimensional.") - if np.any(np.isnan(Z)): - cbook._warn_external( - "Z contains NaN values. This may result in rendering " - "artifacts.") - # TODO: Support masked arrays + Z = cbook._to_unmasked_float_array(Z) X, Y, Z = np.broadcast_arrays(X, Y, Z) rows, cols = Z.shape @@ -1576,7 +2233,7 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, rcount = kwargs.pop('rcount', 50) ccount = kwargs.pop('ccount', 50) - if rcParams['_internal.classic_mode']: + if mpl.rcParams['_internal.classic_mode']: # Strides have priority over counts in classic mode. # So, only compute strides from counts # if counts were explicitly given @@ -1590,24 +2247,12 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, rstride = int(max(np.ceil(rows / rcount), 1)) cstride = int(max(np.ceil(cols / ccount), 1)) - if 'facecolors' in kwargs: - fcolors = kwargs.pop('facecolors') - else: - color = kwargs.pop('color', None) - if color is None: - color = self._get_lines.get_next_color() - color = np.array(mcolors.to_rgba(color)) - fcolors = None + fcolors = kwargs.pop('facecolors', None) cmap = kwargs.get('cmap', None) shade = kwargs.pop('shade', cmap is None) if shade is None: - cbook.warn_deprecated( - "3.1", - message="Passing shade=None to Axes3D.plot_surface() is " - "deprecated since matplotlib 3.1 and will change its " - "semantic or raise an error in matplotlib 3.3. " - "Please use shade=False instead.") + raise ValueError("shade cannot be None.") colset = [] # the sampled facecolor if (rows - 1) % rstride == 0 and \ @@ -1623,8 +2268,8 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, col_inds = list(range(0, cols-1, cstride)) + [cols-1] polys = [] - for rs, rs_next in zip(row_inds[:-1], row_inds[1:]): - for cs, cs_next in zip(col_inds[:-1], col_inds[1:]): + for rs, rs_next in itertools.pairwise(row_inds): + for cs, cs_next in itertools.pairwise(col_inds): ps = [ # +1 ensures we share edges between polygons cbook._array_perimeter(a[rs:rs_next+1, cs:cs_next+1]) @@ -1637,17 +2282,36 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, if fcolors is not None: colset.append(fcolors[rs][cs]) + # In cases where there are non-finite values in the data (possibly NaNs from + # masked arrays), artifacts can be introduced. Here check whether such values + # are present and remove them. + if not isinstance(polys, np.ndarray) or not np.isfinite(polys).all(): + new_polys = [] + new_colset = [] + + # Depending on fcolors, colset is either an empty list or has as + # many elements as polys. In the former case new_colset results in + # a list with None entries, that is discarded later. + for p, col in itertools.zip_longest(polys, colset): + new_poly = np.array(p)[np.isfinite(p).all(axis=1)] + if len(new_poly): + new_polys.append(new_poly) + new_colset.append(col) + + # Replace previous polys and, if fcolors is not None, colset + polys = new_polys + if fcolors is not None: + colset = new_colset + # note that the striding causes some polygons to have more coordinates # than others - polyc = art3d.Poly3DCollection(polys, *args, **kwargs) if fcolors is not None: - if shade: - colset = self._shade_colors( - colset, self._generate_normals(polys), lightsource) - polyc.set_facecolors(colset) - polyc.set_edgecolors(colset) + polyc = art3d.Poly3DCollection( + polys, edgecolors=colset, facecolors=colset, shade=shade, + lightsource=lightsource, axlim_clip=axlim_clip, **kwargs) elif cmap: + polyc = art3d.Poly3DCollection(polys, axlim_clip=axlim_clip, **kwargs) # can't always vectorize, because polys might be jagged if isinstance(polys, np.ndarray): avg_z = polys[..., 2].mean(axis=-1) @@ -1659,99 +2323,21 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, if norm is not None: polyc.set_norm(norm) else: - if shade: - colset = self._shade_colors( - color, self._generate_normals(polys), lightsource) - else: - colset = color - polyc.set_facecolors(colset) + color = kwargs.pop('color', None) + if color is None: + color = self._get_lines.get_next_color() + color = np.array(mcolors.to_rgba(color)) + + polyc = art3d.Poly3DCollection( + polys, facecolors=color, shade=shade, lightsource=lightsource, + axlim_clip=axlim_clip, **kwargs) self.add_collection(polyc) self.auto_scale_xyz(X, Y, Z, had_data) return polyc - def _generate_normals(self, polygons): - """ - Compute the normals of a list of polygons. - - Normals point towards the viewer for a face with its vertices in - counterclockwise order, following the right hand rule. - - Uses three points equally spaced around the polygon. - This normal of course might not make sense for polygons with more than - three points not lying in a plane, but it's a plausible and fast - approximation. - - Parameters - ---------- - polygons: list of (M_i, 3) array-like, or (..., M, 3) array-like - A sequence of polygons to compute normals for, which can have - varying numbers of vertices. If the polygons all have the same - number of vertices and array is passed, then the operation will - be vectorized. - - Returns - ------- - normals: (..., 3) array-like - A normal vector estimated for the polygon. - - """ - if isinstance(polygons, np.ndarray): - # optimization: polygons all have the same number of points, so can - # vectorize - n = polygons.shape[-2] - i1, i2, i3 = 0, n//3, 2*n//3 - v1 = polygons[..., i1, :] - polygons[..., i2, :] - v2 = polygons[..., i2, :] - polygons[..., i3, :] - else: - # The subtraction doesn't vectorize because polygons is jagged. - v1 = np.empty((len(polygons), 3)) - v2 = np.empty((len(polygons), 3)) - for poly_i, ps in enumerate(polygons): - n = len(ps) - i1, i2, i3 = 0, n//3, 2*n//3 - v1[poly_i, :] = ps[i1, :] - ps[i2, :] - v2[poly_i, :] = ps[i2, :] - ps[i3, :] - return np.cross(v1, v2) - - def _shade_colors(self, color, normals, lightsource=None): - """ - Shade *color* using normal vectors given by *normals*. - *color* can also be an array of the same length as *normals*. - """ - if lightsource is None: - # chosen for backwards-compatibility - lightsource = mcolors.LightSource(azdeg=225, altdeg=19.4712) - - with np.errstate(invalid="ignore"): - shade = ((normals / np.linalg.norm(normals, axis=1, keepdims=True)) - @ lightsource.direction) - mask = ~np.isnan(shade) - - if mask.any(): - # convert dot product to allowed shading fractions - in_norm = mcolors.Normalize(-1, 1) - out_norm = mcolors.Normalize(0.3, 1).inverse - - def norm(x): - return out_norm(in_norm(x)) - - shade[~mask] = 0 - - color = mcolors.to_rgba_array(color) - # shape of color should be (M, 4) (where M is number of faces) - # shape of shade should be (M,) - # colors should have final shape of (M, 4) - alpha = color[:, 3] - colors = norm(shade)[:, np.newaxis] * color - colors[:, 3] = alpha - else: - colors = np.asanyarray(color).copy() - - return colors - - def plot_wireframe(self, X, Y, Z, *args, **kwargs): + def plot_wireframe(self, X, Y, Z, *, axlim_clip=False, **kwargs): """ Plot a 3D wireframe. @@ -1764,9 +2350,15 @@ def plot_wireframe(self, X, Y, Z, *args, **kwargs): Parameters ---------- - X, Y, Z : 2d arrays + X, Y, Z : 2D arrays Data values. + axlim_clip : bool, default: False + Whether to hide lines and patches with vertices outside the axes + view limits. + + .. versionadded:: 3.10 + rcount, ccount : int Maximum number of samples used in each direction. If the input data is larger, it will be downsampled (by slicing) to these @@ -1774,8 +2366,6 @@ def plot_wireframe(self, X, Y, Z, *args, **kwargs): not sampled in the corresponding direction, producing a 3D line plot rather than a wireframe plot. Defaults to 50. - .. versionadded:: 2.0 - rstride, cstride : int Downsampling stride in each direction. These arguments are mutually exclusive with *rcount* and *ccount*. If only one of @@ -1788,7 +2378,7 @@ def plot_wireframe(self, X, Y, Z, *args, **kwargs): of the new default of ``rcount = ccount = 50``. **kwargs - Other arguments are forwarded to `.Line3DCollection`. + Other keyword arguments are forwarded to `.Line3DCollection`. """ had_data = self.has_data() @@ -1809,7 +2399,7 @@ def plot_wireframe(self, X, Y, Z, *args, **kwargs): rcount = kwargs.pop('rcount', 50) ccount = kwargs.pop('ccount', 50) - if rcParams['_internal.classic_mode']: + if mpl.rcParams['_internal.classic_mode']: # Strides have priority over counts in classic mode. # So, only compute strides from counts # if counts were explicitly given @@ -1823,56 +2413,57 @@ def plot_wireframe(self, X, Y, Z, *args, **kwargs): rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0 cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0 + if rstride == 0 and cstride == 0: + raise ValueError("Either rstride or cstride must be non zero") + # We want two sets of lines, one running along the "rows" of # Z and another set of lines running along the "columns" of Z. # This transpose will make it easy to obtain the columns. tX, tY, tZ = np.transpose(X), np.transpose(Y), np.transpose(Z) - if rstride: - rii = list(range(0, rows, rstride)) - # Add the last index only if needed - if rows > 0 and rii[-1] != (rows - 1): - rii += [rows-1] + # Compute the indices of the row and column lines to be drawn + # For Z.size == 0, we don't want to draw any lines since the data is empty + if rstride == 0 or Z.size == 0: + rii = np.array([], dtype=int) + elif (rows - 1) % rstride == 0: + # last index is hit: rii[-1] == rows - 1 + rii = np.arange(0, rows, rstride) else: - rii = [] - if cstride: - cii = list(range(0, cols, cstride)) - # Add the last index only if needed - if cols > 0 and cii[-1] != (cols - 1): - cii += [cols-1] + # add the last index + rii = np.arange(0, rows + rstride, rstride) + rii[-1] = rows - 1 + + if cstride == 0 or Z.size == 0: + cii = np.array([], dtype=int) + elif (cols - 1) % cstride == 0: + # last index is hit: cii[-1] == cols - 1 + cii = np.arange(0, cols, cstride) else: - cii = [] - - if rstride == 0 and cstride == 0: - raise ValueError("Either rstride or cstride must be non zero") - - # If the inputs were empty, then just - # reset everything. - if Z.size == 0: - rii = [] - cii = [] - - xlines = [X[i] for i in rii] - ylines = [Y[i] for i in rii] - zlines = [Z[i] for i in rii] - - txlines = [tX[i] for i in cii] - tylines = [tY[i] for i in cii] - tzlines = [tZ[i] for i in cii] - - lines = ([list(zip(xl, yl, zl)) - for xl, yl, zl in zip(xlines, ylines, zlines)] - + [list(zip(xl, yl, zl)) - for xl, yl, zl in zip(txlines, tylines, tzlines)]) - - linec = art3d.Line3DCollection(lines, *args, **kwargs) + # add the last index + cii = np.arange(0, cols + cstride, cstride) + cii[-1] = cols - 1 + + row_lines = np.stack([X[rii], Y[rii], Z[rii]], axis=-1) + col_lines = np.stack([tX[cii], tY[cii], tZ[cii]], axis=-1) + + # We autoscale twice because autoscaling is much faster with vectorized numpy + # arrays, but row_lines and col_lines might not be the same shape, so we can't + # stack them to check them in a single pass. + # Note that while the column and row grid points are the same, the lines + # between them may expand the view limits, so we have to check both. + self.auto_scale_xyz(row_lines[..., 0], row_lines[..., 1], row_lines[..., 2], + had_data) + self.auto_scale_xyz(col_lines[..., 0], col_lines[..., 1], col_lines[..., 2], + had_data=True) + + lines = list(row_lines) + list(col_lines) + linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs) self.add_collection(linec) - self.auto_scale_xyz(X, Y, Z, had_data) return linec def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, - lightsource=None, **kwargs): + lightsource=None, axlim_clip=False, **kwargs): """ Plot a triangulated surface. @@ -1888,7 +2479,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, plot_trisurf(X, Y, triangles=triangles, ...) in which case a Triangulation object will be created. See - `.Triangulation` for a explanation of these possibilities. + `.Triangulation` for an explanation of these possibilities. The remaining arguments are:: @@ -1905,25 +2496,27 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, Color of the surface patches. cmap A colormap for the surface patches. - norm : Normalize + norm : `~matplotlib.colors.Normalize`, optional An instance of Normalize to map values to colors. - vmin, vmax : float, default: None + vmin, vmax : float, optional Minimum and maximum value to map. shade : bool, default: True Whether to shade the facecolors. Shading is always disabled when *cmap* is specified. - lightsource : `~matplotlib.colors.LightSource` + lightsource : `~matplotlib.colors.LightSource`, optional The lightsource to use when *shade* is True. + axlim_clip : bool, default: False + Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 **kwargs - All other arguments are passed on to + All other keyword arguments are passed on to :class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection` Examples -------- .. plot:: gallery/mplot3d/trisurf3d.py .. plot:: gallery/mplot3d/trisurf3d_2.py - - .. versionadded:: 1.2.0 """ had_data = self.has_data() @@ -1951,9 +2544,9 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, zt = z[triangles] verts = np.stack((xt, yt, zt), axis=-1) - polyc = art3d.Poly3DCollection(verts, *args, **kwargs) - if cmap: + polyc = art3d.Poly3DCollection(verts, *args, + axlim_clip=axlim_clip, **kwargs) # average over the three points of each triangle avg_z = verts[:, :, 2].mean(axis=1) polyc.set_array(avg_z) @@ -1962,12 +2555,9 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, if norm is not None: polyc.set_norm(norm) else: - if shade: - normals = self._generate_normals(verts) - colset = self._shade_colors(color, normals, lightsource) - else: - colset = color - polyc.set_facecolors(colset) + polyc = art3d.Poly3DCollection( + verts, *args, shade=shade, lightsource=lightsource, + facecolors=color, axlim_clip=axlim_clip, **kwargs) self.add_collection(polyc) self.auto_scale_xyz(tri.x, tri.y, z, had_data) @@ -1979,89 +2569,94 @@ def _3d_extend_contour(self, cset, stride=5): Extend a contour in 3D by creating """ - levels = cset.levels - colls = cset.collections - dz = (levels[1] - levels[0]) / 2 - - for z, linec in zip(levels, colls): - paths = linec.get_paths() - if not paths: + dz = (cset.levels[1] - cset.levels[0]) / 2 + polyverts = [] + colors = [] + for idx, level in enumerate(cset.levels): + path = cset.get_paths()[idx] + subpaths = [*path._iter_connected_components()] + color = cset.get_edgecolor()[idx] + top = art3d._paths_to_3d_segments(subpaths, level - dz) + bot = art3d._paths_to_3d_segments(subpaths, level + dz) + if not len(top[0]): continue - topverts = art3d._paths_to_3d_segments(paths, z - dz) - botverts = art3d._paths_to_3d_segments(paths, z + dz) - - color = linec.get_color()[0] - - polyverts = [] - normals = [] - nsteps = round(len(topverts[0]) / stride) - if nsteps <= 1: - if len(topverts[0]) > 1: - nsteps = 2 - else: - continue - - stepsize = (len(topverts[0]) - 1) / (nsteps - 1) - for i in range(int(round(nsteps)) - 1): - i1 = int(round(i * stepsize)) - i2 = int(round((i + 1) * stepsize)) - polyverts.append([topverts[0][i1], - topverts[0][i2], - botverts[0][i2], - botverts[0][i1]]) - - # all polygons have 4 vertices, so vectorize - polyverts = np.array(polyverts) - normals = self._generate_normals(polyverts) - - colors = self._shade_colors(color, normals) - colors2 = self._shade_colors(color, normals) - polycol = art3d.Poly3DCollection(polyverts, - facecolors=colors, - edgecolors=colors2) - polycol.set_sort_zpos(z) - self.add_collection3d(polycol) - - for col in colls: - self.collections.remove(col) + nsteps = max(round(len(top[0]) / stride), 2) + stepsize = (len(top[0]) - 1) / (nsteps - 1) + polyverts.extend([ + (top[0][round(i * stepsize)], top[0][round((i + 1) * stepsize)], + bot[0][round((i + 1) * stepsize)], bot[0][round(i * stepsize)]) + for i in range(round(nsteps) - 1)]) + colors.extend([color] * (round(nsteps) - 1)) + self.add_collection3d(art3d.Poly3DCollection( + np.array(polyverts), # All polygons have 4 vertices, so vectorize. + facecolors=colors, edgecolors=colors, shade=True)) + cset.remove() def add_contour_set( - self, cset, extend3d=False, stride=5, zdir='z', offset=None): + self, cset, extend3d=False, stride=5, zdir='z', offset=None, + axlim_clip=False): zdir = '-' + zdir if extend3d: self._3d_extend_contour(cset, stride) else: - for z, linec in zip(cset.levels, cset.collections): - if offset is not None: - z = offset - art3d.line_collection_2d_to_3d(linec, z, zdir=zdir) + art3d.collection_2d_to_3d( + cset, zs=offset if offset is not None else cset.levels, zdir=zdir, + axlim_clip=axlim_clip) - def add_contourf_set(self, cset, zdir='z', offset=None): + def add_contourf_set(self, cset, zdir='z', offset=None, *, axlim_clip=False): + self._add_contourf_set(cset, zdir=zdir, offset=offset, + axlim_clip=axlim_clip) + + def _add_contourf_set(self, cset, zdir='z', offset=None, axlim_clip=False): + """ + Returns + ------- + levels : `numpy.ndarray` + Levels at which the filled contours are added. + """ zdir = '-' + zdir - for z, linec in zip(cset.levels, cset.collections): - if offset is not None: - z = offset - art3d.poly_collection_2d_to_3d(linec, z, zdir=zdir) - linec.set_sort_zpos(z) + midpoints = cset.levels[:-1] + np.diff(cset.levels) / 2 + # Linearly interpolate to get levels for any extensions + if cset._extend_min: + min_level = cset.levels[0] - np.diff(cset.levels[:2]) / 2 + midpoints = np.insert(midpoints, 0, min_level) + if cset._extend_max: + max_level = cset.levels[-1] + np.diff(cset.levels[-2:]) / 2 + midpoints = np.append(midpoints, max_level) + + art3d.collection_2d_to_3d( + cset, zs=offset if offset is not None else midpoints, zdir=zdir, + axlim_clip=axlim_clip) + return midpoints + + @_preprocess_data() def contour(self, X, Y, Z, *args, - extend3d=False, stride=5, zdir='z', offset=None, **kwargs): + extend3d=False, stride=5, zdir='z', offset=None, axlim_clip=False, + **kwargs): """ Create a 3D contour plot. Parameters ---------- - X, Y, Z : array-like - Input data. + X, Y, Z : array-like, + Input data. See `.Axes.contour` for supported data shapes. extend3d : bool, default: False Whether to extend contour in 3D. - stride : int + stride : int, default: 5 Step size for extending contour. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional If specified, plot a projection of the contour lines at this - position in a plane normal to zdir. + position in a plane normal to *zdir*. + axlim_clip : bool, default: False + Whether to hide lines with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + *args, **kwargs Other arguments are forwarded to `matplotlib.axes.Axes.contour`. @@ -2073,21 +2668,20 @@ def contour(self, X, Y, Z, *args, jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) cset = super().contour(jX, jY, jZ, *args, **kwargs) - self.add_contour_set(cset, extend3d, stride, zdir, offset) + self.add_contour_set(cset, extend3d, stride, zdir, offset, axlim_clip) self.auto_scale_xyz(X, Y, Z, had_data) return cset contour3D = contour + @_preprocess_data() def tricontour(self, *args, - extend3d=False, stride=5, zdir='z', offset=None, **kwargs): + extend3d=False, stride=5, zdir='z', offset=None, axlim_clip=False, + **kwargs): """ Create a 3D contour plot. - .. versionchanged:: 1.3.0 - Added support for custom triangulations - .. note:: This method currently produces incorrect output due to a longstanding bug in 3D PolyCollection rendering. @@ -2095,22 +2689,28 @@ def tricontour(self, *args, Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `.Axes.tricontour` for supported data shapes. extend3d : bool, default: False Whether to extend contour in 3D. - stride : int + stride : int, default: 5 Step size for extending contour. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional If specified, plot a projection of the contour lines at this - position in a plane normal to zdir. + position in a plane normal to *zdir*. + axlim_clip : bool, default: False + Whether to hide lines with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER *args, **kwargs Other arguments are forwarded to `matplotlib.axes.Axes.tricontour`. Returns ------- - matplotlib.tri.tricontour.TriContourSet + matplotlib.tri._tricontour.TriContourSet """ had_data = self.has_data() @@ -2128,48 +2728,62 @@ def tricontour(self, *args, tri = Triangulation(jX, jY, tri.triangles, tri.mask) cset = super().tricontour(tri, jZ, *args, **kwargs) - self.add_contour_set(cset, extend3d, stride, zdir, offset) + self.add_contour_set(cset, extend3d, stride, zdir, offset, axlim_clip) self.auto_scale_xyz(X, Y, Z, had_data) return cset - def contourf(self, X, Y, Z, *args, zdir='z', offset=None, **kwargs): + def _auto_scale_contourf(self, X, Y, Z, zdir, levels, had_data): + # Autoscale in the zdir based on the levels added, which are + # different from data range if any contour extensions are present + dim_vals = {'x': X, 'y': Y, 'z': Z, zdir: levels} + # Input data and levels have different sizes, but auto_scale_xyz + # expected same-size input, so manually take min/max limits + limits = [(np.nanmin(dim_vals[dim]), np.nanmax(dim_vals[dim])) + for dim in ['x', 'y', 'z']] + self.auto_scale_xyz(*limits, had_data) + + @_preprocess_data() + def contourf(self, X, Y, Z, *args, + zdir='z', offset=None, axlim_clip=False, **kwargs): """ Create a 3D filled contour plot. Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `.Axes.contourf` for supported data shapes. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional If specified, plot a projection of the contour lines at this - position in a plane normal to zdir. + position in a plane normal to *zdir*. + axlim_clip : bool, default: False + Whether to hide lines with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER *args, **kwargs Other arguments are forwarded to `matplotlib.axes.Axes.contourf`. Returns ------- matplotlib.contour.QuadContourSet - - Notes - ----- - .. versionadded:: 1.1.0 - The *zdir* and *offset* parameters. """ had_data = self.has_data() jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) cset = super().contourf(jX, jY, jZ, *args, **kwargs) - self.add_contourf_set(cset, zdir, offset) + levels = self._add_contourf_set(cset, zdir, offset, axlim_clip) - self.auto_scale_xyz(X, Y, Z, had_data) + self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) return cset contourf3D = contourf - def tricontourf(self, *args, zdir='z', offset=None, **kwargs): + @_preprocess_data() + def tricontourf(self, *args, zdir='z', offset=None, axlim_clip=False, **kwargs): """ Create a 3D filled contour plot. @@ -2180,26 +2794,25 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs): Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `.Axes.tricontourf` for supported data shapes. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional If specified, plot a projection of the contour lines at this position in a plane normal to zdir. + axlim_clip : bool, default: False + Whether to hide lines with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER *args, **kwargs Other arguments are forwarded to `matplotlib.axes.Axes.tricontourf`. Returns ------- - matplotlib.tri.tricontour.TriContourSet - - Notes - ----- - .. versionadded:: 1.1.0 - The *zdir* and *offset* parameters. - .. versionchanged:: 1.3.0 - Added support for custom triangulations + matplotlib.tri._tricontour.TriContourSet """ had_data = self.has_data() @@ -2217,24 +2830,43 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs): tri = Triangulation(jX, jY, tri.triangles, tri.mask) cset = super().tricontourf(tri, jZ, *args, **kwargs) - self.add_contourf_set(cset, zdir, offset) + levels = self._add_contourf_set(cset, zdir, offset, axlim_clip) - self.auto_scale_xyz(X, Y, Z, had_data) + self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) return cset - def add_collection3d(self, col, zs=0, zdir='z'): + def add_collection3d(self, col, zs=0, zdir='z', autolim=True, *, + axlim_clip=False): """ Add a 3D collection object to the plot. 2D collection types are converted to a 3D version by - modifying the object and adding z coordinate information. + modifying the object and adding z coordinate information, + *zs* and *zdir*. + + Supported 2D collection types are: - Supported are: + - `.PolyCollection` + - `.LineCollection` + - `.PatchCollection` (currently not supporting *autolim*) + + Parameters + ---------- + col : `.Collection` + A 2D collection object. + zs : float or array-like, default: 0 + The z-positions to be used for the 2D objects. + zdir : {'x', 'y', 'z'}, default: 'z' + The direction to use for the z-positions. + autolim : bool, default: True + Whether to update the data limits. + axlim_clip : bool, default: False + Whether to hide the scatter points outside the axes view limits. - - PolyCollection - - LineCollection - - PatchCollection + .. versionadded:: 3.10 """ + had_data = self.has_data() + zvals = np.atleast_1d(zs) zsortval = (np.min(zvals) if zvals.size else 0) # FIXME: arbitrary default @@ -2243,27 +2875,50 @@ def add_collection3d(self, col, zs=0, zdir='z'): # object would also pass.) Maybe have a collection3d # abstract class to test for and exclude? if type(col) is mcoll.PolyCollection: - art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir) + art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir, + axlim_clip=axlim_clip) col.set_sort_zpos(zsortval) elif type(col) is mcoll.LineCollection: - art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir) + art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir, + axlim_clip=axlim_clip) col.set_sort_zpos(zsortval) elif type(col) is mcoll.PatchCollection: - art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir) + art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir, + axlim_clip=axlim_clip) col.set_sort_zpos(zsortval) + if autolim: + if isinstance(col, art3d.Line3DCollection): + self.auto_scale_xyz(*np.array(col._segments3d).transpose(), + had_data=had_data) + elif isinstance(col, art3d.Poly3DCollection): + self.auto_scale_xyz(col._faces[..., 0], + col._faces[..., 1], + col._faces[..., 2], had_data=had_data) + elif isinstance(col, art3d.Patch3DCollection): + pass + # FIXME: Implement auto-scaling function for Patch3DCollection + # Currently unable to do so due to issues with Patch3DCollection + # See https://github.com/matplotlib/matplotlib/issues/14298 for details + collection = super().add_collection(col) return collection - def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, - *args, **kwargs): + @_preprocess_data(replace_names=["xs", "ys", "zs", "s", + "edgecolors", "c", "facecolor", + "facecolors", "color"]) + def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=None, + *args, + depthshade_minalpha=None, + axlim_clip=False, + **kwargs): """ Create a scatter plot. Parameters ---------- xs, ys : array-like - The data positions. + The data positions. zs : float or array-like, default: 0 The z-positions. Either an array of the same length as *xs* and *ys* or a single value to place all points in the same plane. @@ -2278,22 +2933,38 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, The marker size in points**2. Either an array of the same length as *xs* and *ys* or a single value to make all markers the same size. - c : color, sequence, or sequence of colors, optional + c : :mpltype:`color`, sequence, or sequence of colors, optional The marker color. Possible values: - A single color format string. - A sequence of colors of length n. - A sequence of n numbers to be mapped to colors using *cmap* and *norm*. - - A 2-D array in which the rows are RGB or RGBA. + - A 2D array in which the rows are RGB or RGBA. For more details see the *c* argument of `~.axes.Axes.scatter`. - depthshade : bool, default: True + depthshade : bool, default: None Whether to shade the scatter markers to give the appearance of depth. Each call to ``scatter()`` will perform its depthshading independently. + If None, use the value from rcParams['axes3d.depthshade']. + + depthshade_minalpha : float, default: None + The lowest alpha value applied by depth-shading. + If None, use the value from rcParams['axes3d.depthshade_minalpha']. + + .. versionadded:: 3.11 + + axlim_clip : bool, default: False + Whether to hide the scatter points outside the axes view limits. + + .. versionadded:: 3.10 + + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs - All other arguments are passed on to `~.axes.Axes.scatter`. + All other keyword arguments are passed on to `~.axes.Axes.scatter`. Returns ------- @@ -2303,20 +2974,32 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, had_data = self.has_data() zs_orig = zs - xs, ys, zs = np.broadcast_arrays( - *[np.ravel(np.ma.filled(t, np.nan)) for t in [xs, ys, zs]]) + xs, ys, zs = cbook._broadcast_with_masks(xs, ys, zs) s = np.ma.ravel(s) # This doesn't have to match x, y in size. - xs, ys, zs, s, c = cbook.delete_masked_points(xs, ys, zs, s, c) + xs, ys, zs, s, c, color = cbook.delete_masked_points( + xs, ys, zs, s, c, kwargs.get('color', None) + ) + if kwargs.get("color") is not None: + kwargs['color'] = color + if depthshade is None: + depthshade = mpl.rcParams['axes3d.depthshade'] + if depthshade_minalpha is None: + depthshade_minalpha = mpl.rcParams['axes3d.depthshade_minalpha'] # For xs and ys, 2D scatter() will do the copying. if np.may_share_memory(zs_orig, zs): # Avoid unnecessary copies. zs = zs.copy() patches = super().scatter(xs, ys, s=s, c=c, *args, **kwargs) - art3d.patch_collection_2d_to_3d(patches, zs=zs, zdir=zdir, - depthshade=depthshade) - + art3d.patch_collection_2d_to_3d( + patches, + zs=zs, + zdir=zdir, + depthshade=depthshade, + depthshade_minalpha=depthshade_minalpha, + axlim_clip=axlim_clip, + ) if self._zmargin < 0.05 and xs.size > 0: self.set_zmargin(0.05) @@ -2326,7 +3009,9 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, scatter3D = scatter - def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): + @_preprocess_data() + def bar(self, left, height, zs=0, zdir='z', *args, + axlim_clip=False, **kwargs): """ Add 2D bar(s). @@ -2336,13 +3021,20 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): The x coordinates of the left sides of the bars. height : 1D array-like The height of the bars. - zs : float or 1D array-like + zs : float or 1D array-like, default: 0 Z coordinate of bars; if a single value is specified, it will be used for all bars. zdir : {'x', 'y', 'z'}, default: 'z' When plotting 2D data, the direction to use as z ('x', 'y' or 'z'). + axlim_clip : bool, default: False + Whether to hide bars with points outside the axes view limits. + + .. versionadded:: 3.10 + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.bar`. + Other keyword arguments are forwarded to + `matplotlib.axes.Axes.bar`. Returns ------- @@ -2352,7 +3044,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): patches = super().bar(left, height, *args, **kwargs) - zs = np.broadcast_to(zs, len(left)) + zs = np.broadcast_to(zs, len(left), subok=True) verts = [] verts_zs = [] @@ -2360,7 +3052,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): vs = art3d._get_patch_verts(p) verts += vs.tolist() verts_zs += [z] * len(vs) - art3d.patch_2d_to_3d(p, z, zdir) + art3d.patch_2d_to_3d(p, z, zdir, axlim_clip) if 'alpha' in kwargs: p.set_alpha(kwargs['alpha']) @@ -2377,12 +3069,14 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): return patches + @_preprocess_data() def bar3d(self, x, y, z, dx, dy, dz, color=None, - zsort='average', shade=True, lightsource=None, *args, **kwargs): + zsort='average', shade=True, lightsource=None, *args, + axlim_clip=False, **kwargs): """ Generate a 3D barplot. - This method creates three dimensional barplot where the width, + This method creates three-dimensional barplot where the width, depth, height, and color of the bars can all be uniquely set. Parameters @@ -2415,16 +3109,24 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, 5. -X 6. +X - zsort : str, optional + zsort : {'average', 'min', 'max'}, default: 'average' The z-axis sorting scheme passed onto `~.art3d.Poly3DCollection` shade : bool, default: True When true, this shades the dark sides of the bars (relative to the plot's source of light). - lightsource : `~matplotlib.colors.LightSource` + lightsource : `~matplotlib.colors.LightSource`, optional The lightsource to use when *shade* is True. + axlim_clip : bool, default: False + Whether to hide the bars with points outside the axes view limits. + + .. versionadded:: 3.10 + + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs Any additional keyword arguments are passed onto `~.art3d.Poly3DCollection`. @@ -2432,8 +3134,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, Returns ------- collection : `~.art3d.Poly3DCollection` - A collection of three dimensional polygons representing - the bars. + A collection of three-dimensional polygons representing the bars. """ had_data = self.has_data() @@ -2523,15 +3224,12 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, if len(facecolors) < len(x): facecolors *= (6 * len(x)) - if shade: - normals = self._generate_normals(polys) - sfacecolors = self._shade_colors(facecolors, normals, lightsource) - else: - sfacecolors = facecolors - col = art3d.Poly3DCollection(polys, zsort=zsort, - facecolor=sfacecolors, + facecolors=facecolors, + shade=shade, + lightsource=lightsource, + axlim_clip=axlim_clip, *args, **kwargs) self.add_collection(col) @@ -2546,19 +3244,17 @@ def set_title(self, label, fontdict=None, loc='center', **kwargs): self.title.set_y(0.92 * y) return ret - def quiver(self, *args, + @_preprocess_data() + def quiver(self, X, Y, Z, U, V, W, *, length=1, arrow_length_ratio=.3, pivot='tail', normalize=False, - **kwargs): + axlim_clip=False, **kwargs): """ - ax.quiver(X, Y, Z, U, V, W, /, length=1, arrow_length_ratio=.3, \ -pivot='tail', normalize=False, **kwargs) - Plot a 3D field of arrows. - The arguments could be array-like or scalars, so long as they - they can be broadcast together. The arguments can also be - masked arrays. If an element in any of argument is masked, then - that corresponding quiver element will not be plotted. + The arguments can be array-like or scalars, so long as they can be + broadcast together. The arguments can also be masked arrays. If an + element in any of argument is masked, then that corresponding quiver + element will not be plotted. Parameters ---------- @@ -2583,12 +3279,20 @@ def quiver(self, *args, Whether all arrows are normalized to have the same length, or keep the lengths defined by *u*, *v*, and *w*. + axlim_clip : bool, default: False + Whether to hide arrows with points outside the axes view limits. + + .. versionadded:: 3.10 + + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs Any additional keyword arguments are delegated to - :class:`~matplotlib.collections.LineCollection` + :class:`.Line3DCollection` """ - def calc_arrows(UVW, angle=15): + def calc_arrows(UVW): # get unit direction vector perpendicular to (u, v, w) x = UVW[:, 0] y = UVW[:, 1] @@ -2596,14 +3300,17 @@ def calc_arrows(UVW, angle=15): x_p = np.divide(y, norm, where=norm != 0, out=np.zeros_like(x)) y_p = np.divide(-x, norm, where=norm != 0, out=np.ones_like(x)) # compute the two arrowhead direction unit vectors - ra = math.radians(angle) - c = math.cos(ra) - s = math.sin(ra) + rangle = math.radians(15) + c = math.cos(rangle) + s = math.sin(rangle) # construct the rotation matrices of shape (3, 3, n) + r13 = y_p * s + r32 = x_p * s + r12 = x_p * y_p * (1 - c) Rpos = np.array( - [[c + (x_p ** 2) * (1 - c), x_p * y_p * (1 - c), y_p * s], - [y_p * x_p * (1 - c), c + (y_p ** 2) * (1 - c), -x_p * s], - [-y_p * s, x_p * s, np.full_like(x_p, c)]]) + [[c + (x_p ** 2) * (1 - c), r12, r13], + [r12, c + (y_p ** 2) * (1 - c), -r32], + [-r13, r32, np.full_like(x_p, c)]]) # opposite rotation negates all the sin terms Rneg = Rpos.copy() Rneg[[0, 1, 2, 2], [2, 2, 0, 1]] *= -1 @@ -2611,64 +3318,36 @@ def calc_arrows(UVW, angle=15): Rpos_vecs = np.einsum("ij...,...j->...i", Rpos, UVW) Rneg_vecs = np.einsum("ij...,...j->...i", Rneg, UVW) # Stack into (n, 2, 3) result. - head_dirs = np.stack([Rpos_vecs, Rneg_vecs], axis=1) - return head_dirs + return np.stack([Rpos_vecs, Rneg_vecs], axis=1) had_data = self.has_data() - # handle args - argi = 6 - if len(args) < argi: - raise ValueError('Wrong number of arguments. Expected %d got %d' % - (argi, len(args))) - - # first 6 arguments are X, Y, Z, U, V, W - input_args = args[:argi] - - # extract the masks, if any - masks = [k.mask for k in input_args - if isinstance(k, np.ma.MaskedArray)] - # broadcast to match the shape - bcast = np.broadcast_arrays(*input_args, *masks) - input_args = bcast[:argi] - masks = bcast[argi:] - if masks: - # combine the masks into one - mask = reduce(np.logical_or, masks) - # put mask on and compress - input_args = [np.ma.array(k, mask=mask).compressed() - for k in input_args] - else: - input_args = [np.ravel(k) for k in input_args] + input_args = cbook._broadcast_with_masks(X, Y, Z, U, V, W, + compress=True) if any(len(v) == 0 for v in input_args): # No quivers, so just make an empty collection and return early - linec = art3d.Line3DCollection([], *args[argi:], **kwargs) + linec = art3d.Line3DCollection([], **kwargs) self.add_collection(linec) return linec shaft_dt = np.array([0., length], dtype=float) arrow_dt = shaft_dt * arrow_length_ratio - cbook._check_in_list(['tail', 'middle', 'tip'], pivot=pivot) + _api.check_in_list(['tail', 'middle', 'tip'], pivot=pivot) if pivot == 'tail': shaft_dt -= length elif pivot == 'middle': shaft_dt -= length / 2 XYZ = np.column_stack(input_args[:3]) - UVW = np.column_stack(input_args[3:argi]).astype(float) + UVW = np.column_stack(input_args[3:]).astype(float) # Normalize rows of UVW - norm = np.linalg.norm(UVW, axis=1) - - # If any row of UVW is all zeros, don't make a quiver for it - mask = norm > 0 - XYZ = XYZ[mask] if normalize: - UVW = UVW[mask] / norm[mask].reshape((-1, 1)) - else: - UVW = UVW[mask] + norm = np.linalg.norm(UVW, axis=1) + norm[norm == 0] = 1 + UVW = UVW / norm.reshape((-1, 1)) if len(XYZ) > 0: # compute the shaft lines all at once with an outer product @@ -2682,11 +3361,11 @@ def calc_arrows(UVW, angle=15): # transpose to get a list of lines heads = heads.swapaxes(0, 1) - lines = [*shafts, *heads] + lines = [*shafts, *heads[::2], *heads[1::2]] else: lines = [] - linec = art3d.Line3DCollection(lines, *args[argi:], **kwargs) + linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs) self.add_collection(linec) self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data) @@ -2696,7 +3375,7 @@ def calc_arrows(UVW, angle=15): quiver3D = quiver def voxels(self, *args, facecolors=None, edgecolors=None, shade=True, - lightsource=None, **kwargs): + lightsource=None, axlim_clip=False, **kwargs): """ ax.voxels([x, y, z,] /, filled, facecolors=None, edgecolors=None, \ **kwargs) @@ -2707,12 +3386,10 @@ def voxels(self, *args, facecolors=None, edgecolors=None, shade=True, ``filled[0, 0, 0]`` placed with its lower corner at the origin. Occluded faces are not plotted. - .. versionadded:: 2.1 - Parameters ---------- filled : 3D np.array of bool - A 3d array of values, with truthy values indicating which voxels + A 3D array of values, with truthy values indicating which voxels to fill x, y, z : 3D np.array, optional @@ -2731,24 +3408,24 @@ def voxels(self, *args, facecolors=None, edgecolors=None, shade=True, These parameters can be: - A single color value, to color all voxels the same color. This - can be either a string, or a 1D rgb/rgba array + can be either a string, or a 1D RGB/RGBA array - ``None``, the default, to use a single color for the faces, and the style default for the edges. - - A 3D ndarray of color names, with each item the color for the - corresponding voxel. The size must match the voxels. - - A 4D ndarray of rgb/rgba data, with the components along the - last axis. + - A 3D `~numpy.ndarray` of color names, with each item the color + for the corresponding voxel. The size must match the voxels. + - A 4D `~numpy.ndarray` of RGB/RGBA data, with the components + along the last axis. shade : bool, default: True - Whether to shade the facecolors. Shading is always disabled when - *cmap* is specified. + Whether to shade the facecolors. - .. versionadded:: 3.1 - - lightsource : `~matplotlib.colors.LightSource` + lightsource : `~matplotlib.colors.LightSource`, optional The lightsource to use when *shade* is True. - .. versionadded:: 3.1 + axlim_clip : bool, default: False + Whether to hide voxels with points outside the axes view limits. + + .. versionadded:: 3.10 **kwargs Additional keyword arguments to pass onto @@ -2803,11 +3480,11 @@ def _broadcast_color_arg(color, name): # 3D array of strings, or 4D array with last axis rgb if np.shape(color)[:3] != filled.shape: raise ValueError( - "When multidimensional, {} must match the shape of " - "filled".format(name)) + f"When multidimensional, {name} must match the shape " + "of filled") return color else: - raise ValueError("Invalid {} argument".format(name)) + raise ValueError(f"Invalid {name} argument") # broadcast and default on facecolors if facecolors is None: @@ -2863,7 +3540,7 @@ def permutation_matrices(n): voxel_faces[i0].append(p0 + square_rot_neg) # draw middle faces - for r1, r2 in zip(rinds[:-1], rinds[1:]): + for r1, r2 in itertools.pairwise(rinds): p1 = permute.dot([p, q, r1]) p2 = permute.dot([p, q, r2]) @@ -2902,26 +3579,23 @@ def permutation_matrices(n): # shade the faces facecolor = facecolors[coord] edgecolor = edgecolors[coord] - if shade: - normals = self._generate_normals(faces) - facecolor = self._shade_colors(facecolor, normals, lightsource) - if edgecolor is not None: - edgecolor = self._shade_colors( - edgecolor, normals, lightsource - ) poly = art3d.Poly3DCollection( - faces, facecolors=facecolor, edgecolors=edgecolor, **kwargs) + faces, facecolors=facecolor, edgecolors=edgecolor, + shade=shade, lightsource=lightsource, axlim_clip=axlim_clip, + **kwargs) self.add_collection3d(poly) polygons[coord] = poly return polygons + @_preprocess_data(replace_names=["x", "y", "z", "xerr", "yerr", "zerr"]) def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', barsabove=False, errorevery=1, ecolor=None, elinewidth=None, capsize=None, capthick=None, xlolims=False, xuplims=False, ylolims=False, yuplims=False, zlolims=False, zuplims=False, - arrow_length_ratio=.4, **kwargs): + axlim_clip=False, + **kwargs): """ Plot lines and/or markers with errorbars around them. @@ -2950,10 +3624,10 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', The format for the data points / data lines. See `.plot` for details. - Use 'none' (case insensitive) to plot errorbars without any data + Use 'none' (case-insensitive) to plot errorbars without any data markers. - ecolor : color, default: None + ecolor : :mpltype:`color`, default: None The color of the errorbar lines. If None, use the color of the line connecting the markers. @@ -2981,10 +3655,10 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', lower limits. In that case a caret symbol is used to indicate this. *lims*-arguments may be scalars, or array-likes of the same length as the errors. To use limits with inverted axes, - `~.Axes.set_xlim` or `~.Axes.set_ylim` must be called before - :meth:`errorbar`. Note the tricky parameter names: setting e.g. - *ylolims* to True means that the y-value is a *lower* limit of the - True value, so, only an *upward*-pointing arrow will be drawn! + `~.set_xlim`, `~.set_ylim`, or `~.set_zlim` must be + called before `errorbar`. Note the tricky parameter names: setting + e.g. *ylolims* to True means that the y-value is a *lower* limit of + the True value, so, only an *upward*-pointing arrow will be drawn! xuplims, yuplims, zuplims : bool, default: False Same as above, but for controlling the upper limits. @@ -2993,14 +3667,15 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', draws error bars on a subset of the data. *errorevery* =N draws error bars on the points (x[::N], y[::N], z[::N]). *errorevery* =(start, N) draws error bars on the points - (x[start::N], y[start::N], z[start::N]). e.g. errorevery=(6, 3) + (x[start::N], y[start::N], z[start::N]). e.g. *errorevery* =(6, 3) adds error bars to the data at (x[6], x[9], x[12], x[15], ...). Used to avoid overlapping error bars when two series share x-axis values. - arrow_length_ratio : float, default: 0.4 - Passed to :meth:`quiver`, the ratio of the arrow head with respect - to the quiver. + axlim_clip : bool, default: False + Whether to hide error bars that are outside the axes limits. + + .. versionadded:: 3.10 Returns ------- @@ -3016,6 +3691,9 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', Other Parameters ---------------- + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + **kwargs All other keyword arguments for styling errorbar lines are passed `~mpl_toolkits.mplot3d.art3d.Line3DCollection`. @@ -3026,34 +3704,13 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', """ had_data = self.has_data() - plot_line = (fmt.lower() != 'none') - label = kwargs.pop("label", None) - - if fmt == '': - fmt_style_kwargs = {} - else: - fmt_style_kwargs = {k: v for k, v in - zip(('linestyle', 'marker', 'color'), - _process_plot_format(fmt)) - if v is not None} - - if fmt == 'none': - # Remove alpha=0 color that _process_plot_format returns - fmt_style_kwargs.pop('color') - - if ('color' in kwargs or 'color' in fmt_style_kwargs): - base_style = {} - if 'color' in kwargs: - base_style['color'] = kwargs.pop('color') - else: - base_style = next(self._get_lines.prop_cycler) + kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D) + # Drop anything that comes in as None to use the default instead. + kwargs = {k: v for k, v in kwargs.items() if v is not None} + kwargs.setdefault('zorder', 2) - base_style['label'] = '_nolegend_' - base_style.update(fmt_style_kwargs) - if 'color' not in base_style: - base_style['color'] = 'C0' - if ecolor is None: - ecolor = base_style['color'] + self._process_unit_info([("x", x), ("y", y), ("z", z)], kwargs, + convert=False) # make sure all the args are iterable; use lists not arrays to # preserve units @@ -3064,24 +3721,48 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', if not len(x) == len(y) == len(z): raise ValueError("'x', 'y', and 'z' must have the same size") - # make the style dict for the 'normal' plot line - if 'zorder' not in kwargs: - kwargs['zorder'] = 2 - plot_line_style = { - **base_style, - **kwargs, - 'zorder': (kwargs['zorder'] - .1 if barsabove else - kwargs['zorder'] + .1), - } - - # make the style dict for the line collections (the bars) - eb_lines_style = dict(base_style) - eb_lines_style.pop('marker', None) - eb_lines_style.pop('markerfacecolor', None) - eb_lines_style.pop('markeredgewidth', None) - eb_lines_style.pop('markeredgecolor', None) - eb_lines_style.pop('linestyle', None) - eb_lines_style['color'] = ecolor + everymask = self._errorevery_to_mask(x, errorevery) + + label = kwargs.pop("label", None) + kwargs['label'] = '_nolegend_' + + # Create the main line and determine overall kwargs for child artists. + # We avoid calling self.plot() directly, or self._get_lines(), because + # that would call self._process_unit_info again, and do other indirect + # data processing. + (data_line, base_style), = self._get_lines._plot_args( + self, (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) + art3d.line_2d_to_3d(data_line, zs=z, axlim_clip=axlim_clip) + + # Do this after creating `data_line` to avoid modifying `base_style`. + if barsabove: + data_line.set_zorder(kwargs['zorder'] - .1) + else: + data_line.set_zorder(kwargs['zorder'] + .1) + + # Add line to plot, or throw it away and use it to determine kwargs. + if fmt.lower() != 'none': + self.add_line(data_line) + else: + data_line = None + # Remove alpha=0 color that _process_plot_format returns. + base_style.pop('color') + + if 'color' not in base_style: + base_style['color'] = 'C0' + if ecolor is None: + ecolor = base_style['color'] + + # Eject any line-specific information from format string, as it's not + # needed for bars or caps. + for key in ['marker', 'markersize', 'markerfacecolor', + 'markeredgewidth', 'markeredgecolor', 'markevery', + 'linestyle', 'fillstyle', 'drawstyle', 'dash_capstyle', + 'dash_joinstyle', 'solid_capstyle', 'solid_joinstyle']: + base_style.pop(key, None) + + # Make the style dict for the line collections (the bars). + eb_lines_style = {**base_style, 'color': ecolor} if elinewidth: eb_lines_style['linewidth'] = elinewidth @@ -3092,42 +3773,20 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', if key in kwargs: eb_lines_style[key] = kwargs[key] - # make the style dict for cap collections (the "hats") - eb_cap_style = dict(base_style) - # eject any marker information from format string - eb_cap_style.pop('marker', None) - eb_cap_style.pop('ls', None) - eb_cap_style['linestyle'] = 'none' + # Make the style dict for caps (the "hats"). + eb_cap_style = {**base_style, 'linestyle': 'None'} if capsize is None: - capsize = kwargs.pop('capsize', rcParams["errorbar.capsize"]) + capsize = mpl.rcParams["errorbar.capsize"] if capsize > 0: eb_cap_style['markersize'] = 2. * capsize if capthick is not None: eb_cap_style['markeredgewidth'] = capthick eb_cap_style['color'] = ecolor - if plot_line: - data_line = art3d.Line3D(x, y, z, **plot_line_style) - self.add_line(data_line) - - try: - offset, errorevery = errorevery - except TypeError: - offset = 0 - - if errorevery < 1 or int(errorevery) != errorevery: - raise ValueError( - 'errorevery must be positive integer or tuple of integers') - if int(offset) != offset: - raise ValueError("errorevery's starting index must be an integer") - - everymask = np.zeros(len(x), bool) - everymask[offset::errorevery] = True - def _apply_mask(arrays, mask): # Return, for each array in *arrays*, the elements for which *mask* # is True, without using fancy indexing. - return [[*compress(array, mask)] for array in arrays] + return [[*itertools.compress(array, mask)] for array in arrays] def _extract_errs(err, data, lomask, himask): # For separate +/- error values we need to unpack err @@ -3136,14 +3795,8 @@ def _extract_errs(err, data, lomask, himask): else: low_err, high_err = err, err - # for compatibility with the 2d errorbar function, when both upper - # and lower limits specified, we need to draw the markers / line - common_mask = (lomask == himask) & everymask - _lomask = lomask | common_mask - _himask = himask | common_mask - - lows = np.where(_lomask, data - low_err, data) - highs = np.where(_himask, data + high_err, data) + lows = np.where(lomask | ~everymask, data, data - low_err) + highs = np.where(himask | ~everymask, data, data + high_err) return lows, highs @@ -3158,6 +3811,33 @@ def _extract_errs(err, data, lomask, himask): capmarker = {0: '|', 1: '|', 2: '_'} i_xyz = {'x': 0, 'y': 1, 'z': 2} + # Calculate marker size from points to quiver length. Because these are + # not markers, and 3D Axes do not use the normal transform stack, this + # is a bit involved. Since the quiver arrows will change size as the + # scene is rotated, they are given a standard size based on viewing + # them directly in planar form. + quiversize = eb_cap_style.get('markersize', + mpl.rcParams['lines.markersize']) ** 2 + quiversize *= self.get_figure(root=True).dpi / 72 + quiversize = self.transAxes.inverted().transform([ + (0, 0), (quiversize, quiversize)]) + quiversize = np.mean(np.diff(quiversize, axis=0)) + # quiversize is now in Axes coordinates, and to convert back to data + # coordinates, we need to run it through the inverse 3D transform. For + # consistency, this uses a fixed elevation, azimuth, and roll. + with cbook._setattr_cm(self, elev=0, azim=0, roll=0): + invM = np.linalg.inv(self.get_proj()) + # elev=azim=roll=0 produces the Y-Z plane, so quiversize in 2D 'x' is + # 'y' in 3D, hence the 1 index. + quiversize = np.dot(invM, [quiversize, 0, 0, 0])[1] + # Quivers use a fixed 15-degree arrow head, so scale up the length so + # that the size corresponds to the base. In other words, this constant + # corresponds to the equation tan(15) = (base / 2) / (arrow length). + quiversize *= 1.8660254037844388 + eb_quiver_style = {**eb_cap_style, + 'length': quiversize, 'arrow_length_ratio': 1} + eb_quiver_style.pop('markersize', None) + # loop over x-, y-, and z-direction and draw relevant elements for zdir, data, err, lolims, uplims in zip( ['x', 'y', 'z'], [x, y, z], [xerr, yerr, zerr], @@ -3178,18 +3858,16 @@ def _extract_errs(err, data, lomask, himask): lolims = np.broadcast_to(lolims, len(data)).astype(bool) uplims = np.broadcast_to(uplims, len(data)).astype(bool) - nolims = ~(lolims | uplims) - # a nested list structure that expands to (xl,xh),(yl,yh),(zl,zh), # where x/y/z and l/h correspond to dimensions and low/high # positions of errorbars in a dimension we're looping over coorderr = [ - _extract_errs(err * dir_vector[i], coord, - ~lolims & everymask, ~uplims & everymask) + _extract_errs(err * dir_vector[i], coord, lolims, uplims) for i, coord in enumerate([x, y, z])] (xl, xh), (yl, yh), (zl, zh) = coorderr # draws capmarkers - flat caps orthogonal to the error bars + nolims = ~(lolims | uplims) if nolims.any() and capsize > 0: lo_caps_xyz = _apply_mask([xl, yl, zl], nolims & everymask) hi_caps_xyz = _apply_mask([xh, yh, zh], nolims & everymask) @@ -3198,35 +3876,26 @@ def _extract_errs(err, data, lomask, himask): # these markers will rotate as the viewing angle changes cap_lo = art3d.Line3D(*lo_caps_xyz, ls='', marker=capmarker[i_zdir], + axlim_clip=axlim_clip, **eb_cap_style) cap_hi = art3d.Line3D(*hi_caps_xyz, ls='', marker=capmarker[i_zdir], + axlim_clip=axlim_clip, **eb_cap_style) self.add_line(cap_lo) self.add_line(cap_hi) caplines.append(cap_lo) caplines.append(cap_hi) - if (lolims | uplims).any(): - limits = [ - _extract_errs(err*dir_vector[i], coord, uplims, lolims) - for i, coord in enumerate([x, y, z])] - - (xlo, xup), (ylo, yup), (zlo, zup) = limits - lomask = lolims & everymask - upmask = uplims & everymask - lolims_xyz = np.array(_apply_mask([xlo, ylo, zlo], upmask)) - uplims_xyz = np.array(_apply_mask([xup, yup, zup], lomask)) - lo_xyz = np.array(_apply_mask([x, y, z], upmask)) - up_xyz = np.array(_apply_mask([x, y, z], lomask)) - x0, y0, z0 = np.concatenate([lo_xyz, up_xyz], axis=-1) - dx, dy, dz = np.concatenate([lolims_xyz - lo_xyz, - uplims_xyz - up_xyz], axis=-1) - self.quiver(x0, y0, z0, dx, dy, dz, - arrow_length_ratio=arrow_length_ratio, - **eb_lines_style) + if lolims.any(): + xh0, yh0, zh0 = _apply_mask([xh, yh, zh], lolims & everymask) + self.quiver(xh0, yh0, zh0, *dir_vector, **eb_quiver_style) + if uplims.any(): + xl0, yl0, zl0 = _apply_mask([xl, yl, zl], uplims & everymask) + self.quiver(xl0, yl0, zl0, *-dir_vector, **eb_quiver_style) errline = art3d.Line3DCollection(np.array(coorderr).T, + axlim_clip=axlim_clip, **eb_lines_style) self.add_collection(errline) errlines.append(errline) @@ -3253,30 +3922,145 @@ def _digout_minmax(err_arr, coord_label): return errlines, caplines, limmarks - def get_tightbbox(self, renderer, call_axes_locator=True, - bbox_extra_artists=None, *, for_layout_only=False): + def get_tightbbox(self, renderer=None, *, call_axes_locator=True, + bbox_extra_artists=None, for_layout_only=False): ret = super().get_tightbbox(renderer, call_axes_locator=call_axes_locator, bbox_extra_artists=bbox_extra_artists, for_layout_only=for_layout_only) batch = [ret] if self._axis3don: - for axis in self._get_axis_list(): + for axis in self._axis_map.values(): if axis.get_visible(): - try: - axis_bb = axis.get_tightbbox( - renderer, - for_layout_only=for_layout_only - ) - except TypeError: - # in case downstream library has redefined axis: - axis_bb = axis.get_tightbbox(renderer) - if axis_bb: - batch.append(axis_bb) + axis_bb = martist._get_tightbbox_for_layout_only( + axis, renderer) + if axis_bb: + batch.append(axis_bb) return mtransforms.Bbox.union(batch) -docstring.interpd.update(Axes3D=artist.kwdoc(Axes3D)) -docstring.dedent_interpd(Axes3D.__init__) + @_preprocess_data() + def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-', + bottom=0, label=None, orientation='z', axlim_clip=False): + """ + Create a 3D stem plot. + + A stem plot draws lines perpendicular to a baseline, and places markers + at the heads. By default, the baseline is defined by *x* and *y*, and + stems are drawn vertically from *bottom* to *z*. + + Parameters + ---------- + x, y, z : array-like + The positions of the heads of the stems. The stems are drawn along + the *orientation*-direction from the baseline at *bottom* (in the + *orientation*-coordinate) to the heads. By default, the *x* and *y* + positions are used for the baseline and *z* for the head position, + but this can be changed by *orientation*. + + linefmt : str, default: 'C0-' + A string defining the properties of the vertical lines. Usually, + this will be a color or a color and a linestyle: + + ========= ============= + Character Line Style + ========= ============= + ``'-'`` solid line + ``'--'`` dashed line + ``'-.'`` dash-dot line + ``':'`` dotted line + ========= ============= + + Note: While it is technically possible to specify valid formats + other than color or color and linestyle (e.g. 'rx' or '-.'), this + is beyond the intention of the method and will most likely not + result in a reasonable plot. + + markerfmt : str, default: 'C0o' + A string defining the properties of the markers at the stem heads. + + basefmt : str, default: 'C3-' + A format string defining the properties of the baseline. + + bottom : float, default: 0 + The position of the baseline, in *orientation*-coordinates. + + label : str, optional + The label to use for the stems in legends. + + orientation : {'x', 'y', 'z'}, default: 'z' + The direction along which stems are drawn. + + axlim_clip : bool, default: False + Whether to hide stems that are outside the axes limits. + + .. versionadded:: 3.10 + + data : indexable object, optional + DATA_PARAMETER_PLACEHOLDER + + Returns + ------- + `.StemContainer` + The container may be treated like a tuple + (*markerline*, *stemlines*, *baseline*) + + Examples + -------- + .. plot:: gallery/mplot3d/stem3d_demo.py + """ + + from matplotlib.container import StemContainer + + had_data = self.has_data() + + _api.check_in_list(['x', 'y', 'z'], orientation=orientation) + + xlim = (np.min(x), np.max(x)) + ylim = (np.min(y), np.max(y)) + zlim = (np.min(z), np.max(z)) + + # Determine the appropriate plane for the baseline and the direction of + # stemlines based on the value of orientation. + if orientation == 'x': + basex, basexlim = y, ylim + basey, baseylim = z, zlim + lines = [[(bottom, thisy, thisz), (thisx, thisy, thisz)] + for thisx, thisy, thisz in zip(x, y, z)] + elif orientation == 'y': + basex, basexlim = x, xlim + basey, baseylim = z, zlim + lines = [[(thisx, bottom, thisz), (thisx, thisy, thisz)] + for thisx, thisy, thisz in zip(x, y, z)] + else: + basex, basexlim = x, xlim + basey, baseylim = y, ylim + lines = [[(thisx, thisy, bottom), (thisx, thisy, thisz)] + for thisx, thisy, thisz in zip(x, y, z)] + + # Determine style for stem lines. + linestyle, linemarker, linecolor = _process_plot_format(linefmt) + linestyle = mpl._val_or_rc(linestyle, 'lines.linestyle') + + # Plot everything in required order. + baseline, = self.plot(basex, basey, basefmt, zs=bottom, + zdir=orientation, label='_nolegend_') + stemlines = art3d.Line3DCollection( + lines, linestyles=linestyle, colors=linecolor, label='_nolegend_', + axlim_clip=axlim_clip) + self.add_collection(stemlines) + markerline, = self.plot(x, y, z, markerfmt, label='_nolegend_') + + stem_container = StemContainer((markerline, stemlines, baseline), + label=label) + self.add_container(stem_container) + + jx, jy, jz = art3d.juggle_axes(basexlim, baseylim, [bottom, bottom], + orientation) + self.auto_scale_xyz([*jx, *xlim], [*jy, *ylim], [*jz, *zlim], had_data) + + return stem_container + + stem3D = stem def get_test_data(delta=0.05): @@ -3293,3 +4077,124 @@ def get_test_data(delta=0.05): Y = Y * 10 Z = Z * 500 return X, Y, Z + + +class _Quaternion: + """ + Quaternions + consisting of scalar, along 1, and vector, with components along i, j, k + """ + + def __init__(self, scalar, vector): + self.scalar = scalar + self.vector = np.array(vector) + + def __neg__(self): + return self.__class__(-self.scalar, -self.vector) + + def __mul__(self, other): + """ + Product of two quaternions + i*i = j*j = k*k = i*j*k = -1 + Quaternion multiplication can be expressed concisely + using scalar and vector parts, + see + """ + return self.__class__( + self.scalar*other.scalar - np.dot(self.vector, other.vector), + self.scalar*other.vector + self.vector*other.scalar + + np.cross(self.vector, other.vector)) + + def conjugate(self): + """The conjugate quaternion -(1/2)*(q+i*q*i+j*q*j+k*q*k)""" + return self.__class__(self.scalar, -self.vector) + + @property + def norm(self): + """The 2-norm, q*q', a scalar""" + return self.scalar*self.scalar + np.dot(self.vector, self.vector) + + def normalize(self): + """Scaling such that norm equals 1""" + n = np.sqrt(self.norm) + return self.__class__(self.scalar/n, self.vector/n) + + def reciprocal(self): + """The reciprocal, 1/q = q'/(q*q') = q' / norm(q)""" + n = self.norm + return self.__class__(self.scalar/n, -self.vector/n) + + def __div__(self, other): + return self*other.reciprocal() + + __truediv__ = __div__ + + def rotate(self, v): + # Rotate the vector v by the quaternion q, i.e., + # calculate (the vector part of) q*v/q + v = self.__class__(0, v) + v = self*v/self + return v.vector + + def __eq__(self, other): + return (self.scalar == other.scalar) and (self.vector == other.vector).all + + def __repr__(self): + return "_Quaternion({}, {})".format(repr(self.scalar), repr(self.vector)) + + @classmethod + def rotate_from_to(cls, r1, r2): + """ + The quaternion for the shortest rotation from vector r1 to vector r2 + i.e., q = sqrt(r2*r1'), normalized. + If r1 and r2 are antiparallel, then the result is ambiguous; + a normal vector will be returned, and a warning will be issued. + """ + k = np.cross(r1, r2) + nk = np.linalg.norm(k) + th = np.arctan2(nk, np.dot(r1, r2)) + th /= 2 + if nk == 0: # r1 and r2 are parallel or anti-parallel + if np.dot(r1, r2) < 0: + warnings.warn("Rotation defined by anti-parallel vectors is ambiguous") + k = np.zeros(3) + k[np.argmin(r1*r1)] = 1 # basis vector most perpendicular to r1-r2 + k = np.cross(r1, k) + k = k / np.linalg.norm(k) # unit vector normal to r1-r2 + q = cls(0, k) + else: + q = cls(1, [0, 0, 0]) # = 1, no rotation + else: + q = cls(np.cos(th), k*np.sin(th)/nk) + return q + + @classmethod + def from_cardan_angles(cls, elev, azim, roll): + """ + Converts the angles to a quaternion + q = exp((roll/2)*e_x)*exp((elev/2)*e_y)*exp((-azim/2)*e_z) + i.e., the angles are a kind of Tait-Bryan angles, -z,y',x". + The angles should be given in radians, not degrees. + """ + ca, sa = np.cos(azim/2), np.sin(azim/2) + ce, se = np.cos(elev/2), np.sin(elev/2) + cr, sr = np.cos(roll/2), np.sin(roll/2) + + qw = ca*ce*cr + sa*se*sr + qx = ca*ce*sr - sa*se*cr + qy = ca*se*cr + sa*ce*sr + qz = ca*se*sr - sa*ce*cr + return cls(qw, [qx, qy, qz]) + + def as_cardan_angles(self): + """ + The inverse of `from_cardan_angles()`. + Note that the angles returned are in radians, not degrees. + The angles are not sensitive to the quaternion's norm(). + """ + qw = self.scalar + qx, qy, qz = self.vector[..., :] + azim = np.arctan2(2*(-qw*qz+qx*qy), qw*qw+qx*qx-qy*qy-qz*qz) + elev = np.arcsin(np.clip(2*(qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz), -1, 1)) + roll = np.arctan2(2*(qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) + return elev, azim, roll diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 110c2a9290fb..4da5031b990c 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -2,15 +2,18 @@ # Created: 23 Sep 2005 # Parts rewritten by Reinier Heeres +import inspect + import numpy as np -import matplotlib.transforms as mtransforms +import matplotlib as mpl from matplotlib import ( - artist, lines as mlines, axis as maxis, patches as mpatches, rcParams) + _api, artist, lines as mlines, axis as maxis, patches as mpatches, + transforms as mtransforms, colors as mcolors) from . import art3d, proj3d -def move_from_center(coord, centers, deltas, axmask=(True, True, True)): +def _move_from_center(coord, centers, deltas, axmask=(True, True, True)): """ For each coordinate where *axmask* is True, move *coord* away from *centers* by *deltas*. @@ -19,7 +22,7 @@ def move_from_center(coord, centers, deltas, axmask=(True, True, True)): return coord + axmask * np.copysign(1, coord - centers) * deltas -def tick_update_position(tick, tickxs, tickys, labelpos): +def _tick_update_position(tick, tickxs, tickys, labelpos): """Update tick line and label position and style.""" tick.label1.set_position(labelpos) @@ -29,47 +32,69 @@ def tick_update_position(tick, tickxs, tickys, labelpos): tick.tick1line.set_linestyle('-') tick.tick1line.set_marker('') tick.tick1line.set_data(tickxs, tickys) - tick.gridline.set_data(0, 0) + tick.gridline.set_data([0], [0]) class Axis(maxis.XAxis): """An Axis class for the 3D plots.""" # These points from the unit cube make up the x, y and z-planes _PLANES = ( - (0, 3, 7, 4), (1, 2, 6, 5), # yz planes - (0, 1, 5, 4), (3, 2, 6, 7), # xz planes - (0, 1, 2, 3), (4, 5, 6, 7), # xy planes + (0, 3, 7, 4), (1, 2, 6, 5), # yz planes + (0, 1, 5, 4), (3, 2, 6, 7), # xz planes + (0, 1, 2, 3), (4, 5, 6, 7), # xy planes ) # Some properties for the axes _AXINFO = { - 'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2), - 'color': (0.95, 0.95, 0.95, 0.5)}, - 'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2), - 'color': (0.90, 0.90, 0.90, 0.5)}, - 'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1), - 'color': (0.925, 0.925, 0.925, 0.5)}, + 'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2)}, + 'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2)}, + 'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1)}, } - def __init__(self, adir, v_intervalx, d_intervalx, axes, *args, - rotate_label=None, **kwargs): - # adir identifies which axes this is - self.adir = adir + def _old_init(self, adir, v_intervalx, d_intervalx, axes, *args, + rotate_label=None, **kwargs): + return locals() + + def _new_init(self, axes, *, rotate_label=None, **kwargs): + return locals() + + def __init__(self, *args, **kwargs): + params = _api.select_matching_signature( + [self._old_init, self._new_init], *args, **kwargs) + if "adir" in params: + _api.warn_deprecated( + "3.6", message=f"The signature of 3D Axis constructors has " + f"changed in %(since)s; the new signature is " + f"{inspect.signature(type(self).__init__)}", pending=True) + if params["adir"] != self.axis_name: + raise ValueError(f"Cannot instantiate {type(self).__name__} " + f"with adir={params['adir']!r}") + axes = params["axes"] + rotate_label = params["rotate_label"] + args = params.get("args", ()) + kwargs = params["kwargs"] + + name = self.axis_name + + self._label_position = 'default' + self._tick_position = 'default' # This is a temporary member variable. # Do not depend on this existing in future releases! - self._axinfo = self._AXINFO[adir].copy() - if rcParams['_internal.classic_mode']: + self._axinfo = self._AXINFO[name].copy() + # Common parts + self._axinfo.update({ + 'label': {'va': 'center', 'ha': 'center', + 'rotation_mode': 'anchor'}, + 'color': mpl.rcParams[f'axes3d.{name}axis.panecolor'], + 'tick': { + 'inward_factor': 0.2, + 'outward_factor': 0.1, + }, + }) + + if mpl.rcParams['_internal.classic_mode']: self._axinfo.update({ - 'label': {'va': 'center', 'ha': 'center'}, - 'tick': { - 'inward_factor': 0.2, - 'outward_factor': 0.1, - 'linewidth': { - True: rcParams['lines.linewidth'], # major - False: rcParams['lines.linewidth'], # minor - } - }, 'axisline': {'linewidth': 0.75, 'color': (0, 0, 0, 1)}, 'grid': { 'color': (0.9, 0.9, 0.9, 1), @@ -77,40 +102,50 @@ def __init__(self, adir, v_intervalx, d_intervalx, axes, *args, 'linestyle': '-', }, }) + self._axinfo['tick'].update({ + 'linewidth': { + True: mpl.rcParams['lines.linewidth'], # major + False: mpl.rcParams['lines.linewidth'], # minor + } + }) else: self._axinfo.update({ - 'label': {'va': 'center', 'ha': 'center'}, - 'tick': { - 'inward_factor': 0.2, - 'outward_factor': 0.1, - 'linewidth': { - True: ( # major - rcParams['xtick.major.width'] if adir in 'xz' else - rcParams['ytick.major.width']), - False: ( # minor - rcParams['xtick.minor.width'] if adir in 'xz' else - rcParams['ytick.minor.width']), - } - }, 'axisline': { - 'linewidth': rcParams['axes.linewidth'], - 'color': rcParams['axes.edgecolor'], + 'linewidth': mpl.rcParams['axes.linewidth'], + 'color': mpl.rcParams['axes.edgecolor'], }, 'grid': { - 'color': rcParams['grid.color'], - 'linewidth': rcParams['grid.linewidth'], - 'linestyle': rcParams['grid.linestyle'], + 'color': mpl.rcParams['grid.color'], + 'linewidth': mpl.rcParams['grid.linewidth'], + 'linestyle': mpl.rcParams['grid.linestyle'], }, }) + self._axinfo['tick'].update({ + 'linewidth': { + True: ( # major + mpl.rcParams['xtick.major.width'] if name in 'xz' + else mpl.rcParams['ytick.major.width']), + False: ( # minor + mpl.rcParams['xtick.minor.width'] if name in 'xz' + else mpl.rcParams['ytick.minor.width']), + } + }) super().__init__(axes, *args, **kwargs) # data and viewing intervals for this direction - self.d_interval = d_intervalx - self.v_interval = v_intervalx + if "d_intervalx" in params: + self.set_data_interval(*params["d_intervalx"]) + if "v_intervalx" in params: + self.set_view_interval(*params["v_intervalx"]) self.set_rotate_label(rotate_label) + self._init3d() # Inline after init3d deprecation elapses. + + __init__.__signature__ = inspect.signature(_new_init) + adir = _api.deprecated("3.6", pending=True)( + property(lambda self: self.axis_name)) - def init3d(self): + def _init3d(self): self.line = mlines.Line2D( xdata=(0, 0), ydata=(0, 0), linewidth=self._axinfo['axisline']['linewidth'], @@ -118,9 +153,7 @@ def init3d(self): antialiased=True) # Store dummy data in Polygon object - self.pane = mpatches.Polygon( - np.array([[0, 0], [0, 1], [1, 0], [0, 0]]), - closed=False, alpha=0.8, facecolor='k', edgecolor='k') + self.pane = mpatches.Polygon([[0, 0], [0, 1]], closed=False) self.set_pane_color(self._axinfo['color']) self.axes._set_artist_props(self.line) @@ -133,6 +166,10 @@ def init3d(self): self.label._transform = self.axes.transData self.offsetText._transform = self.axes.transData + @_api.deprecated("3.6", pending=True) + def init3d(self): # After deprecation elapses, inline _init3d to __init__. + self._init3d() + def get_major_ticks(self, numticks=None): ticks = super().get_major_ticks(numticks) for t in ticks: @@ -149,14 +186,66 @@ def get_minor_ticks(self, numticks=None): obj.set_transform(self.axes.transData) return ticks - def set_pane_pos(self, xys): - xys = np.asarray(xys) - xys = xys[:, :2] - self.pane.xy = xys - self.stale = True + def set_ticks_position(self, position): + """ + Set the ticks position. + + Parameters + ---------- + position : {'lower', 'upper', 'both', 'default', 'none'} + The position of the bolded axis lines, ticks, and tick labels. + """ + _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'], + position=position) + self._tick_position = position + + def get_ticks_position(self): + """ + Get the ticks position. + + Returns + ------- + str : {'lower', 'upper', 'both', 'default', 'none'} + The position of the bolded axis lines, ticks, and tick labels. + """ + return self._tick_position + + def set_label_position(self, position): + """ + Set the label position. + + Parameters + ---------- + position : {'lower', 'upper', 'both', 'default', 'none'} + The position of the axis label. + """ + _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'], + position=position) + self._label_position = position + + def get_label_position(self): + """ + Get the label position. + + Returns + ------- + str : {'lower', 'upper', 'both', 'default', 'none'} + The position of the axis label. + """ + return self._label_position - def set_pane_color(self, color): - """Set pane color to a RGBA tuple.""" + def set_pane_color(self, color, alpha=None): + """ + Set pane color. + + Parameters + ---------- + color : :mpltype:`color` + Color for axis pane. + alpha : float, optional + Alpha value for axis pane. If None, base it on *color*. + """ + color = mcolors.to_rgba(color, alpha) self._axinfo['color'] = color self.pane.set_edgecolor(color) self.pane.set_facecolor(color) @@ -177,109 +266,224 @@ def get_rotate_label(self, text): else: return len(text) > 4 - def _get_coord_info(self, renderer): + def _get_coord_info(self): mins, maxs = np.array([ self.axes.get_xbound(), self.axes.get_ybound(), self.axes.get_zbound(), ]).T - centers = (maxs + mins) / 2. - deltas = (maxs - mins) / 12. - mins = mins - deltas / 4. - maxs = maxs + deltas / 4. - - vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] - tc = self.axes.tunit_cube(vals, renderer.M) - avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2] - for p1, p2, p3, p4 in self._PLANES] - highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)]) - - return mins, maxs, centers, deltas, tc, highs - - def draw_pane(self, renderer): - renderer.open_group('pane3d', gid=self.get_gid()) - - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) + # Project the bounds along the current position of the cube: + bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] + bounds_proj = self.axes._transformed_cube(bounds) + + # Determine which one of the parallel planes are higher up: + means_z0 = np.zeros(3) + means_z1 = np.zeros(3) + for i in range(3): + means_z0[i] = np.mean(bounds_proj[self._PLANES[2 * i], 2]) + means_z1[i] = np.mean(bounds_proj[self._PLANES[2 * i + 1], 2]) + highs = means_z0 < means_z1 + + # Special handling for edge-on views + equals = np.abs(means_z0 - means_z1) <= np.finfo(float).eps + if np.sum(equals) == 2: + vertical = np.where(~equals)[0][0] + if vertical == 2: # looking at XY plane + highs = np.array([True, True, highs[2]]) + elif vertical == 1: # looking at XZ plane + highs = np.array([True, highs[1], False]) + elif vertical == 0: # looking at YZ plane + highs = np.array([highs[0], False, False]) + + return mins, maxs, bounds_proj, highs + + def _calc_centers_deltas(self, maxs, mins): + centers = 0.5 * (maxs + mins) + # In mpl3.8, the scale factor was 1/12. mpl3.9 changes this to + # 1/12 * 24/25 = 0.08 to compensate for the change in automargin + # behavior and keep appearance the same. The 24/25 factor is from the + # 1/48 padding added to each side of the axis in mpl3.8. + scale = 0.08 + deltas = (maxs - mins) * scale + return centers, deltas + + def _get_axis_line_edge_points(self, minmax, maxmin, position=None): + """Get the edge points for the black bolded axis line.""" + # When changing vertical axis some of the axes has to be + # moved to the other plane so it looks the same as if the z-axis + # was the vertical axis. + mb = [minmax, maxmin] # line from origin to nearest corner to camera + mb_rev = mb[::-1] + mm = [[mb, mb_rev, mb_rev], [mb_rev, mb_rev, mb], [mb, mb, mb]] + mm = mm[self.axes._vertical_axis][self._axinfo["i"]] + + juggled = self._axinfo["juggled"] + edge_point_0 = mm[0].copy() # origin point + + if ((position == 'lower' and mm[1][juggled[-1]] < mm[0][juggled[-1]]) or + (position == 'upper' and mm[1][juggled[-1]] > mm[0][juggled[-1]])): + edge_point_0[juggled[-1]] = mm[1][juggled[-1]] + else: + edge_point_0[juggled[0]] = mm[1][juggled[0]] + + edge_point_1 = edge_point_0.copy() + edge_point_1[juggled[1]] = mm[1][juggled[1]] + + return edge_point_0, edge_point_1 + + def _get_all_axis_line_edge_points(self, minmax, maxmin, axis_position=None): + # Determine edge points for the axis lines + edgep1s = [] + edgep2s = [] + position = [] + if axis_position in (None, 'default'): + edgep1, edgep2 = self._get_axis_line_edge_points(minmax, maxmin) + edgep1s = [edgep1] + edgep2s = [edgep2] + position = ['default'] + else: + edgep1_l, edgep2_l = self._get_axis_line_edge_points(minmax, maxmin, + position='lower') + edgep1_u, edgep2_u = self._get_axis_line_edge_points(minmax, maxmin, + position='upper') + if axis_position in ('lower', 'both'): + edgep1s.append(edgep1_l) + edgep2s.append(edgep2_l) + position.append('lower') + if axis_position in ('upper', 'both'): + edgep1s.append(edgep1_u) + edgep2s.append(edgep2_u) + position.append('upper') + return edgep1s, edgep2s, position + + def _get_tickdir(self, position): + """ + Get the direction of the tick. + + Parameters + ---------- + position : str, optional : {'upper', 'lower', 'default'} + The position of the axis. + + Returns + ------- + tickdir : int + Index which indicates which coordinate the tick line will + align with. + """ + _api.check_in_list(('upper', 'lower', 'default'), position=position) + + # TODO: Move somewhere else where it's triggered less: + tickdirs_base = [v["tickdir"] for v in self._AXINFO.values()] # default + elev_mod = np.mod(self.axes.elev + 180, 360) - 180 + azim_mod = np.mod(self.axes.azim, 360) + if position == 'upper': + if elev_mod >= 0: + tickdirs_base = [2, 2, 0] + else: + tickdirs_base = [1, 0, 0] + if 0 <= azim_mod < 180: + tickdirs_base[2] = 1 + elif position == 'lower': + if elev_mod >= 0: + tickdirs_base = [1, 0, 1] + else: + tickdirs_base = [2, 2, 1] + if 0 <= azim_mod < 180: + tickdirs_base[2] = 0 + info_i = [v["i"] for v in self._AXINFO.values()] + + i = self._axinfo["i"] + vert_ax = self.axes._vertical_axis + j = vert_ax - 2 + # default: tickdir = [[1, 2, 1], [2, 2, 0], [1, 0, 0]][vert_ax][i] + tickdir = np.roll(info_i, -j)[np.roll(tickdirs_base, j)][i] + return tickdir + + def active_pane(self): + mins, maxs, tc, highs = self._get_coord_info() info = self._axinfo index = info['i'] if not highs[index]: + loc = mins[index] plane = self._PLANES[2 * index] else: + loc = maxs[index] plane = self._PLANES[2 * index + 1] - xys = [tc[p] for p in plane] - self.set_pane_pos(xys) - self.pane.draw(renderer) + xys = np.array([tc[p] for p in plane]) + return xys, loc + + def draw_pane(self, renderer): + """ + Draw pane. + Parameters + ---------- + renderer : `~matplotlib.backend_bases.RendererBase` subclass + """ + renderer.open_group('pane3d', gid=self.get_gid()) + xys, loc = self.active_pane() + self.pane.xy = xys[:, :2] + self.pane.draw(renderer) renderer.close_group('pane3d') - @artist.allow_rasterization - def draw(self, renderer): - self.label._transform = self.axes.transData - renderer.open_group('axis3d', gid=self.get_gid()) + def _axmask(self): + axmask = [True, True, True] + axmask[self._axinfo["i"]] = False + return axmask + def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, + deltas_per_point, pos): ticks = self._update_ticks() - info = self._axinfo - index = info['i'] - - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) - - # Determine grid lines - minmax = np.where(highs, maxs, mins) - maxmin = np.where(highs, mins, maxs) - - # Draw main axis line - juggled = info['juggled'] - edgep1 = minmax.copy() - edgep1[juggled[0]] = maxmin[juggled[0]] - - edgep2 = edgep1.copy() - edgep2[juggled[1]] = maxmin[juggled[1]] - pep = np.asarray( - proj3d.proj_trans_points([edgep1, edgep2], renderer.M)) - centpt = proj3d.proj_transform(*centers, renderer.M) - self.line.set_data(pep[0], pep[1]) - self.line.draw(renderer) - - # Grid points where the planes meet - xyz0 = np.tile(minmax, (len(ticks), 1)) - xyz0[:, index] = [tick.get_loc() for tick in ticks] + index = info["i"] + juggled = info["juggled"] + + mins, maxs, tc, highs = self._get_coord_info() + centers, deltas = self._calc_centers_deltas(maxs, mins) + + # Draw ticks: + tickdir = self._get_tickdir(pos) + tickdelta = deltas[tickdir] if highs[tickdir] else -deltas[tickdir] + + tick_info = info['tick'] + tick_out = tick_info['outward_factor'] * tickdelta + tick_in = tick_info['inward_factor'] * tickdelta + tick_lw = tick_info['linewidth'] + edgep1_tickdir = edgep1[tickdir] + out_tickdir = edgep1_tickdir + tick_out + in_tickdir = edgep1_tickdir - tick_in + + default_label_offset = 8. # A rough estimate + points = deltas_per_point * deltas + for tick in ticks: + # Get tick line positions + pos = edgep1.copy() + pos[index] = tick.get_loc() + pos[tickdir] = out_tickdir + x1, y1, z1 = proj3d.proj_transform(*pos, self.axes.M) + pos[tickdir] = in_tickdir + x2, y2, z2 = proj3d.proj_transform(*pos, self.axes.M) - # Draw labels - # The transAxes transform is used because the Text object - # rotates the text relative to the display coordinate system. - # Therefore, if we want the labels to remain parallel to the - # axis regardless of the aspect ratio, we need to convert the - # edge points of the plane to display coordinates and calculate - # an angle from that. - # TODO: Maybe Text objects should handle this themselves? - dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) - - self.axes.transAxes.transform([pep[0:2, 0]]))[0] + # Get position of label + labeldeltas = (tick.get_pad() + default_label_offset) * points - lxyz = 0.5 * (edgep1 + edgep2) + pos[tickdir] = edgep1_tickdir + pos = _move_from_center(pos, centers, labeldeltas, self._axmask()) + lx, ly, lz = proj3d.proj_transform(*pos, self.axes.M) - # A rough estimate; points are ambiguous since 3D plots rotate - ax_scale = self.axes.bbox.size / self.figure.bbox.size - ax_inches = np.multiply(ax_scale, self.figure.get_size_inches()) - ax_points_estimate = sum(72. * ax_inches) - deltas_per_point = 48 / ax_points_estimate - default_offset = 21. - labeldeltas = ( - (self.labelpad + default_offset) * deltas_per_point * deltas) - axmask = [True, True, True] - axmask[index] = False - lxyz = move_from_center(lxyz, centers, labeldeltas, axmask) - tlx, tly, tlz = proj3d.proj_transform(*lxyz, renderer.M) - self.label.set_position((tlx, tly)) - if self.get_rotate_label(self.label.get_text()): - angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) - self.label.set_rotation(angle) - self.label.set_va(info['label']['va']) - self.label.set_ha(info['label']['ha']) - self.label.draw(renderer) + _tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly)) + tick.tick1line.set_linewidth(tick_lw[tick._major]) + tick.draw(renderer) - # Draw Offset text + def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, centers, + highs, pep, dx, dy): + # Get general axis information: + info = self._axinfo + index = info["i"] + juggled = info["juggled"] + tickdir = info["tickdir"] # Which of the two edge points do we want to # use for locating the offset text? @@ -290,8 +494,9 @@ def draw(self, renderer): outeredgep = edgep2 outerindex = 1 - pos = move_from_center(outeredgep, centers, labeldeltas, axmask) - olx, oly, olz = proj3d.proj_transform(*pos, renderer.M) + pos = _move_from_center(outeredgep, centers, labeldeltas, + self._axmask()) + olx, oly, olz = proj3d.proj_transform(*pos, self.axes.M) self.offsetText.set_text(self.major.formatter.get_offset()) self.offsetText.set_position((olx, oly)) angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) @@ -300,7 +505,7 @@ def draw(self, renderer): # the alignment point is used as the "fulcrum" for rotation. self.offsetText.set_rotation_mode('anchor') - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Note: the following statement for determining the proper alignment of # the offset text. This was determined entirely by trial-and-error # and should not be in any way considered as "the way". There are @@ -309,13 +514,14 @@ def draw(self, renderer): # using the wrong reference points). # # (TT, FF, TF, FT) are the shorthand for the tuple of - # (centpt[info['tickdir']] <= pep[info['tickdir'], outerindex], + # (centpt[tickdir] <= pep[tickdir, outerindex], # centpt[index] <= pep[index, outerindex]) # # Three-letters (e.g., TFT, FTT) are short-hand for the array of bools # from the variable 'highs'. # --------------------------------------------------------------------- - if centpt[info['tickdir']] > pep[info['tickdir'], outerindex]: + centpt = proj3d.proj_transform(*centers, self.axes.M) + if centpt[tickdir] > pep[tickdir, outerindex]: # if FT and if highs has an even number of Trues if (centpt[index] <= pep[index, outerindex] and np.count_nonzero(highs) % 2 == 0): @@ -333,10 +539,7 @@ def draw(self, renderer): if (centpt[index] > pep[index, outerindex] and np.count_nonzero(highs) % 2 == 0): # Usually mean align left, except if it is axis 2 - if index == 2: - align = 'right' - else: - align = 'left' + align = 'right' if index == 2 else 'left' else: # The TT case align = 'right' @@ -345,7 +548,109 @@ def draw(self, renderer): self.offsetText.set_ha(align) self.offsetText.draw(renderer) - if self.axes._draw_grid and len(ticks): + def _draw_labels(self, renderer, edgep1, edgep2, labeldeltas, centers, dx, dy): + label = self._axinfo["label"] + + # Draw labels + lxyz = 0.5 * (edgep1 + edgep2) + lxyz = _move_from_center(lxyz, centers, labeldeltas, self._axmask()) + tlx, tly, tlz = proj3d.proj_transform(*lxyz, self.axes.M) + self.label.set_position((tlx, tly)) + if self.get_rotate_label(self.label.get_text()): + angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) + self.label.set_rotation(angle) + self.label.set_va(label['va']) + self.label.set_ha(label['ha']) + self.label.set_rotation_mode(label['rotation_mode']) + self.label.draw(renderer) + + @artist.allow_rasterization + def draw(self, renderer): + self.label._transform = self.axes.transData + self.offsetText._transform = self.axes.transData + renderer.open_group("axis3d", gid=self.get_gid()) + + # Get general axis information: + mins, maxs, tc, highs = self._get_coord_info() + centers, deltas = self._calc_centers_deltas(maxs, mins) + + # Calculate offset distances + # A rough estimate; points are ambiguous since 3D plots rotate + reltoinches = self.get_figure(root=False).dpi_scale_trans.inverted() + ax_inches = reltoinches.transform(self.axes.bbox.size) + ax_points_estimate = sum(72. * ax_inches) + deltas_per_point = 48 / ax_points_estimate + default_offset = 21. + labeldeltas = (self.labelpad + default_offset) * deltas_per_point * deltas + + # Determine edge points for the axis lines + minmax = np.where(highs, maxs, mins) # "origin" point + maxmin = np.where(~highs, maxs, mins) # "opposite" corner near camera + + for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points( + minmax, maxmin, self._tick_position)): + # Project the edge points along the current position + pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M) + pep = np.asarray(pep) + + # The transAxes transform is used because the Text object + # rotates the text relative to the display coordinate system. + # Therefore, if we want the labels to remain parallel to the + # axis regardless of the aspect ratio, we need to convert the + # edge points of the plane to display coordinates and calculate + # an angle from that. + # TODO: Maybe Text objects should handle this themselves? + dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) - + self.axes.transAxes.transform([pep[0:2, 0]]))[0] + + # Draw the lines + self.line.set_data(pep[0], pep[1]) + self.line.draw(renderer) + + # Draw ticks + self._draw_ticks(renderer, edgep1, centers, deltas, highs, + deltas_per_point, pos) + + # Draw Offset text + self._draw_offset_text(renderer, edgep1, edgep2, labeldeltas, + centers, highs, pep, dx, dy) + + for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points( + minmax, maxmin, self._label_position)): + # See comments above + pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M) + pep = np.asarray(pep) + dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) - + self.axes.transAxes.transform([pep[0:2, 0]]))[0] + + # Draw labels + self._draw_labels(renderer, edgep1, edgep2, labeldeltas, centers, dx, dy) + + renderer.close_group('axis3d') + self.stale = False + + @artist.allow_rasterization + def draw_grid(self, renderer): + if not self.axes._draw_grid: + return + + renderer.open_group("grid3d", gid=self.get_gid()) + + ticks = self._update_ticks() + if len(ticks): + # Get general axis information: + info = self._axinfo + index = info["i"] + + mins, maxs, tc, highs = self._get_coord_info() + + minmax = np.where(highs, maxs, mins) + maxmin = np.where(~highs, maxs, mins) + + # Grid points where the planes meet + xyz0 = np.tile(minmax, (len(ticks), 1)) + xyz0[:, index] = [tick.get_loc() for tick in ticks] + # Grid lines go from the end of one plane through the plane # intersection (at xyz0) to the end of the other plane. The first # point (0) differs along dimension index-2 and the last (2) along @@ -354,62 +659,26 @@ def draw(self, renderer): lines[:, 0, index - 2] = maxmin[index - 2] lines[:, 2, index - 1] = maxmin[index - 1] self.gridlines.set_segments(lines) - self.gridlines.set_color(info['grid']['color']) - self.gridlines.set_linewidth(info['grid']['linewidth']) - self.gridlines.set_linestyle(info['grid']['linestyle']) - self.gridlines.draw(renderer, project=True) - - # Draw ticks - tickdir = info['tickdir'] - tickdelta = deltas[tickdir] - if highs[tickdir]: - ticksign = 1 - else: - ticksign = -1 + gridinfo = info['grid'] + self.gridlines.set_color(gridinfo['color']) + self.gridlines.set_linewidth(gridinfo['linewidth']) + self.gridlines.set_linestyle(gridinfo['linestyle']) + self.gridlines.do_3d_projection() + self.gridlines.draw(renderer) - for tick in ticks: - # Get tick line positions - pos = edgep1.copy() - pos[index] = tick.get_loc() - pos[tickdir] = ( - edgep1[tickdir] - + info['tick']['outward_factor'] * ticksign * tickdelta) - x1, y1, z1 = proj3d.proj_transform(*pos, renderer.M) - pos[tickdir] = ( - edgep1[tickdir] - - info['tick']['inward_factor'] * ticksign * tickdelta) - x2, y2, z2 = proj3d.proj_transform(*pos, renderer.M) - - # Get position of label - default_offset = 8. # A rough estimate - labeldeltas = ( - (tick.get_pad() + default_offset) * deltas_per_point * deltas) - - axmask = [True, True, True] - axmask[index] = False - pos[tickdir] = edgep1[tickdir] - pos = move_from_center(pos, centers, labeldeltas, axmask) - lx, ly, lz = proj3d.proj_transform(*pos, renderer.M) - - tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly)) - tick.tick1line.set_linewidth( - info['tick']['linewidth'][tick._major]) - tick.draw(renderer) - - renderer.close_group('axis3d') - self.stale = False + renderer.close_group('grid3d') # TODO: Get this to work (more) properly when mplot3d supports the # transforms framework. - def get_tightbbox(self, renderer, *, for_layout_only=False): - # inherited docstring + def get_tightbbox(self, renderer=None, *, for_layout_only=False): + # docstring inherited if not self.get_visible(): return # We have to directly access the internal data structures # (and hope they are up to date) because at draw time we # shift the ticks and their labels around in (x, y) space # based on the projection, the current view port, and their - # position in 3D space. If we extend the transforms framework + # position in 3D space. If we extend the transforms framework # into 3D we would not need to do this different book keeping # than we do in the normal axis major_locs = self.get_majorticklocs() @@ -436,7 +705,7 @@ def get_tightbbox(self, renderer, *, for_layout_only=False): ticks = ticks_to_draw - bb_1, bb_2 = self._get_tick_bboxes(ticks, renderer) + bb_1, bb_2 = self._get_ticklabel_bboxes(ticks, renderer) other = [] if self.line.get_visible(): @@ -447,27 +716,18 @@ def get_tightbbox(self, renderer, *, for_layout_only=False): return mtransforms.Bbox.union([*bb_1, *bb_2, *other]) - @property - def d_interval(self): - return self.get_data_interval() - - @d_interval.setter - def d_interval(self, minmax): - self.set_data_interval(*minmax) - - @property - def v_interval(self): - return self.get_view_interval() - - @v_interval.setter - def v_interval(self, minmax): - self.set_view_interval(*minmax) - - -# Use classes to look at different data limits + d_interval = _api.deprecated( + "3.6", alternative="get_data_interval", pending=True)( + property(lambda self: self.get_data_interval(), + lambda self, minmax: self.set_data_interval(*minmax))) + v_interval = _api.deprecated( + "3.6", alternative="get_view_interval", pending=True)( + property(lambda self: self.get_view_interval(), + lambda self, minmax: self.set_view_interval(*minmax))) class XAxis(Axis): + axis_name = "x" get_view_interval, set_view_interval = maxis._make_getset_interval( "view", "xy_viewLim", "intervalx") get_data_interval, set_data_interval = maxis._make_getset_interval( @@ -475,6 +735,7 @@ class XAxis(Axis): class YAxis(Axis): + axis_name = "y" get_view_interval, set_view_interval = maxis._make_getset_interval( "view", "xy_viewLim", "intervaly") get_data_interval, set_data_interval = maxis._make_getset_interval( @@ -482,6 +743,7 @@ class YAxis(Axis): class ZAxis(Axis): + axis_name = "z" get_view_interval, set_view_interval = maxis._make_getset_interval( "view", "zz_viewLim", "intervalx") get_data_interval, set_data_interval = maxis._make_getset_interval( diff --git a/lib/mpl_toolkits/mplot3d/meson.build b/lib/mpl_toolkits/mplot3d/meson.build new file mode 100644 index 000000000000..2d9cade6c93c --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + 'art3d.py', + 'axes3d.py', + 'axis3d.py', + 'proj3d.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/mplot3d') + +subdir('tests') diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index cce89c5f3554..87c59ae05714 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -3,30 +3,8 @@ """ import numpy as np -import numpy.linalg as linalg - -def _line2d_seg_dist(p1, p2, p0): - """ - Return the distance(s) from line defined by p1 - p2 to point(s) p0. - - p0[0] = x(s) - p0[1] = y(s) - - intersection point p = p1 + u*(p2-p1) - and intersection point lies within segment if u is between 0 and 1 - """ - - x21 = p2[0] - p1[0] - y21 = p2[1] - p1[1] - x01 = np.asarray(p0[0]) - p1[0] - y01 = np.asarray(p0[1]) - p1[1] - - u = (x01*x21 + y01*y21) / (x21**2 + y21**2) - u = np.clip(u, 0, 1) - d = np.hypot(x01 - u*x21, y01 - u*y21) - - return d +from matplotlib import _api def world_transformation(xmin, xmax, @@ -45,131 +23,214 @@ def world_transformation(xmin, xmax, dy /= ay dz /= az - return np.array([[1/dx, 0, 0, -xmin/dx], - [0, 1/dy, 0, -ymin/dy], - [0, 0, 1/dz, -zmin/dz], - [0, 0, 0, 1]]) - - -def view_transformation(E, R, V): - n = (E - R) - ## new -# n /= np.linalg.norm(n) -# u = np.cross(V, n) -# u /= np.linalg.norm(u) -# v = np.cross(n, u) -# Mr = np.diag([1.] * 4) -# Mt = np.diag([1.] * 4) -# Mr[:3,:3] = u, v, n -# Mt[:3,-1] = -E - ## end new - - ## old - n = n / np.linalg.norm(n) - u = np.cross(V, n) - u = u / np.linalg.norm(u) - v = np.cross(n, u) - Mr = [[u[0], u[1], u[2], 0], - [v[0], v[1], v[2], 0], - [n[0], n[1], n[2], 0], - [0, 0, 0, 1]] - # - Mt = [[1, 0, 0, -E[0]], - [0, 1, 0, -E[1]], - [0, 0, 1, -E[2]], - [0, 0, 0, 1]] - ## end old - - return np.dot(Mr, Mt) - - -def persp_transformation(zfront, zback): - a = (zfront+zback)/(zfront-zback) - b = -2*(zfront*zback)/(zfront-zback) - return np.array([[1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, a, b], - [0, 0, -1, 0]]) - - -def ortho_transformation(zfront, zback): + return np.array([[1/dx, 0, 0, -xmin/dx], + [ 0, 1/dy, 0, -ymin/dy], + [ 0, 0, 1/dz, -zmin/dz], + [ 0, 0, 0, 1]]) + + +def _rotation_about_vector(v, angle): + """ + Produce a rotation matrix for an angle in radians about a vector. + """ + vx, vy, vz = v / np.linalg.norm(v) + s = np.sin(angle) + c = np.cos(angle) + t = 2*np.sin(angle/2)**2 # more numerically stable than t = 1-c + + R = np.array([ + [t*vx*vx + c, t*vx*vy - vz*s, t*vx*vz + vy*s], + [t*vy*vx + vz*s, t*vy*vy + c, t*vy*vz - vx*s], + [t*vz*vx - vy*s, t*vz*vy + vx*s, t*vz*vz + c]]) + + return R + + +def _view_axes(E, R, V, roll): + """ + Get the unit viewing axes in data coordinates. + + Parameters + ---------- + E : 3-element numpy array + The coordinates of the eye/camera. + R : 3-element numpy array + The coordinates of the center of the view box. + V : 3-element numpy array + Unit vector in the direction of the vertical axis. + roll : float + The roll angle in radians. + + Returns + ------- + u : 3-element numpy array + Unit vector pointing towards the right of the screen. + v : 3-element numpy array + Unit vector pointing towards the top of the screen. + w : 3-element numpy array + Unit vector pointing out of the screen. + """ + w = (E - R) + w = w/np.linalg.norm(w) + u = np.cross(V, w) + u = u/np.linalg.norm(u) + v = np.cross(w, u) # Will be a unit vector + + # Save some computation for the default roll=0 + if roll != 0: + # A positive rotation of the camera is a negative rotation of the world + Rroll = _rotation_about_vector(w, -roll) + u = np.dot(Rroll, u) + v = np.dot(Rroll, v) + return u, v, w + + +def _view_transformation_uvw(u, v, w, E): + """ + Return the view transformation matrix. + + Parameters + ---------- + u : 3-element numpy array + Unit vector pointing towards the right of the screen. + v : 3-element numpy array + Unit vector pointing towards the top of the screen. + w : 3-element numpy array + Unit vector pointing out of the screen. + E : 3-element numpy array + The coordinates of the eye/camera. + """ + Mr = np.eye(4) + Mt = np.eye(4) + Mr[:3, :3] = [u, v, w] + Mt[:3, -1] = -E + M = np.dot(Mr, Mt) + return M + + +def _persp_transformation(zfront, zback, focal_length): + e = focal_length + a = 1 # aspect ratio + b = (zfront+zback)/(zfront-zback) + c = -2*(zfront*zback)/(zfront-zback) + proj_matrix = np.array([[e, 0, 0, 0], + [0, e/a, 0, 0], + [0, 0, b, c], + [0, 0, -1, 0]]) + return proj_matrix + + +def _ortho_transformation(zfront, zback): # note: w component in the resulting vector will be (zback-zfront), not 1 a = -(zfront + zback) b = -(zfront - zback) - return np.array([[2, 0, 0, 0], - [0, 2, 0, 0], - [0, 0, -2, 0], - [0, 0, a, b]]) + proj_matrix = np.array([[2, 0, 0, 0], + [0, 2, 0, 0], + [0, 0, -2, 0], + [0, 0, a, b]]) + return proj_matrix def _proj_transform_vec(vec, M): - vecw = np.dot(M, vec) - w = vecw[3] - # clip here.. - txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w - return txs, tys, tzs - - -def _proj_transform_vec_clip(vec, M): - vecw = np.dot(M, vec) - w = vecw[3] - # clip here. - txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w - tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1) - if np.any(tis): - tis = vecw[1] < 1 + vecw = np.dot(M, vec.data) + ts = vecw[0:3]/vecw[3] + if np.ma.isMA(vec): + ts = np.ma.array(ts, mask=vec.mask) + return ts[0], ts[1], ts[2] + + +def _proj_transform_vectors(vecs, M): + """ + Vectorized version of ``_proj_transform_vec``. + + Parameters + ---------- + vecs : ... x 3 np.ndarray + Input vectors + M : 4 x 4 np.ndarray + Projection matrix + """ + vecs_shape = vecs.shape + vecs = vecs.reshape(-1, 3).T + + vecs_pad = np.empty((vecs.shape[0] + 1,) + vecs.shape[1:]) + vecs_pad[:-1] = vecs + vecs_pad[-1] = 1 + product = np.dot(M, vecs_pad) + tvecs = product[:3] / product[3] + + return tvecs.T.reshape(vecs_shape) + + +def _proj_transform_vec_clip(vec, M, focal_length): + vecw = np.dot(M, vec.data) + txs, tys, tzs = vecw[0:3] / vecw[3] + if np.isinf(focal_length): # don't clip orthographic projection + tis = np.ones(txs.shape, dtype=bool) + else: + tis = (-1 <= txs) & (txs <= 1) & (-1 <= tys) & (tys <= 1) & (tzs <= 0) + if np.ma.isMA(vec[0]): + tis = tis & ~vec[0].mask + if np.ma.isMA(vec[1]): + tis = tis & ~vec[1].mask + if np.ma.isMA(vec[2]): + tis = tis & ~vec[2].mask + + txs = np.ma.masked_array(txs, ~tis) + tys = np.ma.masked_array(tys, ~tis) + tzs = np.ma.masked_array(tzs, ~tis) return txs, tys, tzs, tis -def inv_transform(xs, ys, zs, M): - iM = linalg.inv(M) +def inv_transform(xs, ys, zs, invM): + """ + Transform the points by the inverse of the projection matrix, *invM*. + """ vec = _vec_pad_ones(xs, ys, zs) - vecr = np.dot(iM, vec) - try: - vecr = vecr / vecr[3] - except OverflowError: - pass + vecr = np.dot(invM, vec) + if vecr.shape == (4,): + vecr = vecr.reshape((4, 1)) + for i in range(vecr.shape[1]): + if vecr[3][i] != 0: + vecr[:, i] = vecr[:, i] / vecr[3][i] return vecr[0], vecr[1], vecr[2] def _vec_pad_ones(xs, ys, zs): - return np.array([xs, ys, zs, np.ones_like(xs)]) + if np.ma.isMA(xs) or np.ma.isMA(ys) or np.ma.isMA(zs): + return np.ma.array([xs, ys, zs, np.ones_like(xs)]) + else: + return np.array([xs, ys, zs, np.ones_like(xs)]) def proj_transform(xs, ys, zs, M): """ - Transform the points by the projection matrix + Transform the points by the projection matrix *M*. """ vec = _vec_pad_ones(xs, ys, zs) return _proj_transform_vec(vec, M) -transform = proj_transform +@_api.deprecated("3.10") +def proj_transform_clip(xs, ys, zs, M): + return _proj_transform_clip(xs, ys, zs, M, focal_length=np.inf) -def proj_transform_clip(xs, ys, zs, M): +def _proj_transform_clip(xs, ys, zs, M, focal_length): """ Transform the points by the projection matrix and return the clipping result returns txs, tys, tzs, tis """ vec = _vec_pad_ones(xs, ys, zs) - return _proj_transform_vec_clip(vec, M) + return _proj_transform_vec_clip(vec, M, focal_length) -def proj_points(points, M): - return np.column_stack(proj_trans_points(points, M)) +def _proj_points(points, M): + return np.column_stack(_proj_trans_points(points, M)) -def proj_trans_points(points, M): - xs, ys, zs = zip(*points) +def _proj_trans_points(points, M): + points = np.asanyarray(points) + xs, ys, zs = points[:, 0], points[:, 1], points[:, 2] return proj_transform(xs, ys, zs, M) - - -def rot_x(V, alpha): - cosa, sina = np.cos(alpha), np.sin(alpha) - M1 = np.array([[1, 0, 0, 0], - [0, cosa, -sina, 0], - [0, sina, cosa, 0], - [0, 0, 0, 1]]) - return np.dot(M1, V) diff --git a/lib/mpl_toolkits/mplot3d/tests/__init__.py b/lib/mpl_toolkits/mplot3d/tests/__init__.py new file mode 100644 index 000000000000..ea4d8ed16a6a --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/__init__.py @@ -0,0 +1,10 @@ +from pathlib import Path + + +# Check that the test directories exist +if not (Path(__file__).parent / "baseline_images").exists(): + raise OSError( + 'The baseline image directory does not exist. ' + 'This is most likely because the test data is not installed. ' + 'You may need to install matplotlib from source to get the ' + 'test data.') diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_array.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_array.png new file mode 100644 index 000000000000..e32717d2ffc4 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_array.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_scalar.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_scalar.png new file mode 100644 index 000000000000..1896e2f34642 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/add_collection3d_zs_scalar.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/arc_pathpatch.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/arc_pathpatch.png new file mode 100644 index 000000000000..5e2e155a100d Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/arc_pathpatch.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects.png new file mode 100644 index 000000000000..a969d3d82b4c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects_adjust_box.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects_adjust_box.png new file mode 100644 index 000000000000..4c24873de4a3 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/aspects_adjust_box.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_cla.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_cla.png new file mode 100644 index 000000000000..7709e7ac06cb Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_cla.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_focal_length.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_focal_length.png new file mode 100644 index 000000000000..c5595a812663 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_focal_length.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_isometric.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_isometric.png new file mode 100644 index 000000000000..01f618994905 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_isometric.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_labelpad.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_labelpad.png new file mode 100644 index 000000000000..0d7eed251e1c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_labelpad.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_ortho.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_ortho.png new file mode 100644 index 000000000000..654951ee73aa Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_ortho.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_primary_views.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_primary_views.png new file mode 100644 index 000000000000..42e67ce4db9b Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_primary_views.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_rotated.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_rotated.png new file mode 100644 index 000000000000..b7129c184f8a Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axes3d_rotated.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axis_positions.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axis_positions.png new file mode 100644 index 000000000000..d4155479d213 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/axis_positions.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d.png new file mode 100644 index 000000000000..852da9e4f066 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_notshaded.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_notshaded.png new file mode 100644 index 000000000000..dc9c40eedc6c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_notshaded.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_shaded.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_shaded.png new file mode 100644 index 000000000000..dd8e2a1f76fe Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/bar3d_shaded.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/computed_zorder.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/computed_zorder.png new file mode 100644 index 000000000000..8e6b6f11602b Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/computed_zorder.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d.png new file mode 100644 index 000000000000..fcffa0d94a28 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d_extend3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d_extend3d.png new file mode 100644 index 000000000000..13373e168804 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contour3d_extend3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d.png new file mode 100644 index 000000000000..b5de55013779 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d_fill.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d_fill.png new file mode 100644 index 000000000000..b37c8d07634f Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/contourf3d_fill.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/equal_box_aspect.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/equal_box_aspect.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_mplot3d/equal_box_aspect.png rename to lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/equal_box_aspect.png diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d.png new file mode 100644 index 000000000000..02644360e1a4 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d_errorevery.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d_errorevery.png new file mode 100644 index 000000000000..455da1901561 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/errorbar3d_errorevery.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_polygon.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_polygon.png new file mode 100644 index 000000000000..f1f160fe5579 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_polygon.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_quad.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_quad.png new file mode 100644 index 000000000000..e405bcffb965 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/fill_between_quad.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/grid_off.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/grid_off.png new file mode 100644 index 000000000000..6fc722750a28 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/grid_off.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/invisible_ticks_axis.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/invisible_ticks_axis.png new file mode 100644 index 000000000000..37a55700e1ef Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/invisible_ticks_axis.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/lines3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/lines3d.png new file mode 100644 index 000000000000..c900cd02e87a Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/lines3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/minor_ticks.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/minor_ticks.png new file mode 100644 index 000000000000..e079c96d78ed Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/minor_ticks.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/mixedsubplot.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/mixedsubplot.png new file mode 100644 index 000000000000..3ec6498025e6 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/mixedsubplot.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/panecolor_rcparams.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/panecolor_rcparams.png new file mode 100644 index 000000000000..d634e2a2b8a7 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/panecolor_rcparams.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/plot_3d_from_2d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/plot_3d_from_2d.png new file mode 100644 index 000000000000..f6164696bcb9 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/plot_3d_from_2d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_alpha.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_alpha.png new file mode 100644 index 000000000000..866acc2bb8a4 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_alpha.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_closed.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_closed.png new file mode 100644 index 000000000000..866acc2bb8a4 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/poly3dcollection_closed.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/proj3d_axes_cube.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube.png rename to lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/proj3d_axes_cube.png diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/proj3d_axes_cube_ortho.png similarity index 100% rename from lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png rename to lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/proj3d_axes_cube_ortho.png diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d.png new file mode 100644 index 000000000000..af8cc16b14cc Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_colorcoded.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_colorcoded.png new file mode 100644 index 000000000000..09b27c306c61 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_colorcoded.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_masked.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_masked.png new file mode 100644 index 000000000000..5f9aaa95ff70 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d_masked.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d.png new file mode 100644 index 000000000000..aa15bb95168c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_color.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_color.png new file mode 100644 index 000000000000..f295ec7132ba Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_color.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_linewidth.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_linewidth.png new file mode 100644 index 000000000000..676ee10370f6 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_linewidth.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter_spiral.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter_spiral.png new file mode 100644 index 000000000000..ee562e27242b Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter_spiral.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/stem3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/stem3d.png new file mode 100644 index 000000000000..59facceb5d41 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/stem3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d.png new file mode 100644 index 000000000000..582dd6903671 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_label_offset_tick_position.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_label_offset_tick_position.png new file mode 100644 index 000000000000..a8b0d4cd665a Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_label_offset_tick_position.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png new file mode 100644 index 000000000000..9e5af36ffbfc Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked_strides.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked_strides.png new file mode 100644 index 000000000000..0277b8f8a610 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked_strides.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_shaded.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_shaded.png new file mode 100644 index 000000000000..bac920c3151c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_shaded.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_zsort_inf.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_zsort_inf.png new file mode 100644 index 000000000000..d533e6a40235 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_zsort_inf.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/text3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/text3d.png new file mode 100644 index 000000000000..15096f05d189 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/text3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/tricontour.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/tricontour.png new file mode 100644 index 000000000000..99fb15b6bcea Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/tricontour.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d.png new file mode 100644 index 000000000000..ea09aadd995f Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d_shaded.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d_shaded.png new file mode 100644 index 000000000000..24e0e1368890 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/trisurf3d_shaded.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-alpha.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-alpha.png new file mode 100644 index 000000000000..3a342051a7b3 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-alpha.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-edge-style.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-edge-style.png new file mode 100644 index 000000000000..69d6b4833f6e Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-edge-style.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png new file mode 100644 index 000000000000..33dfc2f2313a Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-rgb-data.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-rgb-data.png new file mode 100644 index 000000000000..cd8a3d046cdd Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-rgb-data.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-simple.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-simple.png new file mode 100644 index 000000000000..37eb5e6c9888 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-simple.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-xyz.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-xyz.png new file mode 100644 index 000000000000..9c20f04fe709 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-xyz.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3d.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3d.png new file mode 100644 index 000000000000..fb8b7df65ae4 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3d.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dasymmetric.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dasymmetric.png new file mode 100644 index 000000000000..73507bf2f6c1 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dasymmetric.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerocstride.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerocstride.png new file mode 100644 index 000000000000..7e4cf6a0c014 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerocstride.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerorstride.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerorstride.png new file mode 100644 index 000000000000..b2ec3ee4bdc7 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerorstride.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png new file mode 100644 index 000000000000..62e7dbc6cdae Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_bar.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_bar.png new file mode 100644 index 000000000000..72b9da0faffe Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_bar.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_plot.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_plot.png new file mode 100644 index 000000000000..0169979e5846 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/legend_plot.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/conftest.py b/lib/mpl_toolkits/mplot3d/tests/conftest.py new file mode 100644 index 000000000000..61c2de3e07ba --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/conftest.py @@ -0,0 +1,2 @@ +from matplotlib.testing.conftest import (mpl_test_settings, # noqa + pytest_configure, pytest_unconfigure) diff --git a/lib/mpl_toolkits/mplot3d/tests/meson.build b/lib/mpl_toolkits/mplot3d/tests/meson.build new file mode 100644 index 000000000000..7a638aedf72b --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/meson.build @@ -0,0 +1,14 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_art3d.py', + 'test_axes3d.py', + 'test_legend3d.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/mplot3d/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/mplot3d/tests')) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py new file mode 100644 index 000000000000..174c12608ae9 --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -0,0 +1,102 @@ +import numpy as np + +import matplotlib.pyplot as plt + +from matplotlib.backend_bases import MouseEvent +from mpl_toolkits.mplot3d.art3d import ( + Line3DCollection, + Poly3DCollection, + _all_points_on_plane, +) + + +def test_scatter_3d_projection_conservation(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + # fix axes3d projection + ax.roll = 0 + ax.elev = 0 + ax.azim = -45 + ax.stale = True + + x = [0, 1, 2, 3, 4] + scatter_collection = ax.scatter(x, x, x) + fig.canvas.draw_idle() + + # Get scatter location on canvas and freeze the data + scatter_offset = scatter_collection.get_offsets() + scatter_location = ax.transData.transform(scatter_offset) + + # Yaw -44 and -46 are enough to produce two set of scatter + # with opposite z-order without moving points too far + for azim in (-44, -46): + ax.azim = azim + ax.stale = True + fig.canvas.draw_idle() + + for i in range(5): + # Create a mouse event used to locate and to get index + # from each dots + event = MouseEvent("button_press_event", fig.canvas, + *scatter_location[i, :]) + contains, ind = scatter_collection.contains(event) + assert contains is True + assert len(ind["ind"]) == 1 + assert ind["ind"][0] == i + + +def test_zordered_error(): + # Smoke test for https://github.com/matplotlib/matplotlib/issues/26497 + lc = [(np.fromiter([0.0, 0.0, 0.0], dtype="float"), + np.fromiter([1.0, 1.0, 1.0], dtype="float"))] + pc = [np.fromiter([0.0, 0.0], dtype="float"), + np.fromiter([0.0, 1.0], dtype="float"), + np.fromiter([1.0, 1.0], dtype="float")] + + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + ax.add_collection(Line3DCollection(lc)) + ax.scatter(*pc, visible=False) + plt.draw() + + +def test_all_points_on_plane(): + # Non-coplanar points + points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert not _all_points_on_plane(*points.T) + + # Duplicate points + points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 0]]) + assert _all_points_on_plane(*points.T) + + # NaN values + points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, np.nan]]) + assert _all_points_on_plane(*points.T) + + # Less than 3 unique points + points = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + assert _all_points_on_plane(*points.T) + + # All points lie on a line + points = np.array([[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]]) + assert _all_points_on_plane(*points.T) + + # All points lie on two lines, with antiparallel vectors + points = np.array([[-2, 2, 0], [-1, 1, 0], [1, -1, 0], + [0, 0, 0], [2, 0, 0], [1, 0, 0]]) + assert _all_points_on_plane(*points.T) + + # All points lie on a plane + points = np.array([[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0]]) + assert _all_points_on_plane(*points.T) + + +def test_generate_normals(): + # Smoke test for https://github.com/matplotlib/matplotlib/issues/29156 + vertices = ((0, 0, 0), (0, 5, 0), (5, 5, 0), (5, 0, 0)) + shape = Poly3DCollection([vertices], edgecolors='r', shade=True) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.add_collection3d(shape) + plt.draw() diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py new file mode 100644 index 000000000000..79c7baba9bd1 --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -0,0 +1,2693 @@ +import functools +import itertools +import platform +import sys + +import pytest + +from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d +from mpl_toolkits.mplot3d.axes3d import _Quaternion as Quaternion +import matplotlib as mpl +from matplotlib.backend_bases import (MouseButton, MouseEvent, + NavigationToolbar2) +from matplotlib import cm +from matplotlib import colors as mcolors, patches as mpatch +from matplotlib.testing.decorators import image_comparison, check_figures_equal +from matplotlib.testing.widgets import mock_event +from matplotlib.collections import LineCollection, PolyCollection +from matplotlib.patches import Circle, PathPatch +from matplotlib.path import Path +from matplotlib.text import Text + +import matplotlib.pyplot as plt +import numpy as np + + +mpl3d_image_comparison = functools.partial( + image_comparison, remove_text=True, style='default') + + +def plot_cuboid(ax, scale): + # plot a rectangular cuboid with side lengths given by scale (x, y, z) + r = [0, 1] + pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2) + for start, end in pts: + if np.sum(np.abs(start - end)) == r[1] - r[0]: + ax.plot3D(*zip(start*np.array(scale), end*np.array(scale))) + + +@check_figures_equal() +def test_invisible_axes(fig_test, fig_ref): + ax = fig_test.subplots(subplot_kw=dict(projection='3d')) + ax.set_visible(False) + + +@mpl3d_image_comparison(['grid_off.png'], style='mpl20') +def test_grid_off(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.grid(False) + + +@mpl3d_image_comparison(['invisible_ticks_axis.png'], style='mpl20') +def test_invisible_ticks_axis(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_zticks([]) + for axis in [ax.xaxis, ax.yaxis, ax.zaxis]: + axis.line.set_visible(False) + + +@mpl3d_image_comparison(['axis_positions.png'], remove_text=False, style='mpl20') +def test_axis_positions(): + positions = ['upper', 'lower', 'both', 'none'] + fig, axs = plt.subplots(2, 2, subplot_kw={'projection': '3d'}) + for ax, pos in zip(axs.flatten(), positions): + for axis in ax.xaxis, ax.yaxis, ax.zaxis: + axis.set_label_position(pos) + axis.set_ticks_position(pos) + title = f'{pos}' + ax.set(xlabel='x', ylabel='y', zlabel='z', title=title) + + +@mpl3d_image_comparison(['aspects.png'], remove_text=False, style='mpl20') +def test_aspects(): + aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz', 'equal') + _, axs = plt.subplots(2, 3, subplot_kw={'projection': '3d'}) + + for ax in axs.flatten()[0:-1]: + plot_cuboid(ax, scale=[1, 1, 5]) + # plot a cube as well to cover github #25443 + plot_cuboid(axs[1][2], scale=[1, 1, 1]) + + for i, ax in enumerate(axs.flatten()): + ax.set_title(aspects[i]) + ax.set_box_aspect((3, 4, 5)) + ax.set_aspect(aspects[i], adjustable='datalim') + axs[1][2].set_title('equal (cube)') + + +@mpl3d_image_comparison(['aspects_adjust_box.png'], + remove_text=False, style='mpl20') +def test_aspects_adjust_box(): + aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') + fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}, + figsize=(11, 3)) + + for i, ax in enumerate(axs): + plot_cuboid(ax, scale=[4, 3, 5]) + ax.set_title(aspects[i]) + ax.set_aspect(aspects[i], adjustable='box') + + +def test_axes3d_repr(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_label('label') + ax.set_title('title') + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.set_zlabel('z') + assert repr(ax) == ( + "") + + +@mpl3d_image_comparison(['axes3d_primary_views.png'], style='mpl20', + tol=0.05 if sys.platform == "darwin" else 0) +def test_axes3d_primary_views(): + # (elev, azim, roll) + views = [(90, -90, 0), # XY + (0, -90, 0), # XZ + (0, 0, 0), # YZ + (-90, 90, 0), # -XY + (0, 90, 0), # -XZ + (0, 180, 0)] # -YZ + # When viewing primary planes, draw the two visible axes so they intersect + # at their low values + fig, axs = plt.subplots(2, 3, subplot_kw={'projection': '3d'}) + for i, ax in enumerate(axs.flat): + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.set_zlabel('z') + ax.set_proj_type('ortho') + ax.view_init(elev=views[i][0], azim=views[i][1], roll=views[i][2]) + plt.tight_layout() + + +@mpl3d_image_comparison(['bar3d.png'], style='mpl20') +def test_bar3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]): + xs = np.arange(20) + ys = np.arange(20) + cs = [c] * len(xs) + cs[0] = 'c' + ax.bar(xs, ys, zs=z, zdir='y', align='edge', color=cs, alpha=0.8) + + +def test_bar3d_colors(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + for c in ['red', 'green', 'blue', 'yellow']: + xs = np.arange(len(c)) + ys = np.zeros_like(xs) + zs = np.zeros_like(ys) + # Color names with same length as xs/ys/zs should not be split into + # individual letters. + ax.bar3d(xs, ys, zs, 1, 1, 1, color=c) + + +@mpl3d_image_comparison(['bar3d_shaded.png'], style='mpl20') +def test_bar3d_shaded(): + x = np.arange(4) + y = np.arange(5) + x2d, y2d = np.meshgrid(x, y) + x2d, y2d = x2d.ravel(), y2d.ravel() + z = x2d + y2d + 1 # Avoid triggering bug with zero-depth boxes. + + views = [(30, -60, 0), (30, 30, 30), (-30, 30, -90), (300, -30, 0)] + fig = plt.figure(figsize=plt.figaspect(1 / len(views))) + axs = fig.subplots( + 1, len(views), + subplot_kw=dict(projection='3d') + ) + for ax, (elev, azim, roll) in zip(axs, views): + ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=True) + ax.view_init(elev=elev, azim=azim, roll=roll) + fig.canvas.draw() + + +@mpl3d_image_comparison(['bar3d_notshaded.png'], style='mpl20') +def test_bar3d_notshaded(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + x = np.arange(4) + y = np.arange(5) + x2d, y2d = np.meshgrid(x, y) + x2d, y2d = x2d.ravel(), y2d.ravel() + z = x2d + y2d + ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=False) + fig.canvas.draw() + + +def test_bar3d_lightsource(): + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection="3d") + + ls = mcolors.LightSource(azdeg=0, altdeg=90) + + length, width = 3, 4 + area = length * width + + x, y = np.meshgrid(np.arange(length), np.arange(width)) + x = x.ravel() + y = y.ravel() + dz = x + y + + color = [cm.coolwarm(i/area) for i in range(area)] + + collection = ax.bar3d(x=x, y=y, z=0, + dx=1, dy=1, dz=dz, + color=color, shade=True, lightsource=ls) + + # Testing that the custom 90° lightsource produces different shading on + # the top facecolors compared to the default, and that those colors are + # precisely (within floating point rounding errors of 4 ULP) the colors + # from the colormap, due to the illumination parallel to the z-axis. + np.testing.assert_array_max_ulp(color, collection._facecolor3d[1::6], 4) + + +@mpl3d_image_comparison(['contour3d.png'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.002) +def test_contour3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.contour(X, Y, Z, zdir='z', offset=-100, cmap="coolwarm") + ax.contour(X, Y, Z, zdir='x', offset=-40, cmap="coolwarm") + ax.contour(X, Y, Z, zdir='y', offset=40, cmap="coolwarm") + ax.axis(xmin=-40, xmax=40, ymin=-40, ymax=40, zmin=-100, zmax=100) + + +@mpl3d_image_comparison(['contour3d_extend3d.png'], style='mpl20') +def test_contour3d_extend3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.contour(X, Y, Z, zdir='z', offset=-100, cmap="coolwarm", extend3d=True) + ax.set_xlim(-30, 30) + ax.set_ylim(-20, 40) + ax.set_zlim(-80, 80) + + +@mpl3d_image_comparison(['contourf3d.png'], style='mpl20') +def test_contourf3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.contourf(X, Y, Z, zdir='z', offset=-100, cmap="coolwarm") + ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap="coolwarm") + ax.contourf(X, Y, Z, zdir='y', offset=40, cmap="coolwarm") + ax.set_xlim(-40, 40) + ax.set_ylim(-40, 40) + ax.set_zlim(-100, 100) + + +@mpl3d_image_comparison(['contourf3d_fill.png'], style='mpl20') +def test_contourf3d_fill(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25)) + Z = X.clip(0, 0) + # This produces holes in the z=0 surface that causes rendering errors if + # the Poly3DCollection is not aware of path code information (issue #4784) + Z[::5, ::5] = 0.1 + ax.contourf(X, Y, Z, offset=0, levels=[-0.1, 0], cmap="coolwarm") + ax.set_xlim(-2, 2) + ax.set_ylim(-2, 2) + ax.set_zlim(-1, 1) + + +@pytest.mark.parametrize('extend, levels', [['both', [2, 4, 6]], + ['min', [2, 4, 6, 8]], + ['max', [0, 2, 4, 6]]]) +@check_figures_equal() +def test_contourf3d_extend(fig_test, fig_ref, extend, levels): + X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25)) + # Z is in the range [0, 8] + Z = X**2 + Y**2 + + # Manually set the over/under colors to be the end of the colormap + cmap = mpl.colormaps['viridis'].copy() + cmap.set_under(cmap(0)) + cmap.set_over(cmap(255)) + # Set vmin/max to be the min/max values plotted on the reference image + kwargs = {'vmin': 1, 'vmax': 7, 'cmap': cmap} + + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.contourf(X, Y, Z, levels=[0, 2, 4, 6, 8], **kwargs) + + ax_test = fig_test.add_subplot(projection='3d') + ax_test.contourf(X, Y, Z, levels, extend=extend, **kwargs) + + for ax in [ax_ref, ax_test]: + ax.set_xlim(-2, 2) + ax.set_ylim(-2, 2) + ax.set_zlim(-10, 10) + + +@mpl3d_image_comparison(['tricontour.png'], tol=0.02, style='mpl20') +def test_tricontour(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + + np.random.seed(19680801) + x = np.random.rand(1000) - 0.5 + y = np.random.rand(1000) - 0.5 + z = -(x**2 + y**2) + + ax = fig.add_subplot(1, 2, 1, projection='3d') + ax.tricontour(x, y, z) + ax = fig.add_subplot(1, 2, 2, projection='3d') + ax.tricontourf(x, y, z) + + +def test_contour3d_1d_input(): + # Check that 1D sequences of different length for {x, y} doesn't error + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + nx, ny = 30, 20 + x = np.linspace(-10, 10, nx) + y = np.linspace(-10, 10, ny) + z = np.random.randint(0, 2, [ny, nx]) + ax.contour(x, y, z, [0.5]) + + +@mpl3d_image_comparison(['lines3d.png'], style='mpl20') +def test_lines3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) + z = np.linspace(-2, 2, 100) + r = z ** 2 + 1 + x = r * np.sin(theta) + y = r * np.cos(theta) + ax.plot(x, y, z) + + +@check_figures_equal() +def test_plot_scalar(fig_test, fig_ref): + ax1 = fig_test.add_subplot(projection='3d') + ax1.plot([1], [1], "o") + ax2 = fig_ref.add_subplot(projection='3d') + ax2.plot(1, 1, "o") + + +def test_invalid_line_data(): + with pytest.raises(RuntimeError, match='x must be'): + art3d.Line3D(0, [], []) + with pytest.raises(RuntimeError, match='y must be'): + art3d.Line3D([], 0, []) + with pytest.raises(RuntimeError, match='z must be'): + art3d.Line3D([], [], 0) + + line = art3d.Line3D([], [], []) + with pytest.raises(RuntimeError, match='x must be'): + line.set_data_3d(0, [], []) + with pytest.raises(RuntimeError, match='y must be'): + line.set_data_3d([], 0, []) + with pytest.raises(RuntimeError, match='z must be'): + line.set_data_3d([], [], 0) + + +@mpl3d_image_comparison(['mixedsubplot.png'], style='mpl20') +def test_mixedsubplots(): + def f(t): + return np.cos(2*np.pi*t) * np.exp(-t) + + t1 = np.arange(0.0, 5.0, 0.1) + t2 = np.arange(0.0, 5.0, 0.02) + + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure(figsize=plt.figaspect(2.)) + ax = fig.add_subplot(2, 1, 1) + ax.plot(t1, f(t1), 'bo', t2, f(t2), 'k--', markerfacecolor='green') + ax.grid(True) + + ax = fig.add_subplot(2, 1, 2, projection='3d') + X, Y = np.meshgrid(np.arange(-5, 5, 0.25), np.arange(-5, 5, 0.25)) + R = np.hypot(X, Y) + Z = np.sin(R) + + ax.plot_surface(X, Y, Z, rcount=40, ccount=40, + linewidth=0, antialiased=False) + + ax.set_zlim3d(-1, 1) + + +@check_figures_equal() +def test_tight_layout_text(fig_test, fig_ref): + # text is currently ignored in tight layout. So the order of text() and + # tight_layout() calls should not influence the result. + ax1 = fig_test.add_subplot(projection='3d') + ax1.text(.5, .5, .5, s='some string') + fig_test.tight_layout() + + ax2 = fig_ref.add_subplot(projection='3d') + fig_ref.tight_layout() + ax2.text(.5, .5, .5, s='some string') + + +@mpl3d_image_comparison(['scatter3d.png'], style='mpl20') +def test_scatter3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter(np.arange(10), np.arange(10), np.arange(10), + c='r', marker='o') + x = y = z = np.arange(10, 20) + ax.scatter(x, y, z, c='b', marker='^') + z[-1] = 0 # Check that scatter() copies the data. + # Ensure empty scatters do not break. + ax.scatter([], [], [], c='r', marker='X') + + +@mpl3d_image_comparison(['scatter3d_color.png'], style='mpl20') +def test_scatter3d_color(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + # Check that 'none' color works; these two should overlay to produce the + # same as setting just `color`. + ax.scatter(np.arange(10), np.arange(10), np.arange(10), + facecolor='r', edgecolor='none', marker='o') + ax.scatter(np.arange(10), np.arange(10), np.arange(10), + facecolor='none', edgecolor='r', marker='o') + + ax.scatter(np.arange(10, 20), np.arange(10, 20), np.arange(10, 20), + color='b', marker='s') + + +@mpl3d_image_comparison(['scatter3d_linewidth.png'], style='mpl20') +def test_scatter3d_linewidth(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + # Check that array-like linewidth can be set + ax.scatter(np.arange(10), np.arange(10), np.arange(10), + marker='o', linewidth=np.arange(10)) + + +@check_figures_equal() +def test_scatter3d_linewidth_modification(fig_ref, fig_test): + # Changing Path3DCollection linewidths with array-like post-creation + # should work correctly. + ax_test = fig_test.add_subplot(projection='3d') + c = ax_test.scatter(np.arange(10), np.arange(10), np.arange(10), + marker='o') + c.set_linewidths(np.arange(10)) + + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.scatter(np.arange(10), np.arange(10), np.arange(10), marker='o', + linewidths=np.arange(10)) + + +@check_figures_equal() +def test_scatter3d_modification(fig_ref, fig_test): + # Changing Path3DCollection properties post-creation should work correctly. + ax_test = fig_test.add_subplot(projection='3d') + c = ax_test.scatter(np.arange(10), np.arange(10), np.arange(10), + marker='o', depthshade=True) + c.set_facecolor('C1') + c.set_edgecolor('C2') + c.set_alpha([0.3, 0.7] * 5) + assert c.get_depthshade() + c.set_depthshade(False) + assert not c.get_depthshade() + c.set_sizes(np.full(10, 75)) + c.set_linewidths(3) + + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.scatter(np.arange(10), np.arange(10), np.arange(10), marker='o', + facecolor='C1', edgecolor='C2', alpha=[0.3, 0.7] * 5, + depthshade=False, s=75, linewidths=3) + + +@check_figures_equal() +def test_scatter3d_sorting(fig_ref, fig_test): + """Test that marker properties are correctly sorted.""" + + y, x = np.mgrid[:10, :10] + z = np.arange(x.size).reshape(x.shape) + depthshade = False + + sizes = np.full(z.shape, 25) + sizes[0::2, 0::2] = 100 + sizes[1::2, 1::2] = 100 + + facecolors = np.full(z.shape, 'C0') + facecolors[:5, :5] = 'C1' + facecolors[6:, :4] = 'C2' + facecolors[6:, 6:] = 'C3' + + edgecolors = np.full(z.shape, 'C4') + edgecolors[1:5, 1:5] = 'C5' + edgecolors[5:9, 1:5] = 'C6' + edgecolors[5:9, 5:9] = 'C7' + + linewidths = np.full(z.shape, 2) + linewidths[0::2, 0::2] = 5 + linewidths[1::2, 1::2] = 5 + + x, y, z, sizes, facecolors, edgecolors, linewidths = ( + a.flatten() + for a in [x, y, z, sizes, facecolors, edgecolors, linewidths] + ) + + ax_ref = fig_ref.add_subplot(projection='3d') + sets = (np.unique(a) for a in [sizes, facecolors, edgecolors, linewidths]) + for s, fc, ec, lw in itertools.product(*sets): + subset = ( + (sizes != s) | + (facecolors != fc) | + (edgecolors != ec) | + (linewidths != lw) + ) + subset = np.ma.masked_array(z, subset, dtype=float) + + # When depth shading is disabled, the colors are passed through as + # single-item lists; this triggers single path optimization. The + # following reshaping is a hack to disable that, since the optimization + # would not occur for the full scatter which has multiple colors. + fc = np.repeat(fc, sum(~subset.mask)) + + ax_ref.scatter(x, y, subset, s=s, fc=fc, ec=ec, lw=lw, alpha=1, + depthshade=depthshade) + + ax_test = fig_test.add_subplot(projection='3d') + ax_test.scatter(x, y, z, s=sizes, fc=facecolors, ec=edgecolors, + lw=linewidths, alpha=1, depthshade=depthshade) + + +@pytest.mark.parametrize('azim', [-50, 130]) # yellow first, blue first +@check_figures_equal() +def test_marker_draw_order_data_reversed(fig_test, fig_ref, azim): + """ + Test that the draw order does not depend on the data point order. + + For the given viewing angle at azim=-50, the yellow marker should be in + front. For azim=130, the blue marker should be in front. + """ + x = [-1, 1] + y = [1, -1] + z = [0, 0] + color = ['b', 'y'] + ax = fig_test.add_subplot(projection='3d') + ax.scatter(x, y, z, s=3500, c=color) + ax.view_init(elev=0, azim=azim, roll=0) + ax = fig_ref.add_subplot(projection='3d') + ax.scatter(x[::-1], y[::-1], z[::-1], s=3500, c=color[::-1]) + ax.view_init(elev=0, azim=azim, roll=0) + + +@check_figures_equal() +def test_marker_draw_order_view_rotated(fig_test, fig_ref): + """ + Test that the draw order changes with the direction. + + If we rotate *azim* by 180 degrees and exchange the colors, the plot + plot should look the same again. + """ + azim = 130 + x = [-1, 1] + y = [1, -1] + z = [0, 0] + color = ['b', 'y'] + ax = fig_test.add_subplot(projection='3d') + # axis are not exactly invariant under 180 degree rotation -> deactivate + ax.set_axis_off() + ax.scatter(x, y, z, s=3500, c=color) + ax.view_init(elev=0, azim=azim, roll=0) + ax = fig_ref.add_subplot(projection='3d') + ax.set_axis_off() + ax.scatter(x, y, z, s=3500, c=color[::-1]) # color reversed + ax.view_init(elev=0, azim=azim - 180, roll=0) # view rotated by 180 deg + + +@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.019, style='mpl20') +def test_plot_3d_from_2d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + xs = np.arange(0, 5) + ys = np.arange(5, 10) + ax.plot(xs, ys, zs=0, zdir='x') + ax.plot(xs, ys, zs=0, zdir='y') + + +@mpl3d_image_comparison(['fill_between_quad.png'], style='mpl20') +def test_fill_between_quad(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + theta = np.linspace(0, 2*np.pi, 50) + + x1 = np.cos(theta) + y1 = np.sin(theta) + z1 = 0.1 * np.sin(6 * theta) + + x2 = 0.6 * np.cos(theta) + y2 = 0.6 * np.sin(theta) + z2 = 2 + + where = (theta < np.pi/2) | (theta > 3*np.pi/2) + + # Since none of x1 == x2, y1 == y2, or z1 == z2 is True, the fill_between + # mode will map to 'quad' + ax.fill_between(x1, y1, z1, x2, y2, z2, + where=where, mode='auto', alpha=0.5, edgecolor='k') + + +@mpl3d_image_comparison(['fill_between_polygon.png'], style='mpl20') +def test_fill_between_polygon(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + theta = np.linspace(0, 2*np.pi, 50) + + x1 = x2 = theta + y1 = y2 = 0 + z1 = np.cos(theta) + z2 = z1 + 1 + + where = (theta < np.pi/2) | (theta > 3*np.pi/2) + + # Since x1 == x2 and y1 == y2, the fill_between mode will be 'polygon' + ax.fill_between(x1, y1, z1, x2, y2, z2, + where=where, mode='auto', edgecolor='k') + + +@mpl3d_image_comparison(['surface3d.png'], style='mpl20') +def test_surface3d(): + # Remove this line when this test image is regenerated. + plt.rcParams['pcolormesh.snap'] = False + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X = np.arange(-5, 5, 0.25) + Y = np.arange(-5, 5, 0.25) + X, Y = np.meshgrid(X, Y) + R = np.hypot(X, Y) + Z = np.sin(R) + surf = ax.plot_surface(X, Y, Z, rcount=40, ccount=40, cmap="coolwarm", + lw=0, antialiased=False) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_zlim(-1.01, 1.01) + fig.colorbar(surf, shrink=0.5, aspect=5) + + +@image_comparison(['surface3d_label_offset_tick_position.png'], style='mpl20') +def test_surface3d_label_offset_tick_position(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax = plt.figure().add_subplot(projection="3d") + + x, y = np.mgrid[0:6 * np.pi:0.25, 0:4 * np.pi:0.25] + z = np.sqrt(np.abs(np.cos(x) + np.cos(y))) + + ax.plot_surface(x * 1e5, y * 1e6, z * 1e8, cmap='autumn', cstride=2, rstride=2) + ax.set_xlabel("X label") + ax.set_ylabel("Y label") + ax.set_zlabel("Z label") + + +@mpl3d_image_comparison(['surface3d_shaded.png'], style='mpl20') +def test_surface3d_shaded(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X = np.arange(-5, 5, 0.25) + Y = np.arange(-5, 5, 0.25) + X, Y = np.meshgrid(X, Y) + R = np.sqrt(X ** 2 + Y ** 2) + Z = np.sin(R) + ax.plot_surface(X, Y, Z, rstride=5, cstride=5, + color=[0.25, 1, 0.25], lw=1, antialiased=False) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_zlim(-1.01, 1.01) + + +@mpl3d_image_comparison(['surface3d_masked.png'], style='mpl20') +def test_surface3d_masked(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + y = [1, 2, 3, 4, 5, 6, 7, 8] + + x, y = np.meshgrid(x, y) + matrix = np.array( + [ + [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [-1, 1, 2, 3, 4, 4, 4, 3, 2, 1, 1], + [-1, -1., 4, 5, 6, 8, 6, 5, 4, 3, -1.], + [-1, -1., 7, 8, 11, 12, 11, 8, 7, -1., -1.], + [-1, -1., 8, 9, 10, 16, 10, 9, 10, 7, -1.], + [-1, -1., -1., 12, 16, 20, 16, 12, 11, -1., -1.], + [-1, -1., -1., -1., 22, 24, 22, 20, 18, -1., -1.], + [-1, -1., -1., -1., -1., 28, 26, 25, -1., -1., -1.], + ] + ) + z = np.ma.masked_less(matrix, 0) + norm = mcolors.Normalize(vmax=z.max(), vmin=z.min()) + colors = mpl.colormaps["plasma"](norm(z)) + ax.plot_surface(x, y, z, facecolors=colors) + ax.view_init(30, -80, 0) + + +@check_figures_equal() +def test_plot_scatter_masks(fig_test, fig_ref): + x = np.linspace(0, 10, 100) + y = np.linspace(0, 10, 100) + z = np.sin(x) * np.cos(y) + mask = z > 0 + + z_masked = np.ma.array(z, mask=mask) + ax_test = fig_test.add_subplot(projection='3d') + ax_test.scatter(x, y, z_masked) + ax_test.plot(x, y, z_masked) + + x[mask] = y[mask] = z[mask] = np.nan + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.scatter(x, y, z) + ax_ref.plot(x, y, z) + + +@check_figures_equal() +def test_plot_surface_None_arg(fig_test, fig_ref): + x, y = np.meshgrid(np.arange(5), np.arange(5)) + z = x + y + ax_test = fig_test.add_subplot(projection='3d') + ax_test.plot_surface(x, y, z, facecolors=None) + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.plot_surface(x, y, z) + + +@mpl3d_image_comparison(['surface3d_masked_strides.png'], style='mpl20') +def test_surface3d_masked_strides(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + x, y = np.mgrid[-6:6.1:1, -6:6.1:1] + z = np.ma.masked_less(x * y, 2) + + ax.plot_surface(x, y, z, rstride=4, cstride=4) + ax.view_init(60, -45, 0) + + +@mpl3d_image_comparison(['text3d.png'], remove_text=False, style='mpl20') +def test_text3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1)) + xs = (2, 6, 4, 9, 7, 2) + ys = (6, 4, 8, 7, 2, 2) + zs = (4, 2, 5, 6, 1, 7) + + for zdir, x, y, z in zip(zdirs, xs, ys, zs): + label = '(%d, %d, %d), dir=%s' % (x, y, z, zdir) + ax.text(x, y, z, label, zdir) + + ax.text(1, 1, 1, "red", color='red') + ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_xlim3d(0, 10) + ax.set_ylim3d(0, 10) + ax.set_zlim3d(0, 10) + ax.set_xlabel('X axis') + ax.set_ylabel('Y axis') + ax.set_zlabel('Z axis') + + +@check_figures_equal() +def test_text3d_modification(fig_ref, fig_test): + # Modifying the Text position after the fact should work the same as + # setting it directly. + zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1)) + xs = (2, 6, 4, 9, 7, 2) + ys = (6, 4, 8, 7, 2, 2) + zs = (4, 2, 5, 6, 1, 7) + + ax_test = fig_test.add_subplot(projection='3d') + ax_test.set_xlim3d(0, 10) + ax_test.set_ylim3d(0, 10) + ax_test.set_zlim3d(0, 10) + for zdir, x, y, z in zip(zdirs, xs, ys, zs): + t = ax_test.text(0, 0, 0, f'({x}, {y}, {z}), dir={zdir}') + t.set_position_3d((x, y, z), zdir=zdir) + + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.set_xlim3d(0, 10) + ax_ref.set_ylim3d(0, 10) + ax_ref.set_zlim3d(0, 10) + for zdir, x, y, z in zip(zdirs, xs, ys, zs): + ax_ref.text(x, y, z, f'({x}, {y}, {z}), dir={zdir}', zdir=zdir) + + +@mpl3d_image_comparison(['trisurf3d.png'], tol=0.061, style='mpl20') +def test_trisurf3d(): + n_angles = 36 + n_radii = 8 + radii = np.linspace(0.125, 1.0, n_radii) + angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False) + angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) + angles[:, 1::2] += np.pi/n_angles + + x = np.append(0, (radii*np.cos(angles)).flatten()) + y = np.append(0, (radii*np.sin(angles)).flatten()) + z = np.sin(-x*y) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot_trisurf(x, y, z, cmap="jet", linewidth=0.2) + + +@mpl3d_image_comparison(['trisurf3d_shaded.png'], tol=0.03, style='mpl20') +def test_trisurf3d_shaded(): + n_angles = 36 + n_radii = 8 + radii = np.linspace(0.125, 1.0, n_radii) + angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False) + angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) + angles[:, 1::2] += np.pi/n_angles + + x = np.append(0, (radii*np.cos(angles)).flatten()) + y = np.append(0, (radii*np.sin(angles)).flatten()) + z = np.sin(-x*y) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot_trisurf(x, y, z, color=[1, 0.5, 0], linewidth=0.2) + + +@mpl3d_image_comparison(['wireframe3d.png'], style='mpl20') +def test_wireframe3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.plot_wireframe(X, Y, Z, rcount=13, ccount=13) + + +@mpl3d_image_comparison(['wireframe3dasymmetric.png'], style='mpl20') +def test_wireframe3dasymmetric(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.plot_wireframe(X, Y, Z, rcount=3, ccount=13) + + +@mpl3d_image_comparison(['wireframe3dzerocstride.png'], style='mpl20') +def test_wireframe3dzerocstride(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.plot_wireframe(X, Y, Z, rcount=13, ccount=0) + + +@mpl3d_image_comparison(['wireframe3dzerorstride.png'], style='mpl20') +def test_wireframe3dzerorstride(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + ax.plot_wireframe(X, Y, Z, rstride=0, cstride=10) + + +def test_wireframe3dzerostrideraises(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + with pytest.raises(ValueError): + ax.plot_wireframe(X, Y, Z, rstride=0, cstride=0) + + +def test_mixedsamplesraises(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X, Y, Z = axes3d.get_test_data(0.05) + with pytest.raises(ValueError): + ax.plot_wireframe(X, Y, Z, rstride=10, ccount=50) + with pytest.raises(ValueError): + ax.plot_surface(X, Y, Z, cstride=50, rcount=10) + + +# remove tolerance when regenerating the test image +@mpl3d_image_comparison(['quiver3d.png'], style='mpl20', tol=0.003) +def test_quiver3d(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + pivots = ['tip', 'middle', 'tail'] + colors = ['tab:blue', 'tab:orange', 'tab:green'] + for i, (pivot, color) in enumerate(zip(pivots, colors)): + x, y, z = np.meshgrid([-0.5, 0.5], [-0.5, 0.5], [-0.5, 0.5]) + u = -x + v = -y + w = -z + # Offset each set in z direction + z += 2 * i + ax.quiver(x, y, z, u, v, w, length=1, pivot=pivot, color=color) + ax.scatter(x, y, z, color=color) + + ax.set_xlim(-3, 3) + ax.set_ylim(-3, 3) + ax.set_zlim(-1, 5) + + +@check_figures_equal() +def test_quiver3d_empty(fig_test, fig_ref): + fig_ref.add_subplot(projection='3d') + x = y = z = u = v = w = [] + ax = fig_test.add_subplot(projection='3d') + ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True) + + +@mpl3d_image_comparison(['quiver3d_masked.png'], style='mpl20') +def test_quiver3d_masked(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + # Using mgrid here instead of ogrid because masked_where doesn't + # seem to like broadcasting very much... + x, y, z = np.mgrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j] + + u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z) + v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z) + w = (2/3)**0.5 * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z) + u = np.ma.masked_where((-0.4 < x) & (x < 0.1), u, copy=False) + v = np.ma.masked_where((0.1 < y) & (y < 0.7), v, copy=False) + + ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True) + + +@mpl3d_image_comparison(['quiver3d_colorcoded.png'], style='mpl20') +def test_quiver3d_colorcoded(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + x = y = dx = dz = np.zeros(10) + z = dy = np.arange(10.) + + color = plt.colormaps["Reds"](dy/dy.max()) + ax.quiver(x, y, z, dx, dy, dz, colors=color) + ax.set_ylim(0, 10) + + +def test_patch_modification(): + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + circle = Circle((0, 0)) + ax.add_patch(circle) + art3d.patch_2d_to_3d(circle) + circle.set_facecolor((1.0, 0.0, 0.0, 1)) + + assert mcolors.same_color(circle.get_facecolor(), (1, 0, 0, 1)) + fig.canvas.draw() + assert mcolors.same_color(circle.get_facecolor(), (1, 0, 0, 1)) + + +@check_figures_equal() +def test_patch_collection_modification(fig_test, fig_ref): + # Test that modifying Patch3DCollection properties after creation works. + patch1 = Circle((0, 0), 0.05) + patch2 = Circle((0.1, 0.1), 0.03) + facecolors = np.array([[0., 0.5, 0., 1.], [0.5, 0., 0., 0.5]]) + c = art3d.Patch3DCollection([patch1, patch2], linewidths=3, depthshade=True) + + ax_test = fig_test.add_subplot(projection='3d') + ax_test.add_collection3d(c) + c.set_edgecolor('C2') + c.set_facecolor(facecolors) + c.set_alpha(0.7) + assert c.get_depthshade() + c.set_depthshade(False) + assert not c.get_depthshade() + + patch1 = Circle((0, 0), 0.05) + patch2 = Circle((0.1, 0.1), 0.03) + facecolors = np.array([[0., 0.5, 0., 1.], [0.5, 0., 0., 0.5]]) + c = art3d.Patch3DCollection([patch1, patch2], linewidths=3, + edgecolor='C2', facecolor=facecolors, + alpha=0.7, depthshade=False) + + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.add_collection3d(c) + + +def test_poly3dcollection_verts_validation(): + poly = [[0, 0, 1], [0, 1, 1], [0, 1, 0], [0, 0, 0]] + with pytest.raises(ValueError, match=r'list of \(N, 3\) array-like'): + art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) + + poly = np.array(poly, dtype=float) + with pytest.raises(ValueError, match=r'shape \(M, N, 3\)'): + art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) + + +@mpl3d_image_comparison(['poly3dcollection_closed.png'], style='mpl20') +def test_poly3dcollection_closed(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + poly1 = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) + poly2 = np.array([[0, 1, 1], [1, 1, 1], [1, 1, 0]], float) + c1 = art3d.Poly3DCollection([poly1], linewidths=3, edgecolor='k', + facecolor=(0.5, 0.5, 1, 0.5), closed=True) + c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k', + facecolor=(1, 0.5, 0.5, 0.5), closed=False) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) + + +def test_poly_collection_2d_to_3d_empty(): + poly = PolyCollection([]) + art3d.poly_collection_2d_to_3d(poly) + assert isinstance(poly, art3d.Poly3DCollection) + assert poly.get_paths() == [] + + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + ax.add_artist(poly) + minz = poly.do_3d_projection() + assert np.isnan(minz) + + # Ensure drawing actually works. + fig.canvas.draw() + + +@mpl3d_image_comparison(['poly3dcollection_alpha.png'], style='mpl20') +def test_poly3dcollection_alpha(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + poly1 = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) + poly2 = np.array([[0, 1, 1], [1, 1, 1], [1, 1, 0]], float) + c1 = art3d.Poly3DCollection([poly1], linewidths=3, edgecolor='k', + facecolor=(0.5, 0.5, 1), closed=True) + c1.set_alpha(0.5) + c2 = art3d.Poly3DCollection([poly2], linewidths=3, closed=False) + # Post-creation modification should work. + c2.set_facecolor((1, 0.5, 0.5)) + c2.set_edgecolor('k') + c2.set_alpha(0.5) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) + + +@mpl3d_image_comparison(['add_collection3d_zs_array.png'], style='mpl20') +def test_add_collection3d_zs_array(): + theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) + z = np.linspace(-2, 2, 100) + r = z**2 + 1 + x = r * np.sin(theta) + y = r * np.cos(theta) + + points = np.column_stack([x, y, z]).reshape(-1, 1, 3) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + norm = plt.Normalize(0, 2*np.pi) + # 2D LineCollection from x & y values + lc = LineCollection(segments[:, :, :2], cmap='twilight', norm=norm) + lc.set_array(np.mod(theta, 2*np.pi)) + # Add 2D collection at z values to ax + line = ax.add_collection3d(lc, zs=segments[:, :, 2]) + + assert line is not None + + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_xlim(-5, 5) + ax.set_ylim(-4, 6) + ax.set_zlim(-2, 2) + + +@mpl3d_image_comparison(['add_collection3d_zs_scalar.png'], style='mpl20') +def test_add_collection3d_zs_scalar(): + theta = np.linspace(0, 2 * np.pi, 100) + z = 1 + r = z**2 + 1 + x = r * np.sin(theta) + y = r * np.cos(theta) + + points = np.column_stack([x, y]).reshape(-1, 1, 2) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + norm = plt.Normalize(0, 2*np.pi) + lc = LineCollection(segments, cmap='twilight', norm=norm) + lc.set_array(theta) + line = ax.add_collection3d(lc, zs=z) + + assert line is not None + + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_xlim(-5, 5) + ax.set_ylim(-4, 6) + ax.set_zlim(0, 2) + + +def test_line3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + lines = [[(0, 0, 0), (1, 4, 2)], + [(1, 1, 3), (2, 0, 2)], + [(1, 0, 4), (1, 4, 5)]] + + lc = art3d.Line3DCollection(lines) + ax.add_collection3d(lc) + assert np.allclose(ax.get_xlim3d(), (-0.041666666666666664, 2.0416666666666665)) + assert np.allclose(ax.get_ylim3d(), (-0.08333333333333333, 4.083333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.10416666666666666, 5.104166666666667)) + + +def test_poly3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + poly = np.array([[0, 0, 0], [1, 1, 3], [1, 0, 4]]) + col = art3d.Poly3DCollection([poly]) + ax.add_collection3d(col) + assert np.allclose(ax.get_xlim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_ylim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.0833333333333333, 4.083333333333333)) + + +@mpl3d_image_comparison(['axes3d_labelpad.png'], + remove_text=False, style='mpl20') +def test_axes3d_labelpad(): + fig = plt.figure() + ax = fig.add_axes(Axes3D(fig)) + # labelpad respects rcParams + assert ax.xaxis.labelpad == mpl.rcParams['axes.labelpad'] + # labelpad can be set in set_label + ax.set_xlabel('X LABEL', labelpad=10) + assert ax.xaxis.labelpad == 10 + ax.set_ylabel('Y LABEL') + ax.set_zlabel('Z LABEL', labelpad=20) + assert ax.zaxis.labelpad == 20 + assert ax.get_zlabel() == 'Z LABEL' + # or manually + ax.yaxis.labelpad = 20 + ax.zaxis.labelpad = -40 + + # Tick labels also respect tick.pad (also from rcParams) + for i, tick in enumerate(ax.yaxis.get_major_ticks()): + tick.set_pad(tick.get_pad() + 5 - i * 5) + + +@mpl3d_image_comparison(['axes3d_cla.png'], remove_text=False, style='mpl20') +def test_axes3d_cla(): + # fixed in pull request 4553 + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.set_axis_off() + ax.cla() # make sure the axis displayed is 3D (not 2D) + + +@mpl3d_image_comparison(['axes3d_rotated.png'], + remove_text=False, style='mpl20') +def test_axes3d_rotated(): + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.view_init(90, 45, 0) # look down, rotated. Should be square + + +def test_plotsurface_1d_raises(): + x = np.linspace(0.5, 10, num=100) + y = np.linspace(0.5, 10, num=100) + X, Y = np.meshgrid(x, y) + z = np.random.randn(100) + + fig = plt.figure(figsize=(14, 6)) + ax = fig.add_subplot(1, 2, 1, projection='3d') + with pytest.raises(ValueError): + ax.plot_surface(X, Y, z) + + +def _test_proj_make_M(): + # eye point + E = np.array([1000, -1000, 2000]) + R = np.array([100, 100, 100]) + V = np.array([0, 0, 1]) + roll = 0 + u, v, w = proj3d._view_axes(E, R, V, roll) + viewM = proj3d._view_transformation_uvw(u, v, w, E) + perspM = proj3d._persp_transformation(100, -100, 1) + M = np.dot(perspM, viewM) + return M + + +def test_proj_transform(): + M = _test_proj_make_M() + invM = np.linalg.inv(M) + + xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0 + ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0 + zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0 + + txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) + ixs, iys, izs = proj3d.inv_transform(txs, tys, tzs, invM) + + np.testing.assert_almost_equal(ixs, xs) + np.testing.assert_almost_equal(iys, ys) + np.testing.assert_almost_equal(izs, zs) + + +def _test_proj_draw_axes(M, s=1, *args, **kwargs): + xs = [0, s, 0, 0] + ys = [0, 0, s, 0] + zs = [0, 0, 0, s] + txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) + o, ax, ay, az = zip(txs, tys) + lines = [(o, ax), (o, ay), (o, az)] + + fig, ax = plt.subplots(*args, **kwargs) + linec = LineCollection(lines) + ax.add_collection(linec) + for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']): + ax.text(x, y, t) + + return fig, ax + + +@mpl3d_image_comparison(['proj3d_axes_cube.png'], style='mpl20') +def test_proj_axes_cube(): + M = _test_proj_make_M() + + ts = '0 1 2 3 0 4 5 6 7 4'.split() + xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0 + ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0 + zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0 + + txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) + + fig, ax = _test_proj_draw_axes(M, s=400) + + ax.scatter(txs, tys, c=tzs) + ax.plot(txs, tys, c='r') + for x, y, t in zip(txs, tys, ts): + ax.text(x, y, t) + + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_xlim(-0.2, 0.2) + ax.set_ylim(-0.2, 0.2) + + +@mpl3d_image_comparison(['proj3d_axes_cube_ortho.png'], style='mpl20') +def test_proj_axes_cube_ortho(): + E = np.array([200, 100, 100]) + R = np.array([0, 0, 0]) + V = np.array([0, 0, 1]) + roll = 0 + u, v, w = proj3d._view_axes(E, R, V, roll) + viewM = proj3d._view_transformation_uvw(u, v, w, E) + orthoM = proj3d._ortho_transformation(-1, 1) + M = np.dot(orthoM, viewM) + + ts = '0 1 2 3 0 4 5 6 7 4'.split() + xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 100 + ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 100 + zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 100 + + txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) + + fig, ax = _test_proj_draw_axes(M, s=150) + + ax.scatter(txs, tys, s=300-tzs) + ax.plot(txs, tys, c='r') + for x, y, t in zip(txs, tys, ts): + ax.text(x, y, t) + + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + ax.set_xlim(-200, 200) + ax.set_ylim(-200, 200) + + +def test_world(): + xmin, xmax = 100, 120 + ymin, ymax = -100, 100 + zmin, zmax = 0.1, 0.2 + M = proj3d.world_transformation(xmin, xmax, ymin, ymax, zmin, zmax) + np.testing.assert_allclose(M, + [[5e-2, 0, 0, -5], + [0, 5e-3, 0, 5e-1], + [0, 0, 1e1, -1], + [0, 0, 0, 1]]) + + +def test_autoscale(): + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + assert ax.get_zscale() == 'linear' + ax._view_margin = 0 + ax.margins(x=0, y=.1, z=.2) + ax.plot([0, 1], [0, 1], [0, 1]) + assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.2, 1.2) + ax.autoscale(False) + ax.set_autoscalez_on(True) + ax.plot([0, 2], [0, 2], [0, 2]) + assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.4, 2.4) + ax.autoscale(axis='x') + ax.plot([0, 2], [0, 2], [0, 2]) + assert ax.get_w_lims() == (0, 2, -.1, 1.1, -.4, 2.4) + + +@pytest.mark.parametrize('axis', ('x', 'y', 'z')) +@pytest.mark.parametrize('auto', (True, False, None)) +def test_unautoscale(axis, auto): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + x = np.arange(100) + y = np.linspace(-0.1, 0.1, 100) + ax.scatter(x, y) + + get_autoscale_on = getattr(ax, f'get_autoscale{axis}_on') + set_lim = getattr(ax, f'set_{axis}lim') + get_lim = getattr(ax, f'get_{axis}lim') + + post_auto = get_autoscale_on() if auto is None else auto + + set_lim((-0.5, 0.5), auto=auto) + assert post_auto == get_autoscale_on() + fig.canvas.draw() + np.testing.assert_array_equal(get_lim(), (-0.5, 0.5)) + + +@check_figures_equal() +def test_culling(fig_test, fig_ref): + xmins = (-100, -50) + for fig, xmin in zip((fig_test, fig_ref), xmins): + ax = fig.add_subplot(projection='3d') + n = abs(xmin) + 1 + xs = np.linspace(0, xmin, n) + ys = np.ones(n) + zs = np.zeros(n) + ax.plot(xs, ys, zs, 'k') + + ax.set(xlim=(-5, 5), ylim=(-5, 5), zlim=(-5, 5)) + ax.view_init(5, 180, 0) + + +def test_axes3d_focal_length_checks(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + with pytest.raises(ValueError): + ax.set_proj_type('persp', focal_length=0) + with pytest.raises(ValueError): + ax.set_proj_type('ortho', focal_length=1) + + +@mpl3d_image_comparison(['axes3d_focal_length.png'], + remove_text=False, style='mpl20') +def test_axes3d_focal_length(): + fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) + axs[0].set_proj_type('persp', focal_length=np.inf) + axs[1].set_proj_type('persp', focal_length=0.15) + + +@mpl3d_image_comparison(['axes3d_ortho.png'], remove_text=False, style='mpl20') +def test_axes3d_ortho(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_proj_type('ortho') + + +@mpl3d_image_comparison(['axes3d_isometric.png'], style='mpl20') +def test_axes3d_isometric(): + from itertools import combinations, product + fig, ax = plt.subplots(subplot_kw=dict( + projection='3d', + proj_type='ortho', + box_aspect=(4, 4, 4) + )) + r = (-1, 1) # stackoverflow.com/a/11156353 + for s, e in combinations(np.array(list(product(r, r, r))), 2): + if abs(s - e).sum() == r[1] - r[0]: + ax.plot3D(*zip(s, e), c='k') + ax.view_init(elev=np.degrees(np.arctan(1. / np.sqrt(2))), azim=-45, roll=0) + ax.grid(True) + + +@check_figures_equal() +def test_axlim_clip(fig_test, fig_ref): + # With axlim clipping + ax = fig_test.add_subplot(projection="3d") + x = np.linspace(0, 1, 11) + y = np.linspace(0, 1, 11) + X, Y = np.meshgrid(x, y) + Z = X + Y + ax.plot_surface(X, Y, Z, facecolor='C1', edgecolors=None, + rcount=50, ccount=50, axlim_clip=True) + # This ax.plot is to cover the extra surface edge which is not clipped out + ax.plot([0.5, 0.5], [0, 1], [0.5, 1.5], + color='k', linewidth=3, zorder=5, axlim_clip=True) + ax.scatter(X.ravel(), Y.ravel(), Z.ravel() + 1, axlim_clip=True) + ax.quiver(X.ravel(), Y.ravel(), Z.ravel() + 2, + 0*X.ravel(), 0*Y.ravel(), 0*Z.ravel() + 1, + arrow_length_ratio=0, axlim_clip=True) + ax.plot(X[0], Y[0], Z[0] + 3, color='C2', axlim_clip=True) + ax.text(1.1, 0.5, 4, 'test', axlim_clip=True) # won't be visible + ax.set(xlim=(0, 0.5), ylim=(0, 1), zlim=(0, 5)) + + # With manual clipping + ax = fig_ref.add_subplot(projection="3d") + idx = (X <= 0.5) + X = X[idx].reshape(11, 6) + Y = Y[idx].reshape(11, 6) + Z = Z[idx].reshape(11, 6) + ax.plot_surface(X, Y, Z, facecolor='C1', edgecolors=None, + rcount=50, ccount=50, axlim_clip=False) + ax.plot([0.5, 0.5], [0, 1], [0.5, 1.5], + color='k', linewidth=3, zorder=5, axlim_clip=False) + ax.scatter(X.ravel(), Y.ravel(), Z.ravel() + 1, axlim_clip=False) + ax.quiver(X.ravel(), Y.ravel(), Z.ravel() + 2, + 0*X.ravel(), 0*Y.ravel(), 0*Z.ravel() + 1, + arrow_length_ratio=0, axlim_clip=False) + ax.plot(X[0], Y[0], Z[0] + 3, color='C2', axlim_clip=False) + ax.set(xlim=(0, 0.5), ylim=(0, 1), zlim=(0, 5)) + + +@pytest.mark.parametrize('value', [np.inf, np.nan]) +@pytest.mark.parametrize(('setter', 'side'), [ + ('set_xlim3d', 'left'), + ('set_xlim3d', 'right'), + ('set_ylim3d', 'bottom'), + ('set_ylim3d', 'top'), + ('set_zlim3d', 'bottom'), + ('set_zlim3d', 'top'), +]) +def test_invalid_axes_limits(setter, side, value): + limit = {side: value} + fig = plt.figure() + obj = fig.add_subplot(projection='3d') + with pytest.raises(ValueError): + getattr(obj, setter)(**limit) + + +class TestVoxels: + @mpl3d_image_comparison(['voxels-simple.png'], style='mpl20') + def test_simple(self): + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + x, y, z = np.indices((5, 4, 3)) + voxels = (x == y) | (y == z) + ax.voxels(voxels) + + @mpl3d_image_comparison(['voxels-edge-style.png'], style='mpl20') + def test_edge_style(self): + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + x, y, z = np.indices((5, 5, 4)) + voxels = ((x - 2)**2 + (y - 2)**2 + (z-1.5)**2) < 2.2**2 + v = ax.voxels(voxels, linewidths=3, edgecolor='C1') + + # change the edge color of one voxel + v[max(v.keys())].set_edgecolor('C2') + + @mpl3d_image_comparison(['voxels-named-colors.png'], style='mpl20') + def test_named_colors(self): + """Test with colors set to a 3D object array of strings.""" + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + x, y, z = np.indices((10, 10, 10)) + voxels = (x == y) | (y == z) + voxels = voxels & ~(x * y * z < 1) + colors = np.full((10, 10, 10), 'C0', dtype=np.object_) + colors[(x < 5) & (y < 5)] = '0.25' + colors[(x + z) < 10] = 'cyan' + ax.voxels(voxels, facecolors=colors) + + @mpl3d_image_comparison(['voxels-rgb-data.png'], style='mpl20') + def test_rgb_data(self): + """Test with colors set to a 4d float array of rgb data.""" + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + x, y, z = np.indices((10, 10, 10)) + voxels = (x == y) | (y == z) + colors = np.zeros((10, 10, 10, 3)) + colors[..., 0] = x / 9 + colors[..., 1] = y / 9 + colors[..., 2] = z / 9 + ax.voxels(voxels, facecolors=colors) + + @mpl3d_image_comparison(['voxels-alpha.png'], style='mpl20') + def test_alpha(self): + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + x, y, z = np.indices((10, 10, 10)) + v1 = x == y + v2 = np.abs(x - y) < 2 + voxels = v1 | v2 + colors = np.zeros((10, 10, 10, 4)) + colors[v2] = [1, 0, 0, 0.5] + colors[v1] = [0, 1, 0, 0.5] + v = ax.voxels(voxels, facecolors=colors) + + assert type(v) is dict + for coord, poly in v.items(): + assert voxels[coord], "faces returned for absent voxel" + assert isinstance(poly, art3d.Poly3DCollection) + + @mpl3d_image_comparison(['voxels-xyz.png'], + tol=0.01, remove_text=False, style='mpl20') + def test_xyz(self): + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + def midpoints(x): + sl = () + for i in range(x.ndim): + x = (x[sl + np.index_exp[:-1]] + + x[sl + np.index_exp[1:]]) / 2.0 + sl += np.index_exp[:] + return x + + # prepare some coordinates, and attach rgb values to each + r, g, b = np.indices((17, 17, 17)) / 16.0 + rc = midpoints(r) + gc = midpoints(g) + bc = midpoints(b) + + # define a sphere about [0.5, 0.5, 0.5] + sphere = (rc - 0.5)**2 + (gc - 0.5)**2 + (bc - 0.5)**2 < 0.5**2 + + # combine the color components + colors = np.zeros(sphere.shape + (3,)) + colors[..., 0] = rc + colors[..., 1] = gc + colors[..., 2] = bc + + # and plot everything + ax.voxels(r, g, b, sphere, + facecolors=colors, + edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter + linewidth=0.5) + + def test_calling_conventions(self): + x, y, z = np.indices((3, 4, 5)) + filled = np.ones((2, 3, 4)) + + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + + # all the valid calling conventions + for kw in (dict(), dict(edgecolor='k')): + ax.voxels(filled, **kw) + ax.voxels(filled=filled, **kw) + ax.voxels(x, y, z, filled, **kw) + ax.voxels(x, y, z, filled=filled, **kw) + + # duplicate argument + with pytest.raises(TypeError, match='voxels'): + ax.voxels(x, y, z, filled, filled=filled) + # missing arguments + with pytest.raises(TypeError, match='voxels'): + ax.voxels(x, y) + # x, y, z are positional only - this passes them on as attributes of + # Poly3DCollection + with pytest.raises(AttributeError, match="keyword argument 'x'") as exec_info: + ax.voxels(filled=filled, x=x, y=y, z=z) + assert exec_info.value.name == 'x' + + +def test_line3d_set_get_data_3d(): + x, y, z = [0, 1], [2, 3], [4, 5] + x2, y2, z2 = [6, 7], [8, 9], [10, 11] + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + lines = ax.plot(x, y, z) + line = lines[0] + np.testing.assert_array_equal((x, y, z), line.get_data_3d()) + line.set_data_3d(x2, y2, z2) + np.testing.assert_array_equal((x2, y2, z2), line.get_data_3d()) + line.set_xdata(x) + line.set_ydata(y) + line.set_3d_properties(zs=z, zdir='z') + np.testing.assert_array_equal((x, y, z), line.get_data_3d()) + line.set_3d_properties(zs=0, zdir='z') + np.testing.assert_array_equal((x, y, np.zeros_like(z)), line.get_data_3d()) + + +@check_figures_equal() +def test_inverted(fig_test, fig_ref): + # Plot then invert. + ax = fig_test.add_subplot(projection="3d") + ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10]) + ax.invert_yaxis() + # Invert then plot. + ax = fig_ref.add_subplot(projection="3d") + ax.invert_yaxis() + ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10]) + + +def test_inverted_cla(): + # GitHub PR #5450. Setting autoscale should reset + # axes to be non-inverted. + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + # 1. test that a new axis is not inverted per default + assert not ax.xaxis_inverted() + assert not ax.yaxis_inverted() + assert not ax.zaxis_inverted() + ax.set_xlim(1, 0) + ax.set_ylim(1, 0) + ax.set_zlim(1, 0) + assert ax.xaxis_inverted() + assert ax.yaxis_inverted() + assert ax.zaxis_inverted() + ax.cla() + assert not ax.xaxis_inverted() + assert not ax.yaxis_inverted() + assert not ax.zaxis_inverted() + + +def test_ax3d_tickcolour(): + fig = plt.figure() + ax = Axes3D(fig) + + ax.tick_params(axis='x', colors='red') + ax.tick_params(axis='y', colors='red') + ax.tick_params(axis='z', colors='red') + fig.canvas.draw() + + for tick in ax.xaxis.get_major_ticks(): + assert tick.tick1line._color == 'red' + for tick in ax.yaxis.get_major_ticks(): + assert tick.tick1line._color == 'red' + for tick in ax.zaxis.get_major_ticks(): + assert tick.tick1line._color == 'red' + + +@check_figures_equal() +def test_ticklabel_format(fig_test, fig_ref): + axs = fig_test.subplots(4, 5, subplot_kw={"projection": "3d"}) + for ax in axs.flat: + ax.set_xlim(1e7, 1e7 + 10) + for row, name in zip(axs, ["x", "y", "z", "both"]): + row[0].ticklabel_format( + axis=name, style="plain") + row[1].ticklabel_format( + axis=name, scilimits=(-2, 2)) + row[2].ticklabel_format( + axis=name, useOffset=not mpl.rcParams["axes.formatter.useoffset"]) + row[3].ticklabel_format( + axis=name, useLocale=not mpl.rcParams["axes.formatter.use_locale"]) + row[4].ticklabel_format( + axis=name, + useMathText=not mpl.rcParams["axes.formatter.use_mathtext"]) + + def get_formatters(ax, names): + return [getattr(ax, name).get_major_formatter() for name in names] + + axs = fig_ref.subplots(4, 5, subplot_kw={"projection": "3d"}) + for ax in axs.flat: + ax.set_xlim(1e7, 1e7 + 10) + for row, names in zip( + axs, [["xaxis"], ["yaxis"], ["zaxis"], ["xaxis", "yaxis", "zaxis"]] + ): + for fmt in get_formatters(row[0], names): + fmt.set_scientific(False) + for fmt in get_formatters(row[1], names): + fmt.set_powerlimits((-2, 2)) + for fmt in get_formatters(row[2], names): + fmt.set_useOffset(not mpl.rcParams["axes.formatter.useoffset"]) + for fmt in get_formatters(row[3], names): + fmt.set_useLocale(not mpl.rcParams["axes.formatter.use_locale"]) + for fmt in get_formatters(row[4], names): + fmt.set_useMathText( + not mpl.rcParams["axes.formatter.use_mathtext"]) + + +@check_figures_equal() +def test_quiver3D_smoke(fig_test, fig_ref): + pivot = "middle" + # Make the grid + x, y, z = np.meshgrid( + np.arange(-0.8, 1, 0.2), + np.arange(-0.8, 1, 0.2), + np.arange(-0.8, 1, 0.8) + ) + u = v = w = np.ones_like(x) + + for fig, length in zip((fig_ref, fig_test), (1, 1.0)): + ax = fig.add_subplot(projection="3d") + ax.quiver(x, y, z, u, v, w, length=length, pivot=pivot) + + +@image_comparison(["minor_ticks.png"], style="mpl20") +def test_minor_ticks(): + ax = plt.figure().add_subplot(projection="3d") + ax.set_xticks([0.25], minor=True) + ax.set_xticklabels(["quarter"], minor=True) + ax.set_yticks([0.33], minor=True) + ax.set_yticklabels(["third"], minor=True) + ax.set_zticks([0.50], minor=True) + ax.set_zticklabels(["half"], minor=True) + + +# remove tolerance when regenerating the test image +@mpl3d_image_comparison(['errorbar3d_errorevery.png'], style='mpl20', tol=0.003) +def test_errorbar3d_errorevery(): + """Tests errorevery functionality for 3D errorbars.""" + t = np.arange(0, 2*np.pi+.1, 0.01) + x, y, z = np.sin(t), np.cos(3*t), np.sin(5*t) + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + estep = 15 + i = np.arange(t.size) + zuplims = (i % estep == 0) & (i // estep % 3 == 0) + zlolims = (i % estep == 0) & (i // estep % 3 == 2) + + ax.errorbar(x, y, z, 0.2, zuplims=zuplims, zlolims=zlolims, + errorevery=estep) + + +@mpl3d_image_comparison(['errorbar3d.png'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.02) +def test_errorbar3d(): + """Tests limits, color styling, and legend for 3D errorbars.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + d = [1, 2, 3, 4, 5] + e = [.5, .5, .5, .5, .5] + ax.errorbar(x=d, y=d, z=d, xerr=e, yerr=e, zerr=e, capsize=3, + zuplims=[False, True, False, True, True], + zlolims=[True, False, False, True, False], + yuplims=True, + ecolor='purple', label='Error lines') + ax.legend() + + +@image_comparison(['stem3d.png'], style='mpl20', tol=0.009) +def test_stem3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig, axs = plt.subplots(2, 3, figsize=(8, 6), + constrained_layout=True, + subplot_kw={'projection': '3d'}) + + theta = np.linspace(0, 2*np.pi) + x = np.cos(theta - np.pi/2) + y = np.sin(theta - np.pi/2) + z = theta + + for ax, zdir in zip(axs[0], ['x', 'y', 'z']): + ax.stem(x, y, z, orientation=zdir) + ax.set_title(f'orientation={zdir}') + + x = np.linspace(-np.pi/2, np.pi/2, 20) + y = np.ones_like(x) + z = np.cos(x) + + for ax, zdir in zip(axs[1], ['x', 'y', 'z']): + markerline, stemlines, baseline = ax.stem( + x, y, z, + linefmt='C4-.', markerfmt='C1D', basefmt='C2', + orientation=zdir) + ax.set_title(f'orientation={zdir}') + markerline.set(markerfacecolor='none', markeredgewidth=2) + baseline.set_linewidth(3) + + +@image_comparison(["equal_box_aspect.png"], style="mpl20") +def test_equal_box_aspect(): + from itertools import product, combinations + + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + + # Make data + u = np.linspace(0, 2 * np.pi, 100) + v = np.linspace(0, np.pi, 100) + x = np.outer(np.cos(u), np.sin(v)) + y = np.outer(np.sin(u), np.sin(v)) + z = np.outer(np.ones_like(u), np.cos(v)) + + # Plot the surface + ax.plot_surface(x, y, z) + + # draw cube + r = [-1, 1] + for s, e in combinations(np.array(list(product(r, r, r))), 2): + if np.sum(np.abs(s - e)) == r[1] - r[0]: + ax.plot3D(*zip(s, e), color="b") + + # Make axes limits + xyzlim = np.column_stack( + [ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d()] + ) + XYZlim = [min(xyzlim[0]), max(xyzlim[1])] + ax.set_xlim3d(XYZlim) + ax.set_ylim3d(XYZlim) + ax.set_zlim3d(XYZlim) + ax.axis('off') + ax.set_box_aspect((1, 1, 1)) + + with pytest.raises(ValueError, match="Argument zoom ="): + ax.set_box_aspect((1, 1, 1), zoom=-1) + + +def test_colorbar_pos(): + num_plots = 2 + fig, axs = plt.subplots(1, num_plots, figsize=(4, 5), + constrained_layout=True, + subplot_kw={'projection': '3d'}) + for ax in axs: + p_tri = ax.plot_trisurf(np.random.randn(5), np.random.randn(5), + np.random.randn(5)) + + cbar = plt.colorbar(p_tri, ax=axs, orientation='horizontal') + + fig.canvas.draw() + # check that actually on the bottom + assert cbar.ax.get_position().extents[1] < 0.2 + + +def test_inverted_zaxis(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_zlim(0, 1) + assert not ax.zaxis_inverted() + assert ax.get_zlim() == (0, 1) + assert ax.get_zbound() == (0, 1) + + # Change bound + ax.set_zbound((0, 2)) + assert not ax.zaxis_inverted() + assert ax.get_zlim() == (0, 2) + assert ax.get_zbound() == (0, 2) + + # Change invert + ax.invert_zaxis() + assert ax.zaxis_inverted() + assert ax.get_zlim() == (2, 0) + assert ax.get_zbound() == (0, 2) + + # Set upper bound + ax.set_zbound(upper=1) + assert ax.zaxis_inverted() + assert ax.get_zlim() == (1, 0) + assert ax.get_zbound() == (0, 1) + + # Set lower bound + ax.set_zbound(lower=2) + assert ax.zaxis_inverted() + assert ax.get_zlim() == (2, 1) + assert ax.get_zbound() == (1, 2) + + +def test_set_zlim(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + assert np.allclose(ax.get_zlim(), (-1/48, 49/48)) + ax.set_zlim(zmax=2) + assert np.allclose(ax.get_zlim(), (-1/48, 2)) + ax.set_zlim(zmin=1) + assert ax.get_zlim() == (1, 2) + + with pytest.raises( + TypeError, match="Cannot pass both 'lower' and 'min'"): + ax.set_zlim(bottom=0, zmin=1) + with pytest.raises( + TypeError, match="Cannot pass both 'upper' and 'max'"): + ax.set_zlim(top=0, zmax=1) + + +@check_figures_equal() +def test_shared_view(fig_test, fig_ref): + elev, azim, roll = 5, 20, 30 + ax1 = fig_test.add_subplot(131, projection="3d") + ax2 = fig_test.add_subplot(132, projection="3d", shareview=ax1) + ax3 = fig_test.add_subplot(133, projection="3d") + ax3.shareview(ax1) + ax2.view_init(elev=elev, azim=azim, roll=roll, share=True) + + for subplot_num in (131, 132, 133): + ax = fig_ref.add_subplot(subplot_num, projection="3d") + ax.view_init(elev=elev, azim=azim, roll=roll) + + +def test_shared_axes_retick(): + fig = plt.figure() + ax1 = fig.add_subplot(211, projection="3d") + ax2 = fig.add_subplot(212, projection="3d", sharez=ax1) + ax1.plot([0, 1], [0, 1], [0, 2]) + ax2.plot([0, 1], [0, 1], [0, 2]) + ax1.set_zticks([-0.5, 0, 2, 2.5]) + # check that setting ticks on a shared axis is synchronized + assert ax1.get_zlim() == (-0.5, 2.5) + assert ax2.get_zlim() == (-0.5, 2.5) + + +def test_quaternion(): + # 1: + q1 = Quaternion(1, [0, 0, 0]) + assert q1.scalar == 1 + assert (q1.vector == [0, 0, 0]).all + # __neg__: + assert (-q1).scalar == -1 + assert ((-q1).vector == [0, 0, 0]).all + # i, j, k: + qi = Quaternion(0, [1, 0, 0]) + assert qi.scalar == 0 + assert (qi.vector == [1, 0, 0]).all + qj = Quaternion(0, [0, 1, 0]) + assert qj.scalar == 0 + assert (qj.vector == [0, 1, 0]).all + qk = Quaternion(0, [0, 0, 1]) + assert qk.scalar == 0 + assert (qk.vector == [0, 0, 1]).all + # i^2 = j^2 = k^2 = -1: + assert qi*qi == -q1 + assert qj*qj == -q1 + assert qk*qk == -q1 + # identity: + assert q1*qi == qi + assert q1*qj == qj + assert q1*qk == qk + # i*j=k, j*k=i, k*i=j: + assert qi*qj == qk + assert qj*qk == qi + assert qk*qi == qj + assert qj*qi == -qk + assert qk*qj == -qi + assert qi*qk == -qj + # __mul__: + assert (Quaternion(2, [3, 4, 5]) * Quaternion(6, [7, 8, 9]) + == Quaternion(-86, [28, 48, 44])) + # conjugate(): + for q in [q1, qi, qj, qk]: + assert q.conjugate().scalar == q.scalar + assert (q.conjugate().vector == -q.vector).all + assert q.conjugate().conjugate() == q + assert ((q*q.conjugate()).vector == 0).all + # norm: + q0 = Quaternion(0, [0, 0, 0]) + assert q0.norm == 0 + assert q1.norm == 1 + assert qi.norm == 1 + assert qj.norm == 1 + assert qk.norm == 1 + for q in [q0, q1, qi, qj, qk]: + assert q.norm == (q*q.conjugate()).scalar + # normalize(): + for q in [ + Quaternion(2, [0, 0, 0]), + Quaternion(0, [3, 0, 0]), + Quaternion(0, [0, 4, 0]), + Quaternion(0, [0, 0, 5]), + Quaternion(6, [7, 8, 9]) + ]: + assert q.normalize().norm == 1 + # reciprocal(): + for q in [q1, qi, qj, qk]: + assert q*q.reciprocal() == q1 + assert q.reciprocal()*q == q1 + # rotate(): + assert (qi.rotate([1, 2, 3]) == np.array([1, -2, -3])).all + # rotate_from_to(): + for r1, r2, q in [ + ([1, 0, 0], [0, 1, 0], Quaternion(np.sqrt(1/2), [0, 0, np.sqrt(1/2)])), + ([1, 0, 0], [0, 0, 1], Quaternion(np.sqrt(1/2), [0, -np.sqrt(1/2), 0])), + ([1, 0, 0], [1, 0, 0], Quaternion(1, [0, 0, 0])) + ]: + assert Quaternion.rotate_from_to(r1, r2) == q + # rotate_from_to(), special case: + for r1 in [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 1]]: + r1 = np.array(r1) + with pytest.warns(UserWarning): + q = Quaternion.rotate_from_to(r1, -r1) + assert np.isclose(q.norm, 1) + assert np.dot(q.vector, r1) == 0 + # from_cardan_angles(), as_cardan_angles(): + for elev, azim, roll in [(0, 0, 0), + (90, 0, 0), (0, 90, 0), (0, 0, 90), + (0, 30, 30), (30, 0, 30), (30, 30, 0), + (47, 11, -24)]: + for mag in [1, 2]: + q = Quaternion.from_cardan_angles( + np.deg2rad(elev), np.deg2rad(azim), np.deg2rad(roll)) + assert np.isclose(q.norm, 1) + q = Quaternion(mag * q.scalar, mag * q.vector) + np.testing.assert_allclose(np.rad2deg(Quaternion.as_cardan_angles(q)), + (elev, azim, roll), atol=1e-6) + + +@pytest.mark.parametrize('style', + ('azel', 'trackball', 'sphere', 'arcball')) +def test_rotate(style): + """Test rotating using the left mouse button.""" + if style == 'azel': + s = 0.5 + else: + s = mpl.rcParams['axes3d.trackballsize'] / 2 + s *= 0.5 + mpl.rcParams['axes3d.trackballborder'] = 0 + with mpl.rc_context({'axes3d.mouserotationstyle': style}): + for roll, dx, dy in [ + [0, 1, 0], + [30, 1, 0], + [0, 0, 1], + [30, 0, 1], + [0, 0.5, np.sqrt(3)/2], + [30, 0.5, np.sqrt(3)/2], + [0, 2, 0]]: + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.view_init(0, 0, roll) + ax.figure.canvas.draw() + + # drag mouse to change orientation + ax._button_press( + mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) + ax._on_move( + mock_event(ax, button=MouseButton.LEFT, + xdata=s*dx*ax._pseudo_w, ydata=s*dy*ax._pseudo_h)) + ax.figure.canvas.draw() + + c = np.sqrt(3)/2 + expectations = { + ('azel', 0, 1, 0): (0, -45, 0), + ('azel', 0, 0, 1): (-45, 0, 0), + ('azel', 0, 0.5, c): (-38.971143, -22.5, 0), + ('azel', 0, 2, 0): (0, -90, 0), + ('azel', 30, 1, 0): (22.5, -38.971143, 30), + ('azel', 30, 0, 1): (-38.971143, -22.5, 30), + ('azel', 30, 0.5, c): (-22.5, -38.971143, 30), + + ('trackball', 0, 1, 0): (0, -28.64789, 0), + ('trackball', 0, 0, 1): (-28.64789, 0, 0), + ('trackball', 0, 0.5, c): (-24.531578, -15.277726, 3.340403), + ('trackball', 0, 2, 0): (0, -180/np.pi, 0), + ('trackball', 30, 1, 0): (13.869588, -25.319385, 26.87008), + ('trackball', 30, 0, 1): (-24.531578, -15.277726, 33.340403), + ('trackball', 30, 0.5, c): (-13.869588, -25.319385, 33.129920), + + ('sphere', 0, 1, 0): (0, -30, 0), + ('sphere', 0, 0, 1): (-30, 0, 0), + ('sphere', 0, 0.5, c): (-25.658906, -16.102114, 3.690068), + ('sphere', 0, 2, 0): (0, -90, 0), + ('sphere', 30, 1, 0): (14.477512, -26.565051, 26.565051), + ('sphere', 30, 0, 1): (-25.658906, -16.102114, 33.690068), + ('sphere', 30, 0.5, c): (-14.477512, -26.565051, 33.434949), + + ('arcball', 0, 1, 0): (0, -60, 0), + ('arcball', 0, 0, 1): (-60, 0, 0), + ('arcball', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), + ('arcball', 0, 2, 0): (0, 180, 0), + ('arcball', 30, 1, 0): (25.658906, -56.309932, 16.102114), + ('arcball', 30, 0, 1): (-48.590378, -40.893395, 49.106605), + ('arcball', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} + new_elev, new_azim, new_roll = expectations[(style, roll, dx, dy)] + np.testing.assert_allclose((ax.elev, ax.azim, ax.roll), + (new_elev, new_azim, new_roll), atol=1e-6) + + +def test_pan(): + """Test mouse panning using the middle mouse button.""" + + def convert_lim(dmin, dmax): + """Convert min/max limits to center and range.""" + center = (dmin + dmax) / 2 + range_ = dmax - dmin + return center, range_ + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter(0, 0, 0) + fig.canvas.draw() + + x_center0, x_range0 = convert_lim(*ax.get_xlim3d()) + y_center0, y_range0 = convert_lim(*ax.get_ylim3d()) + z_center0, z_range0 = convert_lim(*ax.get_zlim3d()) + + # move mouse diagonally to pan along all axis. + ax._button_press( + mock_event(ax, button=MouseButton.MIDDLE, xdata=0, ydata=0)) + ax._on_move( + mock_event(ax, button=MouseButton.MIDDLE, xdata=1, ydata=1)) + + x_center, x_range = convert_lim(*ax.get_xlim3d()) + y_center, y_range = convert_lim(*ax.get_ylim3d()) + z_center, z_range = convert_lim(*ax.get_zlim3d()) + + # Ranges have not changed + assert x_range == pytest.approx(x_range0) + assert y_range == pytest.approx(y_range0) + assert z_range == pytest.approx(z_range0) + + # But center positions have + assert x_center != pytest.approx(x_center0) + assert y_center != pytest.approx(y_center0) + assert z_center != pytest.approx(z_center0) + + +@pytest.mark.parametrize("tool,button,key,expected", + [("zoom", MouseButton.LEFT, None, # zoom in + ((0.00, 0.06), (0.01, 0.07), (0.02, 0.08))), + ("zoom", MouseButton.LEFT, 'x', # zoom in + ((-0.01, 0.10), (-0.03, 0.08), (-0.06, 0.06))), + ("zoom", MouseButton.LEFT, 'y', # zoom in + ((-0.07, 0.05), (-0.04, 0.08), (0.00, 0.12))), + ("zoom", MouseButton.RIGHT, None, # zoom out + ((-0.09, 0.15), (-0.08, 0.17), (-0.07, 0.18))), + ("pan", MouseButton.LEFT, None, + ((-0.70, -0.58), (-1.04, -0.91), (-1.27, -1.15))), + ("pan", MouseButton.LEFT, 'x', + ((-0.97, -0.84), (-0.58, -0.46), (-0.06, 0.06))), + ("pan", MouseButton.LEFT, 'y', + ((0.20, 0.32), (-0.51, -0.39), (-1.27, -1.15)))]) +def test_toolbar_zoom_pan(tool, button, key, expected): + # NOTE: The expected zoom values are rough ballparks of moving in the view + # to make sure we are getting the right direction of motion. + # The specific values can and should change if the zoom movement + # scaling factor gets updated. + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter(0, 0, 0) + fig.canvas.draw() + xlim0, ylim0, zlim0 = ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d() + + # Mouse from (0, 0) to (1, 1) + d0 = (0, 0) + d1 = (1, 1) + # Convert to screen coordinates ("s"). Events are defined only with pixel + # precision, so round the pixel values, and below, check against the + # corresponding xdata/ydata, which are close but not equal to d0/d1. + s0 = ax.transData.transform(d0).astype(int) + s1 = ax.transData.transform(d1).astype(int) + + # Set up the mouse movements + start_event = MouseEvent( + "button_press_event", fig.canvas, *s0, button, key=key) + drag_event = MouseEvent( + "motion_notify_event", fig.canvas, *s1, button, key=key, buttons={button}) + stop_event = MouseEvent( + "button_release_event", fig.canvas, *s1, button, key=key) + + tb = NavigationToolbar2(fig.canvas) + if tool == "zoom": + tb.zoom() + else: + tb.pan() + + start_event._process() + drag_event._process() + stop_event._process() + + # Should be close, but won't be exact due to screen integer resolution + xlim, ylim, zlim = expected + assert ax.get_xlim3d() == pytest.approx(xlim, abs=0.01) + assert ax.get_ylim3d() == pytest.approx(ylim, abs=0.01) + assert ax.get_zlim3d() == pytest.approx(zlim, abs=0.01) + + # Ensure that back, forward, and home buttons work + tb.back() + assert ax.get_xlim3d() == pytest.approx(xlim0) + assert ax.get_ylim3d() == pytest.approx(ylim0) + assert ax.get_zlim3d() == pytest.approx(zlim0) + + tb.forward() + assert ax.get_xlim3d() == pytest.approx(xlim, abs=0.01) + assert ax.get_ylim3d() == pytest.approx(ylim, abs=0.01) + assert ax.get_zlim3d() == pytest.approx(zlim, abs=0.01) + + tb.home() + assert ax.get_xlim3d() == pytest.approx(xlim0) + assert ax.get_ylim3d() == pytest.approx(ylim0) + assert ax.get_zlim3d() == pytest.approx(zlim0) + + +@mpl.style.context('default') +@check_figures_equal() +def test_scalarmap_update(fig_test, fig_ref): + + x, y, z = np.array(list(itertools.product(*[np.arange(0, 5, 1), + np.arange(0, 5, 1), + np.arange(0, 5, 1)]))).T + c = x + y + + # test + ax_test = fig_test.add_subplot(111, projection='3d') + sc_test = ax_test.scatter(x, y, z, c=c, s=40, cmap='viridis') + # force a draw + fig_test.canvas.draw() + # mark it as "stale" + sc_test.changed() + + # ref + ax_ref = fig_ref.add_subplot(111, projection='3d') + sc_ref = ax_ref.scatter(x, y, z, c=c, s=40, cmap='viridis') + + +def test_subfigure_simple(): + # smoketest that subfigures can work... + fig = plt.figure() + sf = fig.subfigures(1, 2) + ax = sf[0].add_subplot(1, 1, 1, projection='3d') + ax = sf[1].add_subplot(1, 1, 1, projection='3d', label='other') + + +# Update style when regenerating the test image +@image_comparison(baseline_images=['computed_zorder'], remove_text=True, + extensions=['png'], style=('mpl20')) +def test_computed_zorder(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax1 = fig.add_subplot(221, projection='3d') + ax2 = fig.add_subplot(222, projection='3d') + ax2.computed_zorder = False + + # create a horizontal plane + corners = ((0, 0, 0), (0, 5, 0), (5, 5, 0), (5, 0, 0)) + for ax in (ax1, ax2): + tri = art3d.Poly3DCollection([corners], + facecolors='white', + edgecolors='black', + zorder=1) + ax.add_collection3d(tri) + + # plot a vector + ax.plot((2, 2), (2, 2), (0, 4), c='red', zorder=2) + + # plot some points + ax.scatter((3, 3), (1, 3), (1, 3), c='red', zorder=10) + + ax.set_xlim((0, 5.0)) + ax.set_ylim((0, 5.0)) + ax.set_zlim((0, 2.5)) + + ax3 = fig.add_subplot(223, projection='3d') + ax4 = fig.add_subplot(224, projection='3d') + ax4.computed_zorder = False + + dim = 10 + X, Y = np.meshgrid((-dim, dim), (-dim, dim)) + Z = np.zeros((2, 2)) + + angle = 0.5 + X2, Y2 = np.meshgrid((-dim, dim), (0, dim)) + Z2 = Y2 * angle + X3, Y3 = np.meshgrid((-dim, dim), (-dim, 0)) + Z3 = Y3 * angle + + r = 7 + M = 1000 + th = np.linspace(0, 2 * np.pi, M) + x, y, z = r * np.cos(th), r * np.sin(th), angle * r * np.sin(th) + for ax in (ax3, ax4): + ax.plot_surface(X2, Y3, Z3, + color='blue', + alpha=0.5, + linewidth=0, + zorder=-1) + ax.plot(x[y < 0], y[y < 0], z[y < 0], + lw=5, + linestyle='--', + color='green', + zorder=0) + + ax.plot_surface(X, Y, Z, + color='red', + alpha=0.5, + linewidth=0, + zorder=1) + + ax.plot(r * np.sin(th), r * np.cos(th), np.zeros(M), + lw=5, + linestyle='--', + color='black', + zorder=2) + + ax.plot_surface(X2, Y2, Z2, + color='blue', + alpha=0.5, + linewidth=0, + zorder=3) + + ax.plot(x[y > 0], y[y > 0], z[y > 0], lw=5, + linestyle='--', + color='green', + zorder=4) + ax.view_init(elev=20, azim=-20, roll=0) + ax.axis('off') + + +def test_format_coord(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + x = np.arange(10) + ax.plot(x, np.sin(x)) + xv = 0.1 + yv = 0.1 + fig.canvas.draw() + assert ax.format_coord(xv, yv) == 'x=10.5227, y pane=1.0417, z=0.1444' + + # Modify parameters + ax.view_init(roll=30, vertical_axis="y") + fig.canvas.draw() + assert ax.format_coord(xv, yv) == 'x pane=9.1875, y=0.9761, z=0.1291' + + # Reset parameters + ax.view_init() + fig.canvas.draw() + assert ax.format_coord(xv, yv) == 'x=10.5227, y pane=1.0417, z=0.1444' + + # Check orthographic projection + ax.set_proj_type('ortho') + fig.canvas.draw() + assert ax.format_coord(xv, yv) == 'x=10.8869, y pane=1.0417, z=0.1528' + + # Check non-default perspective projection + ax.set_proj_type('persp', focal_length=0.1) + fig.canvas.draw() + assert ax.format_coord(xv, yv) == 'x=9.0620, y pane=1.0417, z=0.1110' + + +def test_get_axis_position(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + x = np.arange(10) + ax.plot(x, np.sin(x)) + fig.canvas.draw() + assert ax.get_axis_position() == (False, True, False) + + +def test_margins(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.margins(0.2) + assert ax.margins() == (0.2, 0.2, 0.2) + ax.margins(0.1, 0.2, 0.3) + assert ax.margins() == (0.1, 0.2, 0.3) + ax.margins(x=0) + assert ax.margins() == (0, 0.2, 0.3) + ax.margins(y=0.1) + assert ax.margins() == (0, 0.1, 0.3) + ax.margins(z=0) + assert ax.margins() == (0, 0.1, 0) + + +def test_margin_getters(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.margins(0.1, 0.2, 0.3) + assert ax.get_xmargin() == 0.1 + assert ax.get_ymargin() == 0.2 + assert ax.get_zmargin() == 0.3 + + +@pytest.mark.parametrize('err, args, kwargs, match', ( + (ValueError, (-1,), {}, r'margin must be greater than -0\.5'), + (ValueError, (1, -1, 1), {}, r'margin must be greater than -0\.5'), + (ValueError, (1, 1, -1), {}, r'margin must be greater than -0\.5'), + (ValueError, tuple(), {'x': -1}, r'margin must be greater than -0\.5'), + (ValueError, tuple(), {'y': -1}, r'margin must be greater than -0\.5'), + (ValueError, tuple(), {'z': -1}, r'margin must be greater than -0\.5'), + (TypeError, (1, ), {'x': 1}, + 'Cannot pass both positional and keyword'), + (TypeError, (1, ), {'x': 1, 'y': 1, 'z': 1}, + 'Cannot pass both positional and keyword'), + (TypeError, (1, ), {'x': 1, 'y': 1}, + 'Cannot pass both positional and keyword'), + (TypeError, (1, 1), {}, 'Must pass a single positional argument for'), +)) +def test_margins_errors(err, args, kwargs, match): + with pytest.raises(err, match=match): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.margins(*args, **kwargs) + + +@check_figures_equal() +def test_text_3d(fig_test, fig_ref): + ax = fig_ref.add_subplot(projection="3d") + txt = Text(0.5, 0.5, r'Foo bar $\int$') + art3d.text_2d_to_3d(txt, z=1) + ax.add_artist(txt) + assert txt.get_position_3d() == (0.5, 0.5, 1) + + ax = fig_test.add_subplot(projection="3d") + t3d = art3d.Text3D(0.5, 0.5, 1, r'Foo bar $\int$') + ax.add_artist(t3d) + assert t3d.get_position_3d() == (0.5, 0.5, 1) + + +def test_draw_single_lines_from_Nx1(): + # Smoke test for GH#23459 + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot([[0], [1]], [[0], [1]], [[0], [1]]) + + +@check_figures_equal() +def test_pathpatch_3d(fig_test, fig_ref): + ax = fig_ref.add_subplot(projection="3d") + path = Path.unit_rectangle() + patch = PathPatch(path) + art3d.pathpatch_2d_to_3d(patch, z=(0, 0.5, 0.7, 1, 0), zdir='y') + ax.add_artist(patch) + + ax = fig_test.add_subplot(projection="3d") + pp3d = art3d.PathPatch3D(path, zs=(0, 0.5, 0.7, 1, 0), zdir='y') + ax.add_artist(pp3d) + + +@image_comparison(baseline_images=['scatter_spiral.png'], + remove_text=True, + style='mpl20') +def test_scatter_spiral(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + th = np.linspace(0, 2 * np.pi * 6, 256) + sc = ax.scatter(np.sin(th), np.cos(th), th, s=(1 + th * 5), c=th ** 2) + + # force at least 1 draw! + fig.canvas.draw() + + +def test_Poly3DCollection_get_path(): + # Smoke test to see that get_path does not raise + # See GH#27361 + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + p = Circle((0, 0), 1.0) + ax.add_patch(p) + art3d.pathpatch_2d_to_3d(p) + p.get_path() + + +def test_Poly3DCollection_get_facecolor(): + # Smoke test to see that get_facecolor does not raise + # See GH#4067 + y, x = np.ogrid[1:10:100j, 1:10:100j] + z2 = np.cos(x) ** 3 - np.sin(y) ** 2 + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + r = ax.plot_surface(x, y, z2, cmap='hot') + r.get_facecolor() + + +def test_Poly3DCollection_get_edgecolor(): + # Smoke test to see that get_edgecolor does not raise + # See GH#4067 + y, x = np.ogrid[1:10:100j, 1:10:100j] + z2 = np.cos(x) ** 3 - np.sin(y) ** 2 + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + r = ax.plot_surface(x, y, z2, cmap='hot') + r.get_edgecolor() + + +@pytest.mark.parametrize( + "vertical_axis, proj_expected, axis_lines_expected, tickdirs_expected", + [ + ( + "z", + [ + [0.0, 1.142857, 0.0, -0.571429], + [0.0, 0.0, 0.857143, -0.428571], + [0.0, 0.0, 0.0, -10.0], + [-1.142857, 0.0, 0.0, 10.571429], + ], + [ + ([0.05617978, 0.06329114], [-0.04213483, -0.04746835]), + ([-0.06329114, 0.06329114], [-0.04746835, -0.04746835]), + ([-0.06329114, -0.06329114], [-0.04746835, 0.04746835]), + ], + [1, 0, 0], + ), + ( + "y", + [ + [1.142857, 0.0, 0.0, -0.571429], + [0.0, 0.857143, 0.0, -0.428571], + [0.0, 0.0, 0.0, -10.0], + [0.0, 0.0, -1.142857, 10.571429], + ], + [ + ([-0.06329114, 0.06329114], [0.04746835, 0.04746835]), + ([0.06329114, 0.06329114], [-0.04746835, 0.04746835]), + ([-0.05617978, -0.06329114], [0.04213483, 0.04746835]), + ], + [2, 2, 0], + ), + ( + "x", + [ + [0.0, 0.0, 1.142857, -0.571429], + [0.857143, 0.0, 0.0, -0.428571], + [0.0, 0.0, 0.0, -10.0], + [0.0, -1.142857, 0.0, 10.571429], + ], + [ + ([-0.06329114, -0.06329114], [0.04746835, -0.04746835]), + ([0.06329114, 0.05617978], [0.04746835, 0.04213483]), + ([0.06329114, -0.06329114], [0.04746835, 0.04746835]), + ], + [1, 2, 1], + ), + ], +) +def test_view_init_vertical_axis( + vertical_axis, proj_expected, axis_lines_expected, tickdirs_expected +): + """ + Test the actual projection, axis lines and ticks matches expected values. + + Parameters + ---------- + vertical_axis : str + Axis to align vertically. + proj_expected : ndarray + Expected values from ax.get_proj(). + axis_lines_expected : tuple of arrays + Edgepoints of the axis line. Expected values retrieved according + to ``ax.get_[xyz]axis().line.get_data()``. + tickdirs_expected : list of int + indexes indicating which axis to create a tick line along. + """ + rtol = 2e-06 + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.get_figure().canvas.draw() + + # Assert the projection matrix: + proj_actual = ax.get_proj() + np.testing.assert_allclose(proj_expected, proj_actual, rtol=rtol) + + for i, axis in enumerate([ax.get_xaxis(), ax.get_yaxis(), ax.get_zaxis()]): + # Assert black lines are correctly aligned: + axis_line_expected = axis_lines_expected[i] + axis_line_actual = axis.line.get_data() + np.testing.assert_allclose(axis_line_expected, axis_line_actual, + rtol=rtol) + + # Assert ticks are correctly aligned: + tickdir_expected = tickdirs_expected[i] + tickdir_actual = axis._get_tickdir('default') + np.testing.assert_array_equal(tickdir_expected, tickdir_actual) + + +@pytest.mark.parametrize("vertical_axis", ["x", "y", "z"]) +def test_on_move_vertical_axis(vertical_axis: str) -> None: + """ + Test vertical axis is respected when rotating the plot interactively. + """ + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.get_figure().canvas.draw() + + proj_before = ax.get_proj() + event_click = mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=1) + ax._button_press(event_click) + + event_move = mock_event(ax, button=MouseButton.LEFT, xdata=0.5, ydata=0.8) + ax._on_move(event_move) + + assert ax._axis_names.index(vertical_axis) == ax._vertical_axis + + # Make sure plot has actually moved: + proj_after = ax.get_proj() + np.testing.assert_raises( + AssertionError, np.testing.assert_allclose, proj_before, proj_after + ) + + +@pytest.mark.parametrize( + "vertical_axis, aspect_expected", + [ + ("x", [1.190476, 0.892857, 1.190476]), + ("y", [0.892857, 1.190476, 1.190476]), + ("z", [1.190476, 1.190476, 0.892857]), + ], +) +def test_set_box_aspect_vertical_axis(vertical_axis, aspect_expected): + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.get_figure().canvas.draw() + + ax.set_box_aspect(None) + + np.testing.assert_allclose(aspect_expected, ax._box_aspect, rtol=1e-6) + + +@image_comparison(baseline_images=['arc_pathpatch.png'], + remove_text=True, + style='mpl20') +def test_arc_pathpatch(): + ax = plt.subplot(1, 1, 1, projection="3d") + a = mpatch.Arc((0.5, 0.5), width=0.5, height=0.9, + angle=20, theta1=10, theta2=130) + ax.add_patch(a) + art3d.pathpatch_2d_to_3d(a, z=0, zdir='z') + + +@image_comparison(baseline_images=['panecolor_rcparams.png'], + remove_text=True, + style='mpl20') +def test_panecolor_rcparams(): + with plt.rc_context({'axes3d.xaxis.panecolor': 'r', + 'axes3d.yaxis.panecolor': 'g', + 'axes3d.zaxis.panecolor': 'b'}): + fig = plt.figure(figsize=(1, 1)) + fig.add_subplot(projection='3d') + + +@check_figures_equal() +def test_mutating_input_arrays_y_and_z(fig_test, fig_ref): + """ + Test to see if the `z` axis does not get mutated + after a call to `Axes3D.plot` + + test cases came from GH#8990 + """ + ax1 = fig_test.add_subplot(111, projection='3d') + x = [1, 2, 3] + y = [0.0, 0.0, 0.0] + z = [0.0, 0.0, 0.0] + ax1.plot(x, y, z, 'o-') + + # mutate y,z to get a nontrivial line + y[:] = [1, 2, 3] + z[:] = [1, 2, 3] + + # draw the same plot without mutating x and y + ax2 = fig_ref.add_subplot(111, projection='3d') + x = [1, 2, 3] + y = [0.0, 0.0, 0.0] + z = [0.0, 0.0, 0.0] + ax2.plot(x, y, z, 'o-') + + +def test_scatter_masked_color(): + """ + Test color parameter usage with non-finite coordinate arrays. + + GH#26236 + """ + + x = [np.nan, 1, 2, 1] + y = [0, np.inf, 2, 1] + z = [0, 1, -np.inf, 1] + colors = [ + [0.0, 0.0, 0.0, 1], + [0.0, 0.0, 0.0, 1], + [0.0, 0.0, 0.0, 1], + [0.0, 0.0, 0.0, 1] + ] + + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + path3d = ax.scatter(x, y, z, color=colors) + + # Assert sizes' equality + assert len(path3d.get_offsets()) ==\ + len(super(type(path3d), path3d).get_facecolors()) + + +@mpl3d_image_comparison(['surface3d_zsort_inf.png'], style='mpl20') +def test_surface3d_zsort_inf(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + x, y = np.mgrid[-2:2:0.1, -2:2:0.1] + z = np.sin(x)**2 + np.cos(y)**2 + z[x.shape[0] // 2:, x.shape[1] // 2:] = np.inf + + ax.plot_surface(x, y, z, cmap='jet') + ax.view_init(elev=45, azim=145) + + +def test_Poly3DCollection_init_value_error(): + # smoke test to ensure the input check works + # GH#26420 + with pytest.raises(ValueError, + match='You must provide facecolors, edgecolors, ' + 'or both for shade to work.'): + poly = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) + c = art3d.Poly3DCollection([poly], shade=True) + + +def test_ndarray_color_kwargs_value_error(): + # smoke test + # ensures ndarray can be passed to color in kwargs for 3d projection plot + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ax.scatter(1, 0, 0, color=np.array([0, 0, 0, 1])) + fig.canvas.draw() diff --git a/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py new file mode 100644 index 000000000000..7fd676df1e31 --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py @@ -0,0 +1,117 @@ +import platform + +import numpy as np + +import matplotlib as mpl +from matplotlib.colors import same_color +from matplotlib.testing.decorators import image_comparison +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import art3d + + +@image_comparison(['legend_plot.png'], remove_text=True, style='mpl20') +def test_legend_plot(): + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + x = np.arange(10) + ax.plot(x, 5 - x, 'o', zdir='y', label='z=1') + ax.plot(x, x - 5, 'o', zdir='y', label='z=-1') + ax.legend() + + +@image_comparison(['legend_bar.png'], remove_text=True, style='mpl20') +def test_legend_bar(): + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + x = np.arange(10) + b1 = ax.bar(x, x, zdir='y', align='edge', color='m') + b2 = ax.bar(x, x[::-1], zdir='x', align='edge', color='g') + ax.legend([b1[0], b2[0]], ['up', 'down']) + + +@image_comparison(['fancy.png'], remove_text=True, style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.011) +def test_fancy(): + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + ax.plot(np.arange(10), np.full(10, 5), np.full(10, 5), 'o--', label='line') + ax.scatter(np.arange(10), np.arange(10, 0, -1), label='scatter') + ax.errorbar(np.full(10, 5), np.arange(10), np.full(10, 10), + xerr=0.5, zerr=0.5, label='errorbar') + ax.legend(loc='lower left', ncols=2, title='My legend', numpoints=1) + + +def test_linecollection_scaled_dashes(): + lines1 = [[(0, .5), (.5, 1)], [(.3, .6), (.2, .2)]] + lines2 = [[[0.7, .2], [.8, .4]], [[.5, .7], [.6, .1]]] + lines3 = [[[0.6, .2], [.8, .4]], [[.5, .7], [.1, .1]]] + lc1 = art3d.Line3DCollection(lines1, linestyles="--", lw=3) + lc2 = art3d.Line3DCollection(lines2, linestyles="-.") + lc3 = art3d.Line3DCollection(lines3, linestyles=":", lw=.5) + + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + ax.add_collection(lc1) + ax.add_collection(lc2) + ax.add_collection(lc3) + + leg = ax.legend([lc1, lc2, lc3], ['line1', 'line2', 'line 3']) + h1, h2, h3 = leg.legend_handles + + for oh, lh in zip((lc1, lc2, lc3), (h1, h2, h3)): + assert oh.get_linestyles()[0] == lh._dash_pattern + + +def test_handlerline3d(): + # Test marker consistency for monolithic Line3D legend handler. + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + ax.scatter([0, 1], [0, 1], marker="v") + handles = [art3d.Line3D([0], [0], [0], marker="v")] + leg = ax.legend(handles, ["Aardvark"], numpoints=1) + assert handles[0].get_marker() == leg.legend_handles[0].get_marker() + + +def test_contour_legend_elements(): + x, y = np.mgrid[1:10, 1:10] + h = x * y + colors = ['blue', '#00FF00', 'red'] + + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + cs = ax.contour(x, y, h, levels=[10, 30, 50], colors=colors, extend='both') + + artists, labels = cs.legend_elements() + assert labels == ['$x = 10.0$', '$x = 30.0$', '$x = 50.0$'] + assert all(isinstance(a, mpl.lines.Line2D) for a in artists) + assert all(same_color(a.get_color(), c) + for a, c in zip(artists, colors)) + + +def test_contourf_legend_elements(): + x, y = np.mgrid[1:10, 1:10] + h = x * y + + fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) + cs = ax.contourf(x, y, h, levels=[10, 30, 50], + colors=['#FFFF00', '#FF00FF', '#00FFFF'], + extend='both') + cs.cmap.set_over('red') + cs.cmap.set_under('blue') + cs.changed() + artists, labels = cs.legend_elements() + assert labels == ['$x \\leq -1e+250s$', + '$10.0 < x \\leq 30.0$', + '$30.0 < x \\leq 50.0$', + '$x > 1e+250s$'] + expected_colors = ('blue', '#FFFF00', '#FF00FF', 'red') + assert all(isinstance(a, mpl.patches.Rectangle) for a in artists) + assert all(same_color(a.get_facecolor(), c) + for a, c in zip(artists, expected_colors)) + + +def test_legend_Poly3dCollection(): + + verts = np.asarray([[0, 0, 0], [0, 1, 1], [1, 0, 1]]) + mesh = art3d.Poly3DCollection([verts], label="surface") + + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + mesh.set_edgecolor('k') + handle = ax.add_collection3d(mesh) + leg = ax.legend() + assert (leg.legend_handles[0].get_facecolor() + == handle.get_facecolor()).all() diff --git a/lib/mpl_toolkits/tests/__init__.py b/lib/mpl_toolkits/tests/__init__.py deleted file mode 100644 index 5b6390f4fe26..000000000000 --- a/lib/mpl_toolkits/tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from pathlib import Path - - -# Check that the test directories exist -if not (Path(__file__).parent / "baseline_images").exists(): - raise IOError( - 'The baseline image directory does not exist. ' - 'This is most likely because the test data is not installed. ' - 'You may need to install matplotlib from source to get the ' - 'test data.') diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png b/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png deleted file mode 100644 index a42548f9f6cd..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png b/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png deleted file mode 100644 index e8116fe1228f..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png b/lib/mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png deleted file mode 100644 index 1f296b6d06d5..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png b/lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png deleted file mode 100644 index 04e8ceecd2cb..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_array.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_array.png deleted file mode 100644 index 26cc0cbb947f..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_array.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_scalar.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_scalar.png deleted file mode 100644 index 875f968f3dd4..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/add_collection3d_zs_scalar.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png deleted file mode 100644 index f93e18398c3e..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png deleted file mode 100644 index 8d0499f7787d..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png deleted file mode 100644 index e974aa95470c..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_rotated.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_rotated.png deleted file mode 100644 index 0c79fd32e42c..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_rotated.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png deleted file mode 100644 index d6520ca196d1..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png deleted file mode 100644 index d718986b09dd..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png deleted file mode 100644 index c22f2a5671d3..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png deleted file mode 100644 index 1d11116743b5..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png deleted file mode 100644 index 33693b5e8ca2..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png deleted file mode 100644 index 3e34fc86556b..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d.png deleted file mode 100644 index 47c22afa12e8..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d_errorevery.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d_errorevery.png deleted file mode 100644 index 7d6bd85e43ef..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/errorbar3d_errorevery.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png deleted file mode 100644 index b1118180ea11..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/minor_ticks.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/minor_ticks.png deleted file mode 100644 index 8270ab1045ae..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/minor_ticks.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png deleted file mode 100644 index 0254e812d89d..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png deleted file mode 100644 index 89e72bfca05f..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_alpha.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_alpha.png deleted file mode 100644 index 9e8b27b949f9..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_alpha.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png deleted file mode 100644 index 9e8b27b949f9..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png deleted file mode 100644 index 9d4ee7ab5938..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png deleted file mode 100644 index 1875e4994545..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png deleted file mode 100644 index cb0fae3c54f9..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png deleted file mode 100644 index 64601ae4d1c8..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png deleted file mode 100644 index b026abbd272f..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png deleted file mode 100644 index f0357508211c..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png deleted file mode 100644 index d594253b04b2..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png deleted file mode 100644 index fcd05a708cfb..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png deleted file mode 100644 index 65a31a7c3a22..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png deleted file mode 100644 index 2956fd926b59..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png deleted file mode 100644 index 7d8eb501601e..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png deleted file mode 100644 index 0e09672f5d83..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png deleted file mode 100644 index c403d1938eb1..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png deleted file mode 100644 index d47e8c54cbf9..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png deleted file mode 100644 index 8bbfc9e90f3e..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png deleted file mode 100644 index 20bf16a37f56..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png deleted file mode 100644 index 938448857ec9..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png deleted file mode 100644 index 0a6e75b4c1a0..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png deleted file mode 100644 index 3e01b0129ff5..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png deleted file mode 100644 index a1891222f2bb..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png deleted file mode 100644 index 9a48b8b41188..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png deleted file mode 100644 index e55304caf197..000000000000 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png and /dev/null differ diff --git a/lib/mpl_toolkits/tests/conftest.py b/lib/mpl_toolkits/tests/conftest.py deleted file mode 100644 index 81829c903c58..000000000000 --- a/lib/mpl_toolkits/tests/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -from matplotlib.testing.conftest import (mpl_test_settings, - mpl_image_comparison_parameters, - pytest_configure, pytest_unconfigure) diff --git a/lib/mpl_toolkits/tests/test_axes_grid.py b/lib/mpl_toolkits/tests/test_axes_grid.py deleted file mode 100644 index 2c2a9e785a96..000000000000 --- a/lib/mpl_toolkits/tests/test_axes_grid.py +++ /dev/null @@ -1,66 +0,0 @@ -from contextlib import ExitStack - -import numpy as np -import pytest - -import matplotlib as mpl -from matplotlib.testing.decorators import image_comparison -import matplotlib.pyplot as plt -from mpl_toolkits.axes_grid1 import ImageGrid - - -# The original version of this test relied on mpl_toolkits's slightly different -# colorbar implementation; moving to matplotlib's own colorbar implementation -# caused the small image comparison error. -@pytest.mark.parametrize("legacy_colorbar", [False, True]) -@image_comparison(['imagegrid_cbar_mode.png'], - remove_text=True, style='mpl20', tol=0.3) -def test_imagegrid_cbar_mode_edge(legacy_colorbar): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - - mpl.rcParams["mpl_toolkits.legacy_colorbar"] = legacy_colorbar - - X, Y = np.meshgrid(np.linspace(0, 6, 30), np.linspace(0, 6, 30)) - arr = np.sin(X) * np.cos(Y) + 1j*(np.sin(3*Y) * np.cos(Y/2.)) - - fig = plt.figure(figsize=(18, 9)) - - positions = (241, 242, 243, 244, 245, 246, 247, 248) - directions = ['row']*4 + ['column']*4 - cbar_locations = ['left', 'right', 'top', 'bottom']*2 - - for position, direction, location in zip( - positions, directions, cbar_locations): - grid = ImageGrid(fig, position, - nrows_ncols=(2, 2), - direction=direction, - cbar_location=location, - cbar_size='20%', - cbar_mode='edge') - ax1, ax2, ax3, ax4, = grid - - ax1.imshow(arr.real, cmap='nipy_spectral') - ax2.imshow(arr.imag, cmap='hot') - ax3.imshow(np.abs(arr), cmap='jet') - ax4.imshow(np.arctan2(arr.imag, arr.real), cmap='hsv') - - with (pytest.warns(mpl.MatplotlibDeprecationWarning) if legacy_colorbar - else ExitStack()): - # In each row/column, the "first" colorbars must be overwritten by - # the "second" ones. To achieve this, clear out the axes first. - for ax in grid: - ax.cax.cla() - cb = ax.cax.colorbar( - ax.images[0], - ticks=mpl.ticker.MaxNLocator(5)) # old default locator. - - -def test_imagegrid(): - mpl.rcParams["mpl_toolkits.legacy_colorbar"] = False - fig = plt.figure() - grid = ImageGrid(fig, 111, nrows_ncols=(1, 1)) - ax = grid[0] - im = ax.imshow([[1, 2]], norm=mpl.colors.LogNorm()) - cb = ax.cax.colorbar(im) - assert isinstance(cb.locator, mpl.colorbar._ColorbarLogLocator) diff --git a/lib/mpl_toolkits/tests/test_axes_grid1.py b/lib/mpl_toolkits/tests/test_axes_grid1.py deleted file mode 100644 index 96830441e8da..000000000000 --- a/lib/mpl_toolkits/tests/test_axes_grid1.py +++ /dev/null @@ -1,480 +0,0 @@ -from itertools import product -import platform - -import matplotlib -import matplotlib.pyplot as plt -from matplotlib import cbook -from matplotlib.cbook import MatplotlibDeprecationWarning -from matplotlib.backend_bases import MouseEvent -from matplotlib.colors import LogNorm -from matplotlib.transforms import Bbox, TransformedBbox -from matplotlib.testing.decorators import ( - image_comparison, remove_ticks_and_titles) - -from mpl_toolkits.axes_grid1 import ( - axes_size as Size, host_subplot, make_axes_locatable, AxesGrid, ImageGrid) -from mpl_toolkits.axes_grid1.anchored_artists import ( - AnchoredSizeBar, AnchoredDirectionArrows) -from mpl_toolkits.axes_grid1.axes_divider import HBoxDivider -from mpl_toolkits.axes_grid1.inset_locator import ( - zoomed_inset_axes, mark_inset, inset_axes, BboxConnectorPatch) -import mpl_toolkits.axes_grid1.mpl_axes - -import pytest - -import numpy as np -from numpy.testing import assert_array_equal, assert_array_almost_equal - - -def test_divider_append_axes(): - fig, ax = plt.subplots() - divider = make_axes_locatable(ax) - axs = { - "main": ax, - "top": divider.append_axes("top", 1.2, pad=0.1, sharex=ax), - "bottom": divider.append_axes("bottom", 1.2, pad=0.1, sharex=ax), - "left": divider.append_axes("left", 1.2, pad=0.1, sharey=ax), - "right": divider.append_axes("right", 1.2, pad=0.1, sharey=ax), - } - fig.canvas.draw() - renderer = fig.canvas.get_renderer() - bboxes = {k: axs[k].get_window_extent() for k in axs} - dpi = fig.dpi - assert bboxes["top"].height == pytest.approx(1.2 * dpi) - assert bboxes["bottom"].height == pytest.approx(1.2 * dpi) - assert bboxes["left"].width == pytest.approx(1.2 * dpi) - assert bboxes["right"].width == pytest.approx(1.2 * dpi) - assert bboxes["top"].y0 - bboxes["main"].y1 == pytest.approx(0.1 * dpi) - assert bboxes["main"].y0 - bboxes["bottom"].y1 == pytest.approx(0.1 * dpi) - assert bboxes["main"].x0 - bboxes["left"].x1 == pytest.approx(0.1 * dpi) - assert bboxes["right"].x0 - bboxes["main"].x1 == pytest.approx(0.1 * dpi) - assert bboxes["left"].y0 == bboxes["main"].y0 == bboxes["right"].y0 - assert bboxes["left"].y1 == bboxes["main"].y1 == bboxes["right"].y1 - assert bboxes["top"].x0 == bboxes["main"].x0 == bboxes["bottom"].x0 - assert bboxes["top"].x1 == bboxes["main"].x1 == bboxes["bottom"].x1 - - -@image_comparison(['twin_axes_empty_and_removed'], extensions=["png"], tol=1) -def test_twin_axes_empty_and_removed(): - # Purely cosmetic font changes (avoid overlap) - matplotlib.rcParams.update({"font.size": 8}) - matplotlib.rcParams.update({"xtick.labelsize": 8}) - matplotlib.rcParams.update({"ytick.labelsize": 8}) - generators = ["twinx", "twiny", "twin"] - modifiers = ["", "host invisible", "twin removed", "twin invisible", - "twin removed\nhost invisible"] - # Unmodified host subplot at the beginning for reference - h = host_subplot(len(modifiers)+1, len(generators), 2) - h.text(0.5, 0.5, "host_subplot", - horizontalalignment="center", verticalalignment="center") - # Host subplots with various modifications (twin*, visibility) applied - for i, (mod, gen) in enumerate(product(modifiers, generators), - len(generators) + 1): - h = host_subplot(len(modifiers)+1, len(generators), i) - t = getattr(h, gen)() - if "twin invisible" in mod: - t.axis[:].set_visible(False) - if "twin removed" in mod: - t.remove() - if "host invisible" in mod: - h.axis[:].set_visible(False) - h.text(0.5, 0.5, gen + ("\n" + mod if mod else ""), - horizontalalignment="center", verticalalignment="center") - plt.subplots_adjust(wspace=0.5, hspace=1) - - -@pytest.mark.parametrize("legacy_colorbar", [False, True]) -def test_axesgrid_colorbar_log_smoketest(legacy_colorbar): - matplotlib.rcParams["mpl_toolkits.legacy_colorbar"] = legacy_colorbar - - fig = plt.figure() - grid = AxesGrid(fig, 111, # modified to be only subplot - nrows_ncols=(1, 1), - ngrids=1, - label_mode="L", - cbar_location="top", - cbar_mode="single", - ) - - Z = 10000 * np.random.rand(10, 10) - im = grid[0].imshow(Z, interpolation="nearest", norm=LogNorm()) - - if legacy_colorbar: - with pytest.warns(MatplotlibDeprecationWarning): - grid.cbar_axes[0].colorbar(im) - else: - grid.cbar_axes[0].colorbar(im) - - -@image_comparison(['inset_locator.png'], style='default', remove_text=True) -def test_inset_locator(): - fig, ax = plt.subplots(figsize=[5, 4]) - - # prepare the demo image - # Z is a 15x15 array - Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy", np_load=True) - extent = (-3, 4, -4, 3) - Z2 = np.zeros((150, 150)) - ny, nx = Z.shape - Z2[30:30+ny, 30:30+nx] = Z - - # extent = [-3, 4, -4, 3] - ax.imshow(Z2, extent=extent, interpolation="nearest", - origin="lower") - - axins = zoomed_inset_axes(ax, zoom=6, loc='upper right') - axins.imshow(Z2, extent=extent, interpolation="nearest", - origin="lower") - axins.yaxis.get_major_locator().set_params(nbins=7) - axins.xaxis.get_major_locator().set_params(nbins=7) - # sub region of the original image - x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 - axins.set_xlim(x1, x2) - axins.set_ylim(y1, y2) - - plt.xticks(visible=False) - plt.yticks(visible=False) - - # draw a bbox of the region of the inset axes in the parent axes and - # connecting lines between the bbox and the inset axes area - mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5") - - asb = AnchoredSizeBar(ax.transData, - 0.5, - '0.5', - loc='lower center', - pad=0.1, borderpad=0.5, sep=5, - frameon=False) - ax.add_artist(asb) - - -@image_comparison(['inset_axes.png'], style='default', remove_text=True) -def test_inset_axes(): - fig, ax = plt.subplots(figsize=[5, 4]) - - # prepare the demo image - # Z is a 15x15 array - Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy", np_load=True) - extent = (-3, 4, -4, 3) - Z2 = np.zeros((150, 150)) - ny, nx = Z.shape - Z2[30:30+ny, 30:30+nx] = Z - - # extent = [-3, 4, -4, 3] - ax.imshow(Z2, extent=extent, interpolation="nearest", - origin="lower") - - # creating our inset axes with a bbox_transform parameter - axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1), - bbox_transform=ax.transAxes) - - axins.imshow(Z2, extent=extent, interpolation="nearest", - origin="lower") - axins.yaxis.get_major_locator().set_params(nbins=7) - axins.xaxis.get_major_locator().set_params(nbins=7) - # sub region of the original image - x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 - axins.set_xlim(x1, x2) - axins.set_ylim(y1, y2) - - plt.xticks(visible=False) - plt.yticks(visible=False) - - # draw a bbox of the region of the inset axes in the parent axes and - # connecting lines between the bbox and the inset axes area - mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5") - - asb = AnchoredSizeBar(ax.transData, - 0.5, - '0.5', - loc='lower center', - pad=0.1, borderpad=0.5, sep=5, - frameon=False) - ax.add_artist(asb) - - -def test_inset_axes_complete(): - dpi = 100 - figsize = (6, 5) - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) - fig.subplots_adjust(.1, .1, .9, .9) - - ins = inset_axes(ax, width=2., height=2., borderpad=0) - fig.canvas.draw() - assert_array_almost_equal( - ins.get_position().extents, - np.array(((0.9*figsize[0]-2.)/figsize[0], - (0.9*figsize[1]-2.)/figsize[1], 0.9, 0.9))) - - ins = inset_axes(ax, width="40%", height="30%", borderpad=0) - fig.canvas.draw() - assert_array_almost_equal( - ins.get_position().extents, - np.array((.9-.8*.4, .9-.8*.3, 0.9, 0.9))) - - ins = inset_axes(ax, width=1., height=1.2, bbox_to_anchor=(200, 100), - loc=3, borderpad=0) - fig.canvas.draw() - assert_array_almost_equal( - ins.get_position().extents, - np.array((200./dpi/figsize[0], 100./dpi/figsize[1], - (200./dpi+1)/figsize[0], (100./dpi+1.2)/figsize[1]))) - - ins1 = inset_axes(ax, width="35%", height="60%", loc=3, borderpad=1) - ins2 = inset_axes(ax, width="100%", height="100%", - bbox_to_anchor=(0, 0, .35, .60), - bbox_transform=ax.transAxes, loc=3, borderpad=1) - fig.canvas.draw() - assert_array_equal(ins1.get_position().extents, - ins2.get_position().extents) - - with pytest.raises(ValueError): - ins = inset_axes(ax, width="40%", height="30%", - bbox_to_anchor=(0.4, 0.5)) - - with pytest.warns(UserWarning): - ins = inset_axes(ax, width="40%", height="30%", - bbox_transform=ax.transAxes) - - -@image_comparison(['fill_facecolor.png'], remove_text=True, style='mpl20') -def test_fill_facecolor(): - fig, ax = plt.subplots(1, 5) - fig.set_size_inches(5, 5) - for i in range(1, 4): - ax[i].yaxis.set_visible(False) - ax[4].yaxis.tick_right() - bbox = Bbox.from_extents(0, 0.4, 1, 0.6) - - # fill with blue by setting 'fc' field - bbox1 = TransformedBbox(bbox, ax[0].transData) - bbox2 = TransformedBbox(bbox, ax[1].transData) - # set color to BboxConnectorPatch - p = BboxConnectorPatch( - bbox1, bbox2, loc1a=1, loc2a=2, loc1b=4, loc2b=3, - ec="r", fc="b") - p.set_clip_on(False) - ax[0].add_patch(p) - # set color to marked area - axins = zoomed_inset_axes(ax[0], 1, loc='upper right') - axins.set_xlim(0, 0.2) - axins.set_ylim(0, 0.2) - plt.gca().axes.get_xaxis().set_ticks([]) - plt.gca().axes.get_yaxis().set_ticks([]) - mark_inset(ax[0], axins, loc1=2, loc2=4, fc="b", ec="0.5") - - # fill with yellow by setting 'facecolor' field - bbox3 = TransformedBbox(bbox, ax[1].transData) - bbox4 = TransformedBbox(bbox, ax[2].transData) - # set color to BboxConnectorPatch - p = BboxConnectorPatch( - bbox3, bbox4, loc1a=1, loc2a=2, loc1b=4, loc2b=3, - ec="r", facecolor="y") - p.set_clip_on(False) - ax[1].add_patch(p) - # set color to marked area - axins = zoomed_inset_axes(ax[1], 1, loc='upper right') - axins.set_xlim(0, 0.2) - axins.set_ylim(0, 0.2) - plt.gca().axes.get_xaxis().set_ticks([]) - plt.gca().axes.get_yaxis().set_ticks([]) - mark_inset(ax[1], axins, loc1=2, loc2=4, facecolor="y", ec="0.5") - - # fill with green by setting 'color' field - bbox5 = TransformedBbox(bbox, ax[2].transData) - bbox6 = TransformedBbox(bbox, ax[3].transData) - # set color to BboxConnectorPatch - p = BboxConnectorPatch( - bbox5, bbox6, loc1a=1, loc2a=2, loc1b=4, loc2b=3, - ec="r", color="g") - p.set_clip_on(False) - ax[2].add_patch(p) - # set color to marked area - axins = zoomed_inset_axes(ax[2], 1, loc='upper right') - axins.set_xlim(0, 0.2) - axins.set_ylim(0, 0.2) - plt.gca().axes.get_xaxis().set_ticks([]) - plt.gca().axes.get_yaxis().set_ticks([]) - mark_inset(ax[2], axins, loc1=2, loc2=4, color="g", ec="0.5") - - # fill with green but color won't show if set fill to False - bbox7 = TransformedBbox(bbox, ax[3].transData) - bbox8 = TransformedBbox(bbox, ax[4].transData) - # BboxConnectorPatch won't show green - p = BboxConnectorPatch( - bbox7, bbox8, loc1a=1, loc2a=2, loc1b=4, loc2b=3, - ec="r", fc="g", fill=False) - p.set_clip_on(False) - ax[3].add_patch(p) - # marked area won't show green - axins = zoomed_inset_axes(ax[3], 1, loc='upper right') - axins.set_xlim(0, 0.2) - axins.set_ylim(0, 0.2) - axins.get_xaxis().set_ticks([]) - axins.get_yaxis().set_ticks([]) - mark_inset(ax[3], axins, loc1=2, loc2=4, fc="g", ec="0.5", fill=False) - - -@image_comparison(['zoomed_axes.png', 'inverted_zoomed_axes.png']) -def test_zooming_with_inverted_axes(): - fig, ax = plt.subplots() - ax.plot([1, 2, 3], [1, 2, 3]) - ax.axis([1, 3, 1, 3]) - inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right') - inset_ax.axis([1.1, 1.4, 1.1, 1.4]) - - fig, ax = plt.subplots() - ax.plot([1, 2, 3], [1, 2, 3]) - ax.axis([3, 1, 3, 1]) - inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right') - inset_ax.axis([1.4, 1.1, 1.4, 1.1]) - - -@image_comparison(['anchored_direction_arrows.png'], - tol=0 if platform.machine() == 'x86_64' else 0.01) -def test_anchored_direction_arrows(): - fig, ax = plt.subplots() - ax.imshow(np.zeros((10, 10)), interpolation='nearest') - - simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y') - ax.add_artist(simple_arrow) - - -@image_comparison(['anchored_direction_arrows_many_args.png']) -def test_anchored_direction_arrows_many_args(): - fig, ax = plt.subplots() - ax.imshow(np.ones((10, 10))) - - direction_arrows = AnchoredDirectionArrows( - ax.transAxes, 'A', 'B', loc='upper right', color='red', - aspect_ratio=-0.5, pad=0.6, borderpad=2, frameon=True, alpha=0.7, - sep_x=-0.06, sep_y=-0.08, back_length=0.1, head_width=9, - head_length=10, tail_width=5) - ax.add_artist(direction_arrows) - - -def test_axes_locatable_position(): - fig, ax = plt.subplots() - divider = make_axes_locatable(ax) - cax = divider.append_axes('right', size='5%', pad='2%') - fig.canvas.draw() - assert np.isclose(cax.get_position(original=False).width, - 0.03621495327102808) - - -@image_comparison(['image_grid.png'], - remove_text=True, style='mpl20', - savefig_kwarg={'bbox_inches': 'tight'}) -def test_image_grid(): - # test that image grid works with bbox_inches=tight. - im = np.arange(100).reshape((10, 10)) - - fig = plt.figure(1, (4, 4)) - grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.1) - - for i in range(4): - grid[i].imshow(im, interpolation='nearest') - grid[i].set_title('test {0}{0}'.format(i)) - - -def test_gettightbbox(): - fig, ax = plt.subplots(figsize=(8, 6)) - - l, = ax.plot([1, 2, 3], [0, 1, 0]) - - ax_zoom = zoomed_inset_axes(ax, 4) - ax_zoom.plot([1, 2, 3], [0, 1, 0]) - - mark_inset(ax, ax_zoom, loc1=1, loc2=3, fc="none", ec='0.3') - - remove_ticks_and_titles(fig) - bbox = fig.get_tightbbox(fig.canvas.get_renderer()) - np.testing.assert_array_almost_equal(bbox.extents, - [-17.7, -13.9, 7.2, 5.4]) - - -@pytest.mark.parametrize("click_on", ["big", "small"]) -@pytest.mark.parametrize("big_on_axes,small_on_axes", [ - ("gca", "gca"), - ("host", "host"), - ("host", "parasite"), - ("parasite", "host"), - ("parasite", "parasite") -]) -def test_picking_callbacks_overlap(big_on_axes, small_on_axes, click_on): - """Test pick events on normal, host or parasite axes.""" - # Two rectangles are drawn and "clicked on", a small one and a big one - # enclosing the small one. The axis on which they are drawn as well as the - # rectangle that is clicked on are varied. - # In each case we expect that both rectangles are picked if we click on the - # small one and only the big one is picked if we click on the big one. - # Also tests picking on normal axes ("gca") as a control. - big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=5) - small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5) - # Machinery for "receiving" events - received_events = [] - def on_pick(event): - received_events.append(event) - plt.gcf().canvas.mpl_connect('pick_event', on_pick) - # Shortcut - rectangles_on_axes = (big_on_axes, small_on_axes) - # Axes setup - axes = {"gca": None, "host": None, "parasite": None} - if "gca" in rectangles_on_axes: - axes["gca"] = plt.gca() - if "host" in rectangles_on_axes or "parasite" in rectangles_on_axes: - axes["host"] = host_subplot(111) - axes["parasite"] = axes["host"].twin() - # Add rectangles to axes - axes[big_on_axes].add_patch(big) - axes[small_on_axes].add_patch(small) - # Simulate picking with click mouse event - if click_on == "big": - click_axes = axes[big_on_axes] - axes_coords = (0.3, 0.3) - else: - click_axes = axes[small_on_axes] - axes_coords = (0.5, 0.5) - # In reality mouse events never happen on parasite axes, only host axes - if click_axes is axes["parasite"]: - click_axes = axes["host"] - (x, y) = click_axes.transAxes.transform(axes_coords) - m = MouseEvent("button_press_event", click_axes.figure.canvas, x, y, - button=1) - click_axes.pick(m) - # Checks - expected_n_events = 2 if click_on == "small" else 1 - assert len(received_events) == expected_n_events - event_rects = [event.artist for event in received_events] - assert big in event_rects - if click_on == "small": - assert small in event_rects - - -def test_hbox_divider(): - arr1 = np.arange(20).reshape((4, 5)) - arr2 = np.arange(20).reshape((5, 4)) - - fig, (ax1, ax2) = plt.subplots(1, 2) - ax1.imshow(arr1) - ax2.imshow(arr2) - - pad = 0.5 # inches. - divider = HBoxDivider( - fig, 111, # Position of combined axes. - horizontal=[Size.AxesX(ax1), Size.Fixed(pad), Size.AxesX(ax2)], - vertical=[Size.AxesY(ax1), Size.Scaled(1), Size.AxesY(ax2)]) - ax1.set_axes_locator(divider.new_locator(0)) - ax2.set_axes_locator(divider.new_locator(2)) - - fig.canvas.draw() - p1 = ax1.get_position() - p2 = ax2.get_position() - assert p1.height == p2.height - assert p2.width / p1.width == pytest.approx((4 / 5) ** 2) - - -def test_axes_class_tuple(): - fig = plt.figure() - axes_class = (mpl_toolkits.axes_grid1.mpl_axes.Axes, {}) - gr = AxesGrid(fig, 111, nrows_ncols=(1, 1), axes_class=axes_class) diff --git a/lib/mpl_toolkits/tests/test_axisartist_axis_artist.py b/lib/mpl_toolkits/tests/test_axisartist_axis_artist.py deleted file mode 100644 index 1bebbfd9b81d..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_axis_artist.py +++ /dev/null @@ -1,99 +0,0 @@ -import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison - -from mpl_toolkits.axisartist import AxisArtistHelperRectlinear -from mpl_toolkits.axisartist.axis_artist import (AxisArtist, AxisLabel, - LabelBase, Ticks, TickLabels) - - -@image_comparison(['axis_artist_ticks.png'], style='default') -def test_ticks(): - fig, ax = plt.subplots() - - ax.xaxis.set_visible(False) - ax.yaxis.set_visible(False) - - locs_angles = [((i / 10, 0.0), i * 30) for i in range(-1, 12)] - - ticks_in = Ticks(ticksize=10, axis=ax.xaxis) - ticks_in.set_locs_angles(locs_angles) - ax.add_artist(ticks_in) - - ticks_out = Ticks(ticksize=10, tick_out=True, color='C3', axis=ax.xaxis) - ticks_out.set_locs_angles(locs_angles) - ax.add_artist(ticks_out) - - -@image_comparison(['axis_artist_labelbase.png'], style='default') -def test_labelbase(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig, ax = plt.subplots() - - ax.plot([0.5], [0.5], "o") - - label = LabelBase(0.5, 0.5, "Test") - label._set_ref_angle(-90) - label._set_offset_radius(offset_radius=50) - label.set_rotation(-90) - label.set(ha="center", va="top") - ax.add_artist(label) - - -@image_comparison(['axis_artist_ticklabels.png'], style='default') -def test_ticklabels(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig, ax = plt.subplots() - - ax.xaxis.set_visible(False) - ax.yaxis.set_visible(False) - - ax.plot([0.2, 0.4], [0.5, 0.5], "o") - - ticks = Ticks(ticksize=10, axis=ax.xaxis) - ax.add_artist(ticks) - locs_angles_labels = [((0.2, 0.5), -90, "0.2"), - ((0.4, 0.5), -120, "0.4")] - tick_locs_angles = [(xy, a + 180) for xy, a, l in locs_angles_labels] - ticks.set_locs_angles(tick_locs_angles) - - ticklabels = TickLabels(axis_direction="left") - ticklabels._locs_angles_labels = locs_angles_labels - ticklabels.set_pad(10) - ax.add_artist(ticklabels) - - ax.plot([0.5], [0.5], "s") - axislabel = AxisLabel(0.5, 0.5, "Test") - axislabel._set_offset_radius(20) - axislabel._set_ref_angle(0) - axislabel.set_axis_direction("bottom") - ax.add_artist(axislabel) - - ax.set_xlim(0, 1) - ax.set_ylim(0, 1) - - -@image_comparison(['axis_artist.png'], style='default') -def test_axis_artist(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig, ax = plt.subplots() - - ax.xaxis.set_visible(False) - ax.yaxis.set_visible(False) - - for loc in ('left', 'right', 'bottom'): - _helper = AxisArtistHelperRectlinear.Fixed(ax, loc=loc) - axisline = AxisArtist(ax, _helper, offset=None, axis_direction=loc) - ax.add_artist(axisline) - - # Settings for bottom AxisArtist. - axisline.set_label("TTT") - axisline.major_ticks.set_tick_out(False) - axisline.label.set_pad(5) - - ax.set_ylabel("Test") diff --git a/lib/mpl_toolkits/tests/test_axisartist_axislines.py b/lib/mpl_toolkits/tests/test_axisartist_axislines.py deleted file mode 100644 index 172633b0280f..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_axislines.py +++ /dev/null @@ -1,95 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison -from matplotlib.transforms import IdentityTransform - -from mpl_toolkits.axisartist.axislines import SubplotZero, Subplot -from mpl_toolkits.axisartist import SubplotHost, ParasiteAxesAuxTrans - -from mpl_toolkits.axisartist import Axes - - -@image_comparison(['SubplotZero.png'], style='default') -def test_SubplotZero(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig = plt.figure() - - ax = SubplotZero(fig, 1, 1, 1) - fig.add_subplot(ax) - - ax.axis["xzero"].set_visible(True) - ax.axis["xzero"].label.set_text("Axis Zero") - - for n in ["top", "right"]: - ax.axis[n].set_visible(False) - - xx = np.arange(0, 2 * np.pi, 0.01) - ax.plot(xx, np.sin(xx)) - ax.set_ylabel("Test") - - -@image_comparison(['Subplot.png'], style='default') -def test_Subplot(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig = plt.figure() - - ax = Subplot(fig, 1, 1, 1) - fig.add_subplot(ax) - - xx = np.arange(0, 2 * np.pi, 0.01) - ax.plot(xx, np.sin(xx)) - ax.set_ylabel("Test") - - ax.axis["top"].major_ticks.set_tick_out(True) - ax.axis["bottom"].major_ticks.set_tick_out(True) - - ax.axis["bottom"].set_label("Tk0") - - -def test_Axes(): - fig = plt.figure() - ax = Axes(fig, [0.15, 0.1, 0.65, 0.8]) - fig.add_axes(ax) - ax.plot([1, 2, 3], [0, 1, 2]) - ax.set_xscale('log') - fig.canvas.draw() - - -@image_comparison(['ParasiteAxesAuxTrans_meshplot.png'], - remove_text=True, style='default', tol=0.075) -def test_ParasiteAxesAuxTrans(): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - - data = np.ones((6, 6)) - data[2, 2] = 2 - data[0, :] = 0 - data[-2, :] = 0 - data[:, 0] = 0 - data[:, -2] = 0 - x = np.arange(6) - y = np.arange(6) - xx, yy = np.meshgrid(x, y) - - funcnames = ['pcolor', 'pcolormesh', 'contourf'] - - fig = plt.figure() - for i, name in enumerate(funcnames): - - ax1 = SubplotHost(fig, 1, 3, i+1) - fig.add_subplot(ax1) - - ax2 = ParasiteAxesAuxTrans(ax1, IdentityTransform()) - ax1.parasites.append(ax2) - if name.startswith('pcolor'): - getattr(ax2, name)(xx, yy, data[:-1, :-1]) - else: - getattr(ax2, name)(xx, yy, data) - ax1.set_xlim((0, 5)) - ax1.set_ylim((0, 5)) - - ax2.contour(xx, yy, data, colors='k') diff --git a/lib/mpl_toolkits/tests/test_axisartist_clip_path.py b/lib/mpl_toolkits/tests/test_axisartist_clip_path.py deleted file mode 100644 index a81c12dcf8e5..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_clip_path.py +++ /dev/null @@ -1,32 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison -from matplotlib.transforms import Bbox - -from mpl_toolkits.axisartist.clip_path import clip_line_to_rect - - -@image_comparison(['clip_path.png'], style='default') -def test_clip_path(): - x = np.array([-3, -2, -1, 0., 1, 2, 3, 2, 1, 0, -1, -2, -3, 5]) - y = np.arange(len(x)) - - fig, ax = plt.subplots() - ax.plot(x, y, lw=1) - - bbox = Bbox.from_extents(-2, 3, 2, 12.5) - rect = plt.Rectangle(bbox.p0, bbox.width, bbox.height, - facecolor='none', edgecolor='k', ls='--') - ax.add_patch(rect) - - clipped_lines, ticks = clip_line_to_rect(x, y, bbox) - for lx, ly in clipped_lines: - ax.plot(lx, ly, lw=1, color='C1') - for px, py in zip(lx, ly): - assert bbox.contains(px, py) - - ccc = iter(['C3o', 'C2x', 'C3o', 'C2x']) - for ttt in ticks: - cc = next(ccc) - for (xx, yy), aa in ttt: - ax.plot([xx], [yy], cc) diff --git a/lib/mpl_toolkits/tests/test_axisartist_floating_axes.py b/lib/mpl_toolkits/tests/test_axisartist_floating_axes.py deleted file mode 100644 index e69b63f99c2b..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_floating_axes.py +++ /dev/null @@ -1,120 +0,0 @@ -import numpy as np - -import matplotlib.pyplot as plt -import matplotlib.projections as mprojections -import matplotlib.transforms as mtransforms -from matplotlib.testing.decorators import image_comparison -from mpl_toolkits.axisartist.axislines import Subplot -from mpl_toolkits.axisartist.floating_axes import ( - FloatingSubplot, - GridHelperCurveLinear) -from mpl_toolkits.axisartist.grid_finder import FixedLocator -from mpl_toolkits.axisartist import angle_helper - - -def test_subplot(): - fig = plt.figure(figsize=(5, 5)) - ax = Subplot(fig, 111) - fig.add_subplot(ax) - - -@image_comparison(['curvelinear3.png'], style='default', tol=0.01) -def test_curvelinear3(): - fig = plt.figure(figsize=(5, 5)) - - tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + - mprojections.PolarAxes.PolarTransform()) - - grid_locator1 = angle_helper.LocatorDMS(15) - tick_formatter1 = angle_helper.FormatterDMS() - - grid_locator2 = FixedLocator([2, 4, 6, 8, 10]) - - grid_helper = GridHelperCurveLinear(tr, - extremes=(0, 360, 10, 3), - grid_locator1=grid_locator1, - grid_locator2=grid_locator2, - tick_formatter1=tick_formatter1, - tick_formatter2=None) - - ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper) - fig.add_subplot(ax1) - - r_scale = 10 - tr2 = mtransforms.Affine2D().scale(1, 1 / r_scale) + tr - grid_locator2 = FixedLocator([30, 60, 90]) - grid_helper2 = GridHelperCurveLinear(tr2, - extremes=(0, 360, - 10 * r_scale, 3 * r_scale), - grid_locator2=grid_locator2) - - ax1.axis["right"] = axis = grid_helper2.new_fixed_axis("right", axes=ax1) - - ax1.axis["left"].label.set_text("Test 1") - ax1.axis["right"].label.set_text("Test 2") - - for an in ["left", "right"]: - ax1.axis[an].set_visible(False) - - axis = grid_helper.new_floating_axis(1, 7, axes=ax1, - axis_direction="bottom") - ax1.axis["z"] = axis - axis.toggle(all=True, label=True) - axis.label.set_text("z = ?") - axis.label.set_visible(True) - axis.line.set_color("0.5") - - ax2 = ax1.get_aux_axes(tr) - - xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] - ax2.scatter(xx, yy) - l, = ax2.plot(xx, yy, "k-") - l.set_clip_path(ax1.patch) - - -@image_comparison(['curvelinear4.png'], style='default', tol=0.015) -def test_curvelinear4(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig = plt.figure(figsize=(5, 5)) - - tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + - mprojections.PolarAxes.PolarTransform()) - - grid_locator1 = angle_helper.LocatorDMS(5) - tick_formatter1 = angle_helper.FormatterDMS() - - grid_locator2 = FixedLocator([2, 4, 6, 8, 10]) - - grid_helper = GridHelperCurveLinear(tr, - extremes=(120, 30, 10, 0), - grid_locator1=grid_locator1, - grid_locator2=grid_locator2, - tick_formatter1=tick_formatter1, - tick_formatter2=None) - - ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper) - fig.add_subplot(ax1) - - ax1.axis["left"].label.set_text("Test 1") - ax1.axis["right"].label.set_text("Test 2") - - for an in ["top"]: - ax1.axis[an].set_visible(False) - - axis = grid_helper.new_floating_axis(1, 70, axes=ax1, - axis_direction="bottom") - ax1.axis["z"] = axis - axis.toggle(all=True, label=True) - axis.label.set_axis_direction("top") - axis.label.set_text("z = ?") - axis.label.set_visible(True) - axis.line.set_color("0.5") - - ax2 = ax1.get_aux_axes(tr) - - xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] - ax2.scatter(xx, yy) - l, = ax2.plot(xx, yy, "k-") - l.set_clip_path(ax1.patch) diff --git a/lib/mpl_toolkits/tests/test_axisartist_grid_finder.py b/lib/mpl_toolkits/tests/test_axisartist_grid_finder.py deleted file mode 100644 index 3a0913c003f2..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_grid_finder.py +++ /dev/null @@ -1,13 +0,0 @@ -from mpl_toolkits.axisartist.grid_finder import ( - FormatterPrettyPrint, - MaxNLocator) - - -def test_pretty_print_format(): - locator = MaxNLocator() - locs, nloc, factor = locator(0, 100) - - fmt = FormatterPrettyPrint() - - assert fmt("left", None, locs) == \ - [r'$\mathdefault{%d}$' % (l, ) for l in locs] diff --git a/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py b/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py deleted file mode 100644 index 05534869a09b..000000000000 --- a/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py +++ /dev/null @@ -1,213 +0,0 @@ -import numpy as np -import platform - -import matplotlib.pyplot as plt -from matplotlib.path import Path -from matplotlib.projections import PolarAxes -from matplotlib.transforms import Affine2D, Transform -from matplotlib.testing.decorators import image_comparison - -from mpl_toolkits.axes_grid1.parasite_axes import ParasiteAxesAuxTrans -from mpl_toolkits.axisartist import SubplotHost -from mpl_toolkits.axes_grid1.parasite_axes import host_subplot_class_factory -from mpl_toolkits.axisartist import angle_helper -from mpl_toolkits.axisartist.axislines import Axes -from mpl_toolkits.axisartist.grid_helper_curvelinear import \ - GridHelperCurveLinear - - -@image_comparison(['custom_transform.png'], style='default', - tol=0.03 if platform.machine() == 'x86_64' else 0.034) -def test_custom_transform(): - class MyTransform(Transform): - input_dims = output_dims = 2 - - def __init__(self, resolution): - """ - Resolution is the number of steps to interpolate between each input - line segment to approximate its path in transformed space. - """ - Transform.__init__(self) - self._resolution = resolution - - def transform(self, ll): - x, y = ll.T - return np.column_stack([x, y - x]) - - transform_non_affine = transform - - def transform_path(self, path): - ipath = path.interpolated(self._resolution) - return Path(self.transform(ipath.vertices), ipath.codes) - - transform_path_non_affine = transform_path - - def inverted(self): - return MyTransformInv(self._resolution) - - class MyTransformInv(Transform): - input_dims = output_dims = 2 - - def __init__(self, resolution): - Transform.__init__(self) - self._resolution = resolution - - def transform(self, ll): - x, y = ll.T - return np.column_stack([x, y + x]) - - def inverted(self): - return MyTransform(self._resolution) - - fig = plt.figure() - - SubplotHost = host_subplot_class_factory(Axes) - - tr = MyTransform(1) - grid_helper = GridHelperCurveLinear(tr) - ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) - fig.add_subplot(ax1) - - ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal") - ax1.parasites.append(ax2) - ax2.plot([3, 6], [5.0, 10.]) - - ax1.set_aspect(1.) - ax1.set_xlim(0, 10) - ax1.set_ylim(0, 10) - - ax1.grid(True) - - -@image_comparison(['polar_box.png'], style='default', - tol={'aarch64': 0.04}.get(platform.machine(), 0.03)) -def test_polar_box(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig = plt.figure(figsize=(5, 5)) - - # PolarAxes.PolarTransform takes radian. However, we want our coordinate - # system in degree - tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() - - # polar projection, which involves cycle, and also has limits in - # its coordinates, needs a special method to find the extremes - # (min, max of the coordinate within the view). - extreme_finder = angle_helper.ExtremeFinderCycle(20, 20, - lon_cycle=360, - lat_cycle=None, - lon_minmax=None, - lat_minmax=(0, np.inf)) - - grid_locator1 = angle_helper.LocatorDMS(12) - tick_formatter1 = angle_helper.FormatterDMS() - - grid_helper = GridHelperCurveLinear(tr, - extreme_finder=extreme_finder, - grid_locator1=grid_locator1, - tick_formatter1=tick_formatter1) - - ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) - - ax1.axis["right"].major_ticklabels.set_visible(True) - ax1.axis["top"].major_ticklabels.set_visible(True) - - # let right axis shows ticklabels for 1st coordinate (angle) - ax1.axis["right"].get_helper().nth_coord_ticks = 0 - # let bottom axis shows ticklabels for 2nd coordinate (radius) - ax1.axis["bottom"].get_helper().nth_coord_ticks = 1 - - fig.add_subplot(ax1) - - ax1.axis["lat"] = axis = grid_helper.new_floating_axis(0, 45, axes=ax1) - axis.label.set_text("Test") - axis.label.set_visible(True) - axis.get_helper()._extremes = 2, 12 - - ax1.axis["lon"] = axis = grid_helper.new_floating_axis(1, 6, axes=ax1) - axis.label.set_text("Test 2") - axis.get_helper()._extremes = -180, 90 - - # A parasite axes with given transform - ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal") - assert ax2.transData == tr + ax1.transData - # Anything you draw in ax2 will match the ticks and grids of ax1. - ax1.parasites.append(ax2) - ax2.plot(np.linspace(0, 30, 50), np.linspace(10, 10, 50)) - - ax1.set_aspect(1.) - ax1.set_xlim(-5, 12) - ax1.set_ylim(-5, 10) - - ax1.grid(True) - - -@image_comparison(['axis_direction.png'], style='default', tol=0.03) -def test_axis_direction(): - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 - - fig = plt.figure(figsize=(5, 5)) - - # PolarAxes.PolarTransform takes radian. However, we want our coordinate - # system in degree - tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() - - # polar projection, which involves cycle, and also has limits in - # its coordinates, needs a special method to find the extremes - # (min, max of the coordinate within the view). - - # 20, 20 : number of sampling points along x, y direction - extreme_finder = angle_helper.ExtremeFinderCycle(20, 20, - lon_cycle=360, - lat_cycle=None, - lon_minmax=None, - lat_minmax=(0, np.inf), - ) - - grid_locator1 = angle_helper.LocatorDMS(12) - tick_formatter1 = angle_helper.FormatterDMS() - - grid_helper = GridHelperCurveLinear(tr, - extreme_finder=extreme_finder, - grid_locator1=grid_locator1, - tick_formatter1=tick_formatter1) - - ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) - - for axis in ax1.axis.values(): - axis.set_visible(False) - - fig.add_subplot(ax1) - - ax1.axis["lat1"] = axis = grid_helper.new_floating_axis( - 0, 130, - axes=ax1, axis_direction="left") - axis.label.set_text("Test") - axis.label.set_visible(True) - axis.get_helper()._extremes = 0.001, 10 - - ax1.axis["lat2"] = axis = grid_helper.new_floating_axis( - 0, 50, - axes=ax1, axis_direction="right") - axis.label.set_text("Test") - axis.label.set_visible(True) - axis.get_helper()._extremes = 0.001, 10 - - ax1.axis["lon"] = axis = grid_helper.new_floating_axis( - 1, 10, - axes=ax1, axis_direction="bottom") - axis.label.set_text("Test 2") - axis.get_helper()._extremes = 50, 130 - axis.major_ticklabels.set_axis_direction("top") - axis.label.set_axis_direction("top") - - grid_helper.grid_finder.grid_locator1.set_params(nbins=5) - grid_helper.grid_finder.grid_locator2.set_params(nbins=5) - - ax1.set_aspect(1.) - ax1.set_xlim(-8, 8) - ax1.set_ylim(-4, 12) - - ax1.grid(True) diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py deleted file mode 100644 index 0b4341b1185b..000000000000 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ /dev/null @@ -1,1174 +0,0 @@ -import functools -import itertools - -import pytest - -from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d -import matplotlib as mpl -from matplotlib import cm -from matplotlib import colors as mcolors -from matplotlib.testing.decorators import image_comparison, check_figures_equal -from matplotlib.collections import LineCollection, PolyCollection -from matplotlib.patches import Circle -import matplotlib.pyplot as plt -import numpy as np - - -mpl3d_image_comparison = functools.partial( - image_comparison, remove_text=True, style='default') - - -def test_aspect_equal_error(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - with pytest.raises(NotImplementedError): - ax.set_aspect('equal') - - -@mpl3d_image_comparison(['bar3d.png']) -def test_bar3d(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]): - xs = np.arange(20) - ys = np.arange(20) - cs = [c] * len(xs) - cs[0] = 'c' - ax.bar(xs, ys, zs=z, zdir='y', align='edge', color=cs, alpha=0.8) - - -def test_bar3d_colors(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - for c in ['red', 'green', 'blue', 'yellow']: - xs = np.arange(len(c)) - ys = np.zeros_like(xs) - zs = np.zeros_like(ys) - # Color names with same length as xs/ys/zs should not be split into - # individual letters. - ax.bar3d(xs, ys, zs, 1, 1, 1, color=c) - - -@mpl3d_image_comparison(['bar3d_shaded.png']) -def test_bar3d_shaded(): - x = np.arange(4) - y = np.arange(5) - x2d, y2d = np.meshgrid(x, y) - x2d, y2d = x2d.ravel(), y2d.ravel() - z = x2d + y2d + 1 # Avoid triggering bug with zero-depth boxes. - - views = [(-60, 30), (30, 30), (30, -30), (120, -30)] - fig = plt.figure(figsize=plt.figaspect(1 / len(views))) - axs = fig.subplots( - 1, len(views), - subplot_kw=dict(projection='3d') - ) - for ax, (azim, elev) in zip(axs, views): - ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=True) - ax.view_init(azim=azim, elev=elev) - fig.canvas.draw() - - -@mpl3d_image_comparison(['bar3d_notshaded.png']) -def test_bar3d_notshaded(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - x = np.arange(4) - y = np.arange(5) - x2d, y2d = np.meshgrid(x, y) - x2d, y2d = x2d.ravel(), y2d.ravel() - z = x2d + y2d - ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=False) - fig.canvas.draw() - - -def test_bar3d_lightsource(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection="3d") - - ls = mcolors.LightSource(azdeg=0, altdeg=90) - - length, width = 3, 4 - area = length * width - - x, y = np.meshgrid(np.arange(length), np.arange(width)) - x = x.ravel() - y = y.ravel() - dz = x + y - - color = [cm.coolwarm(i/area) for i in range(area)] - - collection = ax.bar3d(x=x, y=y, z=0, - dx=1, dy=1, dz=dz, - color=color, shade=True, lightsource=ls) - - # Testing that the custom 90° lightsource produces different shading on - # the top facecolors compared to the default, and that those colors are - # precisely the colors from the colormap, due to the illumination parallel - # to the z-axis. - np.testing.assert_array_equal(color, collection._facecolors3d[1::6]) - - -@mpl3d_image_comparison(['contour3d.png']) -def test_contour3d(): - fig = plt.figure() - ax = fig.gca(projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm) - ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm) - ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm) - ax.set_xlim(-40, 40) - ax.set_ylim(-40, 40) - ax.set_zlim(-100, 100) - - -@mpl3d_image_comparison(['contourf3d.png']) -def test_contourf3d(): - fig = plt.figure() - ax = fig.gca(projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - ax.contourf(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm) - ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm) - ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm) - ax.set_xlim(-40, 40) - ax.set_ylim(-40, 40) - ax.set_zlim(-100, 100) - - -@mpl3d_image_comparison(['contourf3d_fill.png']) -def test_contourf3d_fill(): - fig = plt.figure() - ax = fig.gca(projection='3d') - X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25)) - Z = X.clip(0, 0) - # This produces holes in the z=0 surface that causes rendering errors if - # the Poly3DCollection is not aware of path code information (issue #4784) - Z[::5, ::5] = 0.1 - ax.contourf(X, Y, Z, offset=0, levels=[-0.1, 0], cmap=cm.coolwarm) - ax.set_xlim(-2, 2) - ax.set_ylim(-2, 2) - ax.set_zlim(-1, 1) - - -@mpl3d_image_comparison(['tricontour.png']) -def test_tricontour(): - fig = plt.figure() - - np.random.seed(19680801) - x = np.random.rand(1000) - 0.5 - y = np.random.rand(1000) - 0.5 - z = -(x**2 + y**2) - - ax = fig.add_subplot(1, 2, 1, projection='3d') - ax.tricontour(x, y, z) - ax = fig.add_subplot(1, 2, 2, projection='3d') - ax.tricontourf(x, y, z) - - -@mpl3d_image_comparison(['lines3d.png']) -def test_lines3d(): - fig = plt.figure() - ax = fig.gca(projection='3d') - theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) - z = np.linspace(-2, 2, 100) - r = z ** 2 + 1 - x = r * np.sin(theta) - y = r * np.cos(theta) - ax.plot(x, y, z) - - -@check_figures_equal(extensions=["png"]) -def test_plot_scalar(fig_test, fig_ref): - ax1 = fig_test.gca(projection='3d') - ax1.plot([1], [1], "o") - ax2 = fig_ref.gca(projection='3d') - ax2.plot(1, 1, "o") - - -@mpl3d_image_comparison(['mixedsubplot.png']) -def test_mixedsubplots(): - def f(t): - return np.cos(2*np.pi*t) * np.exp(-t) - - t1 = np.arange(0.0, 5.0, 0.1) - t2 = np.arange(0.0, 5.0, 0.02) - - fig = plt.figure(figsize=plt.figaspect(2.)) - ax = fig.add_subplot(2, 1, 1) - ax.plot(t1, f(t1), 'bo', t2, f(t2), 'k--', markerfacecolor='green') - ax.grid(True) - - ax = fig.add_subplot(2, 1, 2, projection='3d') - X, Y = np.meshgrid(np.arange(-5, 5, 0.25), np.arange(-5, 5, 0.25)) - R = np.hypot(X, Y) - Z = np.sin(R) - - ax.plot_surface(X, Y, Z, rcount=40, ccount=40, - linewidth=0, antialiased=False) - - ax.set_zlim3d(-1, 1) - - -@check_figures_equal(extensions=['png']) -def test_tight_layout_text(fig_test, fig_ref): - # text is currently ignored in tight layout. So the order of text() and - # tight_layout() calls should not influence the result. - ax1 = fig_test.gca(projection='3d') - ax1.text(.5, .5, .5, s='some string') - fig_test.tight_layout() - - ax2 = fig_ref.gca(projection='3d') - fig_ref.tight_layout() - ax2.text(.5, .5, .5, s='some string') - - -@mpl3d_image_comparison(['scatter3d.png']) -def test_scatter3d(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - ax.scatter(np.arange(10), np.arange(10), np.arange(10), - c='r', marker='o') - x = y = z = np.arange(10, 20) - ax.scatter(x, y, z, c='b', marker='^') - z[-1] = 0 # Check that scatter() copies the data. - - -@mpl3d_image_comparison(['scatter3d_color.png']) -def test_scatter3d_color(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - ax.scatter(np.arange(10), np.arange(10), np.arange(10), - color='r', marker='o') - ax.scatter(np.arange(10, 20), np.arange(10, 20), np.arange(10, 20), - color='b', marker='s') - - -@pytest.mark.parametrize('depthshade', [True, False]) -@check_figures_equal(extensions=['png']) -def test_scatter3d_sorting(fig_ref, fig_test, depthshade): - """Test that marker properties are correctly sorted.""" - - y, x = np.mgrid[:10, :10] - z = np.arange(x.size).reshape(x.shape) - - sizes = np.full(z.shape, 25) - sizes[0::2, 0::2] = 100 - sizes[1::2, 1::2] = 100 - - facecolors = np.full(z.shape, 'C0') - facecolors[:5, :5] = 'C1' - facecolors[6:, :4] = 'C2' - facecolors[6:, 6:] = 'C3' - - edgecolors = np.full(z.shape, 'C4') - edgecolors[1:5, 1:5] = 'C5' - edgecolors[5:9, 1:5] = 'C6' - edgecolors[5:9, 5:9] = 'C7' - - linewidths = np.full(z.shape, 2) - linewidths[0::2, 0::2] = 5 - linewidths[1::2, 1::2] = 5 - - x, y, z, sizes, facecolors, edgecolors, linewidths = [ - a.flatten() - for a in [x, y, z, sizes, facecolors, edgecolors, linewidths] - ] - - ax_ref = fig_ref.gca(projection='3d') - sets = (np.unique(a) for a in [sizes, facecolors, edgecolors, linewidths]) - for s, fc, ec, lw in itertools.product(*sets): - subset = ( - (sizes != s) | - (facecolors != fc) | - (edgecolors != ec) | - (linewidths != lw) - ) - subset = np.ma.masked_array(z, subset, dtype=float) - - # When depth shading is disabled, the colors are passed through as - # single-item lists; this triggers single path optimization. The - # following reshaping is a hack to disable that, since the optimization - # would not occur for the full scatter which has multiple colors. - fc = np.repeat(fc, sum(~subset.mask)) - - ax_ref.scatter(x, y, subset, s=s, fc=fc, ec=ec, lw=lw, alpha=1, - depthshade=depthshade) - - ax_test = fig_test.gca(projection='3d') - ax_test.scatter(x, y, z, s=sizes, fc=facecolors, ec=edgecolors, - lw=linewidths, alpha=1, depthshade=depthshade) - - -@pytest.mark.parametrize('azim', [-50, 130]) # yellow first, blue first -@check_figures_equal(extensions=['png']) -def test_marker_draw_order_data_reversed(fig_test, fig_ref, azim): - """ - Test that the draw order does not depend on the data point order. - - For the given viewing angle at azim=-50, the yellow marker should be in - front. For azim=130, the blue marker should be in front. - """ - x = [-1, 1] - y = [1, -1] - z = [0, 0] - color = ['b', 'y'] - ax = fig_test.add_subplot(projection='3d') - ax.scatter(x, y, z, s=3500, c=color) - ax.view_init(elev=0, azim=azim) - ax = fig_ref.add_subplot(projection='3d') - ax.scatter(x[::-1], y[::-1], z[::-1], s=3500, c=color[::-1]) - ax.view_init(elev=0, azim=azim) - - -@check_figures_equal(extensions=['png']) -def test_marker_draw_order_view_rotated(fig_test, fig_ref): - """ - Test that the draw order changes with the direction. - - If we rotate *azim* by 180 degrees and exchange the colors, the plot - plot should look the same again. - """ - azim = 130 - x = [-1, 1] - y = [1, -1] - z = [0, 0] - color = ['b', 'y'] - ax = fig_test.add_subplot(projection='3d') - # axis are not exactly invariant under 180 degree rotation -> deactivate - ax.set_axis_off() - ax.scatter(x, y, z, s=3500, c=color) - ax.view_init(elev=0, azim=azim) - ax = fig_ref.add_subplot(projection='3d') - ax.set_axis_off() - ax.scatter(x, y, z, s=3500, c=color[::-1]) # color reversed - ax.view_init(elev=0, azim=azim - 180) # view rotated by 180 degrees - - -@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.01) -def test_plot_3d_from_2d(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - xs = np.arange(0, 5) - ys = np.arange(5, 10) - ax.plot(xs, ys, zs=0, zdir='x') - ax.plot(xs, ys, zs=0, zdir='y') - - -@mpl3d_image_comparison(['surface3d.png']) -def test_surface3d(): - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - - fig = plt.figure() - ax = fig.gca(projection='3d') - X = np.arange(-5, 5, 0.25) - Y = np.arange(-5, 5, 0.25) - X, Y = np.meshgrid(X, Y) - R = np.hypot(X, Y) - Z = np.sin(R) - surf = ax.plot_surface(X, Y, Z, rcount=40, ccount=40, cmap=cm.coolwarm, - lw=0, antialiased=False) - ax.set_zlim(-1.01, 1.01) - fig.colorbar(surf, shrink=0.5, aspect=5) - - -@mpl3d_image_comparison(['surface3d_shaded.png']) -def test_surface3d_shaded(): - fig = plt.figure() - ax = fig.gca(projection='3d') - X = np.arange(-5, 5, 0.25) - Y = np.arange(-5, 5, 0.25) - X, Y = np.meshgrid(X, Y) - R = np.sqrt(X ** 2 + Y ** 2) - Z = np.sin(R) - ax.plot_surface(X, Y, Z, rstride=5, cstride=5, - color=[0.25, 1, 0.25], lw=1, antialiased=False) - ax.set_zlim(-1.01, 1.01) - - -@mpl3d_image_comparison(['text3d.png'], remove_text=False) -def test_text3d(): - fig = plt.figure() - ax = fig.gca(projection='3d') - - zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1)) - xs = (2, 6, 4, 9, 7, 2) - ys = (6, 4, 8, 7, 2, 2) - zs = (4, 2, 5, 6, 1, 7) - - for zdir, x, y, z in zip(zdirs, xs, ys, zs): - label = '(%d, %d, %d), dir=%s' % (x, y, z, zdir) - ax.text(x, y, z, label, zdir) - - ax.text(1, 1, 1, "red", color='red') - ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes) - ax.set_xlim3d(0, 10) - ax.set_ylim3d(0, 10) - ax.set_zlim3d(0, 10) - ax.set_xlabel('X axis') - ax.set_ylabel('Y axis') - ax.set_zlabel('Z axis') - - -@mpl3d_image_comparison(['trisurf3d.png'], tol=0.03) -def test_trisurf3d(): - n_angles = 36 - n_radii = 8 - radii = np.linspace(0.125, 1.0, n_radii) - angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False) - angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) - angles[:, 1::2] += np.pi/n_angles - - x = np.append(0, (radii*np.cos(angles)).flatten()) - y = np.append(0, (radii*np.sin(angles)).flatten()) - z = np.sin(-x*y) - - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2) - - -@mpl3d_image_comparison(['trisurf3d_shaded.png'], tol=0.03) -def test_trisurf3d_shaded(): - n_angles = 36 - n_radii = 8 - radii = np.linspace(0.125, 1.0, n_radii) - angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False) - angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) - angles[:, 1::2] += np.pi/n_angles - - x = np.append(0, (radii*np.cos(angles)).flatten()) - y = np.append(0, (radii*np.sin(angles)).flatten()) - z = np.sin(-x*y) - - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.plot_trisurf(x, y, z, color=[1, 0.5, 0], linewidth=0.2) - - -@mpl3d_image_comparison(['wireframe3d.png']) -def test_wireframe3d(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - ax.plot_wireframe(X, Y, Z, rcount=13, ccount=13) - - -@mpl3d_image_comparison(['wireframe3dzerocstride.png']) -def test_wireframe3dzerocstride(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - ax.plot_wireframe(X, Y, Z, rcount=13, ccount=0) - - -@mpl3d_image_comparison(['wireframe3dzerorstride.png']) -def test_wireframe3dzerorstride(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - ax.plot_wireframe(X, Y, Z, rstride=0, cstride=10) - - -def test_wireframe3dzerostrideraises(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - with pytest.raises(ValueError): - ax.plot_wireframe(X, Y, Z, rstride=0, cstride=0) - - -def test_mixedsamplesraises(): - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - X, Y, Z = axes3d.get_test_data(0.05) - with pytest.raises(ValueError): - ax.plot_wireframe(X, Y, Z, rstride=10, ccount=50) - with pytest.raises(ValueError): - ax.plot_surface(X, Y, Z, cstride=50, rcount=10) - - -@mpl3d_image_comparison( - ['quiver3d.png', 'quiver3d_pivot_middle.png', 'quiver3d_pivot_tail.png']) -def test_quiver3d(): - x, y, z = np.ogrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j] - u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z) - v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z) - w = (2/3)**0.5 * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z) - for pivot in ['tip', 'middle', 'tail']: - ax = plt.figure().add_subplot(projection='3d') - ax.quiver(x, y, z, u, v, w, length=0.1, pivot=pivot, normalize=True) - - -@check_figures_equal(extensions=["png"]) -def test_quiver3d_empty(fig_test, fig_ref): - fig_ref.add_subplot(projection='3d') - x = y = z = u = v = w = [] - ax = fig_test.add_subplot(projection='3d') - ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True) - - -@mpl3d_image_comparison(['quiver3d_masked.png']) -def test_quiver3d_masked(): - fig = plt.figure() - ax = fig.gca(projection='3d') - - # Using mgrid here instead of ogrid because masked_where doesn't - # seem to like broadcasting very much... - x, y, z = np.mgrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j] - - u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z) - v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z) - w = (2/3)**0.5 * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z) - u = np.ma.masked_where((-0.4 < x) & (x < 0.1), u, copy=False) - v = np.ma.masked_where((0.1 < y) & (y < 0.7), v, copy=False) - - ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True) - - -@mpl3d_image_comparison(['poly3dcollection_closed.png']) -def test_poly3dcollection_closed(): - fig = plt.figure() - ax = fig.gca(projection='3d') - - poly1 = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) - poly2 = np.array([[0, 1, 1], [1, 1, 1], [1, 1, 0]], float) - c1 = art3d.Poly3DCollection([poly1], linewidths=3, edgecolor='k', - facecolor=(0.5, 0.5, 1, 0.5), closed=True) - c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k', - facecolor=(1, 0.5, 0.5, 0.5), closed=False) - ax.add_collection3d(c1) - ax.add_collection3d(c2) - - -def test_poly_collection_2d_to_3d_empty(): - poly = PolyCollection([]) - art3d.poly_collection_2d_to_3d(poly) - assert isinstance(poly, art3d.Poly3DCollection) - assert poly.get_paths() == [] - - -@mpl3d_image_comparison(['poly3dcollection_alpha.png']) -def test_poly3dcollection_alpha(): - fig = plt.figure() - ax = fig.gca(projection='3d') - - poly1 = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) - poly2 = np.array([[0, 1, 1], [1, 1, 1], [1, 1, 0]], float) - c1 = art3d.Poly3DCollection([poly1], linewidths=3, edgecolor='k', - facecolor=(0.5, 0.5, 1), closed=True) - c1.set_alpha(0.5) - c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k', - facecolor=(1, 0.5, 0.5), closed=False) - c2.set_alpha(0.5) - ax.add_collection3d(c1) - ax.add_collection3d(c2) - - -@mpl3d_image_comparison(['add_collection3d_zs_array.png']) -def test_add_collection3d_zs_array(): - theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) - z = np.linspace(-2, 2, 100) - r = z**2 + 1 - x = r * np.sin(theta) - y = r * np.cos(theta) - - points = np.column_stack([x, y, z]).reshape(-1, 1, 3) - segments = np.concatenate([points[:-1], points[1:]], axis=1) - - fig = plt.figure() - ax = fig.gca(projection='3d') - - norm = plt.Normalize(0, 2*np.pi) - # 2D LineCollection from x & y values - lc = LineCollection(segments[:, :, :2], cmap='twilight', norm=norm) - lc.set_array(np.mod(theta, 2*np.pi)) - # Add 2D collection at z values to ax - line = ax.add_collection3d(lc, zs=segments[:, :, 2]) - - assert line is not None - - ax.set_xlim(-5, 5) - ax.set_ylim(-4, 6) - ax.set_zlim(-2, 2) - - -@mpl3d_image_comparison(['add_collection3d_zs_scalar.png']) -def test_add_collection3d_zs_scalar(): - theta = np.linspace(0, 2 * np.pi, 100) - z = 1 - r = z**2 + 1 - x = r * np.sin(theta) - y = r * np.cos(theta) - - points = np.column_stack([x, y]).reshape(-1, 1, 2) - segments = np.concatenate([points[:-1], points[1:]], axis=1) - - fig = plt.figure() - ax = fig.gca(projection='3d') - - norm = plt.Normalize(0, 2*np.pi) - lc = LineCollection(segments, cmap='twilight', norm=norm) - lc.set_array(theta) - line = ax.add_collection3d(lc, zs=z) - - assert line is not None - - ax.set_xlim(-5, 5) - ax.set_ylim(-4, 6) - ax.set_zlim(0, 2) - - -@mpl3d_image_comparison(['axes3d_labelpad.png'], remove_text=False) -def test_axes3d_labelpad(): - fig = plt.figure() - ax = Axes3D(fig) - # labelpad respects rcParams - assert ax.xaxis.labelpad == mpl.rcParams['axes.labelpad'] - # labelpad can be set in set_label - ax.set_xlabel('X LABEL', labelpad=10) - assert ax.xaxis.labelpad == 10 - ax.set_ylabel('Y LABEL') - ax.set_zlabel('Z LABEL') - # or manually - ax.yaxis.labelpad = 20 - ax.zaxis.labelpad = -40 - - # Tick labels also respect tick.pad (also from rcParams) - for i, tick in enumerate(ax.yaxis.get_major_ticks()): - tick.set_pad(tick.get_pad() - i * 5) - - -@mpl3d_image_comparison(['axes3d_cla.png'], remove_text=False) -def test_axes3d_cla(): - # fixed in pull request 4553 - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.set_axis_off() - ax.cla() # make sure the axis displayed is 3D (not 2D) - - -@mpl3d_image_comparison(['axes3d_rotated.png'], remove_text=False) -def test_axes3d_rotated(): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.view_init(90, 45) # look down, rotated. Should be square - - -def test_plotsurface_1d_raises(): - x = np.linspace(0.5, 10, num=100) - y = np.linspace(0.5, 10, num=100) - X, Y = np.meshgrid(x, y) - z = np.random.randn(100) - - fig = plt.figure(figsize=(14, 6)) - ax = fig.add_subplot(1, 2, 1, projection='3d') - with pytest.raises(ValueError): - ax.plot_surface(X, Y, z) - - -def _test_proj_make_M(): - # eye point - E = np.array([1000, -1000, 2000]) - R = np.array([100, 100, 100]) - V = np.array([0, 0, 1]) - viewM = proj3d.view_transformation(E, R, V) - perspM = proj3d.persp_transformation(100, -100) - M = np.dot(perspM, viewM) - return M - - -def test_proj_transform(): - M = _test_proj_make_M() - - xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0 - ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0 - zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0 - - txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) - ixs, iys, izs = proj3d.inv_transform(txs, tys, tzs, M) - - np.testing.assert_almost_equal(ixs, xs) - np.testing.assert_almost_equal(iys, ys) - np.testing.assert_almost_equal(izs, zs) - - -def _test_proj_draw_axes(M, s=1, *args, **kwargs): - xs = [0, s, 0, 0] - ys = [0, 0, s, 0] - zs = [0, 0, 0, s] - txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) - o, ax, ay, az = zip(txs, tys) - lines = [(o, ax), (o, ay), (o, az)] - - fig, ax = plt.subplots(*args, **kwargs) - linec = LineCollection(lines) - ax.add_collection(linec) - for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']): - ax.text(x, y, t) - - return fig, ax - - -@mpl3d_image_comparison(['proj3d_axes_cube.png']) -def test_proj_axes_cube(): - M = _test_proj_make_M() - - ts = '0 1 2 3 0 4 5 6 7 4'.split() - xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0 - ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0 - zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0 - - txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) - - fig, ax = _test_proj_draw_axes(M, s=400) - - ax.scatter(txs, tys, c=tzs) - ax.plot(txs, tys, c='r') - for x, y, t in zip(txs, tys, ts): - ax.text(x, y, t) - - ax.set_xlim(-0.2, 0.2) - ax.set_ylim(-0.2, 0.2) - - -@mpl3d_image_comparison(['proj3d_axes_cube_ortho.png']) -def test_proj_axes_cube_ortho(): - E = np.array([200, 100, 100]) - R = np.array([0, 0, 0]) - V = np.array([0, 0, 1]) - viewM = proj3d.view_transformation(E, R, V) - orthoM = proj3d.ortho_transformation(-1, 1) - M = np.dot(orthoM, viewM) - - ts = '0 1 2 3 0 4 5 6 7 4'.split() - xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 100 - ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 100 - zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 100 - - txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) - - fig, ax = _test_proj_draw_axes(M, s=150) - - ax.scatter(txs, tys, s=300-tzs) - ax.plot(txs, tys, c='r') - for x, y, t in zip(txs, tys, ts): - ax.text(x, y, t) - - ax.set_xlim(-200, 200) - ax.set_ylim(-200, 200) - - -def test_rot(): - V = [1, 0, 0, 1] - rotated_V = proj3d.rot_x(V, np.pi / 6) - np.testing.assert_allclose(rotated_V, [1, 0, 0, 1]) - - V = [0, 1, 0, 1] - rotated_V = proj3d.rot_x(V, np.pi / 6) - np.testing.assert_allclose(rotated_V, [0, np.sqrt(3) / 2, 0.5, 1]) - - -def test_world(): - xmin, xmax = 100, 120 - ymin, ymax = -100, 100 - zmin, zmax = 0.1, 0.2 - M = proj3d.world_transformation(xmin, xmax, ymin, ymax, zmin, zmax) - np.testing.assert_allclose(M, - [[5e-2, 0, 0, -5], - [0, 5e-3, 0, 5e-1], - [0, 0, 1e1, -1], - [0, 0, 0, 1]]) - - -@mpl3d_image_comparison(['proj3d_lines_dists.png']) -def test_lines_dists(): - fig, ax = plt.subplots(figsize=(4, 6), subplot_kw=dict(aspect='equal')) - - xs = (0, 30) - ys = (20, 150) - ax.plot(xs, ys) - p0, p1 = zip(xs, ys) - - xs = (0, 0, 20, 30) - ys = (100, 150, 30, 200) - ax.scatter(xs, ys) - - dist = proj3d._line2d_seg_dist(p0, p1, (xs[0], ys[0])) - dist = proj3d._line2d_seg_dist(p0, p1, np.array((xs, ys))) - for x, y, d in zip(xs, ys, dist): - c = Circle((x, y), d, fill=0) - ax.add_patch(c) - - ax.set_xlim(-50, 150) - ax.set_ylim(0, 300) - - -def test_autoscale(): - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - ax.margins(x=0, y=.1, z=.2) - ax.plot([0, 1], [0, 1], [0, 1]) - assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.2, 1.2) - ax.autoscale(False) - ax.set_autoscalez_on(True) - ax.plot([0, 2], [0, 2], [0, 2]) - assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.4, 2.4) - - -@mpl3d_image_comparison(['axes3d_ortho.png'], remove_text=False) -def test_axes3d_ortho(): - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.set_proj_type('ortho') - - -@pytest.mark.parametrize('value', [np.inf, np.nan]) -@pytest.mark.parametrize(('setter', 'side'), [ - ('set_xlim3d', 'left'), - ('set_xlim3d', 'right'), - ('set_ylim3d', 'bottom'), - ('set_ylim3d', 'top'), - ('set_zlim3d', 'bottom'), - ('set_zlim3d', 'top'), -]) -def test_invalid_axes_limits(setter, side, value): - limit = {side: value} - fig = plt.figure() - obj = fig.add_subplot(111, projection='3d') - with pytest.raises(ValueError): - getattr(obj, setter)(**limit) - - -class TestVoxels: - @mpl3d_image_comparison(['voxels-simple.png']) - def test_simple(self): - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - x, y, z = np.indices((5, 4, 3)) - voxels = (x == y) | (y == z) - ax.voxels(voxels) - - @mpl3d_image_comparison(['voxels-edge-style.png']) - def test_edge_style(self): - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - x, y, z = np.indices((5, 5, 4)) - voxels = ((x - 2)**2 + (y - 2)**2 + (z-1.5)**2) < 2.2**2 - v = ax.voxels(voxels, linewidths=3, edgecolor='C1') - - # change the edge color of one voxel - v[max(v.keys())].set_edgecolor('C2') - - @mpl3d_image_comparison(['voxels-named-colors.png']) - def test_named_colors(self): - """Test with colors set to a 3d object array of strings.""" - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - x, y, z = np.indices((10, 10, 10)) - voxels = (x == y) | (y == z) - voxels = voxels & ~(x * y * z < 1) - colors = np.full((10, 10, 10), 'C0', dtype=np.object_) - colors[(x < 5) & (y < 5)] = '0.25' - colors[(x + z) < 10] = 'cyan' - ax.voxels(voxels, facecolors=colors) - - @mpl3d_image_comparison(['voxels-rgb-data.png']) - def test_rgb_data(self): - """Test with colors set to a 4d float array of rgb data.""" - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - x, y, z = np.indices((10, 10, 10)) - voxels = (x == y) | (y == z) - colors = np.zeros((10, 10, 10, 3)) - colors[..., 0] = x / 9 - colors[..., 1] = y / 9 - colors[..., 2] = z / 9 - ax.voxels(voxels, facecolors=colors) - - @mpl3d_image_comparison(['voxels-alpha.png']) - def test_alpha(self): - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - x, y, z = np.indices((10, 10, 10)) - v1 = x == y - v2 = np.abs(x - y) < 2 - voxels = v1 | v2 - colors = np.zeros((10, 10, 10, 4)) - colors[v2] = [1, 0, 0, 0.5] - colors[v1] = [0, 1, 0, 0.5] - v = ax.voxels(voxels, facecolors=colors) - - assert type(v) is dict - for coord, poly in v.items(): - assert voxels[coord], "faces returned for absent voxel" - assert isinstance(poly, art3d.Poly3DCollection) - - @mpl3d_image_comparison(['voxels-xyz.png'], tol=0.01, remove_text=False) - def test_xyz(self): - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - def midpoints(x): - sl = () - for i in range(x.ndim): - x = (x[sl + np.index_exp[:-1]] + - x[sl + np.index_exp[1:]]) / 2.0 - sl += np.index_exp[:] - return x - - # prepare some coordinates, and attach rgb values to each - r, g, b = np.indices((17, 17, 17)) / 16.0 - rc = midpoints(r) - gc = midpoints(g) - bc = midpoints(b) - - # define a sphere about [0.5, 0.5, 0.5] - sphere = (rc - 0.5)**2 + (gc - 0.5)**2 + (bc - 0.5)**2 < 0.5**2 - - # combine the color components - colors = np.zeros(sphere.shape + (3,)) - colors[..., 0] = rc - colors[..., 1] = gc - colors[..., 2] = bc - - # and plot everything - ax.voxels(r, g, b, sphere, - facecolors=colors, - edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter - linewidth=0.5) - - def test_calling_conventions(self): - x, y, z = np.indices((3, 4, 5)) - filled = np.ones((2, 3, 4)) - - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - - # all the valid calling conventions - for kw in (dict(), dict(edgecolor='k')): - ax.voxels(filled, **kw) - ax.voxels(filled=filled, **kw) - ax.voxels(x, y, z, filled, **kw) - ax.voxels(x, y, z, filled=filled, **kw) - - # duplicate argument - with pytest.raises(TypeError, match='voxels'): - ax.voxels(x, y, z, filled, filled=filled) - # missing arguments - with pytest.raises(TypeError, match='voxels'): - ax.voxels(x, y) - # x, y, z are positional only - this passes them on as attributes of - # Poly3DCollection - with pytest.raises(AttributeError): - ax.voxels(filled=filled, x=x, y=y, z=z) - - -def test_line3d_set_get_data_3d(): - x, y, z = [0, 1], [2, 3], [4, 5] - x2, y2, z2 = [6, 7], [8, 9], [10, 11] - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - lines = ax.plot(x, y, z) - line = lines[0] - np.testing.assert_array_equal((x, y, z), line.get_data_3d()) - line.set_data_3d(x2, y2, z2) - np.testing.assert_array_equal((x2, y2, z2), line.get_data_3d()) - - -@check_figures_equal(extensions=["png"]) -def test_inverted(fig_test, fig_ref): - # Plot then invert. - ax = fig_test.add_subplot(projection="3d") - ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10]) - ax.invert_yaxis() - # Invert then plot. - ax = fig_ref.add_subplot(projection="3d") - ax.invert_yaxis() - ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10]) - - -def test_inverted_cla(): - # GitHub PR #5450. Setting autoscale should reset - # axes to be non-inverted. - fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) - # 1. test that a new axis is not inverted per default - assert not ax.xaxis_inverted() - assert not ax.yaxis_inverted() - assert not ax.zaxis_inverted() - ax.set_xlim(1, 0) - ax.set_ylim(1, 0) - ax.set_zlim(1, 0) - assert ax.xaxis_inverted() - assert ax.yaxis_inverted() - assert ax.zaxis_inverted() - ax.cla() - assert not ax.xaxis_inverted() - assert not ax.yaxis_inverted() - assert not ax.zaxis_inverted() - - -def test_ax3d_tickcolour(): - fig = plt.figure() - ax = Axes3D(fig) - - ax.tick_params(axis='x', colors='red') - ax.tick_params(axis='y', colors='red') - ax.tick_params(axis='z', colors='red') - fig.canvas.draw() - - for tick in ax.xaxis.get_major_ticks(): - assert tick.tick1line._color == 'red' - for tick in ax.yaxis.get_major_ticks(): - assert tick.tick1line._color == 'red' - for tick in ax.zaxis.get_major_ticks(): - assert tick.tick1line._color == 'red' - - -@check_figures_equal(extensions=["png"]) -def test_ticklabel_format(fig_test, fig_ref): - axs = fig_test.subplots(4, 5, subplot_kw={"projection": "3d"}) - for ax in axs.flat: - ax.set_xlim(1e7, 1e7 + 10) - for row, name in zip(axs, ["x", "y", "z", "both"]): - row[0].ticklabel_format( - axis=name, style="plain") - row[1].ticklabel_format( - axis=name, scilimits=(-2, 2)) - row[2].ticklabel_format( - axis=name, useOffset=not mpl.rcParams["axes.formatter.useoffset"]) - row[3].ticklabel_format( - axis=name, useLocale=not mpl.rcParams["axes.formatter.use_locale"]) - row[4].ticklabel_format( - axis=name, - useMathText=not mpl.rcParams["axes.formatter.use_mathtext"]) - - def get_formatters(ax, names): - return [getattr(ax, name).get_major_formatter() for name in names] - - axs = fig_ref.subplots(4, 5, subplot_kw={"projection": "3d"}) - for ax in axs.flat: - ax.set_xlim(1e7, 1e7 + 10) - for row, names in zip( - axs, [["xaxis"], ["yaxis"], ["zaxis"], ["xaxis", "yaxis", "zaxis"]] - ): - for fmt in get_formatters(row[0], names): - fmt.set_scientific(False) - for fmt in get_formatters(row[1], names): - fmt.set_powerlimits((-2, 2)) - for fmt in get_formatters(row[2], names): - fmt.set_useOffset(not mpl.rcParams["axes.formatter.useoffset"]) - for fmt in get_formatters(row[3], names): - fmt.set_useLocale(not mpl.rcParams["axes.formatter.use_locale"]) - for fmt in get_formatters(row[4], names): - fmt.set_useMathText( - not mpl.rcParams["axes.formatter.use_mathtext"]) - - -@check_figures_equal(extensions=["png"]) -def test_quiver3D_smoke(fig_test, fig_ref): - pivot = "middle" - # Make the grid - x, y, z = np.meshgrid( - np.arange(-0.8, 1, 0.2), - np.arange(-0.8, 1, 0.2), - np.arange(-0.8, 1, 0.8) - ) - u = v = w = np.ones_like(x) - - for fig, length in zip((fig_ref, fig_test), (1, 1.0)): - ax = fig.gca(projection="3d") - ax.quiver(x, y, z, u, v, w, length=length, pivot=pivot) - - -@image_comparison(["minor_ticks.png"], style="mpl20") -def test_minor_ticks(): - ax = plt.figure().add_subplot(projection="3d") - ax.set_xticks([0.25], minor=True) - ax.set_xticklabels(["quarter"], minor=True) - ax.set_yticks([0.33], minor=True) - ax.set_yticklabels(["third"], minor=True) - ax.set_zticks([0.50], minor=True) - ax.set_zticklabels(["half"], minor=True) - - -@mpl3d_image_comparison(['errorbar3d_errorevery.png']) -def test_errorbar3d_errorevery(): - """Tests errorevery functionality for 3d errorbars.""" - t = np.arange(0, 2*np.pi+.1, 0.01) - x, y, z = np.sin(t), np.cos(3*t), np.sin(5*t) - - fig = plt.figure() - ax = fig.gca(projection='3d') - - estep = 15 - i = np.arange(t.size) - zuplims = (i % estep == 0) & (i // estep % 3 == 0) - zlolims = (i % estep == 0) & (i // estep % 3 == 2) - - ax.errorbar(x, y, z, 0.2, zuplims=zuplims, zlolims=zlolims, - errorevery=estep) - - -@mpl3d_image_comparison(['errorbar3d.png']) -def test_errorbar3d(): - """Tests limits, color styling, and legend for 3d errorbars.""" - fig = plt.figure() - ax = fig.gca(projection='3d') - - d = [1, 2, 3, 4, 5] - e = [.5, .5, .5, .5, .5] - ax.errorbar(x=d, y=d, z=d, xerr=e, yerr=e, zerr=e, capsize=3, - zuplims=[False, True, False, True, True], - zlolims=[True, False, False, True, False], - yuplims=True, - ecolor='purple', label='Error lines') - ax.legend() - - -@image_comparison(["equal_box_aspect.png"], style="mpl20") -def test_equal_box_aspect(): - from itertools import product, combinations - - fig = plt.figure() - ax = fig.add_subplot(111, projection="3d") - - # Make data - u = np.linspace(0, 2 * np.pi, 100) - v = np.linspace(0, np.pi, 100) - x = np.outer(np.cos(u), np.sin(v)) - y = np.outer(np.sin(u), np.sin(v)) - z = np.outer(np.ones_like(u), np.cos(v)) - - # Plot the surface - ax.plot_surface(x, y, z) - - # draw cube - r = [-1, 1] - for s, e in combinations(np.array(list(product(r, r, r))), 2): - if np.sum(np.abs(s - e)) == r[1] - r[0]: - ax.plot3D(*zip(s, e), color="b") - - # Make axes limits - xyzlim = np.column_stack( - [ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d()] - ) - XYZlim = [min(xyzlim[0]), max(xyzlim[1])] - ax.set_xlim3d(XYZlim) - ax.set_ylim3d(XYZlim) - ax.set_zlim3d(XYZlim) - ax.axis('off') - ax.set_box_aspect((1, 1, 1)) - - -def test_colorbar_pos(): - num_plots = 2 - fig, axs = plt.subplots(1, num_plots, figsize=(4, 5), - constrained_layout=True, - subplot_kw={'projection': '3d'}) - for ax in axs: - p_tri = ax.plot_trisurf(np.random.randn(5), np.random.randn(5), - np.random.randn(5)) - - cbar = plt.colorbar(p_tri, ax=axs, orientation='horizontal') - - fig.canvas.draw() - # check that actually on the bottom - assert cbar.ax.get_position().extents[1] < 0.2 diff --git a/lib/pylab.py b/lib/pylab.py index f9d135d36e2b..64ece55b1cb2 100644 --- a/lib/pylab.py +++ b/lib/pylab.py @@ -1,3 +1,3 @@ -from matplotlib.pylab import * +from matplotlib.pylab import * # noqa: F401, F403 import matplotlib.pylab __doc__ = matplotlib.pylab.__doc__ diff --git a/matplotlibrc.template b/matplotlibrc.template deleted file mode 100644 index 87c7927ad3f8..000000000000 --- a/matplotlibrc.template +++ /dev/null @@ -1,765 +0,0 @@ -#### MATPLOTLIBRC FORMAT - -## NOTE FOR END USERS: DO NOT EDIT THIS FILE! -## -## This is a sample matplotlib configuration file - you can find a copy -## of it on your system in site-packages/matplotlib/mpl-data/matplotlibrc -## (which related to your Python installation location). -## -## You should find a copy of it on your system at -## site-packages/matplotlib/mpl-data/matplotlibrc (relative to your Python -## installation location). DO NOT EDIT IT! -## -## If you wish to change your default style, copy this file to one of the -## following locations -## unix/linux: -## $HOME/.config/matplotlib/matplotlibrc OR -## $XDG_CONFIG_HOME/matplotlib/matplotlibrc (if $XDG_CONFIG_HOME is set) -## other platforms: -## $HOME/.matplotlib/matplotlibrc -## and edit that copy. -## -## See https://matplotlib.org/users/customizing.html#the-matplotlibrc-file -## for more details on the paths which are checked for the configuration file. -## -## Blank lines, or lines starting with a comment symbol, are ignored, as are -## trailing comments. Other lines must have the format: -## key: val # optional comment -## -## Formatting: Use PEP8-like style (as enforced in the rest of the codebase). -## All lines start with an additional '#', so that removing all leading '#'s -## yields a valid style file. -## -## Colors: for the color values below, you can either use -## - a matplotlib color string, such as r, k, or b -## - an rgb tuple, such as (1.0, 0.5, 0.0) -## - a hex string, such as ff00ff -## - a scalar grayscale intensity such as 0.75 -## - a legal html color name, e.g., red, blue, darkslategray -## -## Matplotlib configuration are currently divided into following parts: -## - BACKENDS -## - LINES -## - PATCHES -## - HATCHES -## - BOXPLOT -## - FONT -## - TEXT -## - LaTeX -## - AXES -## - DATES -## - TICKS -## - GRIDS -## - LEGEND -## - FIGURE -## - IMAGES -## - CONTOUR PLOTS -## - ERRORBAR PLOTS -## - HISTOGRAM PLOTS -## - SCATTER PLOTS -## - AGG RENDERING -## - PATHS -## - SAVING FIGURES -## - INTERACTIVE KEYMAPS -## - ANIMATION - -##### CONFIGURATION BEGINS HERE - - -## *************************************************************************** -## * BACKENDS * -## *************************************************************************** -## The default backend. If you omit this parameter, the first working -## backend from the following list is used: -## MacOSX Qt5Agg Gtk3Agg TkAgg WxAgg Agg -## Other choices include: -## Qt5Cairo GTK3Cairo TkCairo WxCairo Cairo -## Qt4Agg Qt4Cairo Wx # deprecated. -## PS PDF SVG Template -## You can also deploy your own backend outside of matplotlib by referring to -## the module name (which must be in the PYTHONPATH) as 'module://my_backend'. -#backend: Agg - -## The port to use for the web server in the WebAgg backend. -#webagg.port: 8988 - -## The address on which the WebAgg web server should be reachable -#webagg.address: 127.0.0.1 - -## If webagg.port is unavailable, a number of other random ports will -## be tried until one that is available is found. -#webagg.port_retries: 50 - -## When True, open the webbrowser to the plot that is shown -#webagg.open_in_browser: True - -## If you are running pyplot inside a GUI and your backend choice -## conflicts, we will automatically try to find a compatible one for -## you if backend_fallback is True -#backend_fallback: True - -#interactive: False -#toolbar: toolbar2 # {None, toolbar2, toolmanager} -#timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris - - -## *************************************************************************** -## * LINES * -## *************************************************************************** -## See https://matplotlib.org/api/artist_api.html#module-matplotlib.lines -## for more information on line properties. -#lines.linewidth: 1.5 # line width in points -#lines.linestyle: - # solid line -#lines.color: C0 # has no affect on plot(); see axes.prop_cycle -#lines.marker: None # the default marker -#lines.markerfacecolor: auto # the default marker face color -#lines.markeredgecolor: auto # the default marker edge color -#lines.markeredgewidth: 1.0 # the line width around the marker symbol -#lines.markersize: 6 # marker size, in points -#lines.dash_joinstyle: round # {miter, round, bevel} -#lines.dash_capstyle: butt # {butt, round, projecting} -#lines.solid_joinstyle: round # {miter, round, bevel} -#lines.solid_capstyle: projecting # {butt, round, projecting} -#lines.antialiased: True # render lines in antialiased (no jaggies) - -## The three standard dash patterns. These are scaled by the linewidth. -#lines.dashed_pattern: 3.7, 1.6 -#lines.dashdot_pattern: 6.4, 1.6, 1, 1.6 -#lines.dotted_pattern: 1, 1.65 -#lines.scale_dashes: True - -#markers.fillstyle: full # {full, left, right, bottom, top, none} - -#pcolor.shading : flat -#pcolormesh.snap : True # Whether to snap the mesh to pixel boundaries. This - # is provided solely to allow old test images to remain - # unchanged. Set to False to obtain the previous behavior. - -## *************************************************************************** -## * PATCHES * -## *************************************************************************** -## Patches are graphical objects that fill 2D space, like polygons or circles. -## See https://matplotlib.org/api/artist_api.html#module-matplotlib.patches -## for more information on patch properties. -#patch.linewidth: 1 # edge width in points. -#patch.facecolor: C0 -#patch.edgecolor: black # if forced, or patch is not filled -#patch.force_edgecolor: False # True to always use edgecolor -#patch.antialiased: True # render patches in antialiased (no jaggies) - - -## *************************************************************************** -## * HATCHES * -## *************************************************************************** -#hatch.color: black -#hatch.linewidth: 1.0 - - -## *************************************************************************** -## * BOXPLOT * -## *************************************************************************** -#boxplot.notch: False -#boxplot.vertical: True -#boxplot.whiskers: 1.5 -#boxplot.bootstrap: None -#boxplot.patchartist: False -#boxplot.showmeans: False -#boxplot.showcaps: True -#boxplot.showbox: True -#boxplot.showfliers: True -#boxplot.meanline: False - -#boxplot.flierprops.color: black -#boxplot.flierprops.marker: o -#boxplot.flierprops.markerfacecolor: none -#boxplot.flierprops.markeredgecolor: black -#boxplot.flierprops.markeredgewidth: 1.0 -#boxplot.flierprops.markersize: 6 -#boxplot.flierprops.linestyle: none -#boxplot.flierprops.linewidth: 1.0 - -#boxplot.boxprops.color: black -#boxplot.boxprops.linewidth: 1.0 -#boxplot.boxprops.linestyle: - - -#boxplot.whiskerprops.color: black -#boxplot.whiskerprops.linewidth: 1.0 -#boxplot.whiskerprops.linestyle: - - -#boxplot.capprops.color: black -#boxplot.capprops.linewidth: 1.0 -#boxplot.capprops.linestyle: - - -#boxplot.medianprops.color: C1 -#boxplot.medianprops.linewidth: 1.0 -#boxplot.medianprops.linestyle: - - -#boxplot.meanprops.color: C2 -#boxplot.meanprops.marker: ^ -#boxplot.meanprops.markerfacecolor: C2 -#boxplot.meanprops.markeredgecolor: C2 -#boxplot.meanprops.markersize: 6 -#boxplot.meanprops.linestyle: -- -#boxplot.meanprops.linewidth: 1.0 - - -## *************************************************************************** -## * FONT * -## *************************************************************************** -## The font properties used by `text.Text`. -## See https://matplotlib.org/api/font_manager_api.html for more information -## on font properties. The 6 font properties used for font matching are -## given below with their default values. -## -## The font.family property has five values: -## - 'serif' (e.g., Times), -## - 'sans-serif' (e.g., Helvetica), -## - 'cursive' (e.g., Zapf-Chancery), -## - 'fantasy' (e.g., Western), and -## - 'monospace' (e.g., Courier). -## Each of these font families has a default list of font names in decreasing -## order of priority associated with them. When text.usetex is False, -## font.family may also be one or more concrete font names. -## -## The font.style property has three values: normal (or roman), italic -## or oblique. The oblique style will be used for italic, if it is not -## present. -## -## The font.variant property has two values: normal or small-caps. For -## TrueType fonts, which are scalable fonts, small-caps is equivalent -## to using a font size of 'smaller', or about 83%% of the current font -## size. -## -## The font.weight property has effectively 13 values: normal, bold, -## bolder, lighter, 100, 200, 300, ..., 900. Normal is the same as -## 400, and bold is 700. bolder and lighter are relative values with -## respect to the current weight. -## -## The font.stretch property has 11 values: ultra-condensed, -## extra-condensed, condensed, semi-condensed, normal, semi-expanded, -## expanded, extra-expanded, ultra-expanded, wider, and narrower. This -## property is not currently implemented. -## -## The font.size property is the default font size for text, given in pts. -## 10 pt is the standard value. -## -## Note that font.size controls default text sizes. To configure -## special text sizes tick labels, axes, labels, title, etc, see the rc -## settings for axes and ticks. Special text sizes can be defined -## relative to font.size, using the following values: xx-small, x-small, -## small, medium, large, x-large, xx-large, larger, or smaller - -#font.family: sans-serif -#font.style: normal -#font.variant: normal -#font.weight: normal -#font.stretch: normal -#font.size: 10.0 - -#font.serif: DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif -#font.sans-serif: DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif -#font.cursive: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, cursive -#font.fantasy: Comic Neue, Comic Sans MS, Chicago, Charcoal, ImpactWestern, Humor Sans, xkcd, fantasy -#font.monospace: DejaVu Sans Mono, Bitstream Vera Sans Mono, Computer Modern Typewriter, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace - - -## *************************************************************************** -## * TEXT * -## *************************************************************************** -## The text properties used by `text.Text`. -## See https://matplotlib.org/api/artist_api.html#module-matplotlib.text -## for more information on text properties -#text.color: black - - -## *************************************************************************** -## * LaTeX * -## *************************************************************************** -## For more information on LaTex properties, see -## https://matplotlib.org/tutorials/text/usetex.html -#text.usetex: False # use latex for all text handling. The following fonts - # are supported through the usual rc parameter settings: - # new century schoolbook, bookman, times, palatino, - # zapf chancery, charter, serif, sans-serif, helvetica, - # avant garde, courier, monospace, computer modern roman, - # computer modern sans serif, computer modern typewriter - # If another font is desired which can loaded using the - # LaTeX \usepackage command, please inquire at the - # matplotlib mailing list -#text.latex.preamble: # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES - # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP - # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. - # text.latex.preamble is a single line of LaTeX code that - # will be passed on to the LaTeX system. It may contain - # any code that is valid for the LaTeX "preamble", i.e. - # between the "\documentclass" and "\begin{document}" - # statements. - # Note that it has to be put on a single line, which may - # become quite long. - # The following packages are always loaded with usetex, so - # beware of package collisions: color, geometry, graphicx, - # type1cm, textcomp. - # Adobe Postscript (PSSNFS) font packages may also be - # loaded, depending on your font settings. - -## FreeType hinting flag ("foo" corresponds to FT_LOAD_FOO); may be one of the -## following (Proprietary Matplotlib-specific synonyms are given in parentheses, -## but their use is discouraged): -## - default: Use the font's native hinter if possible, else FreeType's auto-hinter. -## ("either" is a synonym). -## - no_autohint: Use the font's native hinter if possible, else don't hint. -## ("native" is a synonym.) -## - force_autohint: Use FreeType's auto-hinter. ("auto" is a synonym.) -## - no_hinting: Disable hinting. ("none" is a synonym.) -#text.hinting: force_autohint - -#text.hinting_factor: 8 # Specifies the amount of softness for hinting in the - # horizontal direction. A value of 1 will hint to full - # pixels. A value of 2 will hint to half pixels etc. -#text.kerning_factor : 0 # Specifies the scaling factor for kerning values. This - # is provided solely to allow old test images to remain - # unchanged. Set to 6 to obtain previous behavior. Values - # other than 0 or 6 have no defined meaning. -#text.antialiased: True # If True (default), the text will be antialiased. - # This only affects the Agg backend. - -## The following settings allow you to select the fonts in math mode. -#mathtext.fontset: dejavusans # Should be 'dejavusans' (default), - # 'dejavuserif', 'cm' (Computer Modern), 'stix', - # 'stixsans' or 'custom' (unsupported, may go - # away in the future) -## "mathtext.fontset: custom" is defined by the mathtext.bf, .cal, .it, ... -## settings which map a TeX font name to a fontconfig font pattern. (These -## settings are not used for other font sets.) -#mathtext.bf: sans:bold -#mathtext.cal: cursive -#mathtext.it: sans:italic -#mathtext.rm: sans -#mathtext.sf: sans -#mathtext.tt: monospace -#mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix' - # 'stixsans'] when a symbol can not be found in one of the - # custom math fonts. Select 'None' to not perform fallback - # and replace the missing character by a dummy symbol. -#mathtext.default: it # The default font to use for math. - # Can be any of the LaTeX font names, including - # the special name "regular" for the same font - # used in regular text. - - -## *************************************************************************** -## * AXES * -## *************************************************************************** -## Following are default face and edge colors, default tick sizes, -## default fontsizes for ticklabels, and so on. See -## https://matplotlib.org/api/axes_api.html#module-matplotlib.axes -#axes.facecolor: white # axes background color -#axes.edgecolor: black # axes edge color -#axes.linewidth: 0.8 # edge linewidth -#axes.grid: False # display grid or not -#axes.grid.axis: both # which axis the grid should apply to -#axes.grid.which: major # gridlines at {major, minor, both} ticks -#axes.titlelocation: center # alignment of the title: {left, right, center} -#axes.titlesize: large # fontsize of the axes title -#axes.titleweight: normal # font weight of title -#axes.titlecolor: auto # color of the axes title, auto falls back to - # text.color as default value -#axes.titley: None # position title (axes relative units). None implies auto -#axes.titlepad: 6.0 # pad between axes and title in points -#axes.labelsize: medium # fontsize of the x any y labels -#axes.labelpad: 4.0 # space between label and axis -#axes.labelweight: normal # weight of the x and y labels -#axes.labelcolor: black -#axes.axisbelow: line # draw axis gridlines and ticks: - # - below patches (True) - # - above patches but below lines ('line') - # - above all (False) - -#axes.formatter.limits: -5, 6 # use scientific notation if log10 - # of the axis range is smaller than the - # first or larger than the second -#axes.formatter.use_locale: False # When True, format tick labels - # according to the user's locale. - # For example, use ',' as a decimal - # separator in the fr_FR locale. -#axes.formatter.use_mathtext: False # When True, use mathtext for scientific - # notation. -#axes.formatter.min_exponent: 0 # minimum exponent to format in scientific notation -#axes.formatter.useoffset: True # If True, the tick label formatter - # will default to labeling ticks relative - # to an offset when the data range is - # small compared to the minimum absolute - # value of the data. -#axes.formatter.offset_threshold: 4 # When useoffset is True, the offset - # will be used when it can remove - # at least this number of significant - # digits from tick labels. - -#axes.spines.left: True # display axis spines -#axes.spines.bottom: True -#axes.spines.top: True -#axes.spines.right: True - -#axes.unicode_minus: True # use Unicode for the minus symbol rather than hyphen. See - # https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes -#axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf']) - # color cycle for plot lines as list of string colorspecs: - # single letter, long name, or web-style hex - # As opposed to all other paramters in this file, the color - # values must be enclosed in quotes for this parameter, - # e.g. '1f77b4', instead of 1f77b4. - # See also https://matplotlib.org/tutorials/intermediate/color_cycle.html - # for more details on prop_cycle usage. -#axes.autolimit_mode: data # How to scale axes limits to the data. By using: - # - "data" to use data limits, plus some margin - # - "round_numbers" move to the nearest "round" number -#axes.xmargin: .05 # x margin. See `axes.Axes.margins` -#axes.ymargin: .05 # y margin. See `axes.Axes.margins` -#polaraxes.grid: True # display grid on polar axes -#axes3d.grid: True # display grid on 3d axes - - -## *************************************************************************** -## * AXIS * -## *************************************************************************** -#xaxis.labellocation: center # alignment of the xaxis label: {left, right, center} -#yaxis.labellocation: center # alignment of the yaxis label: {bottom, top, center} - - -## *************************************************************************** -## * DATES * -## *************************************************************************** -## These control the default format strings used in AutoDateFormatter. -## Any valid format datetime format string can be used (see the python -## `datetime` for details). For example, by using: -## - '%%x' will use the locale date representation -## - '%%X' will use the locale time representation -## - '%%c' will use the full locale datetime representation -## These values map to the scales: -## {'year': 365, 'month': 30, 'day': 1, 'hour': 1/24, 'minute': 1 / (24 * 60)} - -#date.autoformatter.year: %Y -#date.autoformatter.month: %Y-%m -#date.autoformatter.day: %Y-%m-%d -#date.autoformatter.hour: %m-%d %H -#date.autoformatter.minute: %d %H:%M -#date.autoformatter.second: %H:%M:%S -#date.autoformatter.microsecond: %M:%S.%f -## The reference date for Matplotlib's internal date representation -## See https://matplotlib.org/examples/ticks_and_spines/date_precision_and_epochs.py -#date.epoch: 1970-01-01T00:00:00 -## 'auto', 'concise': -#date.converter: auto -## For auto converter whether to use interval_multiples: -#date.interval_multiples: True - -## *************************************************************************** -## * TICKS * -## *************************************************************************** -## See https://matplotlib.org/api/axis_api.html#matplotlib.axis.Tick -#xtick.top: False # draw ticks on the top side -#xtick.bottom: True # draw ticks on the bottom side -#xtick.labeltop: False # draw label on the top -#xtick.labelbottom: True # draw label on the bottom -#xtick.major.size: 3.5 # major tick size in points -#xtick.minor.size: 2 # minor tick size in points -#xtick.major.width: 0.8 # major tick width in points -#xtick.minor.width: 0.6 # minor tick width in points -#xtick.major.pad: 3.5 # distance to major tick label in points -#xtick.minor.pad: 3.4 # distance to the minor tick label in points -#xtick.color: black # color of the ticks -#xtick.labelcolor: inherit # color of the tick labels or inherit from xtick.color -#xtick.labelsize: medium # fontsize of the tick labels -#xtick.direction: out # direction: {in, out, inout} -#xtick.minor.visible: False # visibility of minor ticks on x-axis -#xtick.major.top: True # draw x axis top major ticks -#xtick.major.bottom: True # draw x axis bottom major ticks -#xtick.minor.top: True # draw x axis top minor ticks -#xtick.minor.bottom: True # draw x axis bottom minor ticks -#xtick.alignment: center # alignment of xticks - -#ytick.left: True # draw ticks on the left side -#ytick.right: False # draw ticks on the right side -#ytick.labelleft: True # draw tick labels on the left side -#ytick.labelright: False # draw tick labels on the right side -#ytick.major.size: 3.5 # major tick size in points -#ytick.minor.size: 2 # minor tick size in points -#ytick.major.width: 0.8 # major tick width in points -#ytick.minor.width: 0.6 # minor tick width in points -#ytick.major.pad: 3.5 # distance to major tick label in points -#ytick.minor.pad: 3.4 # distance to the minor tick label in points -#ytick.color: black # color of the ticks -#ytick.labelcolor: inherit # color of the tick labels or inherit from ytick.color -#ytick.labelsize: medium # fontsize of the tick labels -#ytick.direction: out # direction: {in, out, inout} -#ytick.minor.visible: False # visibility of minor ticks on y-axis -#ytick.major.left: True # draw y axis left major ticks -#ytick.major.right: True # draw y axis right major ticks -#ytick.minor.left: True # draw y axis left minor ticks -#ytick.minor.right: True # draw y axis right minor ticks -#ytick.alignment: center_baseline # alignment of yticks - - -## *************************************************************************** -## * GRIDS * -## *************************************************************************** -#grid.color: b0b0b0 # grid color -#grid.linestyle: - # solid -#grid.linewidth: 0.8 # in points -#grid.alpha: 1.0 # transparency, between 0.0 and 1.0 - - -## *************************************************************************** -## * LEGEND * -## *************************************************************************** -#legend.loc: best -#legend.frameon: True # if True, draw the legend on a background patch -#legend.framealpha: 0.8 # legend patch transparency -#legend.facecolor: inherit # inherit from axes.facecolor; or color spec -#legend.edgecolor: 0.8 # background patch boundary color -#legend.fancybox: True # if True, use a rounded box for the - # legend background, else a rectangle -#legend.shadow: False # if True, give background a shadow effect -#legend.numpoints: 1 # the number of marker points in the legend line -#legend.scatterpoints: 1 # number of scatter points -#legend.markerscale: 1.0 # the relative size of legend markers vs. original -#legend.fontsize: medium -#legend.title_fontsize: None # None sets to the same as the default axes. - -## Dimensions as fraction of fontsize: -#legend.borderpad: 0.4 # border whitespace -#legend.labelspacing: 0.5 # the vertical space between the legend entries -#legend.handlelength: 2.0 # the length of the legend lines -#legend.handleheight: 0.7 # the height of the legend handle -#legend.handletextpad: 0.8 # the space between the legend line and legend text -#legend.borderaxespad: 0.5 # the border between the axes and legend edge -#legend.columnspacing: 2.0 # column separation - - -## *************************************************************************** -## * FIGURE * -## *************************************************************************** -## See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure -#figure.titlesize: large # size of the figure title (``Figure.suptitle()``) -#figure.titleweight: normal # weight of the figure title -#figure.figsize: 6.4, 4.8 # figure size in inches -#figure.dpi: 100 # figure dots per inch -#figure.facecolor: white # figure facecolor -#figure.edgecolor: white # figure edgecolor -#figure.frameon: True # enable figure frame -#figure.max_open_warning: 20 # The maximum number of figures to open through - # the pyplot interface before emitting a warning. - # If less than one this feature is disabled. -#figure.raise_window : True # Raise the GUI window to front when show() is called. - -## The figure subplot parameters. All dimensions are a fraction of the figure width and height. -#figure.subplot.left: 0.125 # the left side of the subplots of the figure -#figure.subplot.right: 0.9 # the right side of the subplots of the figure -#figure.subplot.bottom: 0.11 # the bottom of the subplots of the figure -#figure.subplot.top: 0.88 # the top of the subplots of the figure -#figure.subplot.wspace: 0.2 # the amount of width reserved for space between subplots, - # expressed as a fraction of the average axis width -#figure.subplot.hspace: 0.2 # the amount of height reserved for space between subplots, - # expressed as a fraction of the average axis height - -## Figure layout -#figure.autolayout: False # When True, automatically adjust subplot - # parameters to make the plot fit the figure - # using `tight_layout` -#figure.constrained_layout.use: False # When True, automatically make plot - # elements fit on the figure. (Not - # compatible with `autolayout`, above). -#figure.constrained_layout.h_pad: 0.04167 # Padding around axes objects. Float representing -#figure.constrained_layout.w_pad: 0.04167 # inches. Default is 3./72. inches (3 pts) -#figure.constrained_layout.hspace: 0.02 # Space between subplot groups. Float representing -#figure.constrained_layout.wspace: 0.02 # a fraction of the subplot widths being separated. - - -## *************************************************************************** -## * IMAGES * -## *************************************************************************** -#image.aspect: equal # {equal, auto} or a number -#image.interpolation: antialiased # see help(imshow) for options -#image.cmap: viridis # A colormap name, gray etc... -#image.lut: 256 # the size of the colormap lookup table -#image.origin: upper # {lower, upper} -#image.resample: True -#image.composite_image: True # When True, all the images on a set of axes are - # combined into a single composite image before - # saving a figure as a vector graphics file, - # such as a PDF. - - -## *************************************************************************** -## * CONTOUR PLOTS * -## *************************************************************************** -#contour.negative_linestyle: dashed # string or on-off ink sequence -#contour.corner_mask: True # {True, False, legacy} -#contour.linewidth: None # {float, None} Size of the contour - # linewidths. If set to None, - # it falls back to `line.linewidth`. - - -## *************************************************************************** -## * ERRORBAR PLOTS * -## *************************************************************************** -#errorbar.capsize: 0 # length of end cap on error bars in pixels - - -## *************************************************************************** -## * HISTOGRAM PLOTS * -## *************************************************************************** -#hist.bins: 10 # The default number of histogram bins or 'auto'. - - -## *************************************************************************** -## * SCATTER PLOTS * -## *************************************************************************** -#scatter.marker: o # The default marker type for scatter plots. -#scatter.edgecolors: face # The default edge colors for scatter plots. - - -## *************************************************************************** -## * AGG RENDERING * -## *************************************************************************** -## Warning: experimental, 2008/10/10 -#agg.path.chunksize: 0 # 0 to disable; values in the range - # 10000 to 100000 can improve speed slightly - # and prevent an Agg rendering failure - # when plotting very large data sets, - # especially if they are very gappy. - # It may cause minor artifacts, though. - # A value of 20000 is probably a good - # starting point. - - -## *************************************************************************** -## * PATHS * -## *************************************************************************** -#path.simplify: True # When True, simplify paths by removing "invisible" - # points to reduce file size and increase rendering - # speed -#path.simplify_threshold: 0.111111111111 # The threshold of similarity below - # which vertices will be removed in - # the simplification process. -#path.snap: True # When True, rectilinear axis-aligned paths will be snapped - # to the nearest pixel when certain criteria are met. - # When False, paths will never be snapped. -#path.sketch: None # May be None, or a 3-tuple of the form: - # (scale, length, randomness). - # - *scale* is the amplitude of the wiggle - # perpendicular to the line (in pixels). - # - *length* is the length of the wiggle along the - # line (in pixels). - # - *randomness* is the factor by which the length is - # randomly scaled. -#path.effects: - - -## *************************************************************************** -## * SAVING FIGURES * -## *************************************************************************** -## The default savefig params can be different from the display params -## e.g., you may want a higher resolution, or to make the figure -## background white -#savefig.dpi: figure # figure dots per inch or 'figure' -#savefig.facecolor: auto # figure facecolor when saving -#savefig.edgecolor: auto # figure edgecolor when saving -#savefig.format: png # {png, ps, pdf, svg} -#savefig.bbox: standard # {tight, standard} - # 'tight' is incompatible with pipe-based animation - # backends (e.g. 'ffmpeg') but will work with those - # based on temporary files (e.g. 'ffmpeg_file') -#savefig.pad_inches: 0.1 # Padding to be used when bbox is set to 'tight' -#savefig.directory: ~ # default directory in savefig dialog box, - # leave empty to always use current working directory -#savefig.transparent: False # setting that controls whether figures are saved with a - # transparent background by default -#savefig.orientation: portrait # Orientation of saved figure - -### tk backend params -#tk.window_focus: False # Maintain shell focus for TkAgg - -### ps backend params -#ps.papersize: letter # {auto, letter, legal, ledger, A0-A10, B0-B10} -#ps.useafm: False # use of afm fonts, results in small files -#ps.usedistiller: False # {ghostscript, xpdf, None} - # Experimental: may produce smaller files. - # xpdf intended for production of publication quality files, - # but requires ghostscript, xpdf and ps2eps -#ps.distiller.res: 6000 # dpi -#ps.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType) - -### PDF backend params -#pdf.compression: 6 # integer from 0 to 9 - # 0 disables compression (good for debugging) -#pdf.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType) -#pdf.use14corefonts : False -#pdf.inheritcolor: False - -### SVG backend params -#svg.image_inline: True # Write raster image data directly into the SVG file -#svg.fonttype: path # How to handle SVG fonts: - # path: Embed characters as paths -- supported - # by most SVG renderers - # None: Assume fonts are installed on the - # machine where the SVG will be viewed. -#svg.hashsalt: None # If not None, use this string as hash salt instead of uuid4 - -### pgf parameter -## See https://matplotlib.org/tutorials/text/pgf.html for more information. -#pgf.rcfonts: True -#pgf.preamble: # See text.latex.preamble for documentation -#pgf.texsystem: xelatex - -### docstring params -#docstring.hardcopy: False # set this when you want to generate hardcopy docstring - - -## *************************************************************************** -## * INTERACTIVE KEYMAPS * -## *************************************************************************** -## Event keys to interact with figures/plots via keyboard. -## See https://matplotlib.org/users/navigation_toolbar.html for more details on -## interactive navigation. Customize these settings according to your needs. -## Leave the field(s) empty if you don't need a key-map. (i.e., fullscreen : '') -#keymap.fullscreen: f, ctrl+f # toggling -#keymap.home: h, r, home # home or reset mnemonic -#keymap.back: left, c, backspace, MouseButton.BACK # forward / backward keys -#keymap.forward: right, v, MouseButton.FORWARD # for quick navigation -#keymap.pan: p # pan mnemonic -#keymap.zoom: o # zoom mnemonic -#keymap.save: s, ctrl+s # saving current figure -#keymap.help: f1 # display help about active tools -#keymap.quit: ctrl+w, cmd+w, q # close the current figure -#keymap.quit_all: # close all figures -#keymap.grid: g # switching on/off major grids in current axes -#keymap.grid_minor: G # switching on/off minor grids in current axes -#keymap.yscale: l # toggle scaling of y-axes ('log'/'linear') -#keymap.xscale: k, L # toggle scaling of x-axes ('log'/'linear') -#keymap.copy: ctrl+c, cmd+c # Copy figure to clipboard - - -## *************************************************************************** -## * ANIMATION * -## *************************************************************************** -#animation.html: none # How to display the animation as HTML in - # the IPython notebook: - # - 'html5' uses HTML5 video tag - # - 'jshtml' creates a Javascript animation -#animation.writer: ffmpeg # MovieWriter 'backend' to use -#animation.codec: h264 # Codec to use for writing movie -#animation.bitrate: -1 # Controls size/quality tradeoff for movie. - # -1 implies let utility auto-determine -#animation.frame_format: png # Controls frame format used by temp files -#animation.ffmpeg_path: ffmpeg # Path to ffmpeg binary. Without full path - # $PATH is searched -#animation.ffmpeg_args: # Additional arguments to pass to ffmpeg -#animation.convert_path: convert # Path to ImageMagick's convert binary. - # On Windows use the full path since convert - # is also the name of a system tool. -#animation.convert_args: # Additional arguments to pass to convert -#animation.embed_limit: 20.0 # Limit, in MB, of size of base64 encoded - # animation in HTML (i.e. IPython notebook) - -#mpl_toolkits.legacy_colorbar: True diff --git a/meson.build b/meson.build new file mode 100644 index 000000000000..54249473fe8e --- /dev/null +++ b/meson.build @@ -0,0 +1,46 @@ +project( + 'matplotlib', + 'c', 'cpp', + version: run_command( + # Also keep version in sync with pyproject.toml. + find_program('python3', 'python', version: '>= 3.11'), + '-m', 'setuptools_scm', check: true).stdout().strip(), + # qt_editor backend is MIT + # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 + # Carlogo, STIX and Computer Modern is OFL + # DejaVu is Bitstream Vera and Public Domain + license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', + license_files: [ + 'LICENSE/LICENSE', + 'LICENSE/LICENSE_AMSFONTS', + 'LICENSE/LICENSE_BAKOMA', + 'LICENSE/LICENSE_CARLOGO', + 'LICENSE/LICENSE_COLORBREWER', + 'LICENSE/LICENSE_COURIERTEN', + 'LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER', + 'LICENSE/LICENSE_QT4_EDITOR', + 'LICENSE/LICENSE_SOLARIZED', + 'LICENSE/LICENSE_STIX', + 'LICENSE/LICENSE_YORICK', + ], + meson_version: '>=1.1.0', + default_options: [ + 'b_lto=true', + 'cpp_std=c++17', + 'auto_features=disabled', # Force FreeType to avoid extra dependencies. + ], +) + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +# https://mesonbuild.com/Python-module.html +py_mod = import('python') +py3 = py_mod.find_installation(pure: false) +py3_dep = py3.dependency() + +pybind11_dep = dependency('pybind11', version: '>=2.13.2') + +subdir('extern') +subdir('src') +subdir('lib') diff --git a/meson.options b/meson.options new file mode 100644 index 000000000000..d21cbedb9bb9 --- /dev/null +++ b/meson.options @@ -0,0 +1,30 @@ +# Options may be set by passing through `pip` or `build` via meson-python: +# https://meson-python.readthedocs.io/en/stable/how-to-guides/config-settings.html + +# By default, Matplotlib downloads and builds its own copies of FreeType and of +# Qhull. You may use the following options to instead link against a system +# FreeType/Qhull. As an exception, Matplotlib defaults to the system version of +# FreeType on AIX. +option('system-freetype', type: 'boolean', value: false, + description: 'Build against system version of FreeType') +option('system-qhull', type: 'boolean', value: false, + description: 'Build against system version of Qhull') + +# Some of Matplotlib's components are optional: the MacOSX backend (installed +# by default on macOS; requires the Cocoa headers included with XCode). You +# can control whether they are installed using the following options. Note that +# the MacOSX backend is never built on Linux or Windows, regardless of the +# config value. +option('macosx', type: 'boolean', value: true, + description: 'Enable MacOSX backend (requires Cocoa)') + +# User-configurable options +# +# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, +# MacOSX, Pdf, Ps, QtAgg, QtCairo, SVG, TkAgg, WX, WXAgg. +# +# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do +# not choose MacOSX if you have disabled the relevant extension modules. The +# default is determined by fallback. +option('rcParams-backend', type: 'string', value: 'auto', + description: 'Set default backend at runtime') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000000..70b078a73d27 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,342 @@ +[project] +name = "matplotlib" +authors = [ + {email = "matplotlib-users@python.org"}, + {name = "John D. Hunter, Michael Droettboom"} +] +description = "Python plotting package" +readme = "README.md" +license = { file = "LICENSE/LICENSE" } +dynamic = ["version"] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Matplotlib", + "Intended Audience :: Science/Research", + "Intended Audience :: Education", + "License :: OSI Approved :: Python Software Foundation License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering :: Visualization", +] + +# When updating the list of dependencies, add an api_changes/development +# entry and also update the following places: +# - lib/matplotlib/__init__.py (matplotlib._check_versions()) +# - requirements/testing/minver.txt +# - doc/devel/dependencies.rst +# - .github/workflows/tests.yml +# - environment.yml +dependencies = [ + "contourpy >= 1.0.1", + "cycler >= 0.10", + "fonttools >= 4.22.0", + "kiwisolver >= 1.3.1", + "numpy >= 1.25", + "packaging >= 20.0", + "pillow >= 9", + "pyparsing >= 3", + "python-dateutil >= 2.7", +] +# Also keep in sync with find_program of meson.build. +requires-python = ">=3.11" + +[project.optional-dependencies] +# Should be a copy of the build dependencies below. +dev = [ + "meson-python>=0.13.1,!=0.17.*", + "pybind11>=2.13.2,!=2.13.3", + "setuptools_scm>=7", + # Not required by us but setuptools_scm without a version, cso _if_ + # installed, then setuptools_scm 8 requires at least this version. + # Unfortunately, we can't do a sort of minimum-if-installed dependency, so + # we need to keep this for now until setuptools_scm _fully_ drops + # setuptools. + "setuptools>=64", +] + +[project.urls] +"Homepage" = "https://matplotlib.org" +"Download" = "https://matplotlib.org/stable/install/index.html" +"Documentation" = "https://matplotlib.org" +"Source Code" = "https://github.com/matplotlib/matplotlib" +"Bug Tracker" = "https://github.com/matplotlib/matplotlib/issues" +"Forum" = "https://discourse.matplotlib.org/" +"Donate" = "https://numfocus.org/donate-to-matplotlib" + +[build-system] +build-backend = "mesonpy" +# Also keep in sync with optional dependencies above. +requires = [ + # meson-python 0.17.x breaks symlinks in sdists. You can remove this pin if + # you really need it and aren't using an sdist. + "meson-python>=0.13.1,!=0.17.*", + "pybind11>=2.13.2,!=2.13.3", + "setuptools_scm>=7", +] + +[tool.meson-python.args] +install = ['--tags=data,python-runtime,runtime'] + +[tool.setuptools_scm] +version_scheme = "release-branch-semver" +local_scheme = "node-and-date" +parentdir_prefix_version = "matplotlib-" +fallback_version = "0.0+UNKNOWN" + +[tool.isort] +known_pydata = "numpy, matplotlib.pyplot" +known_firstparty = "matplotlib,mpl_toolkits" +sections = "FUTURE,STDLIB,THIRDPARTY,PYDATA,FIRSTPARTY,LOCALFOLDER" +force_sort_within_sections = true + +[tool.ruff] +exclude = [ + ".git", + "build", + "doc/gallery", + "doc/tutorials", + "tools/gh_api.py", + ".tox", + ".eggs", + # TODO: fix .ipynb files + "*.ipynb" +] +line-length = 88 +target-version = "py311" + +[tool.ruff.lint] +ignore = [ + "D100", + "D101", + "D102", + "D103", + "D104", + "D105", + "D106", + "D107", + "D200", + "D202", + "D203", + "D204", + "D205", + "D212", + "D301", + "D400", + "D401", + "D402", + "D403", + "D404", + "D413", + "D415", + "D416", + "D417", + "E24", + "E266", + "E305", + "E306", + "E721", + "E741", + "F841", +] +preview = true +explicit-preview-rules = true +select = [ + "D", + "E", + "F", + "W", + # The following error codes require the preview mode to be enabled. + "E201", + "E202", + "E203", + "E221", + "E251", + "E261", + "E272", + "E302", + "E703", +] + +# The following error codes are not supported by ruff v0.2.0 +# They are planned and should be selected once implemented +# even if they are deselected by default. +# These are primarily whitespace/corrected by autoformatters (which we don't use). +# See https://github.com/charliermarsh/ruff/issues/2402 for status on implementation +external = [ + "E122", +] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.per-file-ignores] +"*.pyi" = ["E501"] +"doc/conf.py" = ["E402"] +"galleries/examples/animation/frame_grabbing_sgskip.py" = ["E402"] +"galleries/examples/images_contours_and_fields/tricontour_demo.py" = ["E201"] +"galleries/examples/images_contours_and_fields/tripcolor_demo.py" = ["E201"] +"galleries/examples/images_contours_and_fields/triplot_demo.py" = ["E201"] +"galleries/examples/lines_bars_and_markers/marker_reference.py" = ["E402"] +"galleries/examples/misc/print_stdout_sgskip.py" = ["E402"] +"galleries/examples/misc/table_demo.py" = ["E201"] +"galleries/examples/style_sheets/bmh.py" = ["E501"] +"galleries/examples/subplots_axes_and_figures/demo_constrained_layout.py" = ["E402"] +"galleries/examples/text_labels_and_annotations/custom_legends.py" = ["E402"] +"galleries/examples/ticks/date_concise_formatter.py" = ["E402"] +"galleries/examples/ticks/date_formatters_locators.py" = ["F401"] +"galleries/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/embedding_in_gtk3_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/embedding_in_gtk4_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/gtk3_spreadsheet_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/gtk4_spreadsheet_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/mpl_with_glade3_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/pylab_with_gtk3_sgskip.py" = ["E402"] +"galleries/examples/user_interfaces/pylab_with_gtk4_sgskip.py" = ["E402"] +"galleries/examples/userdemo/pgf_preamble_sgskip.py" = ["E402"] + +"lib/matplotlib/__init__.py" = ["F822"] +"lib/matplotlib/_cm.py" = ["E202", "E203", "E302"] +"lib/matplotlib/_mathtext.py" = ["E221"] +"lib/matplotlib/_mathtext_data.py" = ["E203"] +"lib/matplotlib/backends/backend_template.py" = ["F401"] +"lib/matplotlib/pylab.py" = ["F401", "F403"] +"lib/matplotlib/pyplot.py" = ["F811"] +"lib/matplotlib/tests/test_mathtext.py" = ["E501"] +"lib/matplotlib/transforms.py" = ["E201"] +"lib/matplotlib/tri/_triinterpolate.py" = ["E201", "E221"] +"lib/mpl_toolkits/axes_grid1/axes_size.py" = ["E272"] +"lib/mpl_toolkits/axisartist/angle_helper.py" = ["E221"] +"lib/mpl_toolkits/mplot3d/proj3d.py" = ["E201"] + +"galleries/users_explain/artists/paths.py" = ["E402"] +"galleries/users_explain/quick_start.py" = ["E402"] +"galleries/users_explain/artists/patheffects_guide.py" = ["E402"] +"galleries/users_explain/artists/transforms_tutorial.py" = ["E402", "E501"] +"galleries/users_explain/colors/colormaps.py" = ["E501"] +"galleries/users_explain/colors/colors.py" = ["E402"] +"galleries/tutorials/artists.py" = ["E402"] +"galleries/users_explain/axes/constrainedlayout_guide.py" = ["E402"] +"galleries/users_explain/axes/legend_guide.py" = ["E402"] +"galleries/users_explain/axes/tight_layout_guide.py" = ["E402"] +"galleries/users_explain/animations/animations.py" = ["E501"] +"galleries/tutorials/images.py" = ["E501"] +"galleries/tutorials/pyplot.py" = ["E402", "E501"] +"galleries/users_explain/text/annotations.py" = ["E402", "E501"] +"galleries/users_explain/text/mathtext.py" = ["E501"] +"galleries/users_explain/text/text_intro.py" = ["E402"] +"galleries/users_explain/text/text_props.py" = ["E501"] + +[tool.mypy] +ignore_missing_imports = true +enable_error_code = [ + "ignore-without-code", + "redundant-expr", + "truthy-bool", +] +enable_incomplete_feature = [ + "Unpack", +] +exclude = [ + #stubtest + ".*/matplotlib/(sphinxext|backends|pylab|testing/jpl_units)", + #mypy precommit + "galleries/", + "doc/", + "lib/matplotlib/backends/", + "lib/matplotlib/sphinxext", + "lib/matplotlib/testing/jpl_units", + "lib/mpl_toolkits/", + #removing tests causes errors in backends + "lib/matplotlib/tests/", + # tinypages is used for testing the sphinx ext, + # stubtest will import and run, opening a figure if not excluded + ".*/tinypages", + # pylab's numpy wildcard imports cause re-def failures since numpy 2.2 + "lib/matplotlib/pylab.py", +] +files = [ + "lib/matplotlib", +] +follow_imports = "silent" +warn_unreachable = true + +[tool.rstcheck] +ignore_directives = [ + # matplotlib.sphinxext.mathmpl + "mathmpl", + # matplotlib.sphinxext.plot_directive + "plot", + # sphinxext.math_symbol_table + "math_symbol_table", + # sphinxext.redirect_from + "redirect-from", + # sphinx-design + "card", + "dropdown", + "grid", + "tab-set", + # sphinx-gallery + "minigallery", + "image-sg", + # sphinx.ext.autodoc + "automodule", + "autoclass", + "autofunction", + "autodata", + "automethod", + "autoattribute", + "autoproperty", + # sphinx.ext.autosummary + "autosummary", + # sphinx.ext.ifconfig + "ifconfig", + # sphinx.ext.inheritance_diagram + "inheritance-diagram", + # sphinx-tags + "tags", + # include directive is causing attribute errors + "include" +] +ignore_roles = [ + # matplotlib.sphinxext.mathmpl + "mathmpl", + "math-stix", + # matplotlib.sphinxext.roles + "rc", + # sphinxext.github + "ghissue", + "ghpull", + "ghuser", + # sphinx-design + "octicon", +] +ignore_substitutions = [ + "Artist", + "Axes", + "Axis", + "Figure", + "release" +] + +ignore_messages = [ + "Hyperlink target \".*\" is not referenced.", + "Duplicate implicit target name: \".*\".", + "Duplicate explicit target name: \".*\".", + # sphinx.ext.intersphinx directives + "No role entry for \"external+.*\".", + "Unknown interpreted text role \"external+.*\"." +] + +[tool.pytest.ini_options] +# Because tests can be run from an installed copy, most of our Pytest +# configuration is in the `pytest_configure` function in +# `lib/matplotlib/testing/conftest.py`. +minversion = "7.0" +testpaths = ["lib"] +addopts = [ + "--import-mode=importlib", +] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 905d3a5e5a52..000000000000 --- a/pytest.ini +++ /dev/null @@ -1,7 +0,0 @@ -# Additional configuration is in matplotlib/testing/conftest.py. -[pytest] -minversion = 3.6 - -testpaths = lib -python_files = test_*.py -junit_family = xunit2 diff --git a/requirements/dev/build-requirements.txt b/requirements/dev/build-requirements.txt new file mode 100644 index 000000000000..4d2a098c3c4f --- /dev/null +++ b/requirements/dev/build-requirements.txt @@ -0,0 +1,3 @@ +pybind11>=2.13.2,!=2.13.3 +meson-python +setuptools-scm diff --git a/requirements/dev/dev-requirements.txt b/requirements/dev/dev-requirements.txt index 7b7ae860fb33..3208949ba0e8 100644 --- a/requirements/dev/dev-requirements.txt +++ b/requirements/dev/dev-requirements.txt @@ -1,4 +1,5 @@ +-r build-requirements.txt -r ../doc/doc-requirements.txt --r ../testing/travis_all.txt --r ../testing/travis_extra.txt --r ../testing/flake8.txt +-r ../testing/all.txt +-r ../testing/extra.txt +ruff diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 193c888b2f95..77cb606130b0 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -2,17 +2,25 @@ # # You will first need a matching Matplotlib installation # e.g (from the Matplotlib root directory) -# pip install -e . +# pip install --no-build-isolation --editable .[dev] # # Install the documentation requirements with: # pip install -r requirements/doc/doc-requirements.txt # -sphinx>=1.8.1,!=2.0.0 +sphinx>=5.1.0,!=6.1.2 colorspacious ipython ipywidgets -numpydoc>=0.8 +ipykernel +numpydoc>=1.0 +packaging>=20 +pydata-sphinx-theme~=0.15.0 +mpl-sphinx-theme~=3.9.0 +pyyaml +PyStemmer sphinxcontrib-svg2pdfconverter>=1.1.0 -sphinx-gallery>=0.7 +sphinxcontrib-video>=0.2.1 sphinx-copybutton -scipy +sphinx-design +sphinx-gallery[parallel]>=0.12.0 +sphinx-tags>=0.4.0 diff --git a/requirements/testing/all.txt b/requirements/testing/all.txt new file mode 100644 index 000000000000..e386924a9b67 --- /dev/null +++ b/requirements/testing/all.txt @@ -0,0 +1,13 @@ +# pip requirements for all the CI builds + +black<24 +certifi +coverage!=6.3 +psutil +pytest!=4.6.0,!=5.4.0,!=8.1.0 +pytest-cov +pytest-rerunfailures +pytest-timeout +pytest-xdist +pytest-xvfb +tornado diff --git a/requirements/testing/extra.txt b/requirements/testing/extra.txt new file mode 100644 index 000000000000..e0d84d71c781 --- /dev/null +++ b/requirements/testing/extra.txt @@ -0,0 +1,12 @@ +# Extra pip requirements + +--prefer-binary +ipykernel +# jupyter/nbconvert#1970 for the 7.3 series exclusions +nbconvert[execute]!=6.0.0,!=6.0.1,!=7.3.0,!=7.3.1 +nbformat!=5.0.0,!=5.0.1 +pandas!=0.25.0 +pikepdf +pytz +pywin32; sys.platform == 'win32' +xarray diff --git a/requirements/testing/flake8.txt b/requirements/testing/flake8.txt deleted file mode 100644 index b5277c656632..000000000000 --- a/requirements/testing/flake8.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Extra pip requirements for the GitHub Actions flake8 build - -flake8>=3.8 -pydocstyle<4.0 -flake8-docstrings diff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt index 3cd31922c17a..ee55f6c7b1bf 100644 --- a/requirements/testing/minver.txt +++ b/requirements/testing/minver.txt @@ -1,7 +1,23 @@ -# Extra pip requirements for the minimum-version travis run +# Extra pip requirements for the minimum-version CI run +contourpy==1.0.1 cycler==0.10 +fonttools==4.22.0 +importlib-resources==3.2.0 +kiwisolver==1.3.2 +meson-python==0.13.1 +meson==1.1.0 +numpy==1.25.0 +packaging==20.0 +pillow==9.0.1 +pyparsing==3.0.0 +pytest==7.0.0 python-dateutil==2.7 -numpy==1.16.0 -pyparsing==2.2.1 -kiwisolver==1.0.1 + +# Test ipython/matplotlib-inline before backend mapping moved to mpl. +# This should be tested for a reasonably long transition period, +# but we will eventually remove the test when we no longer support +# ipython/matplotlib-inline versions from before the transition. +ipython==7.29.0 +ipykernel==5.5.6 +matplotlib-inline<0.1.7 diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt new file mode 100644 index 000000000000..343517263f40 --- /dev/null +++ b/requirements/testing/mypy.txt @@ -0,0 +1,26 @@ +# Extra pip requirements for the GitHub Actions mypy build + +mypy>=1.9 +typing-extensions>=4.6 + +# Extra stubs distributed separately from the main pypi package +pandas-stubs +types-pillow +types-python-dateutil +types-psutil + +sphinx + +# Default requirements, included here because mpl itself does not +# need to be installed for mypy to run, but deps are needed +# and pip has no --deps-only install command +contourpy>=1.0.1 +cycler>=0.10 +fonttools>=4.22.0 +kiwisolver>=1.3.1 +packaging>=20.0 +pillow>=9 +pyparsing>=3 +python-dateutil>=2.7 +setuptools_scm>=7 +setuptools>=64 diff --git a/requirements/testing/travis_all.txt b/requirements/testing/travis_all.txt deleted file mode 100644 index 8ee54ccdb905..000000000000 --- a/requirements/testing/travis_all.txt +++ /dev/null @@ -1,11 +0,0 @@ -# pip requirements for all the travis builds - -certifi -coverage -pytest!=4.6.0,!=5.4.0 -pytest-cov -pytest-rerunfailures -pytest-timeout -pytest-xdist -python-dateutil -tornado diff --git a/requirements/testing/travis_extra.txt b/requirements/testing/travis_extra.txt deleted file mode 100644 index 19b774b382f7..000000000000 --- a/requirements/testing/travis_extra.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Extra pip requirements for the travis python 3.7+ builds - -ipykernel -nbconvert[execute] -nbformat!=5.0.0,!=5.0.1 -pandas!=0.25.0 -pikepdf -pytz diff --git a/setup.cfg.template b/setup.cfg.template deleted file mode 100644 index 00ae11c7a4a0..000000000000 --- a/setup.cfg.template +++ /dev/null @@ -1,43 +0,0 @@ -# Rename this file to setup.cfg to modify Matplotlib's build options. - -[metadata] -license_files = LICENSE/* - -[egg_info] - -[libs] -# By default, Matplotlib builds with LTO, which may be slow if you re-compile -# often, and don't need the space saving/speedup. -#enable_lto = True -# By default, Matplotlib downloads and builds its own copy of FreeType, and -# builds its own copy of Qhull. You may set the following to True to instead -# link against a system FreeType/Qhull. -#system_freetype = False -#system_qhull = False - -[packages] -# There are a number of data subpackages from Matplotlib that are -# considered optional. All except 'tests' data (meaning the baseline -# image files) are installed by default, but that can be changed here. -#tests = False -#sample_data = True - -[gui_support] -# Matplotlib supports multiple GUI toolkits, known as backends. -# The MacOSX backend requires the Cocoa headers included with XCode. -# You can select whether to build it by uncommenting the following line. -# It is never built on Linux or Windows, regardless of the config value. -# -#macosx = True - -[rc_options] -# User-configurable options -# -# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, MacOSX, Pdf, Ps, -# Qt4Agg, Qt5Agg, SVG, TkAgg, WX, WXAgg. -# -# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do -# not choose MacOSX if you have disabled the relevant extension modules. The -# default is determined by fallback. -# -#backend = Agg diff --git a/setup.py b/setup.py deleted file mode 100644 index 5fef78641607..000000000000 --- a/setup.py +++ /dev/null @@ -1,300 +0,0 @@ -""" -The matplotlib build options can be modified with a setup.cfg file. See -setup.cfg.template for more information. -""" - -# NOTE: This file must remain Python 2 compatible for the foreseeable future, -# to ensure that we error out properly for people with outdated setuptools -# and/or pip. -import sys - -min_version = (3, 7) - -if sys.version_info < min_version: - error = """ -Beginning with Matplotlib 3.1, Python {0} or above is required. -You are using Python {1}. - -This may be due to an out of date pip. - -Make sure you have pip >= 9.0.1. -""".format('.'.join(str(n) for n in min_version), - '.'.join(str(n) for n in sys.version_info[:3])) - sys.exit(error) - -import os -from pathlib import Path -import shutil -import subprocess - -from setuptools import setup, find_packages, Extension -from setuptools.command.build_ext import build_ext as BuildExtCommand -from setuptools.command.test import test as TestCommand - -# The setuptools version of sdist adds a setup.cfg file to the tree. -# We don't want that, so we simply remove it, and it will fall back to -# vanilla distutils. -try: - from setuptools.command import sdist -except ImportError: - pass -else: - del sdist.sdist.make_release_tree - -from distutils.errors import CompileError -from distutils.dist import Distribution - -import setupext -from setupext import print_raw, print_status - -# Get the version from versioneer -import versioneer -__version__ = versioneer.get_version() - - -# These are the packages in the order we want to display them. -mpl_packages = [ - setupext.Matplotlib(), - setupext.Python(), - setupext.Platform(), - setupext.FreeType(), - setupext.SampleData(), - setupext.Tests(), - setupext.BackendMacOSX(), - ] - - -# From https://bugs.python.org/issue26689 -def has_flag(self, flagname): - """Return whether a flag name is supported on the specified compiler.""" - import tempfile - with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f: - f.write('int main (int argc, char **argv) { return 0; }') - try: - self.compile([f.name], extra_postargs=[flagname]) - except CompileError: - return False - return True - - -class NoopTestCommand(TestCommand): - def __init__(self, dist): - print("Matplotlib does not support running tests with " - "'python setup.py test'. Please run 'pytest'.") - - -class BuildExtraLibraries(BuildExtCommand): - def finalize_options(self): - self.distribution.ext_modules[:] = [ - ext - for package in good_packages - for ext in package.get_extensions() - ] - super().finalize_options() - - def add_optimization_flags(self): - """ - Add optional optimization flags to extension. - - This adds flags for LTO and hidden visibility to both compiled - extensions, and to the environment variables so that vendored libraries - will also use them. If the compiler does not support these flags, then - none are added. - """ - - env = os.environ.copy() - if not setupext.config.getboolean('libs', 'enable_lto', fallback=True): - return env - if sys.platform == 'win32': - return env - - cppflags = [] - if 'CPPFLAGS' in os.environ: - cppflags.append(os.environ['CPPFLAGS']) - cxxflags = [] - if 'CXXFLAGS' in os.environ: - cxxflags.append(os.environ['CXXFLAGS']) - ldflags = [] - if 'LDFLAGS' in os.environ: - ldflags.append(os.environ['LDFLAGS']) - - if has_flag(self.compiler, '-fvisibility=hidden'): - for ext in self.extensions: - ext.extra_compile_args.append('-fvisibility=hidden') - cppflags.append('-fvisibility=hidden') - if has_flag(self.compiler, '-fvisibility-inlines-hidden'): - for ext in self.extensions: - if self.compiler.detect_language(ext.sources) != 'cpp': - continue - ext.extra_compile_args.append('-fvisibility-inlines-hidden') - cxxflags.append('-fvisibility-inlines-hidden') - ranlib = 'RANLIB' in env - if not ranlib and self.compiler.compiler_type == 'unix': - try: - result = subprocess.run(self.compiler.compiler + - ['--version'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True) - except Exception as e: - pass - else: - version = result.stdout.lower() - if 'gcc' in version: - ranlib = shutil.which('gcc-ranlib') - elif 'clang' in version: - if sys.platform == 'darwin': - ranlib = True - else: - ranlib = shutil.which('llvm-ranlib') - if ranlib and has_flag(self.compiler, '-flto'): - for ext in self.extensions: - ext.extra_compile_args.append('-flto') - cppflags.append('-flto') - ldflags.append('-flto') - # Needed so FreeType static library doesn't lose its LTO objects. - if isinstance(ranlib, str): - env['RANLIB'] = ranlib - - env['CPPFLAGS'] = ' '.join(cppflags) - env['CXXFLAGS'] = ' '.join(cxxflags) - env['LDFLAGS'] = ' '.join(ldflags) - - return env - - def build_extensions(self): - # Remove the -Wstrict-prototypes option, it's not valid for C++. Fixed - # in Py3.7 as bpo-5755. - try: - self.compiler.compiler_so.remove('-Wstrict-prototypes') - except (ValueError, AttributeError): - pass - if (self.compiler.compiler_type == 'msvc' and - os.environ.get('MPL_DISABLE_FH4')): - # Disable FH4 Exception Handling implementation so that we don't - # require VCRUNTIME140_1.dll. For more details, see: - # https://devblogs.microsoft.com/cppblog/making-cpp-exception-handling-smaller-x64/ - # https://github.com/joerick/cibuildwheel/issues/423#issuecomment-677763904 - for ext in self.extensions: - ext.extra_compile_args.append('/d2FH4-') - - env = self.add_optimization_flags() - for package in good_packages: - package.do_custom_build(env) - return super().build_extensions() - - -cmdclass = versioneer.get_cmdclass() -cmdclass['test'] = NoopTestCommand -cmdclass['build_ext'] = BuildExtraLibraries - - -package_data = {} # Will be filled below by the various components. - -# If the user just queries for information, don't bother figuring out which -# packages to build or install. -if not (any('--' + opt in sys.argv - for opt in Distribution.display_option_names + ['help']) - or 'clean' in sys.argv): - # Go through all of the packages and figure out which ones we are - # going to build/install. - print_raw() - print_raw("Edit setup.cfg to change the build options; " - "suppress output with --quiet.") - print_raw() - print_raw("BUILDING MATPLOTLIB") - - good_packages = [] - for package in mpl_packages: - try: - message = package.check() - except setupext.Skipped as e: - print_status(package.name, "no [{e}]".format(e=e)) - continue - if message is not None: - print_status(package.name, - "yes [{message}]".format(message=message)) - good_packages.append(package) - - print_raw() - - # Now collect all of the information we need to build all of the packages. - for package in good_packages: - # Extension modules only get added in build_ext, as numpy will have - # been installed (as setup_requires) at that point. - data = package.get_package_data() - for key, val in data.items(): - package_data.setdefault(key, []) - package_data[key] = list(set(val + package_data[key])) - - # Write the default matplotlibrc file - with open('matplotlibrc.template') as fd: - template_lines = fd.read().splitlines(True) - backend_line_idx, = [ # Also asserts that there is a single such line. - idx for idx, line in enumerate(template_lines) - if line.startswith('#backend:')] - if setupext.options['backend']: - template_lines[backend_line_idx] = ( - 'backend: {}'.format(setupext.options['backend'])) - with open('lib/matplotlib/mpl-data/matplotlibrc', 'w') as fd: - fd.write(''.join(template_lines)) - -setup( # Finally, pass this all along to distutils to do the heavy lifting. - name="matplotlib", - version=__version__, - description="Python plotting package", - author="John D. Hunter, Michael Droettboom", - author_email="matplotlib-users@python.org", - url="https://matplotlib.org", - download_url="https://matplotlib.org/users/installing.html", - project_urls={ - 'Documentation': 'https://matplotlib.org', - 'Source Code': 'https://github.com/matplotlib/matplotlib', - 'Bug Tracker': 'https://github.com/matplotlib/matplotlib/issues', - 'Forum': 'https://discourse.matplotlib.org/', - 'Donate': 'https://numfocus.org/donate-to-matplotlib' - }, - long_description=Path("README.rst").read_text(encoding="utf-8"), - long_description_content_type="text/x-rst", - license="PSF", - platforms="any", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Matplotlib', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Education', - 'License :: OSI Approved :: Python Software Foundation License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Topic :: Scientific/Engineering :: Visualization', - ], - - package_dir={"": "lib"}, - packages=find_packages("lib"), - namespace_packages=["mpl_toolkits"], - py_modules=["pylab"], - # Dummy extension to trigger build_ext, which will swap it out with - # real extensions that can depend on numpy for the build. - ext_modules=[Extension("", [])], - package_data=package_data, - - python_requires='>={}'.format('.'.join(str(n) for n in min_version)), - setup_requires=[ - "certifi>=2020.06.20", - "numpy>=1.15", - ], - install_requires=[ - "certifi>=2020.06.20", - "cycler>=0.10", - "kiwisolver>=1.0.1", - "numpy>=1.16", - "pillow>=6.2.0", - "pyparsing>=2.2.1", - "python-dateutil>=2.7", - ], - - cmdclass=cmdclass, -) diff --git a/setupext.py b/setupext.py deleted file mode 100644 index a8a0ae7cdde7..000000000000 --- a/setupext.py +++ /dev/null @@ -1,686 +0,0 @@ -import configparser -from distutils import ccompiler, sysconfig -from distutils.core import Extension -import functools -import glob -import hashlib -from io import BytesIO -import logging -import os -from pathlib import Path -import platform -import shlex -import shutil -import subprocess -import sys -import tarfile -import textwrap -import urllib.request -import versioneer - -_log = logging.getLogger(__name__) - - -def _get_xdg_cache_dir(): - """ - Return the XDG cache directory. - - See https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - """ - cache_dir = os.environ.get('XDG_CACHE_HOME') - if not cache_dir: - cache_dir = os.path.expanduser('~/.cache') - if cache_dir.startswith('~/'): # Expansion failed. - return None - return Path(cache_dir, 'matplotlib') - - -def _get_hash(data): - """Compute the sha256 hash of *data*.""" - hasher = hashlib.sha256() - hasher.update(data) - return hasher.hexdigest() - - -@functools.lru_cache() -def _get_ssl_context(): - import certifi - import ssl - return ssl.create_default_context(cafile=certifi.where()) - - -def download_or_cache(url, sha): - """ - Get bytes from the given url or local cache. - - Parameters - ---------- - url : str - The url to download. - sha : str - The sha256 of the file. - - Returns - ------- - BytesIO - The file loaded into memory. - """ - cache_dir = _get_xdg_cache_dir() - - if cache_dir is not None: # Try to read from cache. - try: - data = (cache_dir / sha).read_bytes() - except IOError: - pass - else: - if _get_hash(data) == sha: - return BytesIO(data) - - # jQueryUI's website blocks direct downloads from urllib.request's - # default User-Agent, but not (for example) wget; so I don't feel too - # bad passing in an empty User-Agent. - with urllib.request.urlopen( - urllib.request.Request(url, headers={"User-Agent": ""}), - context=_get_ssl_context()) as req: - data = req.read() - - file_sha = _get_hash(data) - if file_sha != sha: - raise Exception( - f"The download file does not match the expected sha. {url} was " - f"expected to have {sha} but it had {file_sha}") - - if cache_dir is not None: # Try to cache the downloaded file. - try: - cache_dir.mkdir(parents=True, exist_ok=True) - with open(cache_dir / sha, "xb") as fout: - fout.write(data) - except IOError: - pass - - return BytesIO(data) - - -# SHA256 hashes of the FreeType tarballs -_freetype_hashes = { - '2.6.1': - '0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014', - '2.6.2': - '8da42fc4904e600be4b692555ae1dcbf532897da9c5b9fb5ebd3758c77e5c2d4', - '2.6.3': - '7942096c40ee6fea882bd4207667ad3f24bff568b96b10fd3885e11a7baad9a3', - '2.6.4': - '27f0e38347a1850ad57f84fc4dfed68ba0bc30c96a6fa6138ef84d485dd9a8d7', - '2.6.5': - '3bb24add9b9ec53636a63ea8e867ed978c4f8fdd8f1fa5ccfd41171163d4249a', - '2.7': - '7b657d5f872b0ab56461f3bd310bd1c5ec64619bd15f0d8e08282d494d9cfea4', - '2.7.1': - '162ef25aa64480b1189cdb261228e6c5c44f212aac4b4621e28cf2157efb59f5', - '2.8': - '33a28fabac471891d0523033e99c0005b95e5618dc8ffa7fa47f9dadcacb1c9b', - '2.8.1': - '876711d064a6a1bd74beb18dd37f219af26100f72daaebd2d86cb493d7cd7ec6', - '2.9': - 'bf380e4d7c4f3b5b1c1a7b2bf3abb967bda5e9ab480d0df656e0e08c5019c5e6', - '2.9.1': - 'ec391504e55498adceb30baceebd147a6e963f636eb617424bcfc47a169898ce', - '2.10.0': - '955e17244e9b38adb0c98df66abb50467312e6bb70eac07e49ce6bd1a20e809a', - '2.10.1': - '3a60d391fd579440561bf0e7f31af2222bc610ad6ce4d9d7bd2165bca8669110', -} -# This is the version of FreeType to use when building a local -# version. It must match the value in -# lib/matplotlib.__init__.py and also needs to be changed below in the -# embedded windows build script (grep for "REMINDER" in this file) -LOCAL_FREETYPE_VERSION = '2.6.1' -LOCAL_FREETYPE_HASH = _freetype_hashes.get(LOCAL_FREETYPE_VERSION, 'unknown') - - -# matplotlib build options, which can be altered using setup.cfg -setup_cfg = os.environ.get('MPLSETUPCFG', 'setup.cfg') -config = configparser.ConfigParser() -if os.path.exists(setup_cfg): - config.read(setup_cfg) -options = { - 'backend': config.get('rc_options', 'backend', fallback=None), - 'system_freetype': config.getboolean( - 'libs', 'system_freetype', fallback=sys.platform.startswith('aix')), - 'system_qhull': config.getboolean('libs', 'system_qhull', - fallback=False), -} - - -if '-q' in sys.argv or '--quiet' in sys.argv: - def print_raw(*args, **kwargs): pass # Suppress our own output. -else: - print_raw = print - - -def print_status(package, status): - initial_indent = "%12s: " % package - indent = ' ' * 18 - print_raw(textwrap.fill(str(status), width=80, - initial_indent=initial_indent, - subsequent_indent=indent)) - - -@functools.lru_cache(1) # We only need to compute this once. -def get_pkg_config(): - """ - Get path to pkg-config and set up the PKG_CONFIG environment variable. - """ - if sys.platform == 'win32': - return None - pkg_config = os.environ.get('PKG_CONFIG', 'pkg-config') - if shutil.which(pkg_config) is None: - print( - "IMPORTANT WARNING:\n" - " pkg-config is not installed.\n" - " Matplotlib may not be able to find some of its dependencies.") - return None - pkg_config_path = sysconfig.get_config_var('LIBDIR') - if pkg_config_path is not None: - pkg_config_path = os.path.join(pkg_config_path, 'pkgconfig') - try: - os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path - except KeyError: - os.environ['PKG_CONFIG_PATH'] = pkg_config_path - return pkg_config - - -def pkg_config_setup_extension( - ext, package, - atleast_version=None, alt_exec=None, default_libraries=()): - """Add parameters to the given *ext* for the given *package*.""" - - # First, try to get the flags from pkg-config. - - pkg_config = get_pkg_config() - cmd = [pkg_config, package] if pkg_config else alt_exec - if cmd is not None: - try: - if pkg_config and atleast_version: - subprocess.check_call( - [*cmd, f"--atleast-version={atleast_version}"]) - # Use sys.getfilesystemencoding() to allow round-tripping - # when passed back to later subprocess calls; do not use - # locale.getpreferredencoding() which universal_newlines=True - # would do. - cflags = shlex.split( - os.fsdecode(subprocess.check_output([*cmd, "--cflags"]))) - libs = shlex.split( - os.fsdecode(subprocess.check_output([*cmd, "--libs"]))) - except (OSError, subprocess.CalledProcessError): - pass - else: - ext.extra_compile_args.extend(cflags) - ext.extra_link_args.extend(libs) - return - - # If that fails, fall back on the defaults. - - # conda Windows header and library paths. - # https://github.com/conda/conda/issues/2312 re: getting the env dir. - if sys.platform == 'win32': - conda_env_path = (os.getenv('CONDA_PREFIX') # conda >= 4.1 - or os.getenv('CONDA_DEFAULT_ENV')) # conda < 4.1 - if conda_env_path and os.path.isdir(conda_env_path): - conda_env_path = Path(conda_env_path) - ext.include_dirs.append(str(conda_env_path / "Library/include")) - ext.library_dirs.append(str(conda_env_path / "Library/lib")) - - # Default linked libs. - ext.libraries.extend(default_libraries) - - -class Skipped(Exception): - """ - Exception thrown by `SetupPackage.check` to indicate that a package should - be skipped. - """ - - -class SetupPackage: - - def check(self): - """ - If the package should be installed, return an informative string, or - None if no information should be displayed at all. - - If the package should be skipped, raise a `Skipped` exception. - - If a missing build dependency is fatal, call `sys.exit`. - """ - - def get_package_data(self): - """ - Get a package data dictionary to add to the configuration. - These are merged into to the *package_data* list passed to - `setuptools.setup`. - """ - return {} - - def get_extensions(self): - """ - Return or yield a list of C extensions (`distutils.core.Extension` - objects) to add to the configuration. These are added to the - *extensions* list passed to `setuptools.setup`. - """ - return [] - - def do_custom_build(self, env): - """ - If a package needs to do extra custom things, such as building a - third-party library, before building an extension, it should - override this method. - """ - - -class OptionalPackage(SetupPackage): - config_category = "packages" - default_config = True - - def check(self): - """ - Check whether ``setup.cfg`` requests this package to be installed. - - May be overridden by subclasses for additional checks. - """ - if config.getboolean(self.config_category, self.name, - fallback=self.default_config): - return "installing" - else: # Configuration opt-out by user - raise Skipped("skipping due to configuration") - - -class Platform(SetupPackage): - name = "platform" - - def check(self): - return sys.platform - - -class Python(SetupPackage): - name = "python" - - def check(self): - return sys.version - - -def _pkg_data_helper(pkg, subdir): - """Glob "lib/$pkg/$subdir/**/*", returning paths relative to "lib/$pkg".""" - base = Path("lib", pkg) - return [str(path.relative_to(base)) for path in (base / subdir).rglob("*")] - - -class Matplotlib(SetupPackage): - name = "matplotlib" - - def check(self): - return versioneer.get_version() - - def get_package_data(self): - return { - 'matplotlib': [ - 'mpl-data/matplotlibrc', - *_pkg_data_helper('matplotlib', 'mpl-data/fonts'), - *_pkg_data_helper('matplotlib', 'mpl-data/images'), - *_pkg_data_helper('matplotlib', 'mpl-data/stylelib'), - *_pkg_data_helper('matplotlib', 'backends/web_backend'), - '*.dll', # Only actually matters on Windows. - ], - } - - def get_extensions(self): - # agg - ext = Extension( - "matplotlib.backends._backend_agg", [ - "src/mplutils.cpp", - "src/py_converters.cpp", - "src/_backend_agg.cpp", - "src/_backend_agg_wrapper.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags_and_sources(ext) - FreeType().add_flags(ext) - yield ext - # c_internal_utils - ext = Extension( - "matplotlib._c_internal_utils", ["src/_c_internal_utils.c"], - libraries=({"win32": ["ole32", "shell32", "user32"]} - .get(sys.platform, []))) - yield ext - # contour - ext = Extension( - "matplotlib._contour", [ - "src/_contour.cpp", - "src/_contour_wrapper.cpp", - "src/py_converters.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags(ext) - yield ext - # ft2font - ext = Extension( - "matplotlib.ft2font", [ - "src/ft2font.cpp", - "src/ft2font_wrapper.cpp", - "src/mplutils.cpp", - "src/py_converters.cpp", - ]) - FreeType().add_flags(ext) - add_numpy_flags(ext) - add_libagg_flags(ext) - yield ext - # image - ext = Extension( - "matplotlib._image", [ - "src/_image.cpp", - "src/mplutils.cpp", - "src/_image_wrapper.cpp", - "src/py_converters.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags_and_sources(ext) - yield ext - # path - ext = Extension( - "matplotlib._path", [ - "src/py_converters.cpp", - "src/_path_wrapper.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags_and_sources(ext) - yield ext - # qhull - ext = Extension( - "matplotlib._qhull", ["src/qhull_wrap.c"], - define_macros=[("MPL_DEVNULL", os.devnull)]) - add_numpy_flags(ext) - add_qhull_flags(ext) - yield ext - # tkagg - ext = Extension( - "matplotlib.backends._tkagg", [ - "src/_tkagg.cpp", - "src/py_converters.cpp", - ], - include_dirs=["src"], - # psapi library needed for finding Tcl/Tk at run time. - libraries=({"linux": ["dl"], "win32": ["psapi"], - "cygwin": ["psapi"]}.get(sys.platform, [])), - extra_link_args={"win32": ["-mwindows"]}.get(sys.platform, [])) - add_numpy_flags(ext) - add_libagg_flags(ext) - yield ext - # tri - ext = Extension( - "matplotlib._tri", [ - "src/tri/_tri.cpp", - "src/tri/_tri_wrapper.cpp", - "src/mplutils.cpp", - ]) - add_numpy_flags(ext) - yield ext - # ttconv - ext = Extension( - "matplotlib._ttconv", [ - "src/_ttconv.cpp", - "extern/ttconv/pprdrv_tt.cpp", - "extern/ttconv/pprdrv_tt2.cpp", - "extern/ttconv/ttutil.cpp", - ], - include_dirs=["extern"]) - add_numpy_flags(ext) - yield ext - - -class SampleData(OptionalPackage): - """ - This handles the sample data that ships with matplotlib. It is - technically optional, though most often will be desired. - """ - name = "sample_data" - - def get_package_data(self): - return { - 'matplotlib': [ - *_pkg_data_helper('matplotlib', 'mpl-data/sample_data'), - ], - } - - -class Tests(OptionalPackage): - name = "tests" - default_config = False - - def get_package_data(self): - return { - 'matplotlib': [ - *_pkg_data_helper('matplotlib', 'tests/baseline_images'), - *_pkg_data_helper('matplotlib', 'tests/tinypages'), - 'tests/cmr10.pfb', - 'tests/mpltest.ttf', - ], - 'mpl_toolkits': [ - *_pkg_data_helper('mpl_toolkits', 'tests/baseline_images'), - ] - } - - -def add_numpy_flags(ext): - import numpy as np - ext.include_dirs.append(np.get_include()) - ext.define_macros.extend([ - # Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for each - # extension. - ('PY_ARRAY_UNIQUE_SYMBOL', - 'MPL_' + ext.name.replace('.', '_') + '_ARRAY_API'), - ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'), - # Allow NumPy's printf format specifiers in C++. - ('__STDC_FORMAT_MACROS', 1), - ]) - - -def add_libagg_flags(ext): - # We need a patched Agg not available elsewhere, so always use the vendored - # version. - ext.include_dirs.insert(0, "extern/agg24-svn/include") - - -def add_libagg_flags_and_sources(ext): - # We need a patched Agg not available elsewhere, so always use the vendored - # version. - ext.include_dirs.insert(0, "extern/agg24-svn/include") - agg_sources = [ - "agg_bezier_arc.cpp", - "agg_curves.cpp", - "agg_image_filters.cpp", - "agg_trans_affine.cpp", - "agg_vcgen_contour.cpp", - "agg_vcgen_dash.cpp", - "agg_vcgen_stroke.cpp", - "agg_vpgen_segmentator.cpp", - ] - ext.sources.extend( - os.path.join("extern", "agg24-svn", "src", x) for x in agg_sources) - - -def add_qhull_flags(ext): - if options.get("system_qhull"): - ext.libraries.append("qhull") - else: - ext.include_dirs.insert(0, "extern") - ext.sources.extend(sorted(glob.glob("extern/libqhull/*.c"))) - if sysconfig.get_config_var("LIBM") == "-lm": - ext.libraries.extend("m") - - -# First compile checkdep_freetype2.c, which aborts the compilation either -# with "foo.h: No such file or directory" if the header is not found, or an -# appropriate error message if the header indicates a too-old version. - - -class FreeType(SetupPackage): - name = "freetype" - - def add_flags(self, ext): - ext.sources.insert(0, 'src/checkdep_freetype2.c') - if options.get('system_freetype'): - pkg_config_setup_extension( - # FreeType 2.3 has libtool version 9.11.3 as can be checked - # from the tarball. For FreeType>=2.4, there is a conversion - # table in docs/VERSIONS.txt in the FreeType source tree. - ext, 'freetype2', - atleast_version='9.11.3', - alt_exec=['freetype-config'], - default_libraries=['freetype']) - ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'system')) - else: - src_path = Path('build', f'freetype-{LOCAL_FREETYPE_VERSION}') - # Statically link to the locally-built freetype. - # This is certainly broken on Windows. - ext.include_dirs.insert(0, str(src_path / 'include')) - if sys.platform == 'win32': - libfreetype = 'libfreetype.lib' - else: - libfreetype = 'libfreetype.a' - ext.extra_objects.insert( - 0, str(src_path / 'objs' / '.libs' / libfreetype)) - ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local')) - - def do_custom_build(self, env): - # We're using a system freetype - if options.get('system_freetype'): - return - - src_path = Path('build', f'freetype-{LOCAL_FREETYPE_VERSION}') - - # We've already built freetype - if sys.platform == 'win32': - libfreetype = 'libfreetype.lib' - else: - libfreetype = 'libfreetype.a' - - # bailing because it is already built - if (src_path / 'objs' / '.libs' / libfreetype).is_file(): - return - - # do we need to download / load the source from cache? - if not src_path.exists(): - os.makedirs('build', exist_ok=True) - - tarball = f'freetype-{LOCAL_FREETYPE_VERSION}.tar.gz' - target_urls = [ - (f'https://downloads.sourceforge.net/project/freetype' - f'/freetype2/{LOCAL_FREETYPE_VERSION}/{tarball}'), - (f'https://download.savannah.gnu.org/releases/freetype' - f'/{tarball}') - ] - - for tarball_url in target_urls: - try: - tar_contents = download_or_cache(tarball_url, - LOCAL_FREETYPE_HASH) - break - except Exception: - pass - else: - raise IOError( - f"Failed to download FreeType. Please download one of " - f"{target_urls} and extract it into {src_path} at the " - f"top-level of the source repository.") - - print(f"Extracting {tarball}") - with tarfile.open(fileobj=tar_contents, mode="r:gz") as tgz: - tgz.extractall("build") - - print(f"Building freetype in {src_path}") - if sys.platform != 'win32': # compilation on non-windows - env = {**env, "CFLAGS": "{} -fPIC".format(env.get("CFLAGS", ""))} - subprocess.check_call( - ["./configure", "--with-zlib=no", "--with-bzip2=no", - "--with-png=no", "--with-harfbuzz=no", "--enable-static", - "--disable-shared"], - env=env, cwd=src_path) - if 'GNUMAKE' in env: - make = env['GNUMAKE'] - elif 'MAKE' in env: - make = env['MAKE'] - else: - try: - output = subprocess.check_output(['make', '-v'], - stderr=subprocess.DEVNULL) - except subprocess.CalledProcessError: - output = b'' - if b'GNU' not in output and b'makepp' not in output: - make = 'gmake' - else: - make = 'make' - subprocess.check_call([make], env=env, cwd=src_path) - else: # compilation on windows - shutil.rmtree(src_path / "objs", ignore_errors=True) - msbuild_platform = ( - 'x64' if platform.architecture()[0] == '64bit' else 'Win32') - base_path = Path("build/freetype-2.6.1/builds/windows") - vc = 'vc2010' - sln_path = ( - base_path / vc / "freetype.sln" - ) - # https://developercommunity.visualstudio.com/comments/190992/view.html - (sln_path.parent / "Directory.Build.props").write_text(""" - - - - $([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0')) - - -""") - # It is not a trivial task to determine PlatformToolset to plug it - # into msbuild command, and Directory.Build.props will not override - # the value in the project file. - # The DefaultPlatformToolset is from Microsoft.Cpp.Default.props - with open(base_path / vc / "freetype.vcxproj", 'r+b') as f: - toolset_repl = b'PlatformToolset>$(DefaultPlatformToolset)<' - vcxproj = f.read().replace(b'PlatformToolset>v100<', - toolset_repl) - assert toolset_repl in vcxproj, ( - 'Upgrading Freetype might break this') - f.seek(0) - f.truncate() - f.write(vcxproj) - - cc = ccompiler.new_compiler() - cc.initialize() # Get msbuild in the %PATH% of cc.spawn. - cc.spawn(["msbuild", str(sln_path), - "/t:Clean;Build", - f"/p:Configuration=Release;Platform={msbuild_platform}"]) - # Move to the corresponding Unix build path. - (src_path / "objs" / ".libs").mkdir() - # Be robust against change of FreeType version. - lib_path, = (src_path / "objs" / vc / msbuild_platform).glob( - "freetype*.lib") - shutil.copy2(lib_path, src_path / "objs/.libs/libfreetype.lib") - - -class BackendMacOSX(OptionalPackage): - config_category = 'gui_support' - name = 'macosx' - - def check(self): - if sys.platform != 'darwin': - raise Skipped("Mac OS-X only") - return super().check() - - def get_extensions(self): - sources = [ - 'src/_macosx.m' - ] - ext = Extension('matplotlib.backends._macosx', sources) - ext.extra_compile_args.extend(['-Werror=unguarded-availability']) - ext.extra_link_args.extend(['-framework', 'Cocoa']) - if platform.python_implementation().lower() == 'pypy': - ext.extra_compile_args.append('-DPYPY=1') - yield ext diff --git a/src/.clang-format b/src/.clang-format index d54ffcedf924..f3a6eb540777 100644 --- a/src/.clang-format +++ b/src/.clang-format @@ -1,5 +1,5 @@ --- -# BasedOnStyle: LLVM +# BasedOnStyle: LLVM AccessModifierOffset: -2 ConstructorInitializerIndentWidth: 4 AlignEscapedNewlinesLeft: false @@ -13,7 +13,7 @@ BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BinPackParameters: false -ColumnLimit: 100 +ColumnLimit: 100 ConstructorInitializerAllOnOneLineOrOnePerLine: true DerivePointerBinding: false ExperimentalAutoDetectBinPacking: false @@ -30,14 +30,14 @@ PenaltyReturnTypeOnItsOwnLine: 60 PointerBindsToType: false SpacesBeforeTrailingComments: 1 Cpp11BracedListStyle: false -Standard: Cpp03 -IndentWidth: 4 -TabWidth: 8 -UseTab: Never +Standard: Cpp03 +IndentWidth: 4 +TabWidth: 8 +UseTab: Never BreakBeforeBraces: Linux IndentFunctionDeclarationAfterType: false SpacesInParentheses: false -SpacesInAngles: false +SpacesInAngles: false SpaceInEmptyParentheses: false SpacesInCStyleCastParentheses: false SpaceAfterControlStatementKeyword: true diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index de06d0bfb2ab..4d097bc80716 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -2,37 +2,17 @@ #define NO_IMPORT_ARRAY +#include #include "_backend_agg.h" -#include "mplutils.h" - -void BufferRegion::to_string_argb(uint8_t *buf) -{ - unsigned char *pix; - unsigned char tmp; - size_t i, j; - - memcpy(buf, data, height * stride); - - for (i = 0; i < (size_t)height; ++i) { - pix = buf + i * stride; - for (j = 0; j < (size_t)width; ++j) { - // Convert rgba to argb - tmp = pix[2]; - pix[2] = pix[0]; - pix[0] = tmp; - pix += 4; - } - } -} RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) : width(width), height(height), dpi(dpi), - NUMBYTES(width * height * 4), - pixBuffer(NULL), + NUMBYTES((size_t)width * (size_t)height * 4), + pixBuffer(nullptr), renderingBuffer(), - alphaBuffer(NULL), + alphaBuffer(nullptr), alphaMaskRenderingBuffer(), alphaMask(alphaMaskRenderingBuffer), pixfmtAlphaMask(alphaMaskRenderingBuffer), @@ -45,10 +25,20 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) rendererBase(), rendererAA(), rendererBin(), - theRasterizer(8192), - lastclippath(NULL), + theRasterizer(32768), + lastclippath(nullptr), _fill_color(agg::rgba(1, 1, 1, 0)) { + if (dpi <= 0.0) { + throw std::range_error("dpi must be positive"); + } + + if (width >= 1 << 23 || height >= 1 << 23) { + throw std::range_error( + "Image size of " + std::to_string(width) + "x" + std::to_string(height) + + " pixels is too large. It must be less than 2^23 in each direction."); + } + unsigned stride(width * 4); pixBuffer = new agg::int8u[NUMBYTES]; @@ -85,7 +75,7 @@ BufferRegion *RendererAgg::copy_from_bbox(agg::rect_d in_rect) agg::rect_i rect( (int)in_rect.x1, height - (int)in_rect.y2, (int)in_rect.x2, height - (int)in_rect.y1); - BufferRegion *reg = NULL; + BufferRegion *reg = nullptr; reg = new BufferRegion(rect); agg::rendering_buffer rbuf; @@ -100,21 +90,21 @@ BufferRegion *RendererAgg::copy_from_bbox(agg::rect_d in_rect) void RendererAgg::restore_region(BufferRegion ®ion) { - if (region.get_data() == NULL) { + if (region.get_data() == nullptr) { throw std::runtime_error("Cannot restore_region from NULL data"); } agg::rendering_buffer rbuf; rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); - rendererBase.copy_from(rbuf, 0, region.get_rect().x1, region.get_rect().y1); + rendererBase.copy_from(rbuf, nullptr, region.get_rect().x1, region.get_rect().y1); } // Restore the part of the saved region with offsets void RendererAgg::restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int yy2, int x, int y ) { - if (region.get_data() == NULL) { + if (region.get_data() == nullptr) { throw std::runtime_error("Cannot restore_region from NULL data"); } @@ -128,14 +118,16 @@ RendererAgg::restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int rendererBase.copy_from(rbuf, &rect, x, y); } -bool RendererAgg::render_clippath(py::PathIterator &clippath, +bool RendererAgg::render_clippath(mpl::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; - typedef PathClipper clipped_t; - typedef PathSnapper snapped_t; + /* Unlike normal Paths, the clip path cannot be clipped to the Figure bbox, + * because it needs to remain a complete closed path, so there is no + * PathClipper step. */ + typedef PathSnapper snapped_t; typedef PathSimplifier simplify_t; typedef agg::conv_curve curve_t; @@ -150,11 +142,10 @@ bool RendererAgg::render_clippath(py::PathIterator &clippath, rendererBaseAlphaMask.clear(agg::gray8(0, 0)); transformed_path_t transformed_clippath(clippath, trans); - nan_removed_t nan_removed_clippath(transformed_clippath, true, clippath.has_curves()); - clipped_t clipped_clippath(nan_removed_clippath, !clippath.has_curves(), width, height); - snapped_t snapped_clippath(clipped_clippath, snap_mode, clippath.total_vertices(), 0.0); + nan_removed_t nan_removed_clippath(transformed_clippath, true, clippath.has_codes()); + snapped_t snapped_clippath(nan_removed_clippath, snap_mode, clippath.total_vertices(), 0.0); simplify_t simplified_clippath(snapped_clippath, - clippath.should_simplify() && !clippath.has_curves(), + clippath.should_simplify() && !clippath.has_codes(), clippath.simplify_threshold()); curve_t curved_clippath(simplified_clippath); theRasterizer.add_path(curved_clippath); diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 8436f6840f24..6ecbcba1df18 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -6,15 +6,19 @@ #ifndef MPL_BACKEND_AGG_H #define MPL_BACKEND_AGG_H +#include + #include -#include #include +#include #include "agg_alpha_mask_u8.h" #include "agg_conv_curve.h" #include "agg_conv_dash.h" #include "agg_conv_stroke.h" +#include "agg_conv_transform.h" #include "agg_image_accessors.h" +#include "agg_path_storage.h" #include "agg_pixfmt_amask_adaptor.h" #include "agg_pixfmt_gray.h" #include "agg_pixfmt_rgba.h" @@ -25,7 +29,6 @@ #include "agg_scanline_bin.h" #include "agg_scanline_p.h" #include "agg_scanline_storage_aa.h" -#include "agg_scanline_storage_bin.h" #include "agg_scanline_u.h" #include "agg_span_allocator.h" #include "agg_span_converter.h" @@ -34,16 +37,17 @@ #include "agg_span_image_filter_rgba.h" #include "agg_span_interpolator_linear.h" #include "agg_span_pattern_rgba.h" -#include "util/agg_color_conv_rgb8.h" #include "_backend_agg_basic_types.h" #include "path_converters.h" #include "array.h" #include "agg_workaround.h" +namespace py = pybind11; + /**********************************************************************/ -// a helper class to pass agg::buffer objects around. +// a helper class to pass agg::buffer objects around. class BufferRegion { @@ -61,6 +65,10 @@ class BufferRegion delete[] data; }; + // prevent copying + BufferRegion(const BufferRegion &) = delete; + BufferRegion &operator=(const BufferRegion &) = delete; + agg::int8u *get_data() { return data; @@ -86,19 +94,12 @@ class BufferRegion return stride; } - void to_string_argb(uint8_t *buf); - private: agg::int8u *data; agg::rect_i rect; int width; int height; int stride; - - private: - // prevent copying - BufferRegion(const BufferRegion &); - BufferRegion &operator=(const BufferRegion &); }; #define MARKER_CACHE_SIZE 512 @@ -115,10 +116,10 @@ class RendererAgg typedef agg::renderer_scanline_bin_solid renderer_bin; typedef agg::rasterizer_scanline_aa rasterizer; - typedef agg::scanline_p8 scanline_p8; - typedef agg::scanline_bin scanline_bin; + typedef agg::scanline32_p8 scanline_p8; + typedef agg::scanline32_bin scanline_bin; typedef agg::amask_no_clip_gray8 alpha_mask_type; - typedef agg::scanline_u8_am scanline_am; + typedef agg::scanline32_u8_am scanline_am; typedef agg::renderer_base renderer_base_alpha_mask_type; typedef agg::renderer_scanline_aa_solid renderer_alpha_mask_type; @@ -177,7 +178,7 @@ class RendererAgg LineWidthArray &linewidths, DashesVector &linestyles, AntialiasedArray &antialiaseds, - e_offset_position offset_position); + ColorArray &hatchcolors); template void draw_quad_mesh(GCAgg &gc, @@ -191,12 +192,6 @@ class RendererAgg bool antialiased, ColorArray &edgecolors); - template - void draw_gouraud_triangle(GCAgg &gc, - PointArray &points, - ColorArray &colors, - agg::trans_affine &trans); - template void draw_gouraud_triangles(GCAgg &gc, PointArray &points, @@ -251,7 +246,7 @@ class RendererAgg template void set_clipbox(const agg::rect_d &cliprect, R &rasterizer); - bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); + bool render_clippath(mpl::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); template void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc); @@ -277,9 +272,9 @@ class RendererAgg LineWidthArray &linewidths, DashesVector &linestyles, AntialiasedArray &antialiaseds, - e_offset_position offset_position, bool check_snap, - bool has_curves); + bool has_codes, + ColorArray &hatchcolors); template void _draw_gouraud_triangle(PointArray &points, @@ -348,15 +343,16 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, rendererBase.reset_clipping(true); // Create and transform the path - typedef agg::conv_transform hatch_path_trans_t; + typedef agg::conv_transform hatch_path_trans_t; typedef agg::conv_curve hatch_path_curve_t; typedef agg::conv_stroke hatch_path_stroke_t; - py::PathIterator hatch_path(gc.hatchpath); + mpl::PathIterator hatch_path(gc.hatchpath); agg::trans_affine hatch_trans; hatch_trans *= agg::trans_affine_scaling(1.0, -1.0); hatch_trans *= agg::trans_affine_translation(0.0, 1.0); - hatch_trans *= agg::trans_affine_scaling(hatch_size, hatch_size); + hatch_trans *= agg::trans_affine_scaling(static_cast(hatch_size), + static_cast(hatch_size)); hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans); hatch_path_curve_t hatch_path_curve(hatch_path_trans); hatch_path_stroke_t hatch_path_stroke(hatch_path_curve); @@ -455,7 +451,7 @@ template inline void RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; @@ -472,7 +468,7 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); - bool clip = !face.first && !gc.has_hatchpath() && !path.has_curves(); + bool clip = !face.first && !gc.has_hatchpath(); bool simplify = path.should_simplify() && clip; double snapping_linewidth = points_to_pixels(gc.linewidth); if (gc.color.a == 0.0) { @@ -480,7 +476,7 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, } transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, path.has_curves()); + nan_removed_t nan_removed(tpath, true, path.has_codes()); clipped_t clipped(nan_removed, clip, width, height); snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth); simplify_t simplified(snapped, simplify, path.simplify_threshold()); @@ -498,7 +494,7 @@ inline void RendererAgg::draw_markers(GCAgg &gc, agg::trans_affine &trans, agg::rgba color) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathSnapper snap_t; typedef agg::conv_curve curve_t; @@ -514,7 +510,7 @@ inline void RendererAgg::draw_markers(GCAgg &gc, trans *= agg::trans_affine_translation(0.5, (double)height + 0.5); transformed_path_t marker_path_transformed(marker_path, marker_trans); - nan_removed_t marker_path_nan_removed(marker_path_transformed, true, marker_path.has_curves()); + nan_removed_t marker_path_nan_removed(marker_path_transformed, true, marker_path.has_codes()); snap_t marker_path_snapped(marker_path_nan_removed, gc.snap_mode, marker_path.total_vertices(), @@ -739,22 +735,25 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in rendererBase.reset_clipping(true); if (angle != 0.0) { agg::rendering_buffer srcbuf( - image.data(), (unsigned)image.dim(1), - (unsigned)image.dim(0), (unsigned)image.dim(1)); + image.mutable_data(0, 0), (unsigned)image.shape(1), + (unsigned)image.shape(0), (unsigned)image.shape(1)); agg::pixfmt_gray8 pixf_img(srcbuf); set_clipbox(gc.cliprect, theRasterizer); + auto image_height = static_cast(image.shape(0)), + image_width = static_cast(image.shape(1)); + agg::trans_affine mtx; - mtx *= agg::trans_affine_translation(0, -image.dim(0)); - mtx *= agg::trans_affine_rotation(-angle * agg::pi / 180.0); + mtx *= agg::trans_affine_translation(0, -image_height); + mtx *= agg::trans_affine_rotation(-angle * (agg::pi / 180.0)); mtx *= agg::trans_affine_translation(x, y); agg::path_storage rect; rect.move_to(0, 0); - rect.line_to(image.dim(1), 0); - rect.line_to(image.dim(1), image.dim(0)); - rect.line_to(0, image.dim(0)); + rect.line_to(image_width, 0); + rect.line_to(image_width, image_height); + rect.line_to(0, image_height); rect.line_to(0, 0); agg::conv_transform rect2(rect, mtx); @@ -775,24 +774,28 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in } else { agg::rect_i fig, text; + int deltay = y - image.shape(0); + fig.init(0, 0, width, height); - text.init(x, y - image.dim(0), x + image.dim(1), y); + text.init(x, deltay, x + image.shape(1), y); text.clip(fig); if (gc.cliprect.x1 != 0.0 || gc.cliprect.y1 != 0.0 || gc.cliprect.x2 != 0.0 || gc.cliprect.y2 != 0.0) { agg::rect_i clip; - clip.init(int(mpl_round(gc.cliprect.x1)), - int(mpl_round(height - gc.cliprect.y2)), - int(mpl_round(gc.cliprect.x2)), - int(mpl_round(height - gc.cliprect.y1))); + clip.init(mpl_round_to_int(gc.cliprect.x1), + mpl_round_to_int(height - gc.cliprect.y2), + mpl_round_to_int(gc.cliprect.x2), + mpl_round_to_int(height - gc.cliprect.y1)); text.clip(clip); } if (text.x2 > text.x1) { + int deltax = text.x2 - text.x1; + int deltax2 = text.x1 - x; for (int yi = text.y1; yi < text.y2; ++yi) { - pixFmt.blend_solid_hspan(text.x1, yi, (text.x2 - text.x1), gc.color, - &image(yi - (y - image.dim(0)), text.x1 - x)); + pixFmt.blend_solid_hspan(text.x1, yi, deltax, gc.color, + &image(yi - deltay, deltax2)); } } } @@ -835,20 +838,24 @@ inline void RendererAgg::draw_image(GCAgg &gc, bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); agg::rendering_buffer buffer; - buffer.attach( - image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), -(int)image.dim(1) * 4); + buffer.attach(image.mutable_data(0, 0, 0), + (unsigned)image.shape(1), (unsigned)image.shape(0), + -(int)image.shape(1) * 4); pixfmt pixf(buffer); if (has_clippath) { agg::trans_affine mtx; agg::path_storage rect; - mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.dim(0)))); + auto image_height = static_cast(image.shape(0)), + image_width = static_cast(image.shape(1)); + + mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image_height))); rect.move_to(0, 0); - rect.line_to(image.dim(1), 0); - rect.line_to(image.dim(1), image.dim(0)); - rect.line_to(0, image.dim(0)); + rect.line_to(image_width, 0); + rect.line_to(image_width, image_height); + rect.line_to(0, image_height); rect.line_to(0, 0); agg::conv_transform rect2(rect, mtx); @@ -884,7 +891,7 @@ inline void RendererAgg::draw_image(GCAgg &gc, } else { set_clipbox(gc.cliprect, rendererBase); rendererBase.blend_from( - pixf, 0, (int)x, (int)(height - (y + image.dim(0))), (agg::int8u)(alpha * 255)); + pixf, nullptr, (int)x, (int)(height - (y + image.shape(0))), (agg::int8u)(alpha * 255)); } rendererBase.reset_clipping(true); @@ -911,9 +918,9 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, LineWidthArray &linewidths, DashesVector &linestyles, AntialiasedArray &antialiaseds, - e_offset_position offset_position, bool check_snap, - bool has_curves) + bool has_codes, + ColorArray &hatchcolors) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; @@ -921,19 +928,24 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, typedef PathSnapper snapped_t; typedef agg::conv_curve snapped_curve_t; typedef agg::conv_curve curve_t; + typedef Sketch sketch_clipped_t; + typedef Sketch sketch_curve_t; + typedef Sketch sketch_snapped_t; + typedef Sketch sketch_snapped_curve_t; size_t Npaths = path_generator.num_paths(); - size_t Noffsets = offsets.size(); + size_t Noffsets = safe_first_shape(offsets); size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = transforms.size(); - size_t Nfacecolors = facecolors.size(); - size_t Nedgecolors = edgecolors.size(); - size_t Nlinewidths = linewidths.size(); + size_t Ntransforms = safe_first_shape(transforms); + size_t Nfacecolors = safe_first_shape(facecolors); + size_t Nedgecolors = safe_first_shape(edgecolors); + size_t Nhatchcolors = safe_first_shape(hatchcolors); + size_t Nlinewidths = safe_first_shape(linewidths); size_t Nlinestyles = std::min(linestyles.size(), N); - size_t Naa = antialiaseds.size(); + size_t Naa = safe_first_shape(antialiaseds); - if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { + if ((Nfacecolors == 0 && Nedgecolors == 0 && Nhatchcolors == 0) || Npaths == 0) { return; } @@ -948,6 +960,7 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, facepair_t face; face.first = Nfacecolors != 0; agg::trans_affine trans; + bool do_clip = !face.first && !gc.has_hatchpath(); for (int i = 0; i < (int)N; ++i) { typename PathGenerator::path_iterator path = path_generator(i); @@ -969,11 +982,7 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, double xo = offsets(i % Noffsets, 0); double yo = offsets(i % Noffsets, 1); offset_trans.transform(&xo, &yo); - if (offset_position == OFFSET_POSITION_DATA) { - trans = agg::trans_affine_translation(xo, yo) * trans; - } else { - trans *= agg::trans_affine_translation(xo, yo); - } + trans *= agg::trans_affine_translation(xo, yo); } // These transformations must be done post-offsets @@ -999,33 +1008,34 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, } } - bool do_clip = !face.first && !gc.has_hatchpath() && !has_curves; + if(Nhatchcolors) { + int ic = i % Nhatchcolors; + gc.hatch_color = agg::rgba(hatchcolors(ic, 0), hatchcolors(ic, 1), hatchcolors(ic, 2), hatchcolors(ic, 3)); + } + gc.isaa = antialiaseds(i % Naa); + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_codes); + clipped_t clipped(nan_removed, do_clip, width, height); if (check_snap) { - gc.isaa = antialiaseds(i % Naa); - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, has_curves); - clipped_t clipped(nan_removed, do_clip, width, height); snapped_t snapped( clipped, gc.snap_mode, path.total_vertices(), points_to_pixels(gc.linewidth)); - if (has_curves) { + if (has_codes) { snapped_curve_t curve(snapped); - _draw_path(curve, has_clippath, face, gc); + sketch_snapped_curve_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + _draw_path(sketch, has_clippath, face, gc); } else { - _draw_path(snapped, has_clippath, face, gc); + sketch_snapped_t sketch(snapped, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + _draw_path(sketch, has_clippath, face, gc); } } else { - gc.isaa = antialiaseds(i % Naa); - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, has_curves); - clipped_t clipped(nan_removed, do_clip, width, height); - if (has_curves) { + if (has_codes) { curve_t curve(clipped); - _draw_path(curve, has_clippath, face, gc); + sketch_curve_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + _draw_path(sketch, has_clippath, face, gc); } else { - _draw_path(clipped, has_clippath, face, gc); + sketch_clipped_t sketch(clipped, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + _draw_path(sketch, has_clippath, face, gc); } } } @@ -1048,7 +1058,7 @@ inline void RendererAgg::draw_path_collection(GCAgg &gc, LineWidthArray &linewidths, DashesVector &linestyles, AntialiasedArray &antialiaseds, - e_offset_position offset_position) + ColorArray &hatchcolors) { _draw_path_collection_generic(gc, master_transform, @@ -1064,9 +1074,9 @@ inline void RendererAgg::draw_path_collection(GCAgg &gc, linewidths, linestyles, antialiaseds, - offset_position, true, - true); + true, + hatchcolors); } template @@ -1133,7 +1143,7 @@ class QuadMeshGenerator inline size_t num_paths() const { - return m_meshWidth * m_meshHeight; + return (size_t) m_meshWidth * m_meshHeight; } inline path_iterator operator()(size_t i) const @@ -1160,6 +1170,7 @@ inline void RendererAgg::draw_quad_mesh(GCAgg &gc, array::scalar linewidths(gc.linewidth); array::scalar antialiaseds(antialiased); DashesVector linestyles; + ColorArray hatchcolors = py::array_t().reshape({0, 4}).unchecked(); _draw_path_collection_generic(gc, master_transform, @@ -1175,9 +1186,9 @@ inline void RendererAgg::draw_quad_mesh(GCAgg &gc, linewidths, linestyles, antialiaseds, - OFFSET_POSITION_FIGURE, true, // check_snap - false); + false, + hatchcolors); } template @@ -1200,6 +1211,9 @@ inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, tpoints[i][j] = points(i, j); } trans.transform(&tpoints[i][0], &tpoints[i][1]); + if(std::isnan(tpoints[i][0]) || std::isnan(tpoints[i][1])) { + return; + } } span_alloc_t span_alloc; @@ -1233,34 +1247,33 @@ inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, } } -template -inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc, - PointArray &points, - ColorArray &colors, - agg::trans_affine &trans) -{ - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); - - _draw_gouraud_triangle(points, colors, trans, has_clippath); -} - template inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, PointArray &points, ColorArray &colors, agg::trans_affine &trans) { + if (points.shape(0)) { + check_trailing_shape(points, "points", 3, 2); + } + if (colors.shape(0)) { + check_trailing_shape(colors, "colors", 3, 4); + } + if (points.shape(0) != colors.shape(0)) { + throw py::value_error( + "points and colors arrays must be the same length, got " + + std::to_string(points.shape(0)) + " points and " + + std::to_string(colors.shape(0)) + "colors"); + } + theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); - for (int i = 0; i < points.dim(0); ++i) { - typename PointArray::sub_t point = points.subarray(i); - typename ColorArray::sub_t color = colors.subarray(i); + for (int i = 0; i < points.shape(0); ++i) { + auto point = std::bind(points, i, std::placeholders::_1, std::placeholders::_2); + auto color = std::bind(colors, i, std::placeholders::_1, std::placeholders::_2); _draw_gouraud_triangle(point, color, trans, has_clippath); } diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h index 9f65253d9f50..b424419ec99e 100644 --- a/src/_backend_agg_basic_types.h +++ b/src/_backend_agg_basic_types.h @@ -4,17 +4,23 @@ /* Contains some simple types from the Agg backend that are also used by other modules */ +#include + +#include #include #include "agg_color_rgba.h" #include "agg_math_stroke.h" +#include "agg_trans_affine.h" #include "path_converters.h" #include "py_adaptors.h" +namespace py = pybind11; + struct ClipPath { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; }; @@ -42,7 +48,7 @@ class Dashes } void add_dash_pair(double length, double skip) { - dashes.push_back(std::make_pair(length, skip)); + dashes.emplace_back(length, skip); } size_t size() const { @@ -52,28 +58,22 @@ class Dashes template void dash_to_stroke(T &stroke, double dpi, bool isaa) { - for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) { - double val0 = i->first; - double val1 = i->second; - val0 = val0 * dpi / 72.0; - val1 = val1 * dpi / 72.0; + double scaleddpi = dpi / 72.0; + for (auto [val0, val1] : dashes) { + val0 = val0 * scaleddpi; + val1 = val1 * scaleddpi; if (!isaa) { val0 = (int)val0 + 0.5; val1 = (int)val1 + 0.5; } stroke.add_dash(val0, val1); } - stroke.dash_start(get_dash_offset() * dpi / 72.0); + stroke.dash_start(get_dash_offset() * scaleddpi); } }; typedef std::vector DashesVector; -enum e_offset_position { - OFFSET_POSITION_FIGURE, - OFFSET_POSITION_DATA -}; - class GCAgg { public: @@ -107,7 +107,7 @@ class GCAgg e_snap_mode snap_mode; - py::PathIterator hatchpath; + mpl::PathIterator hatchpath; agg::rgba hatch_color; double hatch_linewidth; @@ -124,4 +124,132 @@ class GCAgg GCAgg &operator=(const GCAgg &); }; +namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::line_cap_e, const_name("line_cap_e")); + + bool load(handle src, bool) { + const std::unordered_map enum_values = { + {"butt", agg::butt_cap}, + {"round", agg::round_cap}, + {"projecting", agg::square_cap}, + }; + value = enum_values.at(src.cast()); + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::line_join_e, const_name("line_join_e")); + + bool load(handle src, bool) { + const std::unordered_map enum_values = { + {"miter", agg::miter_join_revert}, + {"round", agg::round_join}, + {"bevel", agg::bevel_join}, + }; + value = enum_values.at(src.cast()); + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(ClipPath, const_name("ClipPath")); + + bool load(handle src, bool) { + if (src.is_none()) { + return true; + } + + auto [path, trans] = + src.cast, agg::trans_affine>>(); + if (path) { + value.path = *path; + } + value.trans = trans; + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(Dashes, const_name("Dashes")); + + bool load(handle src, bool) { + auto [dash_offset, dashes_seq_or_none] = + src.cast>>(); + + if (!dashes_seq_or_none) { + return true; + } + + auto dashes_seq = *dashes_seq_or_none; + + auto nentries = dashes_seq.size(); + // If the dashpattern has odd length, iterate through it twice (in + // accordance with the pdf/ps/svg specs). + auto dash_pattern_length = (nentries % 2) ? 2 * nentries : nentries; + + for (py::size_t i = 0; i < dash_pattern_length; i += 2) { + auto length = dashes_seq[i % nentries].cast(); + auto skip = dashes_seq[(i + 1) % nentries].cast(); + + value.add_dash_pair(length, skip); + } + + value.set_dash_offset(dash_offset); + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(SketchParams, const_name("SketchParams")); + + bool load(handle src, bool) { + if (src.is_none()) { + value.scale = 0.0; + value.length = 0.0; + value.randomness = 0.0; + return true; + } + + auto params = src.cast>(); + std::tie(value.scale, value.length, value.randomness) = params; + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(GCAgg, const_name("GCAgg")); + + bool load(handle src, bool) { + value.linewidth = src.attr("_linewidth").cast(); + value.alpha = src.attr("_alpha").cast(); + value.forced_alpha = src.attr("_forced_alpha").cast(); + value.color = src.attr("_rgb").cast(); + value.isaa = src.attr("_antialiased").cast(); + value.cap = src.attr("_capstyle").cast(); + value.join = src.attr("_joinstyle").cast(); + value.dashes = src.attr("get_dashes")().cast(); + value.cliprect = src.attr("_cliprect").cast(); + value.clippath = src.attr("get_clip_path")().cast(); + value.snap_mode = src.attr("get_snap")().cast(); + value.hatchpath = src.attr("get_hatch_path")().cast(); + value.hatch_color = src.attr("get_hatch_color")().cast(); + value.hatch_linewidth = src.attr("get_hatch_linewidth")().cast(); + value.sketch = src.attr("get_sketch_params")().cast(); + + return true; + } + }; +}} // namespace PYBIND11_NAMESPACE::detail + #endif diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index e08642e248ef..3dd50b31f64a 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -1,688 +1,287 @@ +#include +#include +#include #include "mplutils.h" -#include "numpy_cpp.h" #include "py_converters.h" #include "_backend_agg.h" -typedef struct -{ - PyObject_HEAD - RendererAgg *x; - Py_ssize_t shape[3]; - Py_ssize_t strides[3]; - Py_ssize_t suboffsets[3]; -} PyRendererAgg; - -typedef struct -{ - PyObject_HEAD - BufferRegion *x; - Py_ssize_t shape[3]; - Py_ssize_t strides[3]; - Py_ssize_t suboffsets[3]; -} PyBufferRegion; - +namespace py = pybind11; +using namespace pybind11::literals; /********************************************************************** * BufferRegion * */ -static PyObject *PyBufferRegion_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyBufferRegion *self; - self = (PyBufferRegion *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} - -static void PyBufferRegion_dealloc(PyBufferRegion *self) -{ - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject *PyBufferRegion_to_string(PyBufferRegion *self, PyObject *args, PyObject *kwds) -{ - return PyBytes_FromStringAndSize((const char *)self->x->get_data(), - self->x->get_height() * self->x->get_stride()); -} - /* TODO: This doesn't seem to be used internally. Remove? */ -static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args, PyObject *kwds) +static void +PyBufferRegion_set_x(BufferRegion *self, int x) { - int x; - if (!PyArg_ParseTuple(args, "i:set_x", &x)) { - return NULL; - } - self->x->get_rect().x1 = x; - - Py_RETURN_NONE; + self->get_rect().x1 = x; } -static PyObject *PyBufferRegion_set_y(PyBufferRegion *self, PyObject *args, PyObject *kwds) +static void +PyBufferRegion_set_y(BufferRegion *self, int y) { - int y; - if (!PyArg_ParseTuple(args, "i:set_y", &y)) { - return NULL; - } - self->x->get_rect().y1 = y; - - Py_RETURN_NONE; + self->get_rect().y1 = y; } -static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args, PyObject *kwds) +static py::object +PyBufferRegion_get_extents(BufferRegion *self) { - agg::rect_i rect = self->x->get_rect(); - - return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); -} - -static PyObject *PyBufferRegion_to_string_argb(PyBufferRegion *self, PyObject *args, PyObject *kwds) -{ - PyObject *bufobj; - uint8_t *buf; - - bufobj = PyBytes_FromStringAndSize(NULL, self->x->get_height() * self->x->get_stride()); - buf = (uint8_t *)PyBytes_AS_STRING(bufobj); - - CALL_CPP_CLEANUP("to_string_argb", (self->x->to_string_argb(buf)), Py_DECREF(bufobj)); - - return bufobj; -} - -int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) -{ - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = self->x->get_data(); - buf->len = self->x->get_width() * self->x->get_height() * 4; - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 3; - self->shape[0] = self->x->get_height(); - self->shape[1] = self->x->get_width(); - self->shape[2] = 4; - buf->shape = self->shape; - self->strides[0] = self->x->get_width() * 4; - self->strides[1] = 4; - self->strides[2] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} - -static PyTypeObject PyBufferRegionType; - -static PyTypeObject *PyBufferRegion_init_type(PyObject *m, PyTypeObject *type) -{ - static PyMethodDef methods[] = { - { "to_string", (PyCFunction)PyBufferRegion_to_string, METH_NOARGS, NULL }, - { "to_string_argb", (PyCFunction)PyBufferRegion_to_string_argb, METH_NOARGS, NULL }, - { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, - { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, - { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, - { NULL } - }; - - static PyBufferProcs buffer_procs; - memset(&buffer_procs, 0, sizeof(PyBufferProcs)); - buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer; - - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.backends._backend_agg.BufferRegion"; - type->tp_basicsize = sizeof(PyBufferRegion); - type->tp_dealloc = (destructor)PyBufferRegion_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - type->tp_methods = methods; - type->tp_new = PyBufferRegion_new; - type->tp_as_buffer = &buffer_procs; - - if (PyType_Ready(type) < 0) { - return NULL; - } + agg::rect_i rect = self->get_rect(); - /* Don't need to add to module, since you can't create buffer - regions directly from Python */ - - return type; + return py::make_tuple(rect.x1, rect.y1, rect.x2, rect.y2); } /********************************************************************** * RendererAgg * */ -static PyObject *PyRendererAgg_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyRendererAgg *self; - self = (PyRendererAgg *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} - -static int PyRendererAgg_init(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_path(RendererAgg *self, + GCAgg &gc, + mpl::PathIterator path, + agg::trans_affine trans, + py::object rgbFace) { - unsigned int width; - unsigned int height; - double dpi; - int debug = 0; - - if (!PyArg_ParseTuple(args, "IId|i:RendererAgg", &width, &height, &dpi, &debug)) { - return -1; - } - - if (dpi <= 0.0) { - PyErr_SetString(PyExc_ValueError, "dpi must be positive"); - return -1; - } - - if (width >= 1 << 16 || height >= 1 << 16) { - PyErr_Format( - PyExc_ValueError, - "Image size of %dx%d pixels is too large. " - "It must be less than 2^16 in each direction.", - width, height); - return -1; + agg::rgba face = rgbFace.cast(); + if (!rgbFace.is_none()) { + if (gc.forced_alpha || rgbFace.cast().size() == 3) { + face.a = gc.alpha; + } } - CALL_CPP_INIT("RendererAgg", self->x = new RendererAgg(width, height, dpi)) - - return 0; -} - -static void PyRendererAgg_dealloc(PyRendererAgg *self) -{ - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); + self->draw_path(gc, path, trans, face); } -static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_text_image(RendererAgg *self, + py::array_t image_obj, + std::variant vx, + std::variant vy, + double angle, + GCAgg &gc) { - GCAgg gc; - py::PathIterator path; - agg::trans_affine trans; - PyObject *faceobj = NULL; - agg::rgba face; - - if (!PyArg_ParseTuple(args, - "O&O&O&|O:draw_path", - &convert_gcagg, - &gc, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &faceobj)) { - return NULL; - } - - if (!convert_face(faceobj, gc, &face)) { - return NULL; + int x, y; + + if (auto value = std::get_if(&vx)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="x", "obj_type"_a="parameter as float", + "alternative"_a="int(x)"); + x = static_cast(*value); + } else if (auto value = std::get_if(&vx)) { + x = *value; + } else { + throw std::runtime_error("Should not happen"); } - CALL_CPP("draw_path", (self->x->draw_path(gc, path, trans, face))); - - Py_RETURN_NONE; -} - -static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - numpy::array_view image; - double x; - double y; - double angle; - GCAgg gc; - - if (!PyArg_ParseTuple(args, - "O&dddO&:draw_text_image", - &image.converter_contiguous, - &image, - &x, - &y, - &angle, - &convert_gcagg, - &gc)) { - return NULL; + if (auto value = std::get_if(&vy)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="y", "obj_type"_a="parameter as float", + "alternative"_a="int(y)"); + y = static_cast(*value); + } else if (auto value = std::get_if(&vy)) { + y = *value; + } else { + throw std::runtime_error("Should not happen"); } - CALL_CPP("draw_text_image", (self->x->draw_text_image(gc, image, x, y, angle))); + // TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const. + auto image = image_obj.mutable_unchecked<2>(); - Py_RETURN_NONE; + self->draw_text_image(gc, image, x, y, angle); } -PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_markers(RendererAgg *self, + GCAgg &gc, + mpl::PathIterator marker_path, + agg::trans_affine marker_path_trans, + mpl::PathIterator path, + agg::trans_affine trans, + py::object rgbFace) { - GCAgg gc; - py::PathIterator marker_path; - agg::trans_affine marker_path_trans; - py::PathIterator path; - agg::trans_affine trans; - PyObject *faceobj = NULL; - agg::rgba face; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&|O:draw_markers", - &convert_gcagg, - &gc, - &convert_path, - &marker_path, - &convert_trans_affine, - &marker_path_trans, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &faceobj)) { - return NULL; - } - - if (!convert_face(faceobj, gc, &face)) { - return NULL; + agg::rgba face = rgbFace.cast(); + if (!rgbFace.is_none()) { + if (gc.forced_alpha || rgbFace.cast().size() == 3) { + face.a = gc.alpha; + } } - CALL_CPP("draw_markers", - (self->x->draw_markers(gc, marker_path, marker_path_trans, path, trans, face))); - - Py_RETURN_NONE; + self->draw_markers(gc, marker_path, marker_path_trans, path, trans, face); } -static PyObject *PyRendererAgg_draw_image(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_image(RendererAgg *self, + GCAgg &gc, + double x, + double y, + py::array_t image_obj) { - GCAgg gc; - double x; - double y; - numpy::array_view image; - - if (!PyArg_ParseTuple(args, - "O&ddO&:draw_image", - &convert_gcagg, - &gc, - &x, - &y, - &image.converter_contiguous, - &image)) { - return NULL; - } + // TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const. + auto image = image_obj.mutable_unchecked<3>(); x = mpl_round(x); y = mpl_round(y); gc.alpha = 1.0; - CALL_CPP("draw_image", (self->x->draw_image(gc, x, y, image))); - - Py_RETURN_NONE; -} - -static PyObject * -PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - GCAgg gc; - agg::trans_affine master_transform; - PyObject *pathobj; - numpy::array_view transforms; - numpy::array_view offsets; - agg::trans_affine offset_trans; - numpy::array_view facecolors; - numpy::array_view edgecolors; - numpy::array_view linewidths; - DashesVector dashes; - numpy::array_view antialiaseds; - PyObject *ignored; - e_offset_position offset_position; - - if (!PyArg_ParseTuple(args, - "O&O&OO&O&O&O&O&O&O&O&OO&:draw_path_collection", - &convert_gcagg, - &gc, - &convert_trans_affine, - &master_transform, - &pathobj, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_colors, - &facecolors, - &convert_colors, - &edgecolors, - &linewidths.converter, - &linewidths, - &convert_dashes_vector, - &dashes, - &antialiaseds.converter, - &antialiaseds, - &ignored, - &convert_offset_position, - &offset_position)) { - return NULL; - } - - try - { - py::PathGenerator path(pathobj); - - CALL_CPP("draw_path_collection", - (self->x->draw_path_collection(gc, - master_transform, - path, - transforms, - offsets, - offset_trans, - facecolors, - edgecolors, - linewidths, - dashes, - antialiaseds, - offset_position))); - } - catch (const py::exception &) - { - return NULL; - } - - Py_RETURN_NONE; -} - -static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - GCAgg gc; - agg::trans_affine master_transform; - unsigned int mesh_width; - unsigned int mesh_height; - numpy::array_view coordinates; - numpy::array_view offsets; - agg::trans_affine offset_trans; - numpy::array_view facecolors; - bool antialiased; - numpy::array_view edgecolors; - - if (!PyArg_ParseTuple(args, - "O&O&IIO&O&O&O&O&O&:draw_quad_mesh", - &convert_gcagg, - &gc, - &convert_trans_affine, - &master_transform, - &mesh_width, - &mesh_height, - &coordinates.converter, - &coordinates, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_colors, - &facecolors, - &convert_bool, - &antialiased, - &convert_colors, - &edgecolors)) { - return NULL; - } - - CALL_CPP("draw_quad_mesh", - (self->x->draw_quad_mesh(gc, - master_transform, - mesh_width, - mesh_height, - coordinates, - offsets, - offset_trans, - facecolors, - antialiased, - edgecolors))); - - Py_RETURN_NONE; -} - -static PyObject * -PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - GCAgg gc; - numpy::array_view points; - numpy::array_view colors; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&|O:draw_gouraud_triangle", - &convert_gcagg, - &gc, - &points.converter, - &points, - &colors.converter, - &colors, - &convert_trans_affine, - &trans)) { - return NULL; - } - - if (points.dim(0) != 3 || points.dim(1) != 2) { - PyErr_Format(PyExc_ValueError, - "points must be a 3x2 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT, - points.dim(0), points.dim(1)); - return NULL; - } - - if (colors.dim(0) != 3 || colors.dim(1) != 4) { - PyErr_Format(PyExc_ValueError, - "colors must be a 3x4 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT, - colors.dim(0), colors.dim(1)); - return NULL; - } - - - CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans))); - - Py_RETURN_NONE; -} - -static PyObject * -PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - GCAgg gc; - numpy::array_view points; - numpy::array_view colors; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&|O:draw_gouraud_triangles", - &convert_gcagg, - &gc, - &points.converter, - &points, - &colors.converter, - &colors, - &convert_trans_affine, - &trans)) { - return NULL; - } - - if (points.size() != 0 && (points.dim(1) != 3 || points.dim(2) != 2)) { - PyErr_Format(PyExc_ValueError, - "points must be a Nx3x2 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT, - points.dim(0), points.dim(1), points.dim(2)); - return NULL; - } - - if (colors.size() != 0 && (colors.dim(1) != 3 || colors.dim(2) != 4)) { - PyErr_Format(PyExc_ValueError, - "colors must be a Nx3x4 array, got %" NPY_INTP_FMT "x%" NPY_INTP_FMT "x%" NPY_INTP_FMT, - colors.dim(0), colors.dim(1), colors.dim(2)); - return NULL; - } - - if (points.size() != colors.size()) { - PyErr_Format(PyExc_ValueError, - "points and colors arrays must be the same length, got %" NPY_INTP_FMT " and %" NPY_INTP_FMT, - points.dim(0), colors.dim(0)); - return NULL; - } - - CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans)); - - Py_RETURN_NONE; + self->draw_image(gc, x, y, image); } -int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags) +static void +PyRendererAgg_draw_path_collection(RendererAgg *self, + GCAgg &gc, + agg::trans_affine master_transform, + mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, + agg::trans_affine offset_trans, + py::array_t facecolors_obj, + py::array_t edgecolors_obj, + py::array_t linewidths_obj, + DashesVector dashes, + py::array_t antialiaseds_obj, + py::object Py_UNUSED(ignored_obj), + // offset position is no longer used + py::object Py_UNUSED(offset_position_obj), + py::array_t hatchcolors_obj) { - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = self->x->pixBuffer; - buf->len = self->x->get_width() * self->x->get_height() * 4; - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 3; - self->shape[0] = self->x->get_height(); - self->shape[1] = self->x->get_width(); - self->shape[2] = 4; - buf->shape = self->shape; - self->strides[0] = self->x->get_width() * 4; - self->strides[1] = 4; - self->strides[2] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); + auto facecolors = convert_colors(facecolors_obj); + auto edgecolors = convert_colors(edgecolors_obj); + auto hatchcolors = convert_colors(hatchcolors_obj); + auto linewidths = linewidths_obj.unchecked<1>(); + auto antialiaseds = antialiaseds_obj.unchecked<1>(); + + self->draw_path_collection(gc, + master_transform, + paths, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + dashes, + antialiaseds, + hatchcolors); } -static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_quad_mesh(RendererAgg *self, + GCAgg &gc, + agg::trans_affine master_transform, + unsigned int mesh_width, + unsigned int mesh_height, + py::array_t coordinates_obj, + py::array_t offsets_obj, + agg::trans_affine offset_trans, + py::array_t facecolors_obj, + bool antialiased, + py::array_t edgecolors_obj) { - CALL_CPP("clear", self->x->clear()); - - Py_RETURN_NONE; + auto coordinates = coordinates_obj.mutable_unchecked<3>(); + auto offsets = convert_points(offsets_obj); + auto facecolors = convert_colors(facecolors_obj); + auto edgecolors = convert_colors(edgecolors_obj); + + self->draw_quad_mesh(gc, + master_transform, + mesh_width, + mesh_height, + coordinates, + offsets, + offset_trans, + facecolors, + antialiased, + edgecolors); } -static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_gouraud_triangles(RendererAgg *self, + GCAgg &gc, + py::array_t points_obj, + py::array_t colors_obj, + agg::trans_affine trans) { - agg::rect_d bbox; - BufferRegion *reg; - PyObject *regobj; - - if (!PyArg_ParseTuple(args, "O&:copy_from_bbox", &convert_rect, &bbox)) { - return 0; - } - - CALL_CPP("copy_from_bbox", (reg = self->x->copy_from_bbox(bbox))); + auto points = points_obj.unchecked<3>(); + auto colors = colors_obj.unchecked<3>(); - regobj = PyBufferRegion_new(&PyBufferRegionType, NULL, NULL); - ((PyBufferRegion *)regobj)->x = reg; - - return regobj; + self->draw_gouraud_triangles(gc, points, colors, trans); } -static PyObject *PyRendererAgg_restore_region(PyRendererAgg *self, PyObject *args, PyObject *kwds) +PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) { - PyBufferRegion *regobj; - int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0; - - if (!PyArg_ParseTuple(args, - "O!|iiiiii:restore_region", - &PyBufferRegionType, - ®obj, - &xx1, - &yy1, - &xx2, - &yy2, - &x, - &y)) { - return 0; - } - - if (PySequence_Size(args) == 1) { - CALL_CPP("restore_region", self->x->restore_region(*(regobj->x))); - } else { - CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y)); - } - - Py_RETURN_NONE; + py::class_(m, "RendererAgg", py::buffer_protocol()) + .def(py::init(), + "width"_a, "height"_a, "dpi"_a) + + .def("draw_path", &PyRendererAgg_draw_path, + "gc"_a, "path"_a, "trans"_a, "face"_a = nullptr) + .def("draw_markers", &PyRendererAgg_draw_markers, + "gc"_a, "marker_path"_a, "marker_path_trans"_a, "path"_a, "trans"_a, + "face"_a = nullptr) + .def("draw_text_image", &PyRendererAgg_draw_text_image, + "image"_a, "x"_a, "y"_a, "angle"_a, "gc"_a) + .def("draw_image", &PyRendererAgg_draw_image, + "gc"_a, "x"_a, "y"_a, "image"_a) + .def("draw_path_collection", &PyRendererAgg_draw_path_collection, + "gc"_a, "master_transform"_a, "paths"_a, "transforms"_a, "offsets"_a, + "offset_trans"_a, "facecolors"_a, "edgecolors"_a, "linewidths"_a, + "dashes"_a, "antialiaseds"_a, "ignored"_a, "offset_position"_a, + py::kw_only(), "hatchcolors"_a = py::array_t().reshape({0, 4})) + .def("draw_quad_mesh", &PyRendererAgg_draw_quad_mesh, + "gc"_a, "master_transform"_a, "mesh_width"_a, "mesh_height"_a, + "coordinates"_a, "offsets"_a, "offset_trans"_a, "facecolors"_a, + "antialiased"_a, "edgecolors"_a) + .def("draw_gouraud_triangles", &PyRendererAgg_draw_gouraud_triangles, + "gc"_a, "points"_a, "colors"_a, "trans"_a = nullptr) + + .def("clear", &RendererAgg::clear) + + .def("copy_from_bbox", &RendererAgg::copy_from_bbox, + "bbox"_a) + .def("restore_region", + py::overload_cast(&RendererAgg::restore_region), + "region"_a) + .def("restore_region", + py::overload_cast(&RendererAgg::restore_region), + "region"_a, "xx1"_a, "yy1"_a, "xx2"_a, "yy2"_a, "x"_a, "y"_a) + + .def_buffer([](RendererAgg *renderer) -> py::buffer_info { + std::vector shape { + renderer->get_height(), + renderer->get_width(), + 4 + }; + std::vector strides { + renderer->get_width() * 4, + 4, + 1 + }; + return py::buffer_info(renderer->pixBuffer, shape, strides); + }); + + py::class_(m, "BufferRegion", py::buffer_protocol()) + // BufferRegion is not constructible from Python, thus no py::init is added. + .def("set_x", &PyBufferRegion_set_x) + .def("set_y", &PyBufferRegion_set_y) + .def("get_extents", &PyBufferRegion_get_extents) + .def_buffer([](BufferRegion *buffer) -> py::buffer_info { + std::vector shape { + buffer->get_height(), + buffer->get_width(), + 4 + }; + std::vector strides { + buffer->get_width() * 4, + 4, + 1 + }; + return py::buffer_info(buffer->get_data(), shape, strides); + }); } - -PyTypeObject PyRendererAggType; - -static PyTypeObject *PyRendererAgg_init_type(PyObject *m, PyTypeObject *type) -{ - static PyMethodDef methods[] = { - {"draw_path", (PyCFunction)PyRendererAgg_draw_path, METH_VARARGS, NULL}, - {"draw_markers", (PyCFunction)PyRendererAgg_draw_markers, METH_VARARGS, NULL}, - {"draw_text_image", (PyCFunction)PyRendererAgg_draw_text_image, METH_VARARGS, NULL}, - {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL}, - {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL}, - {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL}, - {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL}, - {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, - - {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL}, - - {"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL}, - {"restore_region", (PyCFunction)PyRendererAgg_restore_region, METH_VARARGS, NULL}, - {NULL} - }; - - static PyBufferProcs buffer_procs; - memset(&buffer_procs, 0, sizeof(PyBufferProcs)); - buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer; - - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.backends._backend_agg.RendererAgg"; - type->tp_basicsize = sizeof(PyRendererAgg); - type->tp_dealloc = (destructor)PyRendererAgg_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - type->tp_methods = methods; - type->tp_init = (initproc)PyRendererAgg_init; - type->tp_new = PyRendererAgg_new; - type->tp_as_buffer = &buffer_procs; - - if (PyType_Ready(type) < 0) { - return NULL; - } - - if (PyModule_AddObject(m, "RendererAgg", (PyObject *)type)) { - return NULL; - } - - return type; -} - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_backend_agg", - NULL, - 0, - NULL, - NULL, - NULL, - NULL, - NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__backend_agg(void) -{ - PyObject *m; - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - import_array(); - - if (!PyRendererAgg_init_type(m, &PyRendererAggType)) { - return NULL; - } - - if (!PyBufferRegion_init_type(m, &PyBufferRegionType)) { - return NULL; - } - - return m; -} - -#pragma GCC visibility pop diff --git a/src/_c_internal_utils.c b/src/_c_internal_utils.c deleted file mode 100644 index caca31585019..000000000000 --- a/src/_c_internal_utils.c +++ /dev/null @@ -1,97 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -#ifdef _WIN32 -#include -#include -#include -#endif - -static PyObject* mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module) -{ -#ifdef _WIN32 - wchar_t* appid = NULL; - HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); - if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); - } - PyObject* py_appid = PyUnicode_FromWideChar(appid, -1); - CoTaskMemFree(appid); - return py_appid; -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg) -{ -#ifdef _WIN32 - wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL); - if (!appid) { - return NULL; - } - HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); - PyMem_Free(appid); - if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); - } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* mpl_GetForegroundWindow(PyObject* module) -{ -#ifdef _WIN32 - return PyLong_FromVoidPtr(GetForegroundWindow()); -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* mpl_SetForegroundWindow(PyObject* module, PyObject *arg) -{ -#ifdef _WIN32 - HWND handle = PyLong_AsVoidPtr(arg); - if (PyErr_Occurred()) { - return NULL; - } - if (!SetForegroundWindow(handle)) { - return PyErr_Format(PyExc_RuntimeError, "Error setting window"); - } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; -#endif -} - -static PyMethodDef functions[] = { - {"Win32_GetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS, - "Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n" - "Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID. On \n" - "non-Windows platforms, always returns None."}, - {"Win32_SetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_SetCurrentProcessExplicitAppUserModelID, METH_O, - "Win32_SetCurrentProcessExplicitAppUserModelID(appid, /)\n--\n\n" - "Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID. On \n" - "non-Windows platforms, a no-op."}, - {"Win32_GetForegroundWindow", - (PyCFunction)mpl_GetForegroundWindow, METH_NOARGS, - "Win32_GetForegroundWindow()\n--\n\n" - "Wrapper for Windows' GetForegroundWindow. On non-Windows platforms, \n" - "always returns None."}, - {"Win32_SetForegroundWindow", - (PyCFunction)mpl_SetForegroundWindow, METH_O, - "Win32_SetForegroundWindow(hwnd)\n--\n\n" - "Wrapper for Windows' SetForegroundWindow. On non-Windows platforms, \n" - "a no-op."}, - {NULL, NULL}}; // sentinel. -static PyModuleDef util_module = { - PyModuleDef_HEAD_INIT, "_c_internal_utils", "", 0, functions, NULL, NULL, NULL, NULL}; - -#pragma GCC visibility push(default) -PyMODINIT_FUNC PyInit__c_internal_utils(void) -{ - return PyModule_Create(&util_module); -} diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp new file mode 100644 index 000000000000..0dddefaf32e3 --- /dev/null +++ b/src/_c_internal_utils.cpp @@ -0,0 +1,255 @@ +#include +/* Python.h must be included before any system headers, + to ensure visibility macros are properly set. */ +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +// Windows 10, for latest HiDPI API support. +#define WINVER 0x0A00 +#if defined(_WIN32_WINNT) +#if _WIN32_WINNT < WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT WINVER +#endif +#else +#define _WIN32_WINNT WINVER +#endif +#endif +#include +#ifdef __linux__ +#include +#endif +#ifdef _WIN32 +#include +#include +#include +#define UNUSED_ON_NON_WINDOWS(x) x +#else +#define UNUSED_ON_NON_WINDOWS Py_UNUSED +#endif + +namespace py = pybind11; +using namespace pybind11::literals; + +static bool +mpl_xdisplay_is_valid(void) +{ +#ifdef __linux__ + void* libX11; + // The getenv check is redundant but helps performance as it is much faster + // than dlopen(). + if (getenv("DISPLAY") + && (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) { + typedef struct Display* (*XOpenDisplay_t)(char const*); + typedef int (*XCloseDisplay_t)(struct Display*); + struct Display* display = nullptr; + XOpenDisplay_t XOpenDisplay = (XOpenDisplay_t)dlsym(libX11, "XOpenDisplay"); + XCloseDisplay_t XCloseDisplay = (XCloseDisplay_t)dlsym(libX11, "XCloseDisplay"); + if (XOpenDisplay && XCloseDisplay + && (display = XOpenDisplay(nullptr))) { + XCloseDisplay(display); + } + if (dlclose(libX11)) { + throw std::runtime_error(dlerror()); + } + if (display) { + return true; + } + } + return false; +#else + return true; +#endif +} + +static bool +mpl_display_is_valid(void) +{ +#ifdef __linux__ + if (mpl_xdisplay_is_valid()) { + return true; + } + void* libwayland_client; + if (getenv("WAYLAND_DISPLAY") + && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { + typedef struct wl_display* (*wl_display_connect_t)(char const*); + typedef void (*wl_display_disconnect_t)(struct wl_display*); + struct wl_display* display = nullptr; + wl_display_connect_t wl_display_connect = + (wl_display_connect_t)dlsym(libwayland_client, "wl_display_connect"); + wl_display_disconnect_t wl_display_disconnect = + (wl_display_disconnect_t)dlsym(libwayland_client, "wl_display_disconnect"); + if (wl_display_connect && wl_display_disconnect + && (display = wl_display_connect(nullptr))) { + wl_display_disconnect(display); + } + if (dlclose(libwayland_client)) { + throw std::runtime_error(dlerror()); + } + if (display) { + return true; + } + } + return false; +#else + return true; +#endif +} + +static py::object +mpl_GetCurrentProcessExplicitAppUserModelID(void) +{ +#ifdef _WIN32 + wchar_t* appid = NULL; + HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); + if (FAILED(hr)) { + PyErr_SetFromWindowsErr(hr); + throw py::error_already_set(); + } + auto py_appid = py::cast(appid); + CoTaskMemFree(appid); + return py_appid; +#else + return py::none(); +#endif +} + +static void +mpl_SetCurrentProcessExplicitAppUserModelID(const wchar_t* UNUSED_ON_NON_WINDOWS(appid)) +{ +#ifdef _WIN32 + HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); + if (FAILED(hr)) { + PyErr_SetFromWindowsErr(hr); + throw py::error_already_set(); + } +#endif +} + +static py::object +mpl_GetForegroundWindow(void) +{ +#ifdef _WIN32 + if (HWND hwnd = GetForegroundWindow()) { + return py::capsule(hwnd, "HWND"); + } else { + return py::none(); + } +#else + return py::none(); +#endif +} + +static void +mpl_SetForegroundWindow(py::capsule UNUSED_ON_NON_WINDOWS(handle_p)) +{ +#ifdef _WIN32 + if (strcmp(handle_p.name(), "HWND") != 0) { + throw std::runtime_error("Handle must be a value returned from Win32_GetForegroundWindow"); + } + HWND handle = static_cast(handle_p.get_pointer()); + if (!SetForegroundWindow(handle)) { + throw std::runtime_error("Error setting window"); + } +#endif +} + +static void +mpl_SetProcessDpiAwareness_max(void) +{ +#ifdef _WIN32 +#ifdef _DPI_AWARENESS_CONTEXTS_ + // These functions and options were added in later Windows 10 updates, so + // must be loaded dynamically. + typedef BOOL (WINAPI *IsValidDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + typedef BOOL (WINAPI *SetProcessDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + + HMODULE user32 = LoadLibrary("user32.dll"); + IsValidDpiAwarenessContext_t IsValidDpiAwarenessContextPtr = + (IsValidDpiAwarenessContext_t)GetProcAddress( + user32, "IsValidDpiAwarenessContext"); + SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContextPtr = + (SetProcessDpiAwarenessContext_t)GetProcAddress( + user32, "SetProcessDpiAwarenessContext"); + DPI_AWARENESS_CONTEXT ctxs[3] = { + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, // Win10 Creators Update + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, // Win10 + DPI_AWARENESS_CONTEXT_SYSTEM_AWARE}; // Win10 + if (IsValidDpiAwarenessContextPtr != NULL + && SetProcessDpiAwarenessContextPtr != NULL) { + for (size_t i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { + if (IsValidDpiAwarenessContextPtr(ctxs[i])) { + SetProcessDpiAwarenessContextPtr(ctxs[i]); + break; + } + } + } else { + // Added in Windows Vista. + SetProcessDPIAware(); + } + FreeLibrary(user32); +#else + // Added in Windows Vista. + SetProcessDPIAware(); +#endif +#endif +} + +PYBIND11_MODULE(_c_internal_utils, m, py::mod_gil_not_used()) +{ + m.def( + "display_is_valid", &mpl_display_is_valid, + R"""( -- + Check whether the current X11 or Wayland display is valid. + + On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) + succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) + succeeds. + + On other platforms, always returns True.)"""); + m.def( + "xdisplay_is_valid", &mpl_xdisplay_is_valid, + R"""( -- + Check whether the current X11 display is valid. + + On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) + succeeds. Use this function if you need to specifically check for X11 + only (e.g., for Tkinter). + + On other platforms, always returns True.)"""); + m.def( + "Win32_GetCurrentProcessExplicitAppUserModelID", + &mpl_GetCurrentProcessExplicitAppUserModelID, + R"""( -- + Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID. + + On non-Windows platforms, always returns None.)"""); + m.def( + "Win32_SetCurrentProcessExplicitAppUserModelID", + &mpl_SetCurrentProcessExplicitAppUserModelID, + "appid"_a, py::pos_only(), + R"""( -- + Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID. + + On non-Windows platforms, does nothing.)"""); + m.def( + "Win32_GetForegroundWindow", &mpl_GetForegroundWindow, + R"""( -- + Wrapper for Windows' GetForegroundWindow. + + On non-Windows platforms, always returns None.)"""); + m.def( + "Win32_SetForegroundWindow", &mpl_SetForegroundWindow, + "hwnd"_a, + R"""( -- + Wrapper for Windows' SetForegroundWindow. + + On non-Windows platforms, does nothing.)"""); + m.def( + "Win32_SetProcessDpiAwareness_max", &mpl_SetProcessDpiAwareness_max, + R"""( -- + Set Windows' process DPI awareness to best option available. + + On non-Windows platforms, does nothing.)"""); +} diff --git a/src/_contour.cpp b/src/_contour.cpp deleted file mode 100644 index ac655d73de27..000000000000 --- a/src/_contour.cpp +++ /dev/null @@ -1,1784 +0,0 @@ -// This file contains liberal use of asserts to assist code development and -// debugging. Standard matplotlib builds disable asserts so they cause no -// performance reduction. To enable the asserts, you need to undefine the -// NDEBUG macro, which is achieved by adding the following -// undef_macros=['NDEBUG'] -// to the appropriate make_extension call in setupext.py, and then rebuilding. -#define NO_IMPORT_ARRAY - -#include "mplutils.h" -#include "_contour.h" -#include - - -// 'kind' codes. -#define MOVETO 1 -#define LINETO 2 -#define CLOSEPOLY 79 - -// Point indices from current quad index. -#define POINT_SW (quad) -#define POINT_SE (quad+1) -#define POINT_NW (quad+_nx) -#define POINT_NE (quad+_nx+1) - -// CacheItem masks, only accessed directly to set. To read, use accessors -// detailed below. 1 and 2 refer to level indices (lower and upper). -#define MASK_Z_LEVEL 0x0003 // Combines the following two. -#define MASK_Z_LEVEL_1 0x0001 // z > lower_level. -#define MASK_Z_LEVEL_2 0x0002 // z > upper_level. -#define MASK_VISITED_1 0x0004 // Algorithm has visited this quad. -#define MASK_VISITED_2 0x0008 -#define MASK_SADDLE_1 0x0010 // quad is a saddle quad. -#define MASK_SADDLE_2 0x0020 -#define MASK_SADDLE_LEFT_1 0x0040 // Contours turn left at saddle quad. -#define MASK_SADDLE_LEFT_2 0x0080 -#define MASK_SADDLE_START_SW_1 0x0100 // Next visit starts on S or W edge. -#define MASK_SADDLE_START_SW_2 0x0200 -#define MASK_BOUNDARY_S 0x0400 // S edge of quad is a boundary. -#define MASK_BOUNDARY_W 0x0800 // W edge of quad is a boundary. -// EXISTS_QUAD bit is always used, but the 4 EXISTS_CORNER are only used if -// _corner_mask is true. Only one of EXISTS_QUAD or EXISTS_??_CORNER is ever -// set per quad, hence not using unique bits for each; care is needed when -// testing for these flags as they overlap. -#define MASK_EXISTS_QUAD 0x1000 // All of quad exists (is not masked). -#define MASK_EXISTS_SW_CORNER 0x2000 // SW corner exists, NE corner is masked. -#define MASK_EXISTS_SE_CORNER 0x3000 -#define MASK_EXISTS_NW_CORNER 0x4000 -#define MASK_EXISTS_NE_CORNER 0x5000 -#define MASK_EXISTS 0x7000 // Combines all 5 EXISTS masks. - -// The following are only needed for filled contours. -#define MASK_VISITED_S 0x10000 // Algorithm has visited S boundary. -#define MASK_VISITED_W 0x20000 // Algorithm has visited W boundary. -#define MASK_VISITED_CORNER 0x40000 // Algorithm has visited corner edge. - - -// Accessors for various CacheItem masks. li is shorthand for level_index. -#define Z_LEVEL(quad) (_cache[quad] & MASK_Z_LEVEL) -#define Z_NE Z_LEVEL(POINT_NE) -#define Z_NW Z_LEVEL(POINT_NW) -#define Z_SE Z_LEVEL(POINT_SE) -#define Z_SW Z_LEVEL(POINT_SW) -#define VISITED(quad,li) ((_cache[quad] & (li==1 ? MASK_VISITED_1 : MASK_VISITED_2)) != 0) -#define VISITED_S(quad) ((_cache[quad] & MASK_VISITED_S) != 0) -#define VISITED_W(quad) ((_cache[quad] & MASK_VISITED_W) != 0) -#define VISITED_CORNER(quad) ((_cache[quad] & MASK_VISITED_CORNER) != 0) -#define SADDLE(quad,li) ((_cache[quad] & (li==1 ? MASK_SADDLE_1 : MASK_SADDLE_2)) != 0) -#define SADDLE_LEFT(quad,li) ((_cache[quad] & (li==1 ? MASK_SADDLE_LEFT_1 : MASK_SADDLE_LEFT_2)) != 0) -#define SADDLE_START_SW(quad,li) ((_cache[quad] & (li==1 ? MASK_SADDLE_START_SW_1 : MASK_SADDLE_START_SW_2)) != 0) -#define BOUNDARY_S(quad) ((_cache[quad] & MASK_BOUNDARY_S) != 0) -#define BOUNDARY_W(quad) ((_cache[quad] & MASK_BOUNDARY_W) != 0) -#define BOUNDARY_N(quad) BOUNDARY_S(quad+_nx) -#define BOUNDARY_E(quad) BOUNDARY_W(quad+1) -#define EXISTS_QUAD(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_QUAD) -#define EXISTS_NONE(quad) ((_cache[quad] & MASK_EXISTS) == 0) -// The following are only used if _corner_mask is true. -#define EXISTS_SW_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SW_CORNER) -#define EXISTS_SE_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SE_CORNER) -#define EXISTS_NW_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NW_CORNER) -#define EXISTS_NE_CORNER(quad) ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NE_CORNER) -#define EXISTS_ANY_CORNER(quad) (!EXISTS_NONE(quad) && !EXISTS_QUAD(quad)) -#define EXISTS_W_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_NW_CORNER(quad)) -#define EXISTS_E_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SE_CORNER(quad) || EXISTS_NE_CORNER(quad)) -#define EXISTS_S_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_SE_CORNER(quad)) -#define EXISTS_N_EDGE(quad) (EXISTS_QUAD(quad) || EXISTS_NW_CORNER(quad) || EXISTS_NE_CORNER(quad)) -// Note that EXISTS_NE_CORNER(quad) is equivalent to BOUNDARY_SW(quad), etc. - - - -QuadEdge::QuadEdge() - : quad(-1), edge(Edge_None) -{} - -QuadEdge::QuadEdge(long quad_, Edge edge_) - : quad(quad_), edge(edge_) -{} - -bool QuadEdge::operator<(const QuadEdge& other) const -{ - if (quad != other.quad) - return quad < other.quad; - else - return edge < other.edge; -} - -bool QuadEdge::operator==(const QuadEdge& other) const -{ - return quad == other.quad && edge == other.edge; -} - -bool QuadEdge::operator!=(const QuadEdge& other) const -{ - return !operator==(other); -} - -std::ostream& operator<<(std::ostream& os, const QuadEdge& quad_edge) -{ - return os << quad_edge.quad << ' ' << quad_edge.edge; -} - - - -XY::XY() -{} - -XY::XY(const double& x_, const double& y_) - : x(x_), y(y_) -{} - -bool XY::operator==(const XY& other) const -{ - return x == other.x && y == other.y; -} - -bool XY::operator!=(const XY& other) const -{ - return x != other.x || y != other.y; -} - -XY XY::operator*(const double& multiplier) const -{ - return XY(x*multiplier, y*multiplier); -} - -const XY& XY::operator+=(const XY& other) -{ - x += other.x; - y += other.y; - return *this; -} - -const XY& XY::operator-=(const XY& other) -{ - x -= other.x; - y -= other.y; - return *this; -} - -XY XY::operator+(const XY& other) const -{ - return XY(x + other.x, y + other.y); -} - -XY XY::operator-(const XY& other) const -{ - return XY(x - other.x, y - other.y); -} - -std::ostream& operator<<(std::ostream& os, const XY& xy) -{ - return os << '(' << xy.x << ' ' << xy.y << ')'; -} - - - -ContourLine::ContourLine(bool is_hole) - : std::vector(), - _is_hole(is_hole), - _parent(0) -{} - -void ContourLine::add_child(ContourLine* child) -{ - assert(!_is_hole && "Cannot add_child to a hole"); - assert(child != 0 && "Null child ContourLine"); - _children.push_back(child); -} - -void ContourLine::clear_parent() -{ - assert(is_hole() && "Cannot clear parent of non-hole"); - assert(_parent != 0 && "Null parent ContourLine"); - _parent = 0; -} - -const ContourLine::Children& ContourLine::get_children() const -{ - assert(!_is_hole && "Cannot get_children of a hole"); - return _children; -} - -const ContourLine* ContourLine::get_parent() const -{ - assert(_is_hole && "Cannot get_parent of a non-hole"); - return _parent; -} - -ContourLine* ContourLine::get_parent() -{ - assert(_is_hole && "Cannot get_parent of a non-hole"); - return _parent; -} - -bool ContourLine::is_hole() const -{ - return _is_hole; -} - -void ContourLine::push_back(const XY& point) -{ - if (empty() || point != back()) - std::vector::push_back(point); -} - -void ContourLine::set_parent(ContourLine* parent) -{ - assert(_is_hole && "Cannot set parent of a non-hole"); - assert(parent != 0 && "Null parent ContourLine"); - _parent = parent; -} - -void ContourLine::write() const -{ - std::cout << "ContourLine " << this << " of " << size() << " points:"; - for (const_iterator it = begin(); it != end(); ++it) - std::cout << ' ' << *it; - if (is_hole()) - std::cout << " hole, parent=" << get_parent(); - else { - std::cout << " not hole"; - if (!_children.empty()) { - std::cout << ", children="; - for (Children::const_iterator it = _children.begin(); - it != _children.end(); ++it) - std::cout << *it << ' '; - } - } - std::cout << std::endl; -} - - - -Contour::Contour() -{} - -Contour::~Contour() -{ - delete_contour_lines(); -} - -void Contour::delete_contour_lines() -{ - for (iterator line_it = begin(); line_it != end(); ++line_it) { - delete *line_it; - *line_it = 0; - } - std::vector::clear(); -} - -void Contour::write() const -{ - std::cout << "Contour of " << size() << " lines." << std::endl; - for (const_iterator it = begin(); it != end(); ++it) - (*it)->write(); -} - - - -ParentCache::ParentCache(long nx, long x_chunk_points, long y_chunk_points) - : _nx(nx), - _x_chunk_points(x_chunk_points), - _y_chunk_points(y_chunk_points), - _lines(0), // Initialised when first needed. - _istart(0), - _jstart(0) -{ - assert(_x_chunk_points > 0 && _y_chunk_points > 0 && - "Chunk sizes must be positive"); -} - -ContourLine* ParentCache::get_parent(long quad) -{ - long index = quad_to_index(quad); - ContourLine* parent = _lines[index]; - while (parent == 0) { - index -= _x_chunk_points; - assert(index >= 0 && "Failed to find parent in chunk ParentCache"); - parent = _lines[index]; - } - assert(parent != 0 && "Failed to find parent in chunk ParentCache"); - return parent; -} - -long ParentCache::quad_to_index(long quad) const -{ - long i = quad % _nx; - long j = quad / _nx; - long index = (i-_istart) + (j-_jstart)*_x_chunk_points; - - assert(i >= _istart && i < _istart + _x_chunk_points && - "i-index outside chunk"); - assert(j >= _jstart && j < _jstart + _y_chunk_points && - "j-index outside chunk"); - assert(index >= 0 && index < static_cast(_lines.size()) && - "ParentCache index outside chunk"); - - return index; -} - -void ParentCache::set_chunk_starts(long istart, long jstart) -{ - assert(istart >= 0 && jstart >= 0 && - "Chunk start indices cannot be negative"); - _istart = istart; - _jstart = jstart; - if (_lines.empty()) - _lines.resize(_x_chunk_points*_y_chunk_points, 0); - else - std::fill(_lines.begin(), _lines.end(), (ContourLine*)0); -} - -void ParentCache::set_parent(long quad, ContourLine& contour_line) -{ - assert(!_lines.empty() && - "Accessing ParentCache before it has been initialised"); - long index = quad_to_index(quad); - if (_lines[index] == 0) - _lines[index] = (contour_line.is_hole() ? contour_line.get_parent() - : &contour_line); -} - - - -QuadContourGenerator::QuadContourGenerator(const CoordinateArray& x, - const CoordinateArray& y, - const CoordinateArray& z, - const MaskArray& mask, - bool corner_mask, - long chunk_size) - : _x(x), - _y(y), - _z(z), - _nx(static_cast(_x.dim(1))), - _ny(static_cast(_x.dim(0))), - _n(_nx*_ny), - _corner_mask(corner_mask), - _chunk_size(chunk_size > 0 ? std::min(chunk_size, std::max(_nx, _ny)-1) - : std::max(_nx, _ny)-1), - _nxchunk(calc_chunk_count(_nx)), - _nychunk(calc_chunk_count(_ny)), - _chunk_count(_nxchunk*_nychunk), - _cache(new CacheItem[_n]), - _parent_cache(_nx, - chunk_size > 0 ? chunk_size+1 : _nx, - chunk_size > 0 ? chunk_size+1 : _ny) -{ - assert(!_x.empty() && !_y.empty() && !_z.empty() && "Empty array"); - assert(_y.dim(0) == _x.dim(0) && _y.dim(1) == _x.dim(1) && - "Different-sized y and x arrays"); - assert(_z.dim(0) == _x.dim(0) && _z.dim(1) == _x.dim(1) && - "Different-sized z and x arrays"); - assert((mask.empty() || - (mask.dim(0) == _x.dim(0) && mask.dim(1) == _x.dim(1))) && - "Different-sized mask and x arrays"); - - init_cache_grid(mask); -} - -QuadContourGenerator::~QuadContourGenerator() -{ - delete [] _cache; -} - -void QuadContourGenerator::append_contour_line_to_vertices( - ContourLine& contour_line, - PyObject* vertices_list) const -{ - assert(vertices_list != 0 && "Null python vertices_list"); - - // Convert ContourLine to python equivalent, and clear it. - npy_intp dims[2] = {static_cast(contour_line.size()), 2}; - numpy::array_view line(dims); - npy_intp i = 0; - for (ContourLine::const_iterator point = contour_line.begin(); - point != contour_line.end(); ++point, ++i) { - line(i, 0) = point->x; - line(i, 1) = point->y; - } - if (PyList_Append(vertices_list, line.pyobj_steal())) { - Py_XDECREF(vertices_list); - throw std::runtime_error("Unable to add contour line to vertices_list"); - } - - contour_line.clear(); -} - -void QuadContourGenerator::append_contour_to_vertices_and_codes( - Contour& contour, - PyObject* vertices_list, - PyObject* codes_list) const -{ - assert(vertices_list != 0 && "Null python vertices_list"); - assert(codes_list != 0 && "Null python codes_list"); - - // Convert Contour to python equivalent, and clear it. - for (Contour::iterator line_it = contour.begin(); line_it != contour.end(); - ++line_it) { - ContourLine& line = **line_it; - if (line.is_hole()) { - // If hole has already been converted to python its parent will be - // set to 0 and it can be deleted. - if (line.get_parent() != 0) { - delete *line_it; - *line_it = 0; - } - } - else { - // Non-holes are converted to python together with their child - // holes so that they are rendered correctly. - ContourLine::const_iterator point; - ContourLine::Children::const_iterator children_it; - - const ContourLine::Children& children = line.get_children(); - npy_intp npoints = static_cast(line.size() + 1); - for (children_it = children.begin(); children_it != children.end(); - ++children_it) - npoints += static_cast((*children_it)->size() + 1); - - npy_intp vertices_dims[2] = {npoints, 2}; - numpy::array_view vertices(vertices_dims); - double* vertices_ptr = vertices.data(); - - npy_intp codes_dims[1] = {npoints}; - numpy::array_view codes(codes_dims); - unsigned char* codes_ptr = codes.data(); - - for (point = line.begin(); point != line.end(); ++point) { - *vertices_ptr++ = point->x; - *vertices_ptr++ = point->y; - *codes_ptr++ = (point == line.begin() ? MOVETO : LINETO); - } - point = line.begin(); - *vertices_ptr++ = point->x; - *vertices_ptr++ = point->y; - *codes_ptr++ = CLOSEPOLY; - - for (children_it = children.begin(); children_it != children.end(); - ++children_it) { - ContourLine& child = **children_it; - for (point = child.begin(); point != child.end(); ++point) { - *vertices_ptr++ = point->x; - *vertices_ptr++ = point->y; - *codes_ptr++ = (point == child.begin() ? MOVETO : LINETO); - } - point = child.begin(); - *vertices_ptr++ = point->x; - *vertices_ptr++ = point->y; - *codes_ptr++ = CLOSEPOLY; - - child.clear_parent(); // To indicate it can be deleted. - } - - if (PyList_Append(vertices_list, vertices.pyobj_steal()) || - PyList_Append(codes_list, codes.pyobj_steal())) { - Py_XDECREF(vertices_list); - Py_XDECREF(codes_list); - contour.delete_contour_lines(); - throw std::runtime_error("Unable to add contour line to vertices and codes lists"); - } - - delete *line_it; - *line_it = 0; - } - } - - // Delete remaining contour lines. - contour.delete_contour_lines(); -} - -long QuadContourGenerator::calc_chunk_count(long point_count) const -{ - assert(point_count > 0 && "point count must be positive"); - assert(_chunk_size > 0 && "Chunk size must be positive"); - - if (_chunk_size > 0) { - long count = (point_count-1) / _chunk_size; - if (count*_chunk_size < point_count-1) - ++count; - - assert(count >= 1 && "Invalid chunk count"); - return count; - } - else - return 1; -} - -PyObject* QuadContourGenerator::create_contour(const double& level) -{ - init_cache_levels(level, level); - - PyObject* vertices_list = PyList_New(0); - if (vertices_list == 0) - throw std::runtime_error("Failed to create Python list"); - - // Lines that start and end on boundaries. - long ichunk, jchunk, istart, iend, jstart, jend; - for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) { - get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend); - - for (long j = jstart; j < jend; ++j) { - long quad_end = iend + j*_nx; - for (long quad = istart + j*_nx; quad < quad_end; ++quad) { - if (EXISTS_NONE(quad) || VISITED(quad,1)) continue; - - if (BOUNDARY_S(quad) && Z_SW >= 1 && Z_SE < 1 && - start_line(vertices_list, quad, Edge_S, level)) continue; - - if (BOUNDARY_W(quad) && Z_NW >= 1 && Z_SW < 1 && - start_line(vertices_list, quad, Edge_W, level)) continue; - - if (BOUNDARY_N(quad) && Z_NE >= 1 && Z_NW < 1 && - start_line(vertices_list, quad, Edge_N, level)) continue; - - if (BOUNDARY_E(quad) && Z_SE >= 1 && Z_NE < 1 && - start_line(vertices_list, quad, Edge_E, level)) continue; - - if (_corner_mask) { - // Equates to NE boundary. - if (EXISTS_SW_CORNER(quad) && Z_SE >= 1 && Z_NW < 1 && - start_line(vertices_list, quad, Edge_NE, level)) continue; - - // Equates to NW boundary. - if (EXISTS_SE_CORNER(quad) && Z_NE >= 1 && Z_SW < 1 && - start_line(vertices_list, quad, Edge_NW, level)) continue; - - // Equates to SE boundary. - if (EXISTS_NW_CORNER(quad) && Z_SW >= 1 && Z_NE < 1 && - start_line(vertices_list, quad, Edge_SE, level)) continue; - - // Equates to SW boundary. - if (EXISTS_NE_CORNER(quad) && Z_NW >= 1 && Z_SE < 1 && - start_line(vertices_list, quad, Edge_SW, level)) continue; - } - } - } - } - - // Internal loops. - ContourLine contour_line(false); // Reused for each contour line. - for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) { - get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend); - - for (long j = jstart; j < jend; ++j) { - long quad_end = iend + j*_nx; - for (long quad = istart + j*_nx; quad < quad_end; ++quad) { - if (EXISTS_NONE(quad) || VISITED(quad,1)) - continue; - - Edge start_edge = get_start_edge(quad, 1); - if (start_edge == Edge_None) - continue; - - QuadEdge quad_edge(quad, start_edge); - QuadEdge start_quad_edge(quad_edge); - - // To obtain output identical to that produced by legacy code, - // sometimes need to ignore the first point and add it on the - // end instead. - bool ignore_first = (start_edge == Edge_N); - follow_interior(contour_line, quad_edge, 1, level, - !ignore_first, &start_quad_edge, 1, false); - if (ignore_first && !contour_line.empty()) - contour_line.push_back(contour_line.front()); - append_contour_line_to_vertices(contour_line, vertices_list); - - // Repeat if saddle point but not visited. - if (SADDLE(quad,1) && !VISITED(quad,1)) - --quad; - } - } - } - - return vertices_list; -} - -PyObject* QuadContourGenerator::create_filled_contour(const double& lower_level, - const double& upper_level) -{ - init_cache_levels(lower_level, upper_level); - - Contour contour; - - PyObject* vertices = PyList_New(0); - if (vertices == 0) - throw std::runtime_error("Failed to create Python list"); - - PyObject* codes = PyList_New(0); - if (codes == 0) { - Py_XDECREF(vertices); - throw std::runtime_error("Failed to create Python list"); - } - - long ichunk, jchunk, istart, iend, jstart, jend; - for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) { - get_chunk_limits(ijchunk, ichunk, jchunk, istart, iend, jstart, jend); - _parent_cache.set_chunk_starts(istart, jstart); - - for (long j = jstart; j < jend; ++j) { - long quad_end = iend + j*_nx; - for (long quad = istart + j*_nx; quad < quad_end; ++quad) { - if (!EXISTS_NONE(quad)) - single_quad_filled(contour, quad, lower_level, upper_level); - } - } - - // Clear VISITED_W and VISITED_S flags that are reused by later chunks. - if (jchunk < _nychunk-1) { - long quad_end = iend + jend*_nx; - for (long quad = istart + jend*_nx; quad < quad_end; ++quad) - _cache[quad] &= ~MASK_VISITED_S; - } - - if (ichunk < _nxchunk-1) { - long quad_end = iend + jend*_nx; - for (long quad = iend + jstart*_nx; quad < quad_end; quad += _nx) - _cache[quad] &= ~MASK_VISITED_W; - } - - // Create python objects to return for this chunk. - append_contour_to_vertices_and_codes(contour, vertices, codes); - } - - PyObject* tuple = PyTuple_New(2); - if (tuple == 0) { - Py_XDECREF(vertices); - Py_XDECREF(codes); - throw std::runtime_error("Failed to create Python tuple"); - } - - // No error checking here as filling in a brand new pre-allocated tuple. - PyTuple_SET_ITEM(tuple, 0, vertices); - PyTuple_SET_ITEM(tuple, 1, codes); - - return tuple; -} - -XY QuadContourGenerator::edge_interp(const QuadEdge& quad_edge, - const double& level) -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - return interp(get_edge_point_index(quad_edge, true), - get_edge_point_index(quad_edge, false), - level); -} - -unsigned int QuadContourGenerator::follow_boundary( - ContourLine& contour_line, - QuadEdge& quad_edge, - const double& lower_level, - const double& upper_level, - unsigned int level_index, - const QuadEdge& start_quad_edge) -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - assert(is_edge_a_boundary(quad_edge) && "Not a boundary edge"); - assert((level_index == 1 || level_index == 2) && - "level index must be 1 or 2"); - assert(start_quad_edge.quad >= 0 && start_quad_edge.quad < _n && - "Start quad index out of bounds"); - assert(start_quad_edge.edge != Edge_None && "Invalid start edge"); - - // Only called for filled contours, so always updates _parent_cache. - unsigned int end_level = 0; - bool first_edge = true; - bool stop = false; - long& quad = quad_edge.quad; - - while (true) { - // Levels of start and end points of quad_edge. - unsigned int start_level = - (first_edge ? Z_LEVEL(get_edge_point_index(quad_edge, true)) - : end_level); - long end_point = get_edge_point_index(quad_edge, false); - end_level = Z_LEVEL(end_point); - - if (level_index == 1) { - if (start_level <= level_index && end_level == 2) { - // Increasing z, switching levels from 1 to 2. - level_index = 2; - stop = true; - } - else if (start_level >= 1 && end_level == 0) { - // Decreasing z, keeping same level. - stop = true; - } - } - else { // level_index == 2 - if (start_level <= level_index && end_level == 2) { - // Increasing z, keeping same level. - stop = true; - } - else if (start_level >= 1 && end_level == 0) { - // Decreasing z, switching levels from 2 to 1. - level_index = 1; - stop = true; - } - } - - if (!first_edge && !stop && quad_edge == start_quad_edge) - // Return if reached start point of contour line. Do this before - // checking/setting VISITED flags as will already have been - // visited. - break; - - switch (quad_edge.edge) { - case Edge_E: - assert(!VISITED_W(quad+1) && "Already visited"); - _cache[quad+1] |= MASK_VISITED_W; - break; - case Edge_N: - assert(!VISITED_S(quad+_nx) && "Already visited"); - _cache[quad+_nx] |= MASK_VISITED_S; - break; - case Edge_W: - assert(!VISITED_W(quad) && "Already visited"); - _cache[quad] |= MASK_VISITED_W; - break; - case Edge_S: - assert(!VISITED_S(quad) && "Already visited"); - _cache[quad] |= MASK_VISITED_S; - break; - case Edge_NE: - case Edge_NW: - case Edge_SW: - case Edge_SE: - assert(!VISITED_CORNER(quad) && "Already visited"); - _cache[quad] |= MASK_VISITED_CORNER; - break; - default: - assert(0 && "Invalid Edge"); - break; - } - - if (stop) { - // Exiting boundary to enter interior. - contour_line.push_back(edge_interp(quad_edge, - level_index == 1 ? lower_level - : upper_level)); - break; - } - - move_to_next_boundary_edge(quad_edge); - - // Just moved to new quad edge, so label parent of start of quad edge. - switch (quad_edge.edge) { - case Edge_W: - case Edge_SW: - case Edge_S: - case Edge_SE: - if (!EXISTS_SE_CORNER(quad)) - _parent_cache.set_parent(quad, contour_line); - break; - case Edge_E: - case Edge_NE: - case Edge_N: - case Edge_NW: - if (!EXISTS_SW_CORNER(quad)) - _parent_cache.set_parent(quad + 1, contour_line); - break; - default: - assert(0 && "Invalid edge"); - break; - } - - // Add point to contour. - contour_line.push_back(get_point_xy(end_point)); - - if (first_edge) - first_edge = false; - } - - return level_index; -} - -void QuadContourGenerator::follow_interior(ContourLine& contour_line, - QuadEdge& quad_edge, - unsigned int level_index, - const double& level, - bool want_initial_point, - const QuadEdge* start_quad_edge, - unsigned int start_level_index, - bool set_parents) -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds."); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - assert((level_index == 1 || level_index == 2) && - "level index must be 1 or 2"); - assert((start_quad_edge == 0 || - (start_quad_edge->quad >= 0 && start_quad_edge->quad < _n)) && - "Start quad index out of bounds."); - assert((start_quad_edge == 0 || start_quad_edge->edge != Edge_None) && - "Invalid start edge"); - assert((start_level_index == 1 || start_level_index == 2) && - "start level index must be 1 or 2"); - - long& quad = quad_edge.quad; - Edge& edge = quad_edge.edge; - - if (want_initial_point) - contour_line.push_back(edge_interp(quad_edge, level)); - - CacheItem visited_mask = (level_index == 1 ? MASK_VISITED_1 : MASK_VISITED_2); - CacheItem saddle_mask = (level_index == 1 ? MASK_SADDLE_1 : MASK_SADDLE_2); - Dir dir = Dir_Straight; - - while (true) { - assert(!EXISTS_NONE(quad) && "Quad does not exist"); - assert(!(_cache[quad] & visited_mask) && "Quad already visited"); - - // Determine direction to move to next quad. If the quad is already - // labelled as a saddle quad then the direction is easily read from - // the cache. Otherwise the direction is determined differently - // depending on whether the quad is a corner quad or not. - - if (_cache[quad] & saddle_mask) { - // Already identified as a saddle quad, so direction is easy. - dir = (SADDLE_LEFT(quad,level_index) ? Dir_Left : Dir_Right); - _cache[quad] |= visited_mask; - } - else if (EXISTS_ANY_CORNER(quad)) { - // Need z-level of point opposite the entry edge, as that - // determines whether contour turns left or right. - long point_opposite = -1; - switch (edge) { - case Edge_E: - point_opposite = (EXISTS_SE_CORNER(quad) ? POINT_SW - : POINT_NW); - break; - case Edge_N: - point_opposite = (EXISTS_NW_CORNER(quad) ? POINT_SW - : POINT_SE); - break; - case Edge_W: - point_opposite = (EXISTS_SW_CORNER(quad) ? POINT_SE - : POINT_NE); - break; - case Edge_S: - point_opposite = (EXISTS_SW_CORNER(quad) ? POINT_NW - : POINT_NE); - break; - case Edge_NE: point_opposite = POINT_SW; break; - case Edge_NW: point_opposite = POINT_SE; break; - case Edge_SW: point_opposite = POINT_NE; break; - case Edge_SE: point_opposite = POINT_NW; break; - default: assert(0 && "Invalid edge"); break; - } - assert(point_opposite != -1 && "Failed to find opposite point"); - - // Lower-level polygons (level_index == 1) always have higher - // values to the left of the contour. Upper-level contours - // (level_index == 2) are reversed, which is what the fancy XOR - // does below. - if ((Z_LEVEL(point_opposite) >= level_index) ^ (level_index == 2)) - dir = Dir_Right; - else - dir = Dir_Left; - _cache[quad] |= visited_mask; - } - else { - // Calculate configuration of this quad. - long point_left = -1, point_right = -1; - switch (edge) { - case Edge_E: point_left = POINT_SW; point_right = POINT_NW; break; - case Edge_N: point_left = POINT_SE; point_right = POINT_SW; break; - case Edge_W: point_left = POINT_NE; point_right = POINT_SE; break; - case Edge_S: point_left = POINT_NW; point_right = POINT_NE; break; - default: assert(0 && "Invalid edge"); break; - } - - unsigned int config = (Z_LEVEL(point_left) >= level_index) << 1 | - (Z_LEVEL(point_right) >= level_index); - - // Upper level (level_index == 2) polygons are reversed compared to - // lower level ones, i.e. higher values on the right rather than - // the left. - if (level_index == 2) - config = 3 - config; - - // Calculate turn direction to move to next quad along contour line. - if (config == 1) { - // New saddle quad, set up cache bits for it. - double zmid = 0.25*(get_point_z(POINT_SW) + - get_point_z(POINT_SE) + - get_point_z(POINT_NW) + - get_point_z(POINT_NE)); - _cache[quad] |= (level_index == 1 ? MASK_SADDLE_1 : MASK_SADDLE_2); - if ((zmid > level) ^ (level_index == 2)) { - dir = Dir_Right; - } - else { - dir = Dir_Left; - _cache[quad] |= (level_index == 1 ? MASK_SADDLE_LEFT_1 - : MASK_SADDLE_LEFT_2); - } - if (edge == Edge_N || edge == Edge_E) { - // Next visit to this quad must start on S or W. - _cache[quad] |= (level_index == 1 ? MASK_SADDLE_START_SW_1 - : MASK_SADDLE_START_SW_2); - } - } - else { - // Normal (non-saddle) quad. - dir = (config == 0 ? Dir_Left - : (config == 3 ? Dir_Right : Dir_Straight)); - _cache[quad] |= visited_mask; - } - } - - // Use dir to determine exit edge. - edge = get_exit_edge(quad_edge, dir); - - if (set_parents) { - if (edge == Edge_E) - _parent_cache.set_parent(quad+1, contour_line); - else if (edge == Edge_W) - _parent_cache.set_parent(quad, contour_line); - } - - // Add new point to contour line. - contour_line.push_back(edge_interp(quad_edge, level)); - - // Stop if reached boundary. - if (is_edge_a_boundary(quad_edge)) - break; - - move_to_next_quad(quad_edge); - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - - // Return if reached start point of contour line. - if (start_quad_edge != 0 && - quad_edge == *start_quad_edge && - level_index == start_level_index) - break; - } -} - -void QuadContourGenerator::get_chunk_limits(long ijchunk, - long& ichunk, - long& jchunk, - long& istart, - long& iend, - long& jstart, - long& jend) -{ - assert(ijchunk >= 0 && ijchunk < _chunk_count && "ijchunk out of bounds"); - ichunk = ijchunk % _nxchunk; - jchunk = ijchunk / _nxchunk; - istart = ichunk*_chunk_size; - iend = (ichunk == _nxchunk-1 ? _nx : (ichunk+1)*_chunk_size); - jstart = jchunk*_chunk_size; - jend = (jchunk == _nychunk-1 ? _ny : (jchunk+1)*_chunk_size); -} - -Edge QuadContourGenerator::get_corner_start_edge(long quad, - unsigned int level_index) const -{ - assert(quad >= 0 && quad < _n && "Quad index out of bounds"); - assert((level_index == 1 || level_index == 2) && - "level index must be 1 or 2"); - assert(EXISTS_ANY_CORNER(quad) && "Quad is not a corner"); - - // Diagram for NE corner. Rotate for other corners. - // - // edge12 - // point1 +---------+ point2 - // \ | - // \ | edge23 - // edge31 \ | - // \ | - // + point3 - // - long point1, point2, point3; - Edge edge12, edge23, edge31; - switch (_cache[quad] & MASK_EXISTS) { - case MASK_EXISTS_SW_CORNER: - point1 = POINT_SE; point2 = POINT_SW; point3 = POINT_NW; - edge12 = Edge_S; edge23 = Edge_W; edge31 = Edge_NE; - break; - case MASK_EXISTS_SE_CORNER: - point1 = POINT_NE; point2 = POINT_SE; point3 = POINT_SW; - edge12 = Edge_E; edge23 = Edge_S; edge31 = Edge_NW; - break; - case MASK_EXISTS_NW_CORNER: - point1 = POINT_SW; point2 = POINT_NW; point3 = POINT_NE; - edge12 = Edge_W; edge23 = Edge_N; edge31 = Edge_SE; - break; - case MASK_EXISTS_NE_CORNER: - point1 = POINT_NW; point2 = POINT_NE; point3 = POINT_SE; - edge12 = Edge_N; edge23 = Edge_E; edge31 = Edge_SW; - break; - default: - assert(0 && "Invalid EXISTS for quad"); - return Edge_None; - } - - unsigned int config = (Z_LEVEL(point1) >= level_index) << 2 | - (Z_LEVEL(point2) >= level_index) << 1 | - (Z_LEVEL(point3) >= level_index); - - // Upper level (level_index == 2) polygons are reversed compared to lower - // level ones, i.e. higher values on the right rather than the left. - if (level_index == 2) - config = 7 - config; - - switch (config) { - case 0: return Edge_None; - case 1: return edge23; - case 2: return edge12; - case 3: return edge12; - case 4: return edge31; - case 5: return edge23; - case 6: return edge31; - case 7: return Edge_None; - default: assert(0 && "Invalid config"); return Edge_None; - } -} - -long QuadContourGenerator::get_edge_point_index(const QuadEdge& quad_edge, - bool start) const -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - - // Edges are ordered anticlockwise around their quad, as indicated by - // directions of arrows in diagrams below. - // Full quad NW corner (others similar) - // - // POINT_NW Edge_N POINT_NE POINT_NW Edge_N POINT_NE - // +----<-----+ +----<-----+ - // | | | / - // | | | quad / - // Edge_W V quad ^ Edge_E Edge_W V ^ - // | | | / Edge_SE - // | | | / - // +---->-----+ + - // POINT_SW Edge_S POINT_SE POINT_SW - // - const long& quad = quad_edge.quad; - switch (quad_edge.edge) { - case Edge_E: return (start ? POINT_SE : POINT_NE); - case Edge_N: return (start ? POINT_NE : POINT_NW); - case Edge_W: return (start ? POINT_NW : POINT_SW); - case Edge_S: return (start ? POINT_SW : POINT_SE); - case Edge_NE: return (start ? POINT_SE : POINT_NW); - case Edge_NW: return (start ? POINT_NE : POINT_SW); - case Edge_SW: return (start ? POINT_NW : POINT_SE); - case Edge_SE: return (start ? POINT_SW : POINT_NE); - default: assert(0 && "Invalid edge"); return 0; - } -} - -Edge QuadContourGenerator::get_exit_edge(const QuadEdge& quad_edge, - Dir dir) const -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - - const long& quad = quad_edge.quad; - const Edge& edge = quad_edge.edge; - if (EXISTS_ANY_CORNER(quad)) { - // Corner directions are always left or right. A corner is a triangle, - // entered via one edge so the other two edges are the left and right - // ones. - switch (edge) { - case Edge_E: - return (EXISTS_SE_CORNER(quad) - ? (dir == Dir_Left ? Edge_S : Edge_NW) - : (dir == Dir_Right ? Edge_N : Edge_SW)); - case Edge_N: - return (EXISTS_NW_CORNER(quad) - ? (dir == Dir_Right ? Edge_W : Edge_SE) - : (dir == Dir_Left ? Edge_E : Edge_SW)); - case Edge_W: - return (EXISTS_SW_CORNER(quad) - ? (dir == Dir_Right ? Edge_S : Edge_NE) - : (dir == Dir_Left ? Edge_N : Edge_SE)); - case Edge_S: - return (EXISTS_SW_CORNER(quad) - ? (dir == Dir_Left ? Edge_W : Edge_NE) - : (dir == Dir_Right ? Edge_E : Edge_NW)); - case Edge_NE: return (dir == Dir_Left ? Edge_S : Edge_W); - case Edge_NW: return (dir == Dir_Left ? Edge_E : Edge_S); - case Edge_SW: return (dir == Dir_Left ? Edge_N : Edge_E); - case Edge_SE: return (dir == Dir_Left ? Edge_W : Edge_N); - default: assert(0 && "Invalid edge"); return Edge_None; - } - } - else { - // A full quad has four edges, entered via one edge so that other three - // edges correspond to left, straight and right directions. - switch (edge) { - case Edge_E: - return (dir == Dir_Left ? Edge_S : - (dir == Dir_Right ? Edge_N : Edge_W)); - case Edge_N: - return (dir == Dir_Left ? Edge_E : - (dir == Dir_Right ? Edge_W : Edge_S)); - case Edge_W: - return (dir == Dir_Left ? Edge_N : - (dir == Dir_Right ? Edge_S : Edge_E)); - case Edge_S: - return (dir == Dir_Left ? Edge_W : - (dir == Dir_Right ? Edge_E : Edge_N)); - default: assert(0 && "Invalid edge"); return Edge_None; - } - } -} - -XY QuadContourGenerator::get_point_xy(long point) const -{ - assert(point >= 0 && point < _n && "Point index out of bounds."); - return XY(_x.data()[static_cast(point)], - _y.data()[static_cast(point)]); -} - -const double& QuadContourGenerator::get_point_z(long point) const -{ - assert(point >= 0 && point < _n && "Point index out of bounds."); - return _z.data()[static_cast(point)]; -} - -Edge QuadContourGenerator::get_quad_start_edge(long quad, - unsigned int level_index) const -{ - assert(quad >= 0 && quad < _n && "Quad index out of bounds"); - assert((level_index == 1 || level_index == 2) && - "level index must be 1 or 2"); - assert(EXISTS_QUAD(quad) && "Quad does not exist"); - - unsigned int config = (Z_NW >= level_index) << 3 | - (Z_NE >= level_index) << 2 | - (Z_SW >= level_index) << 1 | - (Z_SE >= level_index); - - // Upper level (level_index == 2) polygons are reversed compared to lower - // level ones, i.e. higher values on the right rather than the left. - if (level_index == 2) - config = 15 - config; - - switch (config) { - case 0: return Edge_None; - case 1: return Edge_E; - case 2: return Edge_S; - case 3: return Edge_E; - case 4: return Edge_N; - case 5: return Edge_N; - case 6: - // If already identified as a saddle quad then the start edge is - // read from the cache. Otherwise return either valid start edge - // and the subsequent call to follow_interior() will correctly set - // up saddle bits in cache. - if (!SADDLE(quad,level_index) || SADDLE_START_SW(quad,level_index)) - return Edge_S; - else - return Edge_N; - case 7: return Edge_N; - case 8: return Edge_W; - case 9: - // See comment for 6 above. - if (!SADDLE(quad,level_index) || SADDLE_START_SW(quad,level_index)) - return Edge_W; - else - return Edge_E; - case 10: return Edge_S; - case 11: return Edge_E; - case 12: return Edge_W; - case 13: return Edge_W; - case 14: return Edge_S; - case 15: return Edge_None; - default: assert(0 && "Invalid config"); return Edge_None; - } -} - -Edge QuadContourGenerator::get_start_edge(long quad, - unsigned int level_index) const -{ - if (EXISTS_ANY_CORNER(quad)) - return get_corner_start_edge(quad, level_index); - else - return get_quad_start_edge(quad, level_index); -} - -void QuadContourGenerator::init_cache_grid(const MaskArray& mask) -{ - long i, j, quad; - - if (mask.empty()) { - // No mask, easy to calculate quad existence and boundaries together. - quad = 0; - for (j = 0; j < _ny; ++j) { - for (i = 0; i < _nx; ++i, ++quad) { - _cache[quad] = 0; - - if (i < _nx-1 && j < _ny-1) - _cache[quad] |= MASK_EXISTS_QUAD; - - if ((i % _chunk_size == 0 || i == _nx-1) && j < _ny-1) - _cache[quad] |= MASK_BOUNDARY_W; - - if ((j % _chunk_size == 0 || j == _ny-1) && i < _nx-1) - _cache[quad] |= MASK_BOUNDARY_S; - } - } - } - else { - // Casting avoids problem when sizeof(bool) != sizeof(npy_bool). - const npy_bool* mask_ptr = - reinterpret_cast(mask.data()); - - // Have mask so use two stages. - // Stage 1, determine if quads/corners exist. - quad = 0; - for (j = 0; j < _ny; ++j) { - for (i = 0; i < _nx; ++i, ++quad) { - _cache[quad] = 0; - - if (i < _nx-1 && j < _ny-1) { - unsigned int config = mask_ptr[POINT_NW] << 3 | - mask_ptr[POINT_NE] << 2 | - mask_ptr[POINT_SW] << 1 | - mask_ptr[POINT_SE]; - - if (_corner_mask) { - switch (config) { - case 0: _cache[quad] = MASK_EXISTS_QUAD; break; - case 1: _cache[quad] = MASK_EXISTS_NW_CORNER; break; - case 2: _cache[quad] = MASK_EXISTS_NE_CORNER; break; - case 4: _cache[quad] = MASK_EXISTS_SW_CORNER; break; - case 8: _cache[quad] = MASK_EXISTS_SE_CORNER; break; - default: - // Do nothing, quad is masked out. - break; - } - } - else if (config == 0) - _cache[quad] = MASK_EXISTS_QUAD; - } - } - } - - // Stage 2, calculate W and S boundaries. For each quad use boundary - // data already calculated for quads to W and S, so must iterate - // through quads in correct order (increasing i and j indices). - // Cannot use boundary data for quads to E and N as have not yet - // calculated it. - quad = 0; - for (j = 0; j < _ny; ++j) { - for (i = 0; i < _nx; ++i, ++quad) { - if (_corner_mask) { - bool W_exists_none = (i == 0 || EXISTS_NONE(quad-1)); - bool S_exists_none = (j == 0 || EXISTS_NONE(quad-_nx)); - bool W_exists_E_edge = (i > 0 && EXISTS_E_EDGE(quad-1)); - bool S_exists_N_edge = (j > 0 && EXISTS_N_EDGE(quad-_nx)); - - if ((EXISTS_W_EDGE(quad) && W_exists_none) || - (EXISTS_NONE(quad) && W_exists_E_edge) || - (i % _chunk_size == 0 && EXISTS_W_EDGE(quad) && - W_exists_E_edge)) - _cache[quad] |= MASK_BOUNDARY_W; - - if ((EXISTS_S_EDGE(quad) && S_exists_none) || - (EXISTS_NONE(quad) && S_exists_N_edge) || - (j % _chunk_size == 0 && EXISTS_S_EDGE(quad) && - S_exists_N_edge)) - _cache[quad] |= MASK_BOUNDARY_S; - } - else { - bool W_exists_quad = (i > 0 && EXISTS_QUAD(quad-1)); - bool S_exists_quad = (j > 0 && EXISTS_QUAD(quad-_nx)); - - if ((EXISTS_QUAD(quad) != W_exists_quad) || - (i % _chunk_size == 0 && EXISTS_QUAD(quad) && - W_exists_quad)) - _cache[quad] |= MASK_BOUNDARY_W; - - if ((EXISTS_QUAD(quad) != S_exists_quad) || - (j % _chunk_size == 0 && EXISTS_QUAD(quad) && - S_exists_quad)) - _cache[quad] |= MASK_BOUNDARY_S; - } - } - } - } -} - -void QuadContourGenerator::init_cache_levels(const double& lower_level, - const double& upper_level) -{ - assert(upper_level >= lower_level && - "upper and lower levels are wrong way round"); - - bool two_levels = (lower_level != upper_level); - CacheItem keep_mask = - (_corner_mask ? MASK_EXISTS | MASK_BOUNDARY_S | MASK_BOUNDARY_W - : MASK_EXISTS_QUAD | MASK_BOUNDARY_S | MASK_BOUNDARY_W); - - if (two_levels) { - const double* z_ptr = _z.data(); - for (long quad = 0; quad < _n; ++quad, ++z_ptr) { - _cache[quad] &= keep_mask; - if (*z_ptr > upper_level) - _cache[quad] |= MASK_Z_LEVEL_2; - else if (*z_ptr > lower_level) - _cache[quad] |= MASK_Z_LEVEL_1; - } - } - else { - const double* z_ptr = _z.data(); - for (long quad = 0; quad < _n; ++quad, ++z_ptr) { - _cache[quad] &= keep_mask; - if (*z_ptr > lower_level) - _cache[quad] |= MASK_Z_LEVEL_1; - } - } -} - -XY QuadContourGenerator::interp( - long point1, long point2, const double& level) const -{ - assert(point1 >= 0 && point1 < _n && "Point index 1 out of bounds."); - assert(point2 >= 0 && point2 < _n && "Point index 2 out of bounds."); - assert(point1 != point2 && "Identical points"); - double fraction = (get_point_z(point2) - level) / - (get_point_z(point2) - get_point_z(point1)); - return get_point_xy(point1)*fraction + get_point_xy(point2)*(1.0 - fraction); -} - -bool QuadContourGenerator::is_edge_a_boundary(const QuadEdge& quad_edge) const -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - - switch (quad_edge.edge) { - case Edge_E: return BOUNDARY_E(quad_edge.quad); - case Edge_N: return BOUNDARY_N(quad_edge.quad); - case Edge_W: return BOUNDARY_W(quad_edge.quad); - case Edge_S: return BOUNDARY_S(quad_edge.quad); - case Edge_NE: return EXISTS_SW_CORNER(quad_edge.quad); - case Edge_NW: return EXISTS_SE_CORNER(quad_edge.quad); - case Edge_SW: return EXISTS_NE_CORNER(quad_edge.quad); - case Edge_SE: return EXISTS_NW_CORNER(quad_edge.quad); - default: assert(0 && "Invalid edge"); return true; - } -} - -void QuadContourGenerator::move_to_next_boundary_edge(QuadEdge& quad_edge) const -{ - assert(is_edge_a_boundary(quad_edge) && "QuadEdge is not a boundary"); - - long& quad = quad_edge.quad; - Edge& edge = quad_edge.edge; - - quad = get_edge_point_index(quad_edge, false); - - // quad is now such that POINT_SW is the end point of the quad_edge passed - // to this function. - - // To find the next boundary edge, first attempt to turn left 135 degrees - // and if that edge is a boundary then move to it. If not, attempt to turn - // left 90 degrees, then left 45 degrees, then straight on, etc, until can - // move. - // First determine which edge to attempt first. - int index = 0; - switch (edge) { - case Edge_E: index = 0; break; - case Edge_SE: index = 1; break; - case Edge_S: index = 2; break; - case Edge_SW: index = 3; break; - case Edge_W: index = 4; break; - case Edge_NW: index = 5; break; - case Edge_N: index = 6; break; - case Edge_NE: index = 7; break; - default: assert(0 && "Invalid edge"); break; - } - - // If _corner_mask not set, only need to consider odd index in loop below. - if (!_corner_mask) - ++index; - - // Try each edge in turn until a boundary is found. - int start_index = index; - do - { - switch (index) { - case 0: - if (EXISTS_SE_CORNER(quad-_nx-1)) { // Equivalent to BOUNDARY_NW - quad -= _nx+1; - edge = Edge_NW; - return; - } - break; - case 1: - if (BOUNDARY_N(quad-_nx-1)) { - quad -= _nx+1; - edge = Edge_N; - return; - } - break; - case 2: - if (EXISTS_SW_CORNER(quad-1)) { // Equivalent to BOUNDARY_NE - quad -= 1; - edge = Edge_NE; - return; - } - break; - case 3: - if (BOUNDARY_E(quad-1)) { - quad -= 1; - edge = Edge_E; - return; - } - break; - case 4: - if (EXISTS_NW_CORNER(quad)) { // Equivalent to BOUNDARY_SE - edge = Edge_SE; - return; - } - break; - case 5: - if (BOUNDARY_S(quad)) { - edge = Edge_S; - return; - } - break; - case 6: - if (EXISTS_NE_CORNER(quad-_nx)) { // Equivalent to BOUNDARY_SW - quad -= _nx; - edge = Edge_SW; - return; - } - break; - case 7: - if (BOUNDARY_W(quad-_nx)) { - quad -= _nx; - edge = Edge_W; - return; - } - break; - default: assert(0 && "Invalid index"); break; - } - - if (_corner_mask) - index = (index + 1) % 8; - else - index = (index + 2) % 8; - } while (index != start_index); - - assert(0 && "Failed to find next boundary edge"); -} - -void QuadContourGenerator::move_to_next_quad(QuadEdge& quad_edge) const -{ - assert(quad_edge.quad >= 0 && quad_edge.quad < _n && - "Quad index out of bounds"); - assert(quad_edge.edge != Edge_None && "Invalid edge"); - - // Move from quad_edge.quad to the neighbouring quad in the direction - // specified by quad_edge.edge. - switch (quad_edge.edge) { - case Edge_E: quad_edge.quad += 1; quad_edge.edge = Edge_W; break; - case Edge_N: quad_edge.quad += _nx; quad_edge.edge = Edge_S; break; - case Edge_W: quad_edge.quad -= 1; quad_edge.edge = Edge_E; break; - case Edge_S: quad_edge.quad -= _nx; quad_edge.edge = Edge_N; break; - default: assert(0 && "Invalid edge"); break; - } -} - -void QuadContourGenerator::single_quad_filled(Contour& contour, - long quad, - const double& lower_level, - const double& upper_level) -{ - assert(quad >= 0 && quad < _n && "Quad index out of bounds"); - - // Order of checking is important here as can have different ContourLines - // from both lower and upper levels in the same quad. First check the S - // edge, then move up the quad to the N edge checking as required. - - // Possible starts from S boundary. - if (BOUNDARY_S(quad) && EXISTS_S_EDGE(quad)) { - - // Lower-level start from S boundary into interior. - if (!VISITED_S(quad) && Z_SW >= 1 && Z_SE == 0) - contour.push_back(start_filled(quad, Edge_S, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from S boundary into interior. - if (!VISITED_S(quad) && Z_SW < 2 && Z_SE == 2) - contour.push_back(start_filled(quad, Edge_S, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start following S boundary from W to E. - if (!VISITED_S(quad) && Z_SW <= 1 && Z_SE == 1) - contour.push_back(start_filled(quad, Edge_S, 1, NotHole, Boundary, - lower_level, upper_level)); - - // Upper-level start following S boundary from W to E. - if (!VISITED_S(quad) && Z_SW == 2 && Z_SE == 1) - contour.push_back(start_filled(quad, Edge_S, 2, NotHole, Boundary, - lower_level, upper_level)); - } - - // Possible starts from W boundary. - if (BOUNDARY_W(quad) && EXISTS_W_EDGE(quad)) { - - // Lower-level start from W boundary into interior. - if (!VISITED_W(quad) && Z_NW >= 1 && Z_SW == 0) - contour.push_back(start_filled(quad, Edge_W, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from W boundary into interior. - if (!VISITED_W(quad) && Z_NW < 2 && Z_SW == 2) - contour.push_back(start_filled(quad, Edge_W, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start following W boundary from N to S. - if (!VISITED_W(quad) && Z_NW <= 1 && Z_SW == 1) - contour.push_back(start_filled(quad, Edge_W, 1, NotHole, Boundary, - lower_level, upper_level)); - - // Upper-level start following W boundary from N to S. - if (!VISITED_W(quad) && Z_NW == 2 && Z_SW == 1) - contour.push_back(start_filled(quad, Edge_W, 2, NotHole, Boundary, - lower_level, upper_level)); - } - - // Possible starts from NE boundary. - if (EXISTS_SW_CORNER(quad)) { // i.e. BOUNDARY_NE - - // Lower-level start following NE boundary from SE to NW, hole. - if (!VISITED_CORNER(quad) && Z_NW == 1 && Z_SE == 1) - contour.push_back(start_filled(quad, Edge_NE, 1, Hole, Boundary, - lower_level, upper_level)); - } - // Possible starts from SE boundary. - else if (EXISTS_NW_CORNER(quad)) { // i.e. BOUNDARY_SE - - // Lower-level start from N to SE. - if (!VISITED(quad,1) && Z_NW == 0 && Z_SW == 0 && Z_NE >= 1) - contour.push_back(start_filled(quad, Edge_N, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from SE to N, hole. - if (!VISITED(quad,2) && Z_NW < 2 && Z_SW < 2 && Z_NE == 2) - contour.push_back(start_filled(quad, Edge_SE, 2, Hole, Interior, - lower_level, upper_level)); - - // Upper-level start from N to SE. - if (!VISITED(quad,2) && Z_NW == 2 && Z_SW == 2 && Z_NE < 2) - contour.push_back(start_filled(quad, Edge_N, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start from SE to N, hole. - if (!VISITED(quad,1) && Z_NW >= 1 && Z_SW >= 1 && Z_NE == 0) - contour.push_back(start_filled(quad, Edge_SE, 1, Hole, Interior, - lower_level, upper_level)); - } - // Possible starts from NW boundary. - else if (EXISTS_SE_CORNER(quad)) { // i.e. BOUNDARY_NW - - // Lower-level start from NW to E. - if (!VISITED(quad,1) && Z_SW == 0 && Z_SE == 0 && Z_NE >= 1) - contour.push_back(start_filled(quad, Edge_NW, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from E to NW, hole. - if (!VISITED(quad,2) && Z_SW < 2 && Z_SE < 2 && Z_NE == 2) - contour.push_back(start_filled(quad, Edge_E, 2, Hole, Interior, - lower_level, upper_level)); - - // Upper-level start from NW to E. - if (!VISITED(quad,2) && Z_SW == 2 && Z_SE == 2 && Z_NE < 2) - contour.push_back(start_filled(quad, Edge_NW, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start from E to NW, hole. - if (!VISITED(quad,1) && Z_SW >= 1 && Z_SE >= 1 && Z_NE == 0) - contour.push_back(start_filled(quad, Edge_E, 1, Hole, Interior, - lower_level, upper_level)); - } - // Possible starts from SW boundary. - else if (EXISTS_NE_CORNER(quad)) { // i.e. BOUNDARY_SW - - // Lower-level start from SW boundary into interior. - if (!VISITED_CORNER(quad) && Z_NW >= 1 && Z_SE == 0) - contour.push_back(start_filled(quad, Edge_SW, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from SW boundary into interior. - if (!VISITED_CORNER(quad) && Z_NW < 2 && Z_SE == 2) - contour.push_back(start_filled(quad, Edge_SW, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start following SW boundary from NW to SE. - if (!VISITED_CORNER(quad) && Z_NW <= 1 && Z_SE == 1) - contour.push_back(start_filled(quad, Edge_SW, 1, NotHole, Boundary, - lower_level, upper_level)); - - // Upper-level start following SW boundary from NW to SE. - if (!VISITED_CORNER(quad) && Z_NW == 2 && Z_SE == 1) - contour.push_back(start_filled(quad, Edge_SW, 2, NotHole, Boundary, - lower_level, upper_level)); - } - - // A full (unmasked) quad can only have a start on the NE corner, i.e. from - // N to E (lower level) or E to N (upper level). Any other start will have - // already been created in a call to this function for a prior quad so we - // don't need to test for it again here. - // - // The situation is complicated by the possibility that the quad is a - // saddle quad, in which case a contour line starting on the N could leave - // by either the W or the E. We only need to consider those leaving E. - // - // A NE corner can also have a N to E or E to N start. - if (EXISTS_QUAD(quad) || EXISTS_NE_CORNER(quad)) { - - // Lower-level start from N to E. - if (!VISITED(quad,1) && Z_NW == 0 && Z_SE == 0 && Z_NE >= 1 && - (!SADDLE(quad,1) || SADDLE_LEFT(quad,1))) - contour.push_back(start_filled(quad, Edge_N, 1, NotHole, Interior, - lower_level, upper_level)); - - // Upper-level start from E to N, hole. - if (!VISITED(quad,2) && Z_NW < 2 && Z_SE < 2 && Z_NE == 2 && - (!SADDLE(quad,2) || !SADDLE_LEFT(quad,2))) - contour.push_back(start_filled(quad, Edge_E, 2, Hole, Interior, - lower_level, upper_level)); - - // Upper-level start from N to E. - if (!VISITED(quad,2) && Z_NW == 2 && Z_SE == 2 && Z_NE < 2 && - (!SADDLE(quad,2) || SADDLE_LEFT(quad,2))) - contour.push_back(start_filled(quad, Edge_N, 2, NotHole, Interior, - lower_level, upper_level)); - - // Lower-level start from E to N, hole. - if (!VISITED(quad,1) && Z_NW >= 1 && Z_SE >= 1 && Z_NE == 0 && - (!SADDLE(quad,1) || !SADDLE_LEFT(quad,1))) - contour.push_back(start_filled(quad, Edge_E, 1, Hole, Interior, - lower_level, upper_level)); - - // All possible contours passing through the interior of this quad - // should have already been created, so assert this. - assert((VISITED(quad,1) || get_start_edge(quad, 1) == Edge_None) && - "Found start of contour that should have already been created"); - assert((VISITED(quad,2) || get_start_edge(quad, 2) == Edge_None) && - "Found start of contour that should have already been created"); - } - - // Lower-level start following N boundary from E to W, hole. - // This is required for an internal masked region which is a hole in a - // surrounding contour line. - if (BOUNDARY_N(quad) && EXISTS_N_EDGE(quad) && - !VISITED_S(quad+_nx) && Z_NW == 1 && Z_NE == 1) - contour.push_back(start_filled(quad, Edge_N, 1, Hole, Boundary, - lower_level, upper_level)); -} - -ContourLine* QuadContourGenerator::start_filled( - long quad, - Edge edge, - unsigned int start_level_index, - HoleOrNot hole_or_not, - BoundaryOrInterior boundary_or_interior, - const double& lower_level, - const double& upper_level) -{ - assert(quad >= 0 && quad < _n && "Quad index out of bounds"); - assert(edge != Edge_None && "Invalid edge"); - assert((start_level_index == 1 || start_level_index == 2) && - "start level index must be 1 or 2"); - - ContourLine* contour_line = new ContourLine(hole_or_not == Hole); - if (hole_or_not == Hole) { - // Find and set parent ContourLine. - ContourLine* parent = _parent_cache.get_parent(quad + 1); - assert(parent != 0 && "Failed to find parent ContourLine"); - contour_line->set_parent(parent); - parent->add_child(contour_line); - } - - QuadEdge quad_edge(quad, edge); - const QuadEdge start_quad_edge(quad_edge); - unsigned int level_index = start_level_index; - - // If starts on interior, can only finish on interior. - // If starts on boundary, can only finish on boundary. - - while (true) { - if (boundary_or_interior == Interior) { - double level = (level_index == 1 ? lower_level : upper_level); - follow_interior(*contour_line, quad_edge, level_index, level, - false, &start_quad_edge, start_level_index, true); - } - else { - level_index = follow_boundary( - *contour_line, quad_edge, lower_level, - upper_level, level_index, start_quad_edge); - } - - if (quad_edge == start_quad_edge && (boundary_or_interior == Boundary || - level_index == start_level_index)) - break; - - if (boundary_or_interior == Boundary) - boundary_or_interior = Interior; - else - boundary_or_interior = Boundary; - } - - return contour_line; -} - -bool QuadContourGenerator::start_line( - PyObject* vertices_list, long quad, Edge edge, const double& level) -{ - assert(vertices_list != 0 && "Null python vertices list"); - assert(is_edge_a_boundary(QuadEdge(quad, edge)) && - "QuadEdge is not a boundary"); - - QuadEdge quad_edge(quad, edge); - ContourLine contour_line(false); - follow_interior(contour_line, quad_edge, 1, level, true, 0, 1, false); - append_contour_line_to_vertices(contour_line, vertices_list); - return VISITED(quad,1); -} - -void QuadContourGenerator::write_cache(bool grid_only) const -{ - std::cout << "-----------------------------------------------" << std::endl; - for (long quad = 0; quad < _n; ++quad) - write_cache_quad(quad, grid_only); - std::cout << "-----------------------------------------------" << std::endl; -} - -void QuadContourGenerator::write_cache_quad(long quad, bool grid_only) const -{ - long j = quad / _nx; - long i = quad - j*_nx; - std::cout << quad << ": i=" << i << " j=" << j - << " EXISTS=" << EXISTS_QUAD(quad); - if (_corner_mask) - std::cout << " CORNER=" << EXISTS_SW_CORNER(quad) << EXISTS_SE_CORNER(quad) - << EXISTS_NW_CORNER(quad) << EXISTS_NE_CORNER(quad); - std::cout << " BNDY=" << (BOUNDARY_S(quad)>0) << (BOUNDARY_W(quad)>0); - if (!grid_only) { - std::cout << " Z=" << Z_LEVEL(quad) - << " SAD=" << SADDLE(quad,1) << SADDLE(quad,2) - << " LEFT=" << SADDLE_LEFT(quad,1) << SADDLE_LEFT(quad,2) - << " NW=" << SADDLE_START_SW(quad,1) << SADDLE_START_SW(quad,2) - << " VIS=" << VISITED(quad,1) << VISITED(quad,2) - << VISITED_S(quad) << VISITED_W(quad) - << VISITED_CORNER(quad); - } - std::cout << std::endl; -} diff --git a/src/_contour.h b/src/_contour.h deleted file mode 100644 index 6232b3abd2a7..000000000000 --- a/src/_contour.h +++ /dev/null @@ -1,530 +0,0 @@ -/* - * QuadContourGenerator - * -------------------- - * A QuadContourGenerator generates contours for scalar fields defined on - * quadrilateral grids. A single QuadContourGenerator object can create both - * line contours (at single levels) and filled contours (between pairs of - * levels) for the same field. - * - * A field to be contoured has nx, ny points in the x- and y-directions - * respectively. The quad grid is defined by x and y arrays of shape(ny, nx), - * and the field itself is the z array also of shape(ny, nx). There is an - * optional boolean mask; if it exists then it also has shape(ny, nx). The - * mask applies to grid points rather than quads. - * - * How quads are masked based on the point mask is determined by the boolean - * 'corner_mask' flag. If false then any quad that has one or more of its four - * corner points masked is itself masked. If true the behaviour is the same - * except that any quad which has exactly one of its four corner points masked - * has only the triangular corner (half of the quad) adjacent to that point - * masked; the opposite triangular corner has three unmasked points and is not - * masked. - * - * By default the entire domain of nx*ny points is contoured together which can - * result in some very long polygons. The alternative is to break up the - * domain into subdomains or 'chunks' of smaller size, each of which is - * independently contoured. The size of these chunks is controlled by the - * 'nchunk' (or 'chunk_size') parameter. Chunking not only results in shorter - * polygons but also requires slightly less RAM. It can result in rendering - * artifacts though, depending on backend, antialiased flag and alpha value. - * - * Notation - * -------- - * i and j are array indices in the x- and y-directions respectively. Although - * a single element of an array z can be accessed using z[j][i] or z(j,i), it - * is often convenient to use the single quad index z[quad], where - * quad = i + j*nx - * and hence - * i = quad % nx - * j = quad / nx - * - * Rather than referring to x- and y-directions, compass directions are used - * instead such that W, E, S, N refer to the -x, +x, -y, +y directions - * respectively. To move one quad to the E you would therefore add 1 to the - * quad index, to move one quad to the N you would add nx to the quad index. - * - * Cache - * ----- - * Lots of information that is reused during contouring is stored as single - * bits in a mesh-sized cache, indexed by quad. Each quad's cache entry stores - * information about the quad itself such as if it is masked, and about the - * point at the SW corner of the quad, and about the W and S edges. Hence - * information about each point and each edge is only stored once in the cache. - * - * Cache information is divided into two types: that which is constant over the - * lifetime of the QuadContourGenerator, and that which changes for each - * contouring operation. The former is all grid-specific information such - * as quad and corner masks, and which edges are boundaries, either between - * masked and non-masked regions or between adjacent chunks. The latter - * includes whether points lie above or below the current contour levels, plus - * some flags to indicate how the contouring is progressing. - * - * Line Contours - * ------------- - * A line contour connects points with the same z-value. Each point of such a - * contour occurs on an edge of the grid, at a point linearly interpolated to - * the contour z-level from the z-values at the end points of the edge. The - * direction of a line contour is such that higher values are to the left of - * the contour, so any edge that the contour passes through will have a left- - * hand end point with z > contour level and a right-hand end point with - * z <= contour level. - * - * Line contours are of two types. Firstly there are open line strips that - * start on a boundary, traverse the interior of the domain and end on a - * boundary. Secondly there are closed line loops that occur completely within - * the interior of the domain and do not touch a boundary. - * - * The QuadContourGenerator makes two sweeps through the grid to generate line - * contours for a particular level. In the first sweep it looks only for start - * points that occur on boundaries, and when it finds one it follows the - * contour through the interior until it finishes on another boundary edge. - * Each quad that is visited by the algorithm has a 'visited' flag set in the - * cache to indicate that the quad does not need to be visited again. In the - * second sweep all non-visited quads are checked to see if they contain part - * of an interior closed loop, and again each time one is found it is followed - * through the domain interior until it returns back to its start quad and is - * therefore completed. - * - * The situation is complicated by saddle quads that have two opposite corners - * with z >= contour level and the other two corners with z < contour level. - * These therefore contain two segments of a line contour, and the visited - * flags take account of this by only being set on the second visit. On the - * first visit a number of saddle flags are set in the cache to indicate which - * one of the two segments has been completed so far. - * - * Filled Contours - * --------------- - * Filled contours are produced between two contour levels and are always - * closed polygons. They can occur completely within the interior of the - * domain without touching a boundary, following either the lower or upper - * contour levels. Those on the lower level are exactly like interior line - * contours with higher values on the left. Those on the upper level are - * reversed such that higher values are on the right. - * - * Filled contours can also involve a boundary in which case they consist of - * one or more sections along a boundary and one or more sections through the - * interior. Interior sections can be on either level, and again those on the - * upper level have higher values on the right. Boundary sections can remain - * on either contour level or switch between the two. - * - * Once the start of a filled contour is found, the algorithm is similar to - * that for line contours in that it follows the contour to its end, which - * because filled contours are always closed polygons will be by returning - * back to the start. However, because two levels must be considered, each - * level has its own set of saddle and visited flags and indeed some extra - * visited flags for boundary edges. - * - * The major complication for filled contours is that some polygons can be - * holes (with points ordered clockwise) within other polygons (with points - * ordered anticlockwise). When it comes to rendering filled contours each - * non-hole polygon must be rendered along with its zero or more contained - * holes or the rendering will not be correct. The filled contour finding - * algorithm could progress pretty much as the line contour algorithm does, - * taking each polygon as it is found, but then at the end there would have to - * be an extra step to identify the parent non-hole polygon for each hole. - * This is not a particularly onerous task but it does not scale well and can - * easily dominate the execution time of the contour finding for even modest - * problems. It is much better to identity each hole's parent non-hole during - * the sweep algorithm. - * - * This requirement dictates the order that filled contours are identified. As - * the algorithm sweeps up through the grid, every time a polygon passes - * through a quad a ParentCache object is updated with the new possible parent. - * When a new hole polygon is started, the ParentCache is used to find the - * first possible parent in the same quad or to the S of it. Great care is - * needed each time a new quad is checked to see if a new polygon should be - * started, as a single quad can have multiple polygon starts, e.g. a quad - * could be a saddle quad for both lower and upper contour levels, meaning it - * has four contour line segments passing through it which could all be from - * different polygons. The S-most polygon must be started first, then the next - * S-most and so on until the N-most polygon is started in that quad. - */ -#ifndef MPL_CONTOUR_H -#define MPL_CONTOUR_H - -#include "numpy_cpp.h" -#include -#include -#include -#include - - -// Edge of a quad including diagonal edges of masked quads if _corner_mask true. -typedef enum -{ - // Listing values here so easier to check for debug purposes. - Edge_None = -1, - Edge_E = 0, - Edge_N = 1, - Edge_W = 2, - Edge_S = 3, - // The following are only used if _corner_mask is true. - Edge_NE = 4, - Edge_NW = 5, - Edge_SW = 6, - Edge_SE = 7 -} Edge; - -// Combination of a quad and an edge of that quad. -// An invalid quad edge has quad of -1. -struct QuadEdge -{ - QuadEdge(); - QuadEdge(long quad_, Edge edge_); - bool operator<(const QuadEdge& other) const; - bool operator==(const QuadEdge& other) const; - bool operator!=(const QuadEdge& other) const; - friend std::ostream& operator<<(std::ostream& os, - const QuadEdge& quad_edge); - - long quad; - Edge edge; -}; - -// 2D point with x,y coordinates. -struct XY -{ - XY(); - XY(const double& x_, const double& y_); - bool operator==(const XY& other) const; - bool operator!=(const XY& other) const; - XY operator*(const double& multiplier) const; - const XY& operator+=(const XY& other); - const XY& operator-=(const XY& other); - XY operator+(const XY& other) const; - XY operator-(const XY& other) const; - friend std::ostream& operator<<(std::ostream& os, const XY& xy); - - double x, y; -}; - -// A single line of a contour, which may be a closed line loop or an open line -// strip. Identical adjacent points are avoided using push_back(). -// A ContourLine is either a hole (points ordered clockwise) or it is not -// (points ordered anticlockwise). Each hole has a parent ContourLine that is -// not a hole; each non-hole contains zero or more child holes. A non-hole and -// its child holes must be rendered together to obtain the correct results. -class ContourLine : public std::vector -{ -public: - typedef std::list Children; - - ContourLine(bool is_hole); - void add_child(ContourLine* child); - void clear_parent(); - const Children& get_children() const; - const ContourLine* get_parent() const; - ContourLine* get_parent(); - bool is_hole() const; - void push_back(const XY& point); - void set_parent(ContourLine* parent); - void write() const; - -private: - bool _is_hole; - ContourLine* _parent; // Only set if is_hole, not owned. - Children _children; // Only set if !is_hole, not owned. -}; - - -// A Contour is a collection of zero or more ContourLines. -class Contour : public std::vector -{ -public: - Contour(); - virtual ~Contour(); - void delete_contour_lines(); - void write() const; -}; - - -// Single chunk of ContourLine parents, indexed by quad. As a chunk's filled -// contours are created, the ParentCache is updated each time a ContourLine -// passes through each quad. When a new ContourLine is created, if it is a -// hole its parent ContourLine is read from the ParentCache by looking at the -// start quad, then each quad to the S in turn until a non-zero ContourLine is -// found. -class ParentCache -{ -public: - ParentCache(long nx, long x_chunk_points, long y_chunk_points); - ContourLine* get_parent(long quad); - void set_chunk_starts(long istart, long jstart); - void set_parent(long quad, ContourLine& contour_line); - -private: - long quad_to_index(long quad) const; - - long _nx; - long _x_chunk_points, _y_chunk_points; // Number of points not quads. - std::vector _lines; // Not owned. - long _istart, _jstart; -}; - - -// See overview of algorithm at top of file. -class QuadContourGenerator -{ -public: - typedef numpy::array_view CoordinateArray; - typedef numpy::array_view MaskArray; - - // Constructor with optional mask. - // x, y, z: double arrays of shape (ny,nx). - // mask: boolean array, ether empty (if no mask), or of shape (ny,nx). - // corner_mask: flag for different masking behaviour. - // chunk_size: 0 for no chunking, or +ve integer for size of chunks that - // the domain is subdivided into. - QuadContourGenerator(const CoordinateArray& x, - const CoordinateArray& y, - const CoordinateArray& z, - const MaskArray& mask, - bool corner_mask, - long chunk_size); - - // Destructor. - ~QuadContourGenerator(); - - // Create and return polygons for a line (i.e. non-filled) contour at the - // specified level. - PyObject* create_contour(const double& level); - - // Create and return polygons for a filled contour between the two - // specified levels. - PyObject* create_filled_contour(const double& lower_level, - const double& upper_level); - -private: - // Typedef for following either a boundary of the domain or the interior; - // clearer than using a boolean. - typedef enum - { - Boundary, - Interior - } BoundaryOrInterior; - - // Typedef for direction of movement from one quad to the next. - typedef enum - { - Dir_Right = -1, - Dir_Straight = 0, - Dir_Left = +1 - } Dir; - - // Typedef for a polygon being a hole or not; clearer than using a boolean. - typedef enum - { - NotHole, - Hole - } HoleOrNot; - - // Append a C++ ContourLine to the end of a python list. Used for line - // contours where each ContourLine is converted to a separate numpy array - // of (x,y) points. - // Clears the ContourLine too. - void append_contour_line_to_vertices(ContourLine& contour_line, - PyObject* vertices_list) const; - - // Append a C++ Contour to the end of two python lists. Used for filled - // contours where each non-hole ContourLine and its child holes are - // represented by a numpy array of (x,y) points and a second numpy array of - // 'kinds' or 'codes' that indicates where the points array is split into - // individual polygons. - // Clears the Contour too, freeing each ContourLine as soon as possible - // for minimum RAM usage. - void append_contour_to_vertices_and_codes(Contour& contour, - PyObject* vertices_list, - PyObject* codes_list) const; - - // Return number of chunks that fit in the specified point_count. - long calc_chunk_count(long point_count) const; - - // Return the point on the specified QuadEdge that intersects the specified - // level. - XY edge_interp(const QuadEdge& quad_edge, const double& level); - - // Follow a contour along a boundary, appending points to the ContourLine - // as it progresses. Only called for filled contours. Stops when the - // contour leaves the boundary to move into the interior of the domain, or - // when the start_quad_edge is reached in which case the ContourLine is a - // completed closed loop. Always adds the end point of each boundary edge - // to the ContourLine, regardless of whether moving to another boundary - // edge or leaving the boundary into the interior. Never adds the start - // point of the first boundary edge to the ContourLine. - // contour_line: ContourLine to append points to. - // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge - // that is stopped on. - // lower_level: lower contour z-value. - // upper_level: upper contour z-value. - // level_index: level index started on (1 = lower, 2 = upper level). - // start_quad_edge: QuadEdge that the ContourLine started from, which is - // used to check if the ContourLine is finished. - // Returns the end level_index. - unsigned int follow_boundary(ContourLine& contour_line, - QuadEdge& quad_edge, - const double& lower_level, - const double& upper_level, - unsigned int level_index, - const QuadEdge& start_quad_edge); - - // Follow a contour across the interior of the domain, appending points to - // the ContourLine as it progresses. Called for both line and filled - // contours. Stops when the contour reaches a boundary or, if the - // start_quad_edge is specified, when quad_edge == start_quad_edge and - // level_index == start_level_index. Always adds the end point of each - // quad traversed to the ContourLine; only adds the start point of the - // first quad if want_initial_point flag is true. - // contour_line: ContourLine to append points to. - // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge - // that is stopped on. - // level_index: level index started on (1 = lower, 2 = upper level). - // level: contour z-value. - // want_initial_point: whether want to append the initial point to the - // ContourLine or not. - // start_quad_edge: the QuadEdge that the ContourLine started from to - // check if the ContourLine is finished, or 0 if no check should occur. - // start_level_index: the level_index that the ContourLine started from. - // set_parents: whether should set ParentCache as it progresses or not. - // This is true for filled contours, false for line contours. - void follow_interior(ContourLine& contour_line, - QuadEdge& quad_edge, - unsigned int level_index, - const double& level, - bool want_initial_point, - const QuadEdge* start_quad_edge, - unsigned int start_level_index, - bool set_parents); - - // Return the index limits of a particular chunk. - void get_chunk_limits(long ijchunk, - long& ichunk, - long& jchunk, - long& istart, - long& iend, - long& jstart, - long& jend); - - // Check if a contour starts within the specified corner quad on the - // specified level_index, and if so return the start edge. Otherwise - // return Edge_None. - Edge get_corner_start_edge(long quad, unsigned int level_index) const; - - // Return index of point at start or end of specified QuadEdge, assuming - // anticlockwise ordering around non-masked quads. - long get_edge_point_index(const QuadEdge& quad_edge, bool start) const; - - // Return the edge to exit a quad from, given the specified entry quad_edge - // and direction to move in. - Edge get_exit_edge(const QuadEdge& quad_edge, Dir dir) const; - - // Return the (x,y) coordinates of the specified point index. - XY get_point_xy(long point) const; - - // Return the z-value of the specified point index. - const double& get_point_z(long point) const; - - // Check if a contour starts within the specified non-corner quad on the - // specified level_index, and if so return the start edge. Otherwise - // return Edge_None. - Edge get_quad_start_edge(long quad, unsigned int level_index) const; - - // Check if a contour starts within the specified quad, whether it is a - // corner or a full quad, and if so return the start edge. Otherwise - // return Edge_None. - Edge get_start_edge(long quad, unsigned int level_index) const; - - // Initialise the cache to contain grid information that is constant - // across the lifetime of this object, i.e. does not vary between calls to - // create_contour() and create_filled_contour(). - void init_cache_grid(const MaskArray& mask); - - // Initialise the cache with information that is specific to contouring the - // specified two levels. The levels are the same for contour lines, - // different for filled contours. - void init_cache_levels(const double& lower_level, - const double& upper_level); - - // Return the (x,y) point at which the level intersects the line connecting - // the two specified point indices. - XY interp(long point1, long point2, const double& level) const; - - // Return true if the specified QuadEdge is a boundary, i.e. is either an - // edge between a masked and non-masked quad/corner or is a chunk boundary. - bool is_edge_a_boundary(const QuadEdge& quad_edge) const; - - // Follow a boundary from one QuadEdge to the next in an anticlockwise - // manner around the non-masked region. - void move_to_next_boundary_edge(QuadEdge& quad_edge) const; - - // Move from the quad specified by quad_edge.quad to the neighbouring quad - // by crossing the edge specified by quad_edge.edge. - void move_to_next_quad(QuadEdge& quad_edge) const; - - // Check for filled contours starting within the specified quad and - // complete any that are found, appending them to the specified Contour. - void single_quad_filled(Contour& contour, - long quad, - const double& lower_level, - const double& upper_level); - - // Start and complete a filled contour line. - // quad: index of quad to start ContourLine in. - // edge: edge of quad to start ContourLine from. - // start_level_index: the level_index that the ContourLine starts from. - // hole_or_not: whether the ContourLine is a hole or not. - // boundary_or_interior: whether the ContourLine starts on a boundary or - // the interior. - // lower_level: lower contour z-value. - // upper_level: upper contour z-value. - // Returns newly created ContourLine. - ContourLine* start_filled(long quad, - Edge edge, - unsigned int start_level_index, - HoleOrNot hole_or_not, - BoundaryOrInterior boundary_or_interior, - const double& lower_level, - const double& upper_level); - - // Start and complete a line contour that both starts and end on a - // boundary, traversing the interior of the domain. - // vertices_list: Python list that the ContourLine should be appended to. - // quad: index of quad to start ContourLine in. - // edge: boundary edge to start ContourLine from. - // level: contour z-value. - // Returns true if the start quad does not need to be visited again, i.e. - // VISITED(quad,1). - bool start_line(PyObject* vertices_list, - long quad, - Edge edge, - const double& level); - - // Debug function that writes the cache status to stdout. - void write_cache(bool grid_only = false) const; - - // Debug function that writes that cache status for a single quad to - // stdout. - void write_cache_quad(long quad, bool grid_only) const; - - - - // Note that mask is not stored as once it has been used to initialise the - // cache it is no longer needed. - CoordinateArray _x, _y, _z; - long _nx, _ny; // Number of points in each direction. - long _n; // Total number of points (and hence quads). - - bool _corner_mask; - long _chunk_size; // Number of quads per chunk (not points). - // Always > 0, unlike python nchunk which is 0 - // for no chunking. - - long _nxchunk, _nychunk; // Number of chunks in each direction. - long _chunk_count; // Total number of chunks. - - typedef uint32_t CacheItem; - CacheItem* _cache; - - ParentCache _parent_cache; // On W quad sides. -}; - -#endif // _CONTOUR_H diff --git a/src/_contour_wrapper.cpp b/src/_contour_wrapper.cpp deleted file mode 100644 index da891eb70048..000000000000 --- a/src/_contour_wrapper.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include "_contour.h" -#include "mplutils.h" -#include "py_converters.h" -#include "py_exceptions.h" - -/* QuadContourGenerator */ - -typedef struct -{ - PyObject_HEAD - QuadContourGenerator* ptr; -} PyQuadContourGenerator; - -static PyTypeObject PyQuadContourGeneratorType; - -static PyObject* PyQuadContourGenerator_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - PyQuadContourGenerator* self; - self = (PyQuadContourGenerator*)type->tp_alloc(type, 0); - self->ptr = NULL; - return (PyObject*)self; -} - -const char* PyQuadContourGenerator_init__doc__ = - "QuadContourGenerator(x, y, z, mask, corner_mask, chunk_size)\n" - "--\n\n" - "Create a new C++ QuadContourGenerator object\n"; - -static int PyQuadContourGenerator_init(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds) -{ - QuadContourGenerator::CoordinateArray x, y, z; - QuadContourGenerator::MaskArray mask; - bool corner_mask; - long chunk_size; - - if (!PyArg_ParseTuple(args, "O&O&O&O&O&l", - &x.converter_contiguous, &x, - &y.converter_contiguous, &y, - &z.converter_contiguous, &z, - &mask.converter_contiguous, &mask, - &convert_bool, &corner_mask, - &chunk_size)) { - return -1; - } - - if (x.empty() || y.empty() || z.empty() || - y.dim(0) != x.dim(0) || z.dim(0) != x.dim(0) || - y.dim(1) != x.dim(1) || z.dim(1) != x.dim(1)) { - PyErr_SetString(PyExc_ValueError, - "x, y and z must all be 2D arrays with the same dimensions"); - return -1; - } - - if (z.dim(0) < 2 || z.dim(1) < 2) { - PyErr_SetString(PyExc_ValueError, - "x, y and z must all be at least 2x2 arrays"); - return -1; - } - - // Mask array is optional, if set must be same size as other arrays. - if (!mask.empty() && (mask.dim(0) != x.dim(0) || mask.dim(1) != x.dim(1))) { - PyErr_SetString(PyExc_ValueError, - "If mask is set it must be a 2D array with the same dimensions as x."); - return -1; - } - - CALL_CPP_INIT("QuadContourGenerator", - (self->ptr = new QuadContourGenerator( - x, y, z, mask, corner_mask, chunk_size))); - return 0; -} - -static void PyQuadContourGenerator_dealloc(PyQuadContourGenerator* self) -{ - delete self->ptr; - Py_TYPE(self)->tp_free((PyObject *)self); -} - -const char* PyQuadContourGenerator_create_contour__doc__ = - "create_contour(level)\n" - "--\n\n" - "Create and return a non-filled contour."; - -static PyObject* PyQuadContourGenerator_create_contour(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds) -{ - double level; - if (!PyArg_ParseTuple(args, "d:create_contour", &level)) { - return NULL; - } - - PyObject* result; - CALL_CPP("create_contour", (result = self->ptr->create_contour(level))); - return result; -} - -const char* PyQuadContourGenerator_create_filled_contour__doc__ = - "create_filled_contour(lower_level, upper_level)\n" - "--\n\n" - "Create and return a filled contour"; - -static PyObject* PyQuadContourGenerator_create_filled_contour(PyQuadContourGenerator* self, PyObject* args, PyObject* kwds) -{ - double lower_level, upper_level; - if (!PyArg_ParseTuple(args, "dd:create_filled_contour", - &lower_level, &upper_level)) { - return NULL; - } - - if (lower_level >= upper_level) - { - PyErr_SetString(PyExc_ValueError, - "filled contour levels must be increasing"); - return NULL; - } - - PyObject* result; - CALL_CPP("create_filled_contour", - (result = self->ptr->create_filled_contour(lower_level, - upper_level))); - return result; -} - -static PyTypeObject* PyQuadContourGenerator_init_type(PyObject* m, PyTypeObject* type) -{ - static PyMethodDef methods[] = { - {"create_contour", (PyCFunction)PyQuadContourGenerator_create_contour, METH_VARARGS, PyQuadContourGenerator_create_contour__doc__}, - {"create_filled_contour", (PyCFunction)PyQuadContourGenerator_create_filled_contour, METH_VARARGS, PyQuadContourGenerator_create_filled_contour__doc__}, - {NULL} - }; - - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.QuadContourGenerator"; - type->tp_doc = PyQuadContourGenerator_init__doc__; - type->tp_basicsize = sizeof(PyQuadContourGenerator); - type->tp_dealloc = (destructor)PyQuadContourGenerator_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT; - type->tp_methods = methods; - type->tp_new = PyQuadContourGenerator_new; - type->tp_init = (initproc)PyQuadContourGenerator_init; - - if (PyType_Ready(type) < 0) { - return NULL; - } - - if (PyModule_AddObject(m, "QuadContourGenerator", (PyObject*)type)) { - return NULL; - } - - return type; -} - - -/* Module */ - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_contour", - NULL, - 0, - NULL, - NULL, - NULL, - NULL, - NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__contour(void) -{ - PyObject *m; - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - if (!PyQuadContourGenerator_init_type(m, &PyQuadContourGeneratorType)) { - return NULL; - } - - import_array(); - - return m; -} - -#pragma GCC visibility pop diff --git a/src/_enums.h b/src/_enums.h new file mode 100644 index 000000000000..18f3d9aac9fa --- /dev/null +++ b/src/_enums.h @@ -0,0 +1,95 @@ +#ifndef MPL_ENUMS_H +#define MPL_ENUMS_H + +#include + +// Extension for pybind11: Pythonic enums. +// This allows creating classes based on ``enum.*`` types. +// This code was copied from mplcairo, with some slight tweaks. +// The API is: +// +// - P11X_DECLARE_ENUM(py_name: str, py_base_cls: str, ...: {str, enum value}): +// py_name: The name to expose in the module. +// py_base_cls: The name of the enum base class to use. +// ...: The enum name/value pairs to expose. +// +// Use this macro to declare an enum and its values. +// +// - py11x::bind_enums(m: pybind11::module): +// m: The module to use to register the enum classes. +// +// Place this in PYBIND11_MODULE to register the enums declared by P11X_DECLARE_ENUM. + +// a1 includes the opening brace and a2 the closing brace. +// This definition is compatible with older compiler versions compared to +// #define P11X_ENUM_TYPE(...) decltype(std::map{std::pair __VA_ARGS__})::mapped_type +#define P11X_ENUM_TYPE(a1, a2, ...) decltype(std::pair a1, a2)::second_type + +#define P11X_CAT2(a, b) a##b +#define P11X_CAT(a, b) P11X_CAT2(a, b) + +namespace p11x { + namespace { + namespace py = pybind11; + + // Holder is (py_base_cls, [(name, value), ...]) before module init; + // converted to the Python class object after init. + auto enums = std::unordered_map{}; + + auto bind_enums(py::module mod) -> void + { + for (auto& [py_name, spec]: enums) { + auto const& [py_base_cls, pairs] = + spec.cast>(); + mod.attr(py::cast(py_name)) = spec = + py::module::import("enum").attr(py_base_cls.c_str())( + py_name, pairs, py::arg("module") = mod.attr("__name__")); + } + } + } +} + +// Immediately converting the args to a vector outside of the lambda avoids +// name collisions. +#define P11X_DECLARE_ENUM(py_name, py_base_cls, ...) \ + namespace p11x { \ + namespace { \ + [[maybe_unused]] auto const P11X_CAT(enum_placeholder_, __COUNTER__) = \ + [](auto args) { \ + py::gil_scoped_acquire gil; \ + using int_t = std::underlying_type_t; \ + auto pairs = std::vector>{}; \ + for (auto& [k, v]: args) { \ + pairs.emplace_back(k, int_t(v)); \ + } \ + p11x::enums[py_name] = pybind11::cast(std::pair{py_base_cls, pairs}); \ + return 0; \ + } (std::vector{std::pair __VA_ARGS__}); \ + } \ + } \ + namespace pybind11::detail { \ + template<> struct type_caster { \ + using type = P11X_ENUM_TYPE(__VA_ARGS__); \ + static_assert(std::is_enum_v, "Not an enum"); \ + PYBIND11_TYPE_CASTER(type, _(py_name)); \ + bool load(handle src, bool) { \ + auto cls = p11x::enums.at(py_name); \ + PyObject* tmp = nullptr; \ + if (pybind11::isinstance(src, cls) \ + && (tmp = PyNumber_Index(src.attr("value").ptr()))) { \ + auto ival = PyLong_AsLong(tmp); \ + value = decltype(value)(ival); \ + Py_DECREF(tmp); \ + return !(ival == -1 && !PyErr_Occurred()); \ + } else { \ + return false; \ + } \ + } \ + static handle cast(decltype(value) obj, return_value_policy, handle) { \ + auto cls = p11x::enums.at(py_name); \ + return cls(std::underlying_type_t(obj)).inc_ref(); \ + } \ + }; \ + } + +#endif /* MPL_ENUMS_H */ diff --git a/src/_image.cpp b/src/_image.cpp deleted file mode 100644 index 28e509a4a445..000000000000 --- a/src/_image.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#define NO_IMPORT_ARRAY - -#include - -// utilities for irregular grids -void _bin_indices_middle( - unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min) -{ - int i, j, j_last; - unsigned int *rowstart = irows; - const float *ys2 = ys1 + 1; - const float *yl = ys1 + ny; - float yo = y_min + dy / 2.0f; - float ym = 0.5f * (*ys1 + *ys2); - // y/rows - j = 0; - j_last = j; - for (i = 0; i < nrows; i++, yo += dy, rowstart++) { - while (ys2 != yl && yo > ym) { - ys1 = ys2; - ys2 = ys1 + 1; - ym = 0.5f * (*ys1 + *ys2); - j++; - } - *rowstart = j - j_last; - j_last = j; - } -} - -void _bin_indices_middle_linear(float *arows, - unsigned int *irows, - int nrows, - const float *y, - unsigned long ny, - float dy, - float y_min) -{ - int i; - int ii = 0; - int iilast = (int)ny - 1; - float sc = 1 / dy; - int iy0 = (int)floor(sc * (y[ii] - y_min)); - int iy1 = (int)floor(sc * (y[ii + 1] - y_min)); - float invgap = 1.0f / (iy1 - iy0); - for (i = 0; i < nrows && i <= iy0; i++) { - irows[i] = 0; - arows[i] = 1.0; - } - for (; i < nrows; i++) { - while (i > iy1 && ii < iilast) { - ii++; - iy0 = iy1; - iy1 = (int)floor(sc * (y[ii + 1] - y_min)); - invgap = 1.0f / (iy1 - iy0); - } - if (i >= iy0 && i <= iy1) { - irows[i] = ii; - arows[i] = (iy1 - i) * invgap; - } else - break; - } - for (; i < nrows; i++) { - irows[i] = iilast - 1; - arows[i] = 0.0; - } -} - -void _bin_indices(int *irows, int nrows, const double *y, unsigned long ny, double sc, double offs) -{ - int i; - if (sc * (y[ny - 1] - y[0]) > 0) { - int ii = 0; - int iilast = (int)ny - 1; - int iy0 = (int)floor(sc * (y[ii] - offs)); - int iy1 = (int)floor(sc * (y[ii + 1] - offs)); - for (i = 0; i < nrows && i < iy0; i++) { - irows[i] = -1; - } - for (; i < nrows; i++) { - while (i > iy1 && ii < iilast) { - ii++; - iy0 = iy1; - iy1 = (int)floor(sc * (y[ii + 1] - offs)); - } - if (i >= iy0 && i <= iy1) - irows[i] = ii; - else - break; - } - for (; i < nrows; i++) { - irows[i] = -1; - } - } else { - int iilast = (int)ny - 1; - int ii = iilast; - int iy0 = (int)floor(sc * (y[ii] - offs)); - int iy1 = (int)floor(sc * (y[ii - 1] - offs)); - for (i = 0; i < nrows && i < iy0; i++) { - irows[i] = -1; - } - for (; i < nrows; i++) { - while (i > iy1 && ii > 1) { - ii--; - iy0 = iy1; - iy1 = (int)floor(sc * (y[ii - 1] - offs)); - } - if (i >= iy0 && i <= iy1) - irows[i] = ii - 1; - else - break; - } - for (; i < nrows; i++) { - irows[i] = -1; - } - } -} diff --git a/src/_image.h b/src/_image.h deleted file mode 100644 index 37a080fff1d4..000000000000 --- a/src/_image.h +++ /dev/null @@ -1,198 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -/* image.h - * - */ - -#ifndef MPL_IMAGE_H -#define MPL_IMAGE_H - -#include - - -// utilities for irregular grids -void _bin_indices_middle( - unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min); -void _bin_indices_middle_linear(float *arows, - unsigned int *irows, - int nrows, - const float *y, - unsigned long ny, - float dy, - float y_min); -void _bin_indices(int *irows, int nrows, const double *y, unsigned long ny, double sc, double offs); - -template -void pcolor(CoordinateArray &x, - CoordinateArray &y, - ColorArray &d, - unsigned int rows, - unsigned int cols, - float bounds[4], - int interpolation, - OutputArray &out) -{ - if (rows >= 32768 || cols >= 32768) { - throw std::runtime_error("rows and cols must both be less than 32768"); - } - - float x_min = bounds[0]; - float x_max = bounds[1]; - float y_min = bounds[2]; - float y_max = bounds[3]; - float width = x_max - x_min; - float height = y_max - y_min; - float dx = width / ((float)cols); - float dy = height / ((float)rows); - - // Check we have something to output to - if (rows == 0 || cols == 0) { - throw std::runtime_error("Cannot scale to zero size"); - } - - if (d.dim(2) != 4) { - throw std::runtime_error("data must be in RGBA format"); - } - - // Check dimensions match - unsigned long nx = x.dim(0); - unsigned long ny = y.dim(0); - if (nx != (unsigned long)d.dim(1) || ny != (unsigned long)d.dim(0)) { - throw std::runtime_error("data and axis dimensions do not match"); - } - - // Allocate memory for pointer arrays - std::vector rowstarts(rows); - std::vector colstarts(cols); - - // Calculate the pointer arrays to map input x to output x - unsigned int i, j; - unsigned int *colstart = &colstarts[0]; - unsigned int *rowstart = &rowstarts[0]; - const float *xs1 = x.data(); - const float *ys1 = y.data(); - - // Copy data to output buffer - const unsigned char *start; - const unsigned char *inposition; - size_t inrowsize = nx * 4; - size_t rowsize = cols * 4; - unsigned char *position = (unsigned char *)out.data(); - unsigned char *oldposition = NULL; - start = d.data(); - - if (interpolation == NEAREST) { - _bin_indices_middle(colstart, cols, xs1, nx, dx, x_min); - _bin_indices_middle(rowstart, rows, ys1, ny, dy, y_min); - for (i = 0; i < rows; i++, rowstart++) { - if (i > 0 && *rowstart == 0) { - memcpy(position, oldposition, rowsize * sizeof(unsigned char)); - oldposition = position; - position += rowsize; - } else { - oldposition = position; - start += *rowstart * inrowsize; - inposition = start; - for (j = 0, colstart = &colstarts[0]; j < cols; j++, position += 4, colstart++) { - inposition += *colstart * 4; - memcpy(position, inposition, 4 * sizeof(unsigned char)); - } - } - } - } else if (interpolation == BILINEAR) { - std::vector acols(cols); - std::vector arows(rows); - - _bin_indices_middle_linear(&acols[0], colstart, cols, xs1, nx, dx, x_min); - _bin_indices_middle_linear(&arows[0], rowstart, rows, ys1, ny, dy, y_min); - double a00, a01, a10, a11, alpha, beta; - - // Copy data to output buffer - for (i = 0; i < rows; i++) { - for (j = 0; j < cols; j++) { - alpha = arows[i]; - beta = acols[j]; - - a00 = alpha * beta; - a01 = alpha * (1.0 - beta); - a10 = (1.0 - alpha) * beta; - a11 = 1.0 - a00 - a01 - a10; - - for (size_t k = 0; k < 4; ++k) { - position[k] = - d(rowstart[i], colstart[j], k) * a00 + - d(rowstart[i], colstart[j] + 1, k) * a01 + - d(rowstart[i] + 1, colstart[j], k) * a10 + - d(rowstart[i] + 1, colstart[j] + 1, k) * a11; - } - position += 4; - } - } - } -} - -template -void pcolor2(CoordinateArray &x, - CoordinateArray &y, - ColorArray &d, - unsigned int rows, - unsigned int cols, - float bounds[4], - Color &bg, - OutputArray &out) -{ - double x_left = bounds[0]; - double x_right = bounds[1]; - double y_bot = bounds[2]; - double y_top = bounds[3]; - - // Check we have something to output to - if (rows == 0 || cols == 0) { - throw std::runtime_error("rows or cols is zero; there are no pixels"); - } - - if (d.dim(2) != 4) { - throw std::runtime_error("data must be in RGBA format"); - } - - // Check dimensions match - unsigned long nx = x.dim(0); - unsigned long ny = y.dim(0); - if (nx != (unsigned long)d.dim(1) + 1 || ny != (unsigned long)d.dim(0) + 1) { - throw std::runtime_error("data and axis bin boundary dimensions are incompatible"); - } - - if (bg.dim(0) != 4) { - throw std::runtime_error("bg must be in RGBA format"); - } - - std::vector irows(rows); - std::vector jcols(cols); - - // Calculate the pointer arrays to map input x to output x - size_t i, j; - const double *x0 = x.data(); - const double *y0 = y.data(); - double sx = cols / (x_right - x_left); - double sy = rows / (y_top - y_bot); - _bin_indices(&jcols[0], cols, x0, nx, sx, x_left); - _bin_indices(&irows[0], rows, y0, ny, sy, y_bot); - - // Copy data to output buffer - unsigned char *position = (unsigned char *)out.data(); - - for (i = 0; i < rows; i++) { - for (j = 0; j < cols; j++) { - if (irows[i] == -1 || jcols[j] == -1) { - memcpy(position, (const unsigned char *)bg.data(), 4 * sizeof(unsigned char)); - } else { - for (size_t k = 0; k < 4; ++k) { - position[k] = d(irows[i], jcols[j], k); - } - } - position += 4; - } - } -} - -#endif diff --git a/src/_image_resample.h b/src/_image_resample.h index a508afa33ee4..7e6c32c6bf64 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -3,10 +3,11 @@ #ifndef MPL_RESAMPLE_H #define MPL_RESAMPLE_H +#define MPL_DISABLE_AGG_GRAY_CLIPPING + #include "agg_image_accessors.h" #include "agg_path_storage.h" #include "agg_pixfmt_gray.h" -#include "agg_pixfmt_rgb.h" #include "agg_pixfmt_rgba.h" #include "agg_renderer_base.h" #include "agg_renderer_scanline.h" @@ -25,7 +26,7 @@ //---------------------------------------------------------------------------- // Anti-Grain Geometry - Version 2.4 -// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com) +// Copyright (C) 2002-2005 Maxim Shemanarev (http://antigrain.com/) // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. @@ -35,7 +36,7 @@ //---------------------------------------------------------------------------- // Contact: mcseem@antigrain.com // mcseemagg@yahoo.com -// http://www.antigrain.com +// http://antigrain.com/ //---------------------------------------------------------------------------- // // Adaptation for high precision colors has been sponsored by @@ -59,20 +60,16 @@ namespace agg value_type a; //-------------------------------------------------------------------- - gray64() {} + gray64() = default; //-------------------------------------------------------------------- - explicit gray64(value_type v_, value_type a_ = 1) : - v(v_), a(a_) {} + explicit gray64(value_type v_, value_type a_ = 1) : v(v_), a(a_) {} //-------------------------------------------------------------------- - gray64(const self_type& c, value_type a_) : - v(c.v), a(a_) {} + gray64(const self_type& c, value_type a_) : v(c.v), a(a_) {} //-------------------------------------------------------------------- - gray64(const gray64& c) : - v(c.v), - a(c.a) {} + gray64(const gray64& c) = default; //-------------------------------------------------------------------- static AGG_INLINE double to_double(value_type a) @@ -245,7 +242,7 @@ namespace agg value_type a; //-------------------------------------------------------------------- - rgba64() {} + rgba64() = default; //-------------------------------------------------------------------- rgba64(value_type r_, value_type g_, value_type b_, value_type a_= 1) : @@ -496,244 +493,51 @@ typedef enum { SINC, LANCZOS, BLACKMAN, - _n_interpolation } interpolation_e; -template -class type_mapping; - - -template <> class type_mapping -{ - public: - typedef agg::rgba8 color_type; - typedef fixed_blender_rgba_plain blender_type; - typedef fixed_blender_rgba_pre pre_blender_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_rgba_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_rgba type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_rgba_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::rgba16 color_type; - typedef fixed_blender_rgba_plain blender_type; - typedef fixed_blender_rgba_pre pre_blender_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_rgba_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_rgba type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_rgba_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::rgba32 color_type; - typedef agg::blender_rgba_plain blender_type; - typedef agg::blender_rgba_pre pre_blender_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_rgba_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_rgba type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_rgba_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::rgba64 color_type; - typedef agg::blender_rgba_plain blender_type; - typedef agg::blender_rgba_pre pre_blender_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_type; - typedef agg::pixfmt_alpha_blend_rgba pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_rgba_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_rgba type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_rgba_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::gray64 color_type; - typedef agg::blender_gray blender_type; - typedef agg::pixfmt_alpha_blend_gray pixfmt_type; - typedef pixfmt_type pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_gray_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_gray type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_gray_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::gray32 color_type; - typedef agg::blender_gray blender_type; - typedef agg::pixfmt_alpha_blend_gray pixfmt_type; - typedef pixfmt_type pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_gray_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_gray type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_gray_nn type; - }; -}; - - -template <> class type_mapping -{ - public: - typedef agg::gray16 color_type; - typedef agg::blender_gray blender_type; - typedef agg::pixfmt_alpha_blend_gray pixfmt_type; - typedef pixfmt_type pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_gray_affine type; - }; +// T is rgba if and only if it has an T::r field. +template struct is_grayscale : std::true_type {}; +template struct is_grayscale> : std::false_type {}; +template constexpr bool is_grayscale_v = is_grayscale::value; - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_gray type; - }; - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_gray_nn type; - }; -}; - - -template <> class type_mapping +template +struct type_mapping { - public: - typedef agg::gray8 color_type; - typedef agg::blender_gray blender_type; - typedef agg::pixfmt_alpha_blend_gray pixfmt_type; - typedef pixfmt_type pixfmt_pre_type; - - template - struct span_gen_affine_type - { - typedef agg::span_image_resample_gray_affine type; - }; - - template - struct span_gen_filter_type - { - typedef agg::span_image_filter_gray type; - }; - - template - struct span_gen_nn_type - { - typedef agg::span_image_filter_gray_nn type; - }; + using blender_type = std::conditional_t< + is_grayscale_v, + agg::blender_gray, + std::conditional_t< + std::is_same_v, + fixed_blender_rgba_plain, + agg::blender_rgba_plain + > + >; + using pixfmt_type = std::conditional_t< + is_grayscale_v, + agg::pixfmt_alpha_blend_gray, + agg::pixfmt_alpha_blend_rgba + >; + template using span_gen_affine_type = std::conditional_t< + is_grayscale_v, + agg::span_image_resample_gray_affine, + agg::span_image_resample_rgba_affine + >; + template using span_gen_filter_type = std::conditional_t< + is_grayscale_v, + agg::span_image_filter_gray, + agg::span_image_filter_rgba + >; + template using span_gen_nn_type = std::conditional_t< + is_grayscale_v, + agg::span_image_filter_gray_nn, + agg::span_image_filter_rgba_nn + >; }; - -template +template class span_conv_alpha { public: @@ -748,7 +552,8 @@ class span_conv_alpha { if (m_alpha != 1.0) { do { - span->a *= m_alpha; + span->a = static_cast( + static_cast(span->a) * m_alpha); ++span; } while (--len); } @@ -811,7 +616,6 @@ static void get_filter(const resample_params_t ¶ms, { switch (params.interpolation) { case NEAREST: - case _n_interpolation: // Never should get here. Here to silence compiler warnings. break; @@ -882,29 +686,35 @@ static void get_filter(const resample_params_t ¶ms, } -template +template void resample( - const T *input, int in_width, int in_height, - T *output, int out_width, int out_height, + const void *input, int in_width, int in_height, + void *output, int out_width, int out_height, resample_params_t ¶ms) { - typedef type_mapping type_mapping_t; + using type_mapping_t = type_mapping; - typedef typename type_mapping_t::pixfmt_type input_pixfmt_t; - typedef typename type_mapping_t::pixfmt_type output_pixfmt_t; + using input_pixfmt_t = typename type_mapping_t::pixfmt_type; + using output_pixfmt_t = typename type_mapping_t::pixfmt_type; - typedef agg::renderer_base renderer_t; - typedef agg::rasterizer_scanline_aa rasterizer_t; + using renderer_t = agg::renderer_base; + using rasterizer_t = agg::rasterizer_scanline_aa; + using scanline_t = agg::scanline32_u8; - typedef agg::wrap_mode_reflect reflect_t; - typedef agg::image_accessor_wrap image_accessor_t; + using reflect_t = agg::wrap_mode_reflect; + using image_accessor_t = agg::image_accessor_wrap; - typedef agg::span_allocator span_alloc_t; - typedef span_conv_alpha span_conv_alpha_t; + using span_alloc_t = agg::span_allocator; + using span_conv_alpha_t = span_conv_alpha; - typedef agg::span_interpolator_linear<> affine_interpolator_t; - typedef agg::span_interpolator_adaptor, lookup_distortion> - arbitrary_interpolator_t; + using affine_interpolator_t = agg::span_interpolator_linear<>; + using arbitrary_interpolator_t = + agg::span_interpolator_adaptor, lookup_distortion>; + + size_t itemsize = sizeof(color_type); + if (is_grayscale::value) { + itemsize /= 2; // agg::grayXX includes an alpha channel which we don't have. + } if (params.interpolation != NEAREST && params.is_affine && @@ -917,19 +727,19 @@ void resample( span_alloc_t span_alloc; rasterizer_t rasterizer; - agg::scanline_u8 scanline; + scanline_t scanline; span_conv_alpha_t conv_alpha(params.alpha); agg::rendering_buffer input_buffer; - input_buffer.attach((unsigned char *)input, in_width, in_height, - in_width * sizeof(T)); + input_buffer.attach( + (unsigned char *)input, in_width, in_height, in_width * itemsize); input_pixfmt_t input_pixfmt(input_buffer); image_accessor_t input_accessor(input_pixfmt); agg::rendering_buffer output_buffer; - output_buffer.attach((unsigned char *)output, out_width, out_height, - out_width * sizeof(T)); + output_buffer.attach( + (unsigned char *)output, out_width, out_height, out_width * itemsize); output_pixfmt_t output_pixfmt(output_buffer); renderer_t renderer(output_pixfmt); @@ -958,20 +768,18 @@ void resample( if (params.interpolation == NEAREST) { if (params.is_affine) { - typedef typename type_mapping_t::template span_gen_nn_type::type span_gen_t; - typedef agg::span_converter span_conv_t; - typedef agg::renderer_scanline_aa nn_renderer_t; - + using span_gen_t = typename type_mapping_t::template span_gen_nn_type; + using span_conv_t = agg::span_converter; + using nn_renderer_t = agg::renderer_scanline_aa; affine_interpolator_t interpolator(inverted); span_gen_t span_gen(input_accessor, interpolator); span_conv_t span_conv(span_gen, conv_alpha); nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, nn_renderer); } else { - typedef typename type_mapping_t::template span_gen_nn_type::type span_gen_t; - typedef agg::span_converter span_conv_t; - typedef agg::renderer_scanline_aa nn_renderer_t; - + using span_gen_t = typename type_mapping_t::template span_gen_nn_type; + using span_conv_t = agg::span_converter; + using nn_renderer_t = agg::renderer_scanline_aa; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height); arbitrary_interpolator_t interpolator(inverted, dist); @@ -985,20 +793,18 @@ void resample( get_filter(params, filter); if (params.is_affine && params.resample) { - typedef typename type_mapping_t::template span_gen_affine_type::type span_gen_t; - typedef agg::span_converter span_conv_t; - typedef agg::renderer_scanline_aa int_renderer_t; - + using span_gen_t = typename type_mapping_t::template span_gen_affine_type; + using span_conv_t = agg::span_converter; + using int_renderer_t = agg::renderer_scanline_aa; affine_interpolator_t interpolator(inverted); span_gen_t span_gen(input_accessor, interpolator, filter); span_conv_t span_conv(span_gen, conv_alpha); int_renderer_t int_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, int_renderer); } else { - typedef typename type_mapping_t::template span_gen_filter_type::type span_gen_t; - typedef agg::span_converter span_conv_t; - typedef agg::renderer_scanline_aa int_renderer_t; - + using span_gen_t = typename type_mapping_t::template span_gen_filter_type; + using span_conv_t = agg::span_converter; + using int_renderer_t = agg::renderer_scanline_aa; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height); arbitrary_interpolator_t interpolator(inverted, dist); diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 24fc733cc0ae..87d2b3b288ec 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -1,497 +1,237 @@ -#include "mplutils.h" +#include +#include + #include "_image_resample.h" -#include "_image.h" -#include "numpy_cpp.h" #include "py_converters.h" +namespace py = pybind11; +using namespace pybind11::literals; /********************************************************************** * Free functions * */ const char* image_resample__doc__ = -"resample(input_array, output_array, matrix, interpolation=NEAREST, alpha=1.0, norm=False, radius=1)\n\n" - -"Resample input_array, blending it in-place into output_array, using an\n" -"affine transformation.\n\n" +R"""(Resample input_array, blending it in-place into output_array, using an affine transform. -"Parameters\n" -"----------\n" -"input_array : 2-d or 3-d Numpy array of float, double or uint8\n" -" If 2-d, the image is grayscale. If 3-d, the image must be of size\n" -" 4 in the last dimension and represents RGBA data.\n\n" +Parameters +---------- +input_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8` + If 2-d, the image is grayscale. If 3-d, the image must be of size 4 in the last + dimension and represents RGBA data. -"output_array : 2-d or 3-d Numpy array of float, double or uint8\n" -" The dtype and number of dimensions must match `input_array`.\n\n" +output_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8` + The dtype and number of dimensions must match `input_array`. -"transform : matplotlib.transforms.Transform instance\n" -" The transformation from the input array to the output\n" -" array.\n\n" +transform : matplotlib.transforms.Transform instance + The transformation from the input array to the output array. -"interpolation : int, optional\n" -" The interpolation method. Must be one of the following constants\n" -" defined in this module:\n\n" +interpolation : int, default: NEAREST + The interpolation method. Must be one of the following constants defined in this + module: -" NEAREST (default), BILINEAR, BICUBIC, SPLINE16, SPLINE36,\n" -" HANNING, HAMMING, HERMITE, KAISER, QUADRIC, CATROM, GAUSSIAN,\n" -" BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN\n\n" + NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36, HANNING, HAMMING, HERMITE, KAISER, + QUADRIC, CATROM, GAUSSIAN, BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN -"resample : bool, optional\n" -" When `True`, use a full resampling method. When `False`, only\n" -" resample when the output image is larger than the input image.\n\n" +resample : bool, optional + When `True`, use a full resampling method. When `False`, only resample when the + output image is larger than the input image. -"alpha : float, optional\n" -" The level of transparency to apply. 1.0 is completely opaque.\n" -" 0.0 is completely transparent.\n\n" +alpha : float, default: 1 + The transparency level, from 0 (transparent) to 1 (opaque). -"norm : bool, optional\n" -" Whether to norm the interpolation function. Default is `False`.\n\n" +norm : bool, default: False + Whether to norm the interpolation function. -"radius: float, optional\n" -" The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n" -" Default is 1.\n"; +radius: float, default: 1 + The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN. +)"""; -static PyArrayObject * -_get_transform_mesh(PyObject *py_affine, npy_intp *dims) +static py::array_t +_get_transform_mesh(const py::object& transform, const py::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ /* Given a non-affine transform object, create a mesh that maps - every pixel in the output image to the input image. This is used + every pixel center in the output image to the input image. This is used as a lookup table during the actual resampling. */ - PyObject *py_inverse = NULL; - npy_intp out_dims[3]; + // If attribute doesn't exist, raises Python AttributeError + auto inverse = transform.attr("inverted")(); - out_dims[0] = dims[0] * dims[1]; - out_dims[1] = 2; - - py_inverse = PyObject_CallMethod(py_affine, "inverted", NULL); - if (py_inverse == NULL) { - return NULL; - } + py::ssize_t mesh_dims[2] = {dims[0]*dims[1], 2}; + py::array_t input_mesh(mesh_dims); + auto p = input_mesh.mutable_data(); - numpy::array_view input_mesh(out_dims); - double *p = (double *)input_mesh.data(); - - for (npy_intp y = 0; y < dims[0]; ++y) { - for (npy_intp x = 0; x < dims[1]; ++x) { - *p++ = (double)x; - *p++ = (double)y; + for (auto y = 0; y < dims[0]; ++y) { + for (auto x = 0; x < dims[1]; ++x) { + // The convention for the supplied transform is that pixel centers + // are at 0.5, 1.5, 2.5, etc. + *p++ = (double)x + 0.5; + *p++ = (double)y + 0.5; } } - PyObject *output_mesh = PyObject_CallMethod( - py_inverse, "transform", "O", input_mesh.pyobj_steal()); - - Py_DECREF(py_inverse); - - if (output_mesh == NULL) { - return NULL; - } - - PyArrayObject *output_mesh_array = - (PyArrayObject *)PyArray_ContiguousFromAny( - output_mesh, NPY_DOUBLE, 2, 2); + auto output_mesh = inverse.attr("transform")(input_mesh); - Py_DECREF(output_mesh); + auto output_mesh_array = + py::array_t(output_mesh); - if (output_mesh_array == NULL) { - return NULL; + if (output_mesh_array.ndim() != 2) { + throw std::runtime_error( + "Inverse transformed mesh array should be 2D not {}D"_s.format( + output_mesh_array.ndim())); } return output_mesh_array; } -static PyObject * -image_resample(PyObject *self, PyObject* args, PyObject *kwargs) +// Using generic py::array for input and output arrays rather than the more usual +// py::array_t as this function supports multiple array dtypes. +static void +image_resample(py::array input_array, + py::array& output_array, + const py::object& transform, + interpolation_e interpolation, + bool resample_, // Avoid name clash with resample() function + float alpha, + bool norm, + float radius) { - PyObject *py_input_array = NULL; - PyObject *py_output_array = NULL; - PyObject *py_transform = NULL; - resample_params_t params; + // Validate input_array + auto dtype = input_array.dtype(); // Validated when determine resampler below + auto ndim = input_array.ndim(); - PyArrayObject *input_array = NULL; - PyArrayObject *output_array = NULL; - PyArrayObject *transform_mesh_array = NULL; - - params.interpolation = NEAREST; - params.transform_mesh = NULL; - params.resample = false; - params.norm = false; - params.radius = 1.0; - params.alpha = 1.0; - - const char *kwlist[] = { - "input_array", "output_array", "transform", "interpolation", - "resample", "alpha", "norm", "radius", NULL }; - - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "OOO|iO&dO&d:resample", (char **)kwlist, - &py_input_array, &py_output_array, &py_transform, - ¶ms.interpolation, &convert_bool, ¶ms.resample, - ¶ms.alpha, &convert_bool, ¶ms.norm, ¶ms.radius)) { - return NULL; + if (ndim != 2 && ndim != 3) { + throw std::invalid_argument("Input array must be a 2D or 3D array"); } - if (params.interpolation < 0 || params.interpolation >= _n_interpolation) { - PyErr_Format(PyExc_ValueError, "invalid interpolation value %d", - params.interpolation); - goto error; + if (ndim == 3 && input_array.shape(2) != 4) { + throw std::invalid_argument( + "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of {}"_s.format( + input_array.shape(2))); } - input_array = (PyArrayObject *)PyArray_FromAny( - py_input_array, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL); - if (input_array == NULL) { - goto error; - } + // Ensure input array is contiguous, regardless of dtype + input_array = py::array::ensure(input_array, py::array::c_style); - if (!PyArray_Check(py_output_array)) { - PyErr_SetString(PyExc_ValueError, "output array must be a NumPy array"); - goto error; - } - output_array = (PyArrayObject *)py_output_array; - if (!PyArray_IS_C_CONTIGUOUS(output_array)) { - PyErr_SetString(PyExc_ValueError, "output array must be C-contiguous"); - goto error; - } - if (PyArray_NDIM(output_array) < 2 || PyArray_NDIM(output_array) > 3) { - PyErr_SetString(PyExc_ValueError, - "output array must be 2- or 3-dimensional"); - goto error; - } + // Validate output array + auto out_ndim = output_array.ndim(); - if (py_transform == NULL || py_transform == Py_None) { - params.is_affine = true; - } else { - PyObject *py_is_affine; - int py_is_affine2; - py_is_affine = PyObject_GetAttrString(py_transform, "is_affine"); - if (py_is_affine == NULL) { - goto error; - } + if (out_ndim != ndim) { + throw std::invalid_argument( + "Input ({}D) and output ({}D) arrays have different dimensionalities"_s.format( + ndim, out_ndim)); + } - py_is_affine2 = PyObject_IsTrue(py_is_affine); - Py_DECREF(py_is_affine); + if (out_ndim == 3 && output_array.shape(2) != 4) { + throw std::invalid_argument( + "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of {}"_s.format( + output_array.shape(2))); + } - if (py_is_affine2 == -1) { - goto error; - } else if (py_is_affine2) { - if (!convert_trans_affine(py_transform, ¶ms.affine)) { - goto error; - } - params.is_affine = true; - } else { - transform_mesh_array = _get_transform_mesh( - py_transform, PyArray_DIMS(output_array)); - if (transform_mesh_array == NULL) { - goto error; - } - params.transform_mesh = (double *)PyArray_DATA(transform_mesh_array); - params.is_affine = false; - } + if (!output_array.dtype().is(dtype)) { + throw std::invalid_argument("Input and output arrays have mismatched types"); } - if (PyArray_NDIM(input_array) != PyArray_NDIM(output_array)) { - PyErr_Format( - PyExc_ValueError, - "Mismatched number of dimensions. Got %d and %d.", - PyArray_NDIM(input_array), PyArray_NDIM(output_array)); - goto error; + if ((output_array.flags() & py::array::c_style) == 0) { + throw std::invalid_argument("Output array must be C-contiguous"); } - if (PyArray_TYPE(input_array) != PyArray_TYPE(output_array)) { - PyErr_SetString(PyExc_ValueError, "Mismatched types"); - goto error; + if (!output_array.writeable()) { + throw std::invalid_argument("Output array must be writeable"); } - if (PyArray_NDIM(input_array) == 3) { - if (PyArray_DIM(output_array, 2) != 4) { - PyErr_SetString( - PyExc_ValueError, - "Output array must be RGBA"); - goto error; - } + resample_params_t params; + params.interpolation = interpolation; + params.transform_mesh = nullptr; + params.resample = resample_; + params.norm = norm; + params.radius = radius; + params.alpha = alpha; + + // Only used if transform is not affine. + // Need to keep it in scope for the duration of this function. + py::array_t transform_mesh; + + // Validate transform + if (transform.is_none()) { + params.is_affine = true; + } else { + // Raises Python AttributeError if no such attribute or TypeError if cast fails + bool is_affine = py::cast(transform.attr("is_affine")); - if (PyArray_DIM(input_array, 2) == 4) { - switch(PyArray_TYPE(input_array)) { - case NPY_BYTE: - case NPY_UINT8: - Py_BEGIN_ALLOW_THREADS - resample( - (agg::rgba8 *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (agg::rgba8 *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_UINT16: - case NPY_INT16: - Py_BEGIN_ALLOW_THREADS - resample( - (agg::rgba16 *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (agg::rgba16 *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_FLOAT32: - Py_BEGIN_ALLOW_THREADS - resample( - (agg::rgba32 *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (agg::rgba32 *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_FLOAT64: - Py_BEGIN_ALLOW_THREADS - resample( - (agg::rgba64 *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (agg::rgba64 *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - default: - PyErr_SetString( - PyExc_ValueError, - "3-dimensional arrays must be of dtype unsigned byte, " - "unsigned short, float32 or float64"); - goto error; - } + if (is_affine) { + convert_trans_affine(transform, params.affine); + params.is_affine = true; } else { - PyErr_Format( - PyExc_ValueError, - "If 3-dimensional, array must be RGBA. Got %" NPY_INTP_FMT " planes.", - PyArray_DIM(input_array, 2)); - goto error; - } - } else { // NDIM == 2 - switch (PyArray_TYPE(input_array)) { - case NPY_DOUBLE: - Py_BEGIN_ALLOW_THREADS - resample( - (double *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (double *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_FLOAT: - Py_BEGIN_ALLOW_THREADS - resample( - (float *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (float *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_UINT8: - case NPY_BYTE: - Py_BEGIN_ALLOW_THREADS - resample( - (unsigned char *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (unsigned char *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - case NPY_UINT16: - case NPY_INT16: - Py_BEGIN_ALLOW_THREADS - resample( - (unsigned short *)PyArray_DATA(input_array), - PyArray_DIM(input_array, 1), - PyArray_DIM(input_array, 0), - (unsigned short *)PyArray_DATA(output_array), - PyArray_DIM(output_array, 1), - PyArray_DIM(output_array, 0), - params); - Py_END_ALLOW_THREADS - break; - default: - PyErr_SetString(PyExc_ValueError, "Unsupported dtype"); - goto error; + transform_mesh = _get_transform_mesh(transform, output_array.shape()); + params.transform_mesh = transform_mesh.data(); + params.is_affine = false; } } - Py_DECREF(input_array); - Py_XDECREF(transform_mesh_array); - Py_RETURN_NONE; - - error: - Py_XDECREF(input_array); - Py_XDECREF(transform_mesh_array); - return NULL; -} - - -const char *image_pcolor__doc__ = - "pcolor(x, y, data, rows, cols, bounds)\n" - "\n" - "Generate a pseudo-color image from data on a non-uniform grid using\n" - "nearest neighbour or linear interpolation.\n" - "bounds = (x_min, x_max, y_min, y_max)\n" - "interpolation = NEAREST or BILINEAR \n"; - -static PyObject *image_pcolor(PyObject *self, PyObject *args, PyObject *kwds) -{ - numpy::array_view x; - numpy::array_view y; - numpy::array_view d; - npy_intp rows, cols; - float bounds[4]; - int interpolation; - - if (!PyArg_ParseTuple(args, - "O&O&O&nn(ffff)i:pcolor", - &x.converter, - &x, - &y.converter, - &y, - &d.converter_contiguous, - &d, - &rows, - &cols, - &bounds[0], - &bounds[1], - &bounds[2], - &bounds[3], - &interpolation)) { - return NULL; + if (auto resampler = + (ndim == 2) ? ( + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + nullptr) : ( + // ndim == 3 + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + nullptr)) { + Py_BEGIN_ALLOW_THREADS + resampler( + input_array.data(), input_array.shape(1), input_array.shape(0), + output_array.mutable_data(), output_array.shape(1), output_array.shape(0), + params); + Py_END_ALLOW_THREADS + } else { + throw std::invalid_argument("arrays must be of dtype byte, short, float32 or float64"); } - - npy_intp dim[3] = {rows, cols, 4}; - numpy::array_view output(dim); - - CALL_CPP("pcolor", (pcolor(x, y, d, rows, cols, bounds, interpolation, output))); - - return output.pyobj(); } -const char *image_pcolor2__doc__ = - "pcolor2(x, y, data, rows, cols, bounds, bg)\n" - "\n" - "Generate a pseudo-color image from data on a non-uniform grid\n" - "specified by its cell boundaries.\n" - "bounds = (x_left, x_right, y_bot, y_top)\n" - "bg = ndarray of 4 uint8 representing background rgba\n"; -static PyObject *image_pcolor2(PyObject *self, PyObject *args, PyObject *kwds) +PYBIND11_MODULE(_image, m, py::mod_gil_not_used()) { - numpy::array_view x; - numpy::array_view y; - numpy::array_view d; - npy_intp rows, cols; - float bounds[4]; - numpy::array_view bg; - - if (!PyArg_ParseTuple(args, - "O&O&O&nn(ffff)O&:pcolor2", - &x.converter_contiguous, - &x, - &y.converter_contiguous, - &y, - &d.converter_contiguous, - &d, - &rows, - &cols, - &bounds[0], - &bounds[1], - &bounds[2], - &bounds[3], - &bg.converter, - &bg)) { - return NULL; - } - - npy_intp dim[3] = {rows, cols, 4}; - numpy::array_view output(dim); - - CALL_CPP("pcolor2", (pcolor2(x, y, d, rows, cols, bounds, bg, output))); - - return output.pyobj(); + py::enum_(m, "_InterpolationType") + .value("NEAREST", NEAREST) + .value("BILINEAR", BILINEAR) + .value("BICUBIC", BICUBIC) + .value("SPLINE16", SPLINE16) + .value("SPLINE36", SPLINE36) + .value("HANNING", HANNING) + .value("HAMMING", HAMMING) + .value("HERMITE", HERMITE) + .value("KAISER", KAISER) + .value("QUADRIC", QUADRIC) + .value("CATROM", CATROM) + .value("GAUSSIAN", GAUSSIAN) + .value("BESSEL", BESSEL) + .value("MITCHELL", MITCHELL) + .value("SINC", SINC) + .value("LANCZOS", LANCZOS) + .value("BLACKMAN", BLACKMAN) + .export_values(); + + m.def("resample", &image_resample, + "input_array"_a, + "output_array"_a, + "transform"_a, + "interpolation"_a = interpolation_e::NEAREST, + "resample"_a = false, + "alpha"_a = 1, + "norm"_a = false, + "radius"_a = 1, + image_resample__doc__); } - -static PyMethodDef module_functions[] = { - {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__}, - {"pcolor", (PyCFunction)image_pcolor, METH_VARARGS, image_pcolor__doc__}, - {"pcolor2", (PyCFunction)image_pcolor2, METH_VARARGS, image_pcolor2__doc__}, - {NULL} -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_image", - NULL, - 0, - module_functions, - NULL, - NULL, - NULL, - NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__image(void) -{ - PyObject *m; - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - if (PyModule_AddIntConstant(m, "NEAREST", NEAREST) || - PyModule_AddIntConstant(m, "BILINEAR", BILINEAR) || - PyModule_AddIntConstant(m, "BICUBIC", BICUBIC) || - PyModule_AddIntConstant(m, "SPLINE16", SPLINE16) || - PyModule_AddIntConstant(m, "SPLINE36", SPLINE36) || - PyModule_AddIntConstant(m, "HANNING", HANNING) || - PyModule_AddIntConstant(m, "HAMMING", HAMMING) || - PyModule_AddIntConstant(m, "HERMITE", HERMITE) || - PyModule_AddIntConstant(m, "KAISER", KAISER) || - PyModule_AddIntConstant(m, "QUADRIC", QUADRIC) || - PyModule_AddIntConstant(m, "CATROM", CATROM) || - PyModule_AddIntConstant(m, "GAUSSIAN", GAUSSIAN) || - PyModule_AddIntConstant(m, "BESSEL", BESSEL) || - PyModule_AddIntConstant(m, "MITCHELL", MITCHELL) || - PyModule_AddIntConstant(m, "SINC", SINC) || - PyModule_AddIntConstant(m, "LANCZOS", LANCZOS) || - PyModule_AddIntConstant(m, "BLACKMAN", BLACKMAN) || - PyModule_AddIntConstant(m, "_n_interpolation", _n_interpolation)) { - return NULL; - } - - import_array(); - - return m; -} - -#pragma GCC visibility pop diff --git a/src/_macosx.m b/src/_macosx.m index a502c1a837d4..aa2a6e68cda5 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -1,63 +1,20 @@ #define PY_SSIZE_T_CLEAN #include #include -#include #include -#ifndef PYPY -/* Remove this once Python is fixed: https://bugs.python.org/issue23237 */ -#define PYOSINPUTHOOK_REPETITIVE 1 -#endif - /* Proper way to check for the OS X version we are compiling for, from - http://developer.apple.com/documentation/DeveloperTools/Conceptual/cross_development */ -#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 -#define COMPILING_FOR_10_7 -#endif -#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100 -#define COMPILING_FOR_10_10 -#endif + * https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html -#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 -/* A lot of symbols were renamed in Sierra and cause deprecation warnings - so define macros for the new names if we are compiling on an older SDK */ -#define NSEventMaskAny NSAnyEventMask -#define NSEventTypeApplicationDefined NSApplicationDefined -#define NSEventModifierFlagCommand NSCommandKeyMask -#define NSEventModifierFlagControl NSControlKeyMask -#define NSEventModifierFlagOption NSAlternateKeyMask -#define NSEventModifierFlagShift NSShiftKeyMask -#define NSEventTypeKeyUp NSKeyUp -#define NSEventTypeKeyDown NSKeyDown -#define NSEventTypeMouseMoved NSMouseMoved -#define NSEventTypeLeftMouseDown NSLeftMouseDown -#define NSEventTypeRightMouseDown NSRightMouseDown -#define NSEventTypeOtherMouseDown NSOtherMouseDown -#define NSEventTypeLeftMouseDragged NSLeftMouseDragged -#define NSEventTypeRightMouseDragged NSRightMouseDragged -#define NSEventTypeOtherMouseDragged NSOtherMouseDragged -#define NSEventTypeLeftMouseUp NSLeftMouseUp -#define NSEventTypeRightMouseUp NSRightMouseUp -#define NSEventTypeOtherMouseUp NSOtherMouseUp -#define NSWindowStyleMaskClosable NSClosableWindowMask -#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask -#define NSWindowStyleMaskResizable NSResizableWindowMask -#define NSWindowStyleMaskTitled NSTitledWindowMask -#endif - -#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400 -/* A few more deprecations in Mojave */ + * Renamed symbols cause deprecation warnings, so define macros for the new + * names if we are compiling on an older SDK */ +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101400 #define NSButtonTypeMomentaryLight NSMomentaryLightButton #define NSButtonTypePushOnPushOff NSPushOnPushOffButton #define NSBezelStyleShadowlessSquare NSShadowlessSquareBezelStyle #define CGContext graphicsPort #endif -/* CGFloat was defined in Mac OS X 10.5 */ -#ifndef CGFLOAT_DEFINED -#define CGFloat float -#endif - /* Various NSApplicationDefined event subtypes */ #define STOP_EVENT_LOOP 2 @@ -68,152 +25,106 @@ Needed to know when to stop the NSApp */ static long FigureWindowCount = 0; -/* -------------------------- Helper function ---------------------------- */ - -static void -_stdin_callback(CFReadStreamRef stream, CFStreamEventType eventType, void* info) -{ - CFRunLoopRef runloop = info; - CFRunLoopStop(runloop); +/* Keep track of modifier key states for flagsChanged + to keep track of press vs release */ +static bool lastCommand = false; +static bool lastControl = false; +static bool lastShift = false; +static bool lastOption = false; +static bool lastCapsLock = false; +/* Keep track of whether this specific key modifier was pressed or not */ +static bool keyChangeCommand = false; +static bool keyChangeControl = false; +static bool keyChangeShift = false; +static bool keyChangeOption = false; +static bool keyChangeCapsLock = false; +/* Keep track of the current mouse up/down state for open/closed cursor hand */ +static bool leftMouseGrabbing = false; +// Global variable to store the original SIGINT handler +static PyOS_sighandler_t originalSigintAction = NULL; + +// Stop the current app's run loop, sending an event to ensure it actually stops +static void stopWithEvent() { + [NSApp stop: nil]; + // Post an event to trigger the actual stopping. + [NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: 0 + data1: 0 + data2: 0] + atStart: YES]; +} + +// Signal handler for SIGINT, only argument matching for stopWithEvent +static void handleSigint(int signal) { + stopWithEvent(); +} + +// Helper function to flush all events. +// This is needed in some instances to ensure e.g. that windows are properly closed. +// It is used in the input hook as well as wrapped in a version callable from Python. +static void flushEvents() { + while (true) { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) { + break; + } + [NSApp sendEvent:event]; + } } -static int sigint_fd = -1; +static int wait_for_stdin() { + // Short circuit if no windows are active + // Rely on Python's input handling to manage CPU usage + // This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still + // need to be processed to properly close the windows. + if (![[NSApp windows] count]) { + flushEvents(); + return 1; + } -static void _sigint_handler(int sig) -{ - const char c = 'i'; - write(sigint_fd, &c, 1); -} + @autoreleasepool { + // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too + originalSigintAction = PyOS_setsig(SIGINT, handleSigint); -static void _sigint_callback(CFSocketRef s, - CFSocketCallBackType type, - CFDataRef address, - const void * data, - void *info) -{ - char c; - int* interrupted = info; - CFSocketNativeHandle handle = CFSocketGetNative(s); - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - read(handle, &c, 1); - *interrupted = 1; - CFRunLoopStop(runloop); -} + // Create an NSFileHandle for standard input + NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; -static CGEventRef _eventtap_callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) -{ - CFRunLoopRef runloop = refcon; - CFRunLoopStop(runloop); - return event; -} -static int wait_for_stdin(void) -{ - int interrupted = 0; - const UInt8 buffer[] = "/dev/fd/0"; - const CFIndex n = (CFIndex)strlen((char*)buffer); - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, - buffer, - n, - false); - CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, - url); - CFRelease(url); + // Register for data available notifications on standard input + id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification + object: stdinHandle + queue: [NSOperationQueue mainQueue] // Use the main queue + usingBlock: ^(NSNotification *notification) {stopWithEvent();} + ]; - CFReadStreamOpen(stream); -#ifdef PYOSINPUTHOOK_REPETITIVE - if (!CFReadStreamHasBytesAvailable(stream)) - /* This is possible because of how PyOS_InputHook is called from Python */ - { -#endif - int error; - int channel[2]; - CFSocketRef sigint_socket = NULL; - PyOS_sighandler_t py_sigint_handler = NULL; - CFStreamClientContext clientContext = {0, NULL, NULL, NULL, NULL}; - clientContext.info = runloop; - CFReadStreamSetClient(stream, - kCFStreamEventHasBytesAvailable, - _stdin_callback, - &clientContext); - CFReadStreamScheduleWithRunLoop(stream, runloop, kCFRunLoopDefaultMode); - error = socketpair(AF_UNIX, SOCK_STREAM, 0, channel); - if (error==0) - { - CFSocketContext context; - context.version = 0; - context.info = &interrupted; - context.retain = NULL; - context.release = NULL; - context.copyDescription = NULL; - fcntl(channel[0], F_SETFL, O_WRONLY | O_NONBLOCK); - sigint_socket = CFSocketCreateWithNative( - kCFAllocatorDefault, - channel[1], - kCFSocketReadCallBack, - _sigint_callback, - &context); - if (sigint_socket) - { - CFRunLoopSourceRef source; - source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, - sigint_socket, - 0); - CFRelease(sigint_socket); - if (source) - { - CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); - CFRelease(source); - sigint_fd = channel[0]; - py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler); - } - } - } + // Wait in the background for anything that happens to stdin + [stdinHandle waitForDataInBackgroundAndNotify]; - NSEvent* event; - while (true) { - while (true) { - event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) break; - [NSApp sendEvent: event]; - } - CFRunLoopRun(); - if (interrupted || CFReadStreamHasBytesAvailable(stream)) break; - } + // Run the application's event loop, which will be interrupted on stdin or SIGINT + [NSApp run]; - if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); - CFReadStreamUnscheduleFromRunLoop(stream, - runloop, - kCFRunLoopCommonModes); - if (sigint_socket) CFSocketInvalidate(sigint_socket); - if (error==0) { - close(channel[0]); - close(channel[1]); - } -#ifdef PYOSINPUTHOOK_REPETITIVE - } -#endif - CFReadStreamClose(stream); - CFRelease(stream); - if (interrupted) { - errno = EINTR; - raise(SIGINT); - return -1; + // Remove the input handler as an observer + [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; + + + // Restore the original SIGINT handler upon exiting the function + PyOS_setsig(SIGINT, originalSigintAction); + + return 1; } - return 1; } /* ---------------------------- Cocoa classes ---------------------------- */ - -@interface WindowServerConnectionManager : NSObject -{ -} -+ (WindowServerConnectionManager*)sharedManager; -- (void)launch:(NSNotification*)notification; +@interface MatplotlibAppDelegate : NSObject +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; @end @interface Window : NSWindow @@ -222,31 +133,22 @@ @interface Window : NSWindow - (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; - (BOOL)closeButtonPressed; -- (void)dealloc; -@end - -@interface ToolWindow : NSWindow -{ -} -- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window; -- (void)masterCloses:(NSNotification*)notification; -- (void)close; @end @interface View : NSView { PyObject* canvas; NSRect rubberband; - NSTrackingRectTag tracking; @public double device_scale; } - (void)dealloc; - (void)drawRect:(NSRect)rect; +- (void)updateDevicePixelRatio:(double)scale; +- (void)windowDidChangeBackingProperties:(NSNotification*)notification; - (void)windowDidResize:(NSNotification*)notification; - (View*)initWithFrame:(NSRect)rect; - (void)setCanvas: (PyObject*)newCanvas; - (void)windowWillClose:(NSNotification*)notification; - (BOOL)windowShouldClose:(NSNotification*)notification; -- (BOOL)isFlipped; - (void)mouseEntered:(NSEvent*)event; - (void)mouseExited:(NSEvent*)event; - (void)mouseDown:(NSEvent*)event; @@ -266,35 +168,64 @@ - (void)keyDown:(NSEvent*)event; - (void)keyUp:(NSEvent*)event; - (void)scrollWheel:(NSEvent *)event; - (BOOL)acceptsFirstResponder; -//- (void)flagsChanged:(NSEvent*)event; +- (void)flagsChanged:(NSEvent*)event; @end /* ---------------------------- Python classes ---------------------------- */ +// Acquire the GIL, call a method with no args, discarding the result and +// printing any exception. +static void gil_call_method(PyObject* obj, const char* name) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject* result = PyObject_CallMethod(obj, name, NULL); + if (result) { + Py_DECREF(result); + } else { + PyErr_Print(); + } + PyGILState_Release(gstate); +} + +void process_event(char const* cls_name, char const* fmt, ...) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject* module = NULL, * cls = NULL, + * args = NULL, * kwargs = NULL, + * event = NULL, * result = NULL; + va_list argp; + va_start(argp, fmt); + if (!(module = PyImport_ImportModule("matplotlib.backend_bases")) + || !(cls = PyObject_GetAttrString(module, cls_name)) + || !(args = PyTuple_New(0)) + || !(kwargs = Py_VaBuildValue(fmt, argp)) + || !(event = PyObject_Call(cls, args, kwargs)) + || !(result = PyObject_CallMethod(event, "_process", ""))) { + PyErr_Print(); + } + va_end(argp); + Py_XDECREF(module); + Py_XDECREF(cls); + Py_XDECREF(args); + Py_XDECREF(kwargs); + Py_XDECREF(event); + Py_XDECREF(result); + PyGILState_Release(gstate); +} + static bool backend_inited = false; static void lazy_init(void) { - if (backend_inited) { - return; - } + if (backend_inited) { return; } backend_inited = true; NSApp = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp setDelegate: [[[MatplotlibAppDelegate alloc] init] autorelease]]; -#ifndef PYPY - /* TODO: remove ifndef after the new PyPy with the PyOS_InputHook implementation - get released: https://bitbucket.org/pypy/pypy/commits/caaf91a */ + // Run our own event loop while waiting for stdin on the Python side + // this is needed to keep the application responsive while waiting for input PyOS_InputHook = wait_for_stdin; -#endif - - WindowServerConnectionManager* connectionManager = [WindowServerConnectionManager sharedManager]; - NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; - NSNotificationCenter* notificationCenter = [workspace notificationCenter]; - [notificationCenter addObserver: connectionManager - selector: @selector(launch:) - name: NSWorkspaceDidLaunchApplicationNotification - object: nil]; } static PyObject* @@ -307,23 +238,110 @@ static void lazy_init(void) { } } +static PyObject* +wake_on_fd_write(PyObject* unused, PyObject* args) +{ + int fd; + if (!PyArg_ParseTuple(args, "i", &fd)) { return NULL; } + NSFileHandle* fh = [[NSFileHandle alloc] initWithFileDescriptor: fd]; + [fh waitForDataInBackgroundAndNotify]; + [[NSNotificationCenter defaultCenter] + addObserverForName: NSFileHandleDataAvailableNotification + object: fh + queue: nil + usingBlock: ^(NSNotification* note) { + PyGILState_STATE gstate = PyGILState_Ensure(); + PyErr_CheckSignals(); + PyGILState_Release(gstate); + }]; + Py_RETURN_NONE; +} + +static PyObject* +stop(PyObject* self) +{ + stopWithEvent(); + Py_RETURN_NONE; +} + static CGFloat _get_device_scale(CGContextRef cr) { CGSize pixelSize = CGContextConvertSizeToDeviceSpace(cr, CGSizeMake(1, 1)); return pixelSize.width; } +bool mpl_check_button(bool present, PyObject* set, char const* name) { + PyObject* module = NULL, * cls = NULL, * button = NULL; + bool failed = ( + present + && (!(module = PyImport_ImportModule("matplotlib.backend_bases")) + || !(cls = PyObject_GetAttrString(module, "MouseButton")) + || !(button = PyObject_GetAttrString(cls, name)) + || PySet_Add(set, button))); + Py_XDECREF(module); + Py_XDECREF(cls); + Py_XDECREF(button); + return failed; +} + +PyObject* mpl_buttons() +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject* set = NULL; + NSUInteger buttons = [NSEvent pressedMouseButtons]; + + if (!(set = PySet_New(NULL)) + || mpl_check_button(buttons & (1 << 0), set, "LEFT") + || mpl_check_button(buttons & (1 << 1), set, "RIGHT") + || mpl_check_button(buttons & (1 << 2), set, "MIDDLE") + || mpl_check_button(buttons & (1 << 3), set, "BACK") + || mpl_check_button(buttons & (1 << 4), set, "FORWARD")) { + Py_CLEAR(set); // On failure, return NULL with an exception set. + } + PyGILState_Release(gstate); + return set; +} + +bool mpl_check_modifier(bool present, PyObject* list, char const* name) +{ + PyObject* py_name = NULL; + bool failed = ( + present + && (!(py_name = PyUnicode_FromString(name)) + || (PyList_Append(list, py_name)))); + Py_XDECREF(py_name); + return failed; +} + +PyObject* mpl_modifiers(NSEvent* event) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject* list = NULL; + NSUInteger modifiers = [event modifierFlags]; + if (!(list = PyList_New(0)) + || mpl_check_modifier(modifiers & NSEventModifierFlagControl, list, "ctrl") + || mpl_check_modifier(modifiers & NSEventModifierFlagOption, list, "alt") + || mpl_check_modifier(modifiers & NSEventModifierFlagShift, list, "shift") + || mpl_check_modifier(modifiers & NSEventModifierFlagCommand, list, "cmd")) { + Py_CLEAR(list); // On failure, return NULL with an exception set. + } + PyGILState_Release(gstate); + return list; +} + typedef struct { PyObject_HEAD View* view; } FigureCanvas; +static PyTypeObject FigureCanvasType; + static PyObject* FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { lazy_init(); FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0); - if (!self) return NULL; + if (!self) { return NULL; } self->view = [View alloc]; return (PyObject*)self; } @@ -331,16 +349,27 @@ static CGFloat _get_device_scale(CGContextRef cr) static int FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds) { - int width; - int height; - if(!self->view) - { + if (!self->view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } - - if(!PyArg_ParseTuple(args, "ii", &width, &height)) return -1; - + PyObject *builtins = NULL, + *super_obj = NULL, + *super_init = NULL, + *init_res = NULL, + *wh = NULL; + // super(FigureCanvasMac, self).__init__(*args, **kwargs) + if (!(builtins = PyImport_AddModule("builtins")) // borrowed. + || !(super_obj = PyObject_CallMethod(builtins, "super", "OO", &FigureCanvasType, self)) + || !(super_init = PyObject_GetAttrString(super_obj, "__init__")) + || !(init_res = PyObject_Call(super_init, args, kwds))) { + goto exit; + } + int width, height; + if (!(wh = PyObject_CallMethod((PyObject*)self, "get_width_height", "")) + || !PyArg_ParseTuple(wh, "ii", &width, &height)) { + goto exit; + } NSRect rect = NSMakeRect(0.0, 0.0, width, height); self->view = [self->view initWithFrame: rect]; self->view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; @@ -352,17 +381,20 @@ static CGFloat _get_device_scale(CGContextRef cr) owner: self->view userInfo: nil]]; [self->view setCanvas: (PyObject*)self]; - return 0; + +exit: + Py_XDECREF(super_obj); + Py_XDECREF(super_init); + Py_XDECREF(init_res); + Py_XDECREF(wh); + return PyErr_Occurred() ? -1 : 0; } static void FigureCanvas_dealloc(FigureCanvas* self) { - if (self->view) - { - [self->view setCanvas: NULL]; - [self->view release]; - } + [self->view setCanvas: NULL]; + [self->view release]; Py_TYPE(self)->tp_free((PyObject*)self); } @@ -374,41 +406,50 @@ static CGFloat _get_device_scale(CGContextRef cr) } static PyObject* -FigureCanvas_draw(FigureCanvas* self) +FigureCanvas_update(FigureCanvas* self) { - View* view = self->view; - - if(view) /* The figure may have been closed already */ - { - [view display]; - } - + [self->view setNeedsDisplay: YES]; Py_RETURN_NONE; } static PyObject* -FigureCanvas_draw_idle(FigureCanvas* self) +FigureCanvas_flush_events(FigureCanvas* self) { - View* view = self->view; - if(!view) - { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - return NULL; - } - [view setNeedsDisplay: YES]; + // We run the app, matching any events that are waiting in the queue + // to process, breaking out of the loop when no events remain and + // displaying the canvas if needed. + Py_BEGIN_ALLOW_THREADS + + flushEvents(); + + Py_END_ALLOW_THREADS + + [self->view displayIfNeeded]; Py_RETURN_NONE; } static PyObject* -FigureCanvas_flush_events(FigureCanvas* self) +FigureCanvas_set_cursor(PyObject* unused, PyObject* args) { - View* view = self->view; - if(!view) - { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - return NULL; + int i; + if (!PyArg_ParseTuple(args, "i", &i)) { return NULL; } + switch (i) { + case 1: [[NSCursor arrowCursor] set]; break; + case 2: [[NSCursor pointingHandCursor] set]; break; + case 3: [[NSCursor crosshairCursor] set]; break; + case 4: + if (leftMouseGrabbing) { + [[NSCursor closedHandCursor] set]; + } else { + [[NSCursor openHandCursor] set]; + } + break; + /* macOS handles busy state itself so no need to set a cursor here */ + case 5: break; + case 6: [[NSCursor resizeLeftRightCursor] set]; break; + case 7: [[NSCursor resizeUpDownCursor] set]; break; + default: return NULL; } - [view displayIfNeeded]; Py_RETURN_NONE; } @@ -416,41 +457,20 @@ static CGFloat _get_device_scale(CGContextRef cr) FigureCanvas_set_rubberband(FigureCanvas* self, PyObject *args) { View* view = self->view; - int x0, y0, x1, y1; - NSRect rubberband; - if(!view) - { + if (!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } - if(!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) return NULL; - + int x0, y0, x1, y1; + if (!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) { + return NULL; + } x0 /= view->device_scale; x1 /= view->device_scale; y0 /= view->device_scale; y1 /= view->device_scale; - - if (x1 > x0) - { - rubberband.origin.x = x0; - rubberband.size.width = x1 - x0; - } - else - { - rubberband.origin.x = x1; - rubberband.size.width = x0 - x1; - } - if (y1 > y0) - { - rubberband.origin.y = y0; - rubberband.size.height = y1 - y0; - } - else - { - rubberband.origin.y = y1; - rubberband.size.height = y0 - y1; - } - + NSRect rubberband = NSMakeRect(x0 < x1 ? x0 : x1, y0 < y1 ? y0 : y1, + abs(x1 - x0), abs(y1 - y0)); [view setRubberband: rubberband]; Py_RETURN_NONE; } @@ -458,64 +478,22 @@ static CGFloat _get_device_scale(CGContextRef cr) static PyObject* FigureCanvas_remove_rubberband(FigureCanvas* self) { - View* view = self->view; - if(!view) - { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - return NULL; - } - [view removeRubberband]; + [self->view removeRubberband]; Py_RETURN_NONE; } static PyObject* -FigureCanvas_start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) +FigureCanvas__start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { float timeout = 0.0; static char* kwlist[] = {"timeout", NULL}; - if(!PyArg_ParseTupleAndKeywords(args, keywords, "f", kwlist, &timeout)) + if (!PyArg_ParseTupleAndKeywords(args, keywords, "f", kwlist, &timeout)) { return NULL; - - int error; - int interrupted = 0; - int channel[2]; - CFSocketRef sigint_socket = NULL; - PyOS_sighandler_t py_sigint_handler = NULL; - - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - - error = pipe(channel); - if (error==0) - { - CFSocketContext context = {0, NULL, NULL, NULL, NULL}; - fcntl(channel[1], F_SETFL, O_WRONLY | O_NONBLOCK); - - context.info = &interrupted; - sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault, - channel[0], - kCFSocketReadCallBack, - _sigint_callback, - &context); - if (sigint_socket) - { - CFRunLoopSourceRef source; - source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, - sigint_socket, - 0); - CFRelease(sigint_socket); - if (source) - { - CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); - CFRelease(source); - sigint_fd = channel[1]; - py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler); - } - } - else - close(channel[0]); } + Py_BEGIN_ALLOW_THREADS + NSDate* date = (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout] : [NSDate distantFuture]; @@ -524,15 +502,11 @@ static CGFloat _get_device_scale(CGContextRef cr) untilDate: date inMode: NSDefaultRunLoopMode dequeue: YES]; - if (!event || [event type]==NSEventTypeApplicationDefined) break; + if (!event || [event type]==NSEventTypeApplicationDefined) { break; } [NSApp sendEvent: event]; } - if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); - - if (sigint_socket) CFSocketInvalidate(sigint_socket); - if (error==0) close(channel[1]); - if (interrupted) raise(SIGINT); + Py_END_ALLOW_THREADS Py_RETURN_NONE; } @@ -553,87 +527,49 @@ static CGFloat _get_device_scale(CGContextRef cr) Py_RETURN_NONE; } -static PyMethodDef FigureCanvas_methods[] = { - {"draw", - (PyCFunction)FigureCanvas_draw, - METH_NOARGS, - NULL, // docstring inherited. - }, - {"draw_idle", - (PyCFunction)FigureCanvas_draw_idle, - METH_NOARGS, - NULL, // docstring inherited. - }, - {"flush_events", - (PyCFunction)FigureCanvas_flush_events, - METH_NOARGS, - "Flush the GUI events for the figure." - }, - {"set_rubberband", - (PyCFunction)FigureCanvas_set_rubberband, - METH_VARARGS, - "Specifies a new rubberband rectangle and invalidates it." - }, - {"remove_rubberband", - (PyCFunction)FigureCanvas_remove_rubberband, - METH_NOARGS, - "Removes the current rubberband rectangle." - }, - {"start_event_loop", - (PyCFunction)FigureCanvas_start_event_loop, - METH_KEYWORDS | METH_VARARGS, - "Runs the event loop until the timeout or until stop_event_loop is called.\n", - }, - {"stop_event_loop", - (PyCFunction)FigureCanvas_stop_event_loop, - METH_NOARGS, - "Stops the event loop that was started by start_event_loop.\n", - }, - {NULL} /* Sentinel */ -}; - -static char FigureCanvas_doc[] = -"A FigureCanvas object wraps a Cocoa NSView object.\n"; - static PyTypeObject FigureCanvasType = { PyVarObject_HEAD_INIT(NULL, 0) - "_macosx.FigureCanvas", /*tp_name*/ - sizeof(FigureCanvas), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)FigureCanvas_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)FigureCanvas_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - FigureCanvas_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - FigureCanvas_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)FigureCanvas_init, /* tp_init */ - 0, /* tp_alloc */ - FigureCanvas_new, /* tp_new */ + .tp_name = "matplotlib.backends._macosx.FigureCanvas", + .tp_doc = PyDoc_STR("A FigureCanvas object wraps a Cocoa NSView object."), + .tp_basicsize = sizeof(FigureCanvas), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_new = (newfunc)FigureCanvas_new, + .tp_init = (initproc)FigureCanvas_init, + .tp_dealloc = (destructor)FigureCanvas_dealloc, + .tp_repr = (reprfunc)FigureCanvas_repr, + + .tp_methods = (PyMethodDef[]){ + {"update", + (PyCFunction)FigureCanvas_update, + METH_NOARGS, + NULL}, // docstring inherited + {"flush_events", + (PyCFunction)FigureCanvas_flush_events, + METH_NOARGS, + NULL}, // docstring inherited + {"set_cursor", + (PyCFunction)FigureCanvas_set_cursor, + METH_VARARGS, + PyDoc_STR("Set the active cursor.")}, + {"set_rubberband", + (PyCFunction)FigureCanvas_set_rubberband, + METH_VARARGS, + PyDoc_STR("Specify a new rubberband rectangle and invalidate it.")}, + {"remove_rubberband", + (PyCFunction)FigureCanvas_remove_rubberband, + METH_NOARGS, + PyDoc_STR("Remove the current rubberband rectangle.")}, + {"_start_event_loop", + (PyCFunction)FigureCanvas__start_event_loop, + METH_KEYWORDS | METH_VARARGS, + NULL}, // docstring inherited + {"stop_event_loop", + (PyCFunction)FigureCanvas_stop_event_loop, + METH_NOARGS, + NULL}, // docstring inherited + {} // sentinel + }, }; typedef struct { @@ -646,10 +582,9 @@ static CGFloat _get_device_scale(CGContextRef cr) { lazy_init(); Window* window = [Window alloc]; - if (!window) return NULL; + if (!window) { return NULL; } FigureManager *self = (FigureManager*)type->tp_alloc(type, 0); - if (!self) - { + if (!self) { [window release]; return NULL; } @@ -661,43 +596,26 @@ static CGFloat _get_device_scale(CGContextRef cr) static int FigureManager_init(FigureManager *self, PyObject *args, PyObject *kwds) { - NSRect rect; - Window* window; - View* view; - const char* title; - PyObject* size; - int width, height; - PyObject* obj; - FigureCanvas* canvas; - - if(!self->window) - { - PyErr_SetString(PyExc_RuntimeError, "NSWindow* is NULL"); + PyObject* canvas; + if (!PyArg_ParseTuple(args, "O", &canvas)) { return -1; } - if(!PyArg_ParseTuple(args, "Os", &obj, &title)) return -1; - - canvas = (FigureCanvas*)obj; - view = canvas->view; - if (!view) /* Something really weird going on */ - { + View* view = ((FigureCanvas*)canvas)->view; + if (!view) { /* Something really weird going on */ PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } - size = PyObject_CallMethod(obj, "get_width_height", ""); - if(!size) return -1; - if(!PyArg_ParseTuple(size, "ii", &width, &height)) - { Py_DECREF(size); - return -1; + PyObject* size = PyObject_CallMethod(canvas, "get_width_height", ""); + int width, height; + if (!size || !PyArg_ParseTuple(size, "ii", &width, &height)) { + Py_XDECREF(size); + return -1; } Py_DECREF(size); - rect.origin.x = 100; - rect.origin.y = 350; - rect.size.height = height; - rect.size.width = width; + NSRect rect = NSMakeRect( /* x */ 100, /* y */ 350, width, height); self->window = [self->window initWithContentRect: rect styleMask: NSWindowStyleMaskTitled @@ -707,17 +625,34 @@ static CGFloat _get_device_scale(CGContextRef cr) backing: NSBackingStoreBuffered defer: YES withManager: (PyObject*)self]; - window = self->window; - [window setTitle: [NSString stringWithCString: title - encoding: NSASCIIStringEncoding]]; - + Window* window = self->window; [window setDelegate: view]; [window makeFirstResponder: view]; [[window contentView] addSubview: view]; + [view updateDevicePixelRatio: [window backingScaleFactor]]; return 0; } +static PyObject* +FigureManager__set_window_mode(FigureManager* self, PyObject* args) +{ + const char* window_mode; + if (!PyArg_ParseTuple(args, "s", &window_mode) || !self->window) { + return NULL; + } + + NSString* window_mode_str = [NSString stringWithUTF8String: window_mode]; + if ([window_mode_str isEqualToString: @"tab"]) { + [self->window setTabbingMode: NSWindowTabbingModePreferred]; + } else if ([window_mode_str isEqualToString: @"window"]) { + [self->window setTabbingMode: NSWindowTabbingModeDisallowed]; + } else { // system settings + [self->window setTabbingMode: NSWindowTabbingModeAutomatic]; + } + Py_RETURN_NONE; +} + static PyObject* FigureManager_repr(FigureManager* self) { @@ -728,34 +663,67 @@ static CGFloat _get_device_scale(CGContextRef cr) static void FigureManager_dealloc(FigureManager* self) { - Window* window = self->window; - if(window) - { - [window close]; - } + [self->window close]; Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* -FigureManager_show(FigureManager* self) +FigureManager__show(FigureManager* self) { - Window* window = self->window; - if(window) - { - [window makeKeyAndOrderFront: nil]; - [window orderFrontRegardless]; - } + [self->window makeKeyAndOrderFront: nil]; + Py_RETURN_NONE; +} + +static PyObject* +FigureManager__raise(FigureManager* self) +{ + [self->window orderFrontRegardless]; Py_RETURN_NONE; } static PyObject* FigureManager_destroy(FigureManager* self) { - Window* window = self->window; - if(window) - { - [window close]; - self->window = NULL; + [self->window close]; + self->window = NULL; + Py_RETURN_NONE; +} + +static PyObject* +FigureManager_set_icon(PyObject* null, PyObject* args) { + PyObject* icon_path; + if (!PyArg_ParseTuple(args, "O&", &PyUnicode_FSDecoder, &icon_path)) { + return NULL; + } + const char* icon_path_ptr = PyUnicode_AsUTF8(icon_path); + if (!icon_path_ptr) { + Py_DECREF(icon_path); + return NULL; + } + @autoreleasepool { + NSString* ns_icon_path = [NSString stringWithUTF8String: icon_path_ptr]; + Py_DECREF(icon_path); + if (!ns_icon_path) { + PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); + return NULL; + } + NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; + if (!image) { + PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); + return NULL; + } + if (!image.valid) { + PyErr_SetString(PyExc_RuntimeError, "Image is not valid"); + return NULL; + } + @try { + NSApplication* app = [NSApplication sharedApplication]; + app.applicationIconImage = image; + } + @catch (NSException* exception) { + PyErr_SetString(PyExc_RuntimeError, exception.reason.UTF8String); + return NULL; + } } Py_RETURN_NONE; } @@ -768,32 +736,16 @@ static CGFloat _get_device_scale(CGContextRef cr) if (!PyArg_ParseTuple(args, "s", &title)) { return NULL; } - Window* window = self->window; - if(window) - { - NSString* ns_title = [[[NSString alloc] - initWithCString: title - encoding: NSUTF8StringEncoding] autorelease]; - [window setTitle: ns_title]; - } + [self->window setTitle: [NSString stringWithUTF8String: title]]; Py_RETURN_NONE; } static PyObject* FigureManager_get_window_title(FigureManager* self) { - Window* window = self->window; - PyObject* result = NULL; - if(window) - { - NSString* title = [window title]; - if (title) { - const char* cTitle = [title UTF8String]; - result = PyUnicode_FromString(cTitle); - } - } - if (result) { - return result; + NSString* title = [self->window title]; + if (title) { + return PyUnicode_FromString([title UTF8String]); } else { Py_RETURN_NONE; } @@ -807,94 +759,82 @@ static CGFloat _get_device_scale(CGContextRef cr) return NULL; } Window* window = self->window; - if(window) - { + if (window) { + CGFloat device_pixel_ratio = [window backingScaleFactor]; + width /= device_pixel_ratio; + height /= device_pixel_ratio; // 36 comes from hard-coded size of toolbar later in code [window setContentSize: NSMakeSize(width, height + 36.)]; } Py_RETURN_NONE; } -static PyMethodDef FigureManager_methods[] = { - {"show", - (PyCFunction)FigureManager_show, - METH_NOARGS, - "Shows the window associated with the figure manager." - }, - {"destroy", - (PyCFunction)FigureManager_destroy, - METH_NOARGS, - "Closes the window associated with the figure manager." - }, - {"set_window_title", - (PyCFunction)FigureManager_set_window_title, - METH_VARARGS, - "Sets the title of the window associated with the figure manager." - }, - {"get_window_title", - (PyCFunction)FigureManager_get_window_title, - METH_NOARGS, - "Returns the title of the window associated with the figure manager." - }, - {"resize", - (PyCFunction)FigureManager_resize, - METH_VARARGS, - "Resize the window (in pixels)." - }, - {NULL} /* Sentinel */ -}; - -static char FigureManager_doc[] = -"A FigureManager object wraps a Cocoa NSWindow object.\n"; +static PyObject* +FigureManager_full_screen_toggle(FigureManager* self) +{ + [self->window toggleFullScreen: nil]; + Py_RETURN_NONE; +} static PyTypeObject FigureManagerType = { PyVarObject_HEAD_INIT(NULL, 0) - "_macosx.FigureManager", /*tp_name*/ - sizeof(FigureManager), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)FigureManager_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)FigureManager_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - FigureManager_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - FigureManager_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)FigureManager_init, /* tp_init */ - 0, /* tp_alloc */ - FigureManager_new, /* tp_new */ + .tp_name = "matplotlib.backends._macosx.FigureManager", + .tp_doc = PyDoc_STR("A FigureManager object wraps a Cocoa NSWindow object."), + .tp_basicsize = sizeof(FigureManager), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_new = (newfunc)FigureManager_new, + .tp_init = (initproc)FigureManager_init, + .tp_dealloc = (destructor)FigureManager_dealloc, + .tp_repr = (reprfunc)FigureManager_repr, + + .tp_methods = (PyMethodDef[]){ // All docstrings are inherited. + {"_show", + (PyCFunction)FigureManager__show, + METH_NOARGS}, + {"_raise", + (PyCFunction)FigureManager__raise, + METH_NOARGS}, + {"destroy", + (PyCFunction)FigureManager_destroy, + METH_NOARGS}, + {"_set_window_mode", + (PyCFunction)FigureManager__set_window_mode, + METH_VARARGS, + PyDoc_STR("Set the window open mode (system, tab, window)")}, + {"set_icon", + (PyCFunction)FigureManager_set_icon, + METH_STATIC | METH_VARARGS, + PyDoc_STR("Set application icon")}, + {"set_window_title", + (PyCFunction)FigureManager_set_window_title, + METH_VARARGS}, + {"get_window_title", + (PyCFunction)FigureManager_get_window_title, + METH_NOARGS}, + {"resize", + (PyCFunction)FigureManager_resize, + METH_VARARGS}, + {"full_screen_toggle", + (PyCFunction)FigureManager_full_screen_toggle, + METH_NOARGS}, + {} // sentinel + }, }; +@implementation MatplotlibAppDelegate +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { + return YES; +} +@end + @interface NavigationToolbar2Handler : NSObject { PyObject* toolbar; NSButton* panbutton; NSButton* zoombutton; } - (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)toolbar; -- (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons; +- (void)installCallbacks:(SEL[7])actions forButtons:(NSButton*[7])buttons; - (void)home:(id)sender; - (void)back:(id)sender; - (void)forward:(id)sender; @@ -906,7 +846,6 @@ - (void)save_figure:(id)sender; typedef struct { PyObject_HEAD - NSPopUpButton* menu; NSTextView* messagebox; NavigationToolbar2Handler* handler; int height; @@ -914,186 +853,52 @@ - (void)save_figure:(id)sender; @implementation NavigationToolbar2Handler - (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)theToolbar -{ [self init]; +{ + [self init]; toolbar = theToolbar; return self; } -- (void)installCallbacks:(SEL[7])actions forButtons: (NSButton*[7])buttons +- (void)installCallbacks:(SEL[7])actions forButtons:(NSButton*[7])buttons { - int i; - for (i = 0; i < 7; i++) - { + for (int i = 0; i < 7; i++) { SEL action = actions[i]; NSButton* button = buttons[i]; [button setTarget: self]; [button setAction: action]; - if (action==@selector(pan:)) panbutton = button; - if (action==@selector(zoom:)) zoombutton = button; + if (action == @selector(pan:)) { panbutton = button; } + if (action == @selector(zoom:)) { zoombutton = button; } } } --(void)home:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "home", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); -} +-(void)home:(id)sender { gil_call_method(toolbar, "home"); } +-(void)back:(id)sender { gil_call_method(toolbar, "back"); } +-(void)forward:(id)sender { gil_call_method(toolbar, "forward"); } --(void)back:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "back", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); +-(void)pan:(id)sender +{ + if ([sender state]) { [zoombutton setState:NO]; } + gil_call_method(toolbar, "pan"); } --(void)forward:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "forward", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); +-(void)zoom:(id)sender +{ + if ([sender state]) { [panbutton setState:NO]; } + gil_call_method(toolbar, "zoom"); } --(void)pan:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - if ([sender state]) - { - if (zoombutton) [zoombutton setState: NO]; - } - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "pan", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); -} - --(void)zoom:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - if ([sender state]) - { - if (panbutton) [panbutton setState: NO]; - } - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "zoom", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); -} - --(void)configure_subplots:(id)sender -{ PyObject* canvas; - View* view; - PyObject* size; - NSRect rect; - int width, height; - - rect.origin.x = 100; - rect.origin.y = 350; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* master = PyObject_GetAttrString(toolbar, "canvas"); - if (master==nil) - { - PyErr_Print(); - PyGILState_Release(gstate); - return; - } - canvas = PyObject_CallMethod(toolbar, "prepare_configure_subplots", ""); - if(!canvas) - { - PyErr_Print(); - Py_DECREF(master); - PyGILState_Release(gstate); - return; - } - - view = ((FigureCanvas*)canvas)->view; - if (!view) /* Something really weird going on */ - { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - PyErr_Print(); - Py_DECREF(canvas); - Py_DECREF(master); - PyGILState_Release(gstate); - return; - } - - size = PyObject_CallMethod(canvas, "get_width_height", ""); - Py_DECREF(canvas); - if(!size) - { - PyErr_Print(); - Py_DECREF(master); - PyGILState_Release(gstate); - return; - } - - int ok = PyArg_ParseTuple(size, "ii", &width, &height); - Py_DECREF(size); - if (!ok) - { - PyErr_Print(); - Py_DECREF(master); - PyGILState_Release(gstate); - return; - } - - NSWindow* mw = [((FigureCanvas*)master)->view window]; - Py_DECREF(master); - PyGILState_Release(gstate); - - rect.size.width = width; - rect.size.height = height; - - ToolWindow* window = [ [ToolWindow alloc] initWithContentRect: rect - master: mw]; - [window setContentView: view]; - [view release]; - [window makeKeyAndOrderFront: nil]; -} - --(void)save_figure:(id)sender -{ PyObject* result; - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(toolbar, "save_figure", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); -} -@end +-(void)configure_subplots:(id)sender { gil_call_method(toolbar, "configure_subplots"); } +-(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } +@end static PyObject* NavigationToolbar2_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { lazy_init(); NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc]; - if (!handler) return NULL; + if (!handler) { return NULL; } NavigationToolbar2 *self = (NavigationToolbar2*)type->tp_alloc(type, 0); - if (!self) - { + if (!self) { [handler release]; return NULL; } @@ -1104,41 +909,31 @@ -(void)save_figure:(id)sender static int NavigationToolbar2_init(NavigationToolbar2 *self, PyObject *args, PyObject *kwds) { - PyObject* obj; FigureCanvas* canvas; - View* view; - - int i; - NSRect rect; - NSSize size; - NSSize scale; + const char* images[7]; + const char* tooltips[7]; const float gap = 2; const int height = 36; const int imagesize = 24; - self->height = height; - - obj = PyObject_GetAttrString((PyObject*)self, "canvas"); - if (obj==NULL) - { - PyErr_SetString(PyExc_AttributeError, "Attempt to install toolbar for NULL canvas"); - return -1; - } - Py_DECREF(obj); /* Don't increase the reference count */ - if (!PyObject_IsInstance(obj, (PyObject*) &FigureCanvasType)) - { - PyErr_SetString(PyExc_TypeError, "Attempt to install toolbar for object that is not a FigureCanvas"); + if (!PyArg_ParseTuple(args, "O!(sssssss)(sssssss)", + &FigureCanvasType, &canvas, + &images[0], &images[1], &images[2], &images[3], + &images[4], &images[5], &images[6], + &tooltips[0], &tooltips[1], &tooltips[2], &tooltips[3], + &tooltips[4], &tooltips[5], &tooltips[6])) { return -1; } - canvas = (FigureCanvas*)obj; - view = canvas->view; - if(!view) - { + + View* view = canvas->view; + if (!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } + self->height = height; + NSRect bounds = [view bounds]; NSWindow* window = [view window]; @@ -1148,16 +943,6 @@ -(void)save_figure:(id)sender bounds.size.height += height; [window setContentSize: bounds.size]; - const char* images[7]; - const char* tooltips[7]; - if (!PyArg_ParseTuple(args, "(sssssss)(sssssss)", - &images[0], &images[1], &images[2], &images[3], - &images[4], &images[5], &images[6], - &tooltips[0], &tooltips[1], &tooltips[2], &tooltips[3], - &tooltips[4], &tooltips[5], &tooltips[6])) { - return -1; - } - NSButton* buttons[7]; SEL actions[7] = {@selector(home:), @selector(back:), @@ -1174,30 +959,29 @@ -(void)save_figure:(id)sender NSButtonTypeMomentaryLight, NSButtonTypeMomentaryLight}; - rect.origin.x = 0; - rect.origin.y = 0; - rect.size.width = imagesize; - rect.size.height = imagesize; -#ifdef COMPILING_FOR_10_7 + NSRect rect; + NSSize size; + NSSize scale; + + rect = NSMakeRect(0, 0, imagesize, imagesize); rect = [window convertRectToBacking: rect]; -#endif size = rect.size; - scale.width = imagesize / size.width; - scale.height = imagesize / size.height; + scale = NSMakeSize(imagesize / size.width, imagesize / size.height); rect.size.width = 32; rect.size.height = 32; rect.origin.x = gap; rect.origin.y = 0.5*(height - rect.size.height); - for (i = 0; i < 7; i++) { - NSString* filename = [NSString stringWithCString: images[i] - encoding: NSUTF8StringEncoding]; - NSString* tooltip = [NSString stringWithCString: tooltips[i] - encoding: NSUTF8StringEncoding]; + for (int i = 0; i < 7; i++) { + NSString* filename = [NSString stringWithUTF8String: images[i]]; + NSString* tooltip = [NSString stringWithUTF8String: tooltips[i]]; NSImage* image = [[NSImage alloc] initWithContentsOfFile: filename]; buttons[i] = [[NSButton alloc] initWithFrame: rect]; [image setSize: size]; + // Specify that it is a template image so the content tint + // color gets updated with the system theme (dark/light) + [image setTemplate: YES]; [buttons[i] setBezelStyle: NSBezelStyleShadowlessSquare]; [buttons[i] setButtonType: buttontypes[i]]; [buttons[i] setImage: image]; @@ -1214,22 +998,20 @@ -(void)save_figure:(id)sender [self->handler installCallbacks: actions forButtons: buttons]; NSFont* font = [NSFont systemFontOfSize: 0.0]; - rect.size.width = 300; - rect.size.height = 0; - rect.origin.x += height; - NSTextView* messagebox = [[NSTextView alloc] initWithFrame: rect]; - if (@available(macOS 10.11, *)) { - messagebox.textContainer.maximumNumberOfLines = 2; - messagebox.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; - } + // rect.origin.x is now at the far right edge of the buttons + // we want the messagebox to take up the rest of the toolbar area + // Make it a zero-width box if we don't have enough room + rect.size.width = fmax(bounds.size.width - rect.origin.x, 0); + rect.origin.x = bounds.size.width - rect.size.width; + NSTextView* messagebox = [[[NSTextView alloc] initWithFrame: rect] autorelease]; + messagebox.textContainer.maximumNumberOfLines = 2; + messagebox.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; + messagebox.alignment = NSTextAlignmentRight; [messagebox setFont: font]; [messagebox setDrawsBackground: NO]; [messagebox setSelectable: NO]; /* if selectable, the messagebox can become first responder, * which is not supposed to happen */ - rect = [messagebox frame]; - rect.origin.y = 0.5 * (height - rect.size.height); - [messagebox setFrameOrigin: rect.origin]; [[window contentView] addSubview: messagebox]; [messagebox release]; [[window contentView] display]; @@ -1251,88 +1033,59 @@ -(void)save_figure:(id)sender return PyUnicode_FromFormat("NavigationToolbar2 object %p", (void*)self); } -static char NavigationToolbar2_doc[] = -"NavigationToolbar2\n"; - static PyObject* NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args) { const char* message; - if(!PyArg_ParseTuple(args, "y", &message)) return NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { return NULL; } NSTextView* messagebox = self->messagebox; - if (messagebox) - { + if (messagebox) { NSString* text = [NSString stringWithUTF8String: message]; [messagebox setString: text]; - // Adjust width with the window size + // Adjust width and height with the window size and content NSRect rectWindow = [messagebox.superview frame]; NSRect rect = [messagebox frame]; + // Entire region to the right of the buttons rect.size.width = rectWindow.size.width - rect.origin.x; [messagebox setFrame: rect]; - - // Adjust height with the content size + // We want to control the vertical position of + // the rect by the content size to center it vertically [messagebox.layoutManager ensureLayoutForTextContainer: messagebox.textContainer]; - NSRect contentSize = [messagebox.layoutManager usedRectForTextContainer: messagebox.textContainer]; - rect = [messagebox frame]; - rect.origin.y = 0.5 * (self->height - contentSize.size.height); + NSRect contentRect = [messagebox.layoutManager usedRectForTextContainer: messagebox.textContainer]; + rect.origin.y = 0.5 * (self->height - contentRect.size.height); + rect.size.height = contentRect.size.height; [messagebox setFrame: rect]; + // Disable cursorRects so that the cursor doesn't get updated by events + // in NSApp (like resizing TextViews), we want to handle the cursor + // changes from within MPL with set_cursor() ourselves + [[messagebox.superview window] disableCursorRects]; } Py_RETURN_NONE; } -static PyMethodDef NavigationToolbar2_methods[] = { - {"set_message", - (PyCFunction)NavigationToolbar2_set_message, - METH_VARARGS, - "Set the message to be displayed on the toolbar." - }, - {NULL} /* Sentinel */ -}; - static PyTypeObject NavigationToolbar2Type = { PyVarObject_HEAD_INIT(NULL, 0) - "_macosx.NavigationToolbar2", /*tp_name*/ - sizeof(NavigationToolbar2), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)NavigationToolbar2_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)NavigationToolbar2_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - NavigationToolbar2_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - NavigationToolbar2_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)NavigationToolbar2_init, /* tp_init */ - 0, /* tp_alloc */ - NavigationToolbar2_new, /* tp_new */ + .tp_name = "matplotlib.backends._macosx.NavigationToolbar2", + .tp_doc = PyDoc_STR("NavigationToolbar2"), + .tp_basicsize = sizeof(NavigationToolbar2), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_new = (newfunc)NavigationToolbar2_new, + .tp_init = (initproc)NavigationToolbar2_init, + .tp_dealloc = (destructor)NavigationToolbar2_dealloc, + .tp_repr = (reprfunc)NavigationToolbar2_repr, + + .tp_methods = (PyMethodDef[]){ // All docstrings are inherited. + {"set_message", + (PyCFunction)NavigationToolbar2_set_message, + METH_VARARGS}, + {} // sentinel + }, }; static PyObject* @@ -1340,130 +1093,28 @@ -(void)save_figure:(id)sender { int result; const char* title; + const char* directory; const char* default_filename; - if (!PyArg_ParseTuple(args, "ss", &title, &default_filename)) { + if (!PyArg_ParseTuple(args, "sss", &title, &directory, &default_filename)) { return NULL; } NSSavePanel* panel = [NSSavePanel savePanel]; - [panel setTitle: [NSString stringWithCString: title - encoding: NSASCIIStringEncoding]]; - NSString* ns_default_filename = - [[NSString alloc] - initWithCString: default_filename - encoding: NSUTF8StringEncoding]; - [panel setNameFieldStringValue: ns_default_filename]; + [panel setTitle: [NSString stringWithUTF8String: title]]; + [panel setDirectoryURL: [NSURL fileURLWithPath: [NSString stringWithUTF8String: directory] + isDirectory: YES]]; + [panel setNameFieldStringValue: [NSString stringWithUTF8String: default_filename]]; result = [panel runModal]; - [ns_default_filename release]; -#ifdef COMPILING_FOR_10_10 - if (result == NSModalResponseOK) -#else - if (result == NSOKButton) -#endif - { - NSURL* url = [panel URL]; - NSString* filename = [url path]; + if (result == NSModalResponseOK) { + NSString *filename = [[panel URL] path]; if (!filename) { PyErr_SetString(PyExc_RuntimeError, "Failed to obtain filename"); return 0; } - unsigned int n = [filename length]; - unichar* buffer = malloc(n*sizeof(unichar)); - [filename getCharacters: buffer]; - PyObject* string = PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, buffer, n); - free(buffer); - return string; + return PyUnicode_FromString([filename UTF8String]); } Py_RETURN_NONE; } -static PyObject* -set_cursor(PyObject* unused, PyObject* args) -{ - int i; - if(!PyArg_ParseTuple(args, "i", &i)) return NULL; - switch (i) - { case 0: [[NSCursor pointingHandCursor] set]; break; - case 1: [[NSCursor arrowCursor] set]; break; - case 2: [[NSCursor crosshairCursor] set]; break; - case 3: [[NSCursor openHandCursor] set]; break; - /* OSX handles busy state itself so no need to set a cursor here */ - case 4: break; - default: return NULL; - } - Py_RETURN_NONE; -} - -@implementation WindowServerConnectionManager -static WindowServerConnectionManager *sharedWindowServerConnectionManager = nil; - -+ (WindowServerConnectionManager *)sharedManager -{ - if (sharedWindowServerConnectionManager == nil) - { - sharedWindowServerConnectionManager = [[super allocWithZone:NULL] init]; - } - return sharedWindowServerConnectionManager; -} - -+ (id)allocWithZone:(NSZone *)zone -{ - return [[self sharedManager] retain]; -} - -+ (id)copyWithZone:(NSZone *)zone -{ - return self; -} - -+ (id)retain -{ - return self; -} - -- (NSUInteger)retainCount -{ - return NSUIntegerMax; //denotes an object that cannot be released -} - -- (oneway void)release -{ - // Don't release a singleton object -} - -- (id)autorelease -{ - return self; -} - -- (void)launch:(NSNotification*)notification -{ - CFRunLoopRef runloop; - CFMachPortRef port; - CFRunLoopSourceRef source; - NSDictionary* dictionary = [notification userInfo]; - if (! [[dictionary valueForKey:@"NSApplicationName"] - isEqualToString:@"Python"]) - return; - NSNumber* psnLow = [dictionary valueForKey: @"NSApplicationProcessSerialNumberLow"]; - NSNumber* psnHigh = [dictionary valueForKey: @"NSApplicationProcessSerialNumberHigh"]; - ProcessSerialNumber psn; - psn.highLongOfPSN = [psnHigh intValue]; - psn.lowLongOfPSN = [psnLow intValue]; - runloop = CFRunLoopGetCurrent(); - port = CGEventTapCreateForPSN(&psn, - kCGHeadInsertEventTap, - kCGEventTapOptionListenOnly, - kCGEventMaskForAllEvents, - &_eventtap_callback, - runloop); - source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, - port, - 0); - CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); - CFRelease(port); -} -@end - @implementation Window - (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager { @@ -1488,15 +1139,7 @@ - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen - (BOOL)closeButtonPressed { - PyObject* result; - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(manager, "close", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); + gil_call_method(manager, "_close_button_pressed"); return YES; } @@ -1508,58 +1151,13 @@ - (void)close /* This is needed for show(), which should exit from [NSApp run] * after all windows are closed. */ -} - -- (void)dealloc -{ - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); + // For each new window, we have incremented the manager reference, so + // we need to bring that down during close and not just dealloc. Py_DECREF(manager); - PyGILState_Release(gstate); - /* The reference count of the view that was added as a subview to the - * content view of this window was increased during the call to addSubview, - * and is decreased during the call to [super dealloc]. - */ - [super dealloc]; -} -@end - -@implementation ToolWindow -- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window -{ - [self initWithContentRect: rect - styleMask: NSWindowStyleMaskTitled - | NSWindowStyleMaskClosable - | NSWindowStyleMaskResizable - | NSWindowStyleMaskMiniaturizable - backing: NSBackingStoreBuffered - defer: YES]; - [self setTitle: @"Subplot Configuration Tool"]; - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector(masterCloses:) - name: NSWindowWillCloseNotification - object: window]; - return self; -} - -- (void)masterCloses:(NSNotification*)notification -{ - [self close]; -} - -- (void)close -{ - [[NSNotificationCenter defaultCenter] removeObserver: self]; - [super close]; } @end @implementation View -- (BOOL)isFlipped -{ - return NO; -} - - (View*)initWithFrame:(NSRect)rect { self = [super initWithFrame: rect]; @@ -1571,7 +1169,7 @@ - (View*)initWithFrame:(NSRect)rect - (void)dealloc { FigureCanvas* fc = (FigureCanvas*)canvas; - if (fc) fc->view = NULL; + if (fc) { fc->view = NULL; } [super dealloc]; } @@ -1581,8 +1179,10 @@ - (void)setCanvas: (PyObject*)newCanvas } static void _buffer_release(void* info, const void* data, size_t size) { + PyGILState_STATE gstate = PyGILState_Ensure(); PyBuffer_Release((Py_buffer *)info); free(info); + PyGILState_Release(gstate); } static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) @@ -1607,7 +1207,7 @@ static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) const size_t bitsPerPixel = bitsPerComponent * nComponents; const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; - CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); if (!colorspace) { _buffer_release(buffer, NULL, 0); return 1; @@ -1660,17 +1260,8 @@ -(void)drawRect:(NSRect)rect CGContextRef cr = [[NSGraphicsContext currentContext] CGContext]; - double new_device_scale = _get_device_scale(cr); - - if (device_scale != new_device_scale) { - device_scale = new_device_scale; - if (!PyObject_CallMethod(canvas, "_set_device_scale", "d", device_scale, NULL)) { - PyErr_Print(); - goto exit; - } - } - if (!(renderer = PyObject_CallMethod(canvas, "_draw", "", NULL)) - || !(renderer_buffer = PyObject_GetAttrString(renderer, "_renderer"))) { + if (!(renderer = PyObject_CallMethod(canvas, "get_renderer", "")) + || !(renderer_buffer = PyObject_CallMethod(renderer, "buffer_rgba", ""))) { PyErr_Print(); goto exit; } @@ -1679,7 +1270,18 @@ -(void)drawRect:(NSRect)rect goto exit; } if (!NSIsEmptyRect(rubberband)) { - NSFrameRect(rubberband); + // We use bezier paths so we can stroke the outside with a dash + // pattern alternating white/black with two separate paths offset + // in phase. + NSBezierPath *white_path = [NSBezierPath bezierPathWithRect: rubberband]; + NSBezierPath *black_path = [NSBezierPath bezierPathWithRect: rubberband]; + CGFloat dash_pattern[2] = {3, 3}; + [white_path setLineDash: dash_pattern count: 2 phase: 0]; + [black_path setLineDash: dash_pattern count: 2 phase: 3]; + [[NSColor whiteColor] setStroke]; + [white_path stroke]; + [[NSColor blackColor] setStroke]; + [black_path stroke]; } exit: @@ -1689,6 +1291,38 @@ -(void)drawRect:(NSRect)rect PyGILState_Release(gstate); } +- (void)updateDevicePixelRatio:(double)scale +{ + PyObject* change = NULL; + PyGILState_STATE gstate = PyGILState_Ensure(); + + device_scale = scale; + if (!(change = PyObject_CallMethod(canvas, "_set_device_pixel_ratio", "d", device_scale))) { + PyErr_Print(); + goto exit; + } + if (PyObject_IsTrue(change)) { + // Notify that there was a resize_event that took place + process_event( + "ResizeEvent", "{s:s, s:O}", + "name", "resize_event", "canvas", canvas); + gil_call_method(canvas, "draw_idle"); + [self setNeedsDisplay: YES]; + } + + exit: + Py_XDECREF(change); + + PyGILState_Release(gstate); +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification +{ + Window* window = [notification object]; + + [self updateDevicePixelRatio: [window backingScaleFactor]]; +} + - (void)windowDidResize: (NSNotification*)notification { int width, height; @@ -1703,7 +1337,7 @@ - (void)windowDidResize: (NSNotification*)notification PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod( canvas, "resize", "ii", width, height); - if(result) + if (result) Py_DECREF(result); else PyErr_Print(); @@ -1713,16 +1347,9 @@ - (void)windowDidResize: (NSNotification*)notification - (void)windowWillClose:(NSNotification*)notification { - PyGILState_STATE gstate; - PyObject* result; - - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "close_event", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); + process_event( + "CloseEvent", "{s:s, s:O}", + "name", "close_event", "canvas", canvas); } - (BOOL)windowShouldClose:(NSNotification*)notification @@ -1738,57 +1365,45 @@ - (BOOL)windowShouldClose:(NSNotification*)notification data1: 0 data2: 0]; [NSApp postEvent: event atStart: true]; - if ([window respondsToSelector: @selector(closeButtonPressed)]) - { BOOL closed = [((Window*) window) closeButtonPressed]; - /* If closed, the window has already been closed via the manager. */ - if (closed) return NO; + if ([window respondsToSelector: @selector(closeButtonPressed)]) { + BOOL closed = [((Window*) window) closeButtonPressed]; + /* If closed, the window has already been closed via the manager. */ + if (closed) { return NO; } } return YES; } - (void)mouseEntered:(NSEvent *)event { - PyGILState_STATE gstate; - PyObject* result; - int x, y; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; - - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "enter_notify_event", "O(ii)", - Py_None, x, y); - - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); + process_event( + "LocationEvent", "{s:s, s:O, s:i, s:i, s:N}", + "name", "figure_enter_event", "canvas", canvas, "x", x, "y", y, + "modifiers", mpl_modifiers(event)); } - (void)mouseExited:(NSEvent *)event { - PyGILState_STATE gstate; - PyObject* result; - - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "leave_notify_event", ""); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - PyGILState_Release(gstate); + int x, y; + NSPoint location = [event locationInWindow]; + location = [self convertPoint: location fromView: nil]; + x = location.x * device_scale; + y = location.y * device_scale; + process_event( + "LocationEvent", "{s:s, s:O, s:i, s:i, s:N}", + "name", "figure_leave_event", "canvas", canvas, "x", x, "y", y, + "modifiers", mpl_modifiers(event)); } - (void)mouseDown:(NSEvent *)event { int x, y; - int num; + int button; int dblclick = 0; - PyObject* result; - PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; @@ -1798,63 +1413,56 @@ - (void)mouseDown:(NSEvent *)event { unsigned int modifier = [event modifierFlags]; if (modifier & NSEventModifierFlagControl) /* emulate a right-button click */ - num = 3; + button = 3; else if (modifier & NSEventModifierFlagOption) /* emulate a middle-button click */ - num = 2; + button = 2; else { - num = 1; - if ([NSCursor currentCursor]==[NSCursor openHandCursor]) + button = 1; + if ([NSCursor currentCursor]==[NSCursor openHandCursor]) { + leftMouseGrabbing = true; [[NSCursor closedHandCursor] set]; + } } break; } - case NSEventTypeOtherMouseDown: num = 2; break; - case NSEventTypeRightMouseDown: num = 3; break; + case NSEventTypeOtherMouseDown: button = 2; break; + case NSEventTypeRightMouseDown: button = 3; break; default: return; /* Unknown mouse event */ } if ([event clickCount] == 2) { dblclick = 1; } - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); + process_event( + "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:i, s:N}", + "name", "button_press_event", "canvas", canvas, "x", x, "y", y, + "button", button, "dblclick", dblclick, "modifiers", mpl_modifiers(event)); } - (void)mouseUp:(NSEvent *)event { - int num; + int button; int x, y; - PyObject* result; - PyGILState_STATE gstate; NSPoint location = [event locationInWindow]; location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; switch ([event type]) { case NSEventTypeLeftMouseUp: - num = 1; + leftMouseGrabbing = false; + button = 1; if ([NSCursor currentCursor]==[NSCursor closedHandCursor]) [[NSCursor openHandCursor] set]; break; - case NSEventTypeOtherMouseUp: num = 2; break; - case NSEventTypeRightMouseUp: num = 3; break; + case NSEventTypeOtherMouseUp: button = 2; break; + case NSEventTypeRightMouseUp: button = 3; break; default: return; /* Unknown mouse event */ } - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); + process_event( + "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:N}", + "name", "button_release_event", "canvas", canvas, "x", x, "y", y, + "button", button, "modifiers", mpl_modifiers(event)); } - (void)mouseMoved:(NSEvent *)event @@ -1864,14 +1472,10 @@ - (void)mouseMoved:(NSEvent *)event location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); + process_event( + "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", + "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, + "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } - (void)mouseDragged:(NSEvent *)event @@ -1881,281 +1485,171 @@ - (void)mouseDragged:(NSEvent *)event location = [self convertPoint: location fromView: nil]; x = location.x * device_scale; y = location.y * device_scale; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -- (void)rightMouseDown:(NSEvent *)event -{ - int x, y; - int num = 3; - int dblclick = 0; - PyObject* result; - PyGILState_STATE gstate; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - gstate = PyGILState_Ensure(); - if ([event clickCount] == 2) { - dblclick = 1; - } - result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -- (void)rightMouseUp:(NSEvent *)event -{ - int x, y; - int num = 3; - PyObject* result; - PyGILState_STATE gstate; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -- (void)rightMouseDragged:(NSEvent *)event -{ - int x, y; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -- (void)otherMouseDown:(NSEvent *)event -{ - int x, y; - int num = 2; - int dblclick = 0; - PyObject* result; - PyGILState_STATE gstate; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - gstate = PyGILState_Ensure(); - if ([event clickCount] == 2) { - dblclick = 1; - } - result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); + process_event( + "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", + "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, + "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } -- (void)otherMouseUp:(NSEvent *)event -{ - int x, y; - int num = 2; - PyObject* result; - PyGILState_STATE gstate; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "button_release_event", "iii", x, y, num); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -- (void)otherMouseDragged:(NSEvent *)event -{ - int x, y; - NSPoint location = [event locationInWindow]; - location = [self convertPoint: location fromView: nil]; - x = location.x * device_scale; - y = location.y * device_scale; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "motion_notify_event", "ii", x, y); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} +- (void)rightMouseDown:(NSEvent *)event { [self mouseDown: event]; } +- (void)rightMouseUp:(NSEvent *)event { [self mouseUp: event]; } +- (void)rightMouseDragged:(NSEvent *)event { [self mouseDragged: event]; } +- (void)otherMouseDown:(NSEvent *)event { [self mouseDown: event]; } +- (void)otherMouseUp:(NSEvent *)event { [self mouseUp: event]; } +- (void)otherMouseDragged:(NSEvent *)event { [self mouseDragged: event]; } - (void)setRubberband:(NSRect)rect { - if (!NSIsEmptyRect(rubberband)) [self setNeedsDisplayInRect: rubberband]; + // The space we want to redraw is a union of the previous rubberband + // with the new rubberband and then expanded (negative inset) by one + // in each direction to account for the stroke linewidth. + [self setNeedsDisplayInRect: NSInsetRect(NSUnionRect(rect, rubberband), -1, -1)]; rubberband = rect; - [self setNeedsDisplayInRect: rubberband]; } - (void)removeRubberband { - if (NSIsEmptyRect(rubberband)) return; + if (NSIsEmptyRect(rubberband)) { return; } [self setNeedsDisplayInRect: rubberband]; rubberband = NSZeroRect; } - - - (const char*)convertKeyEvent:(NSEvent*)event { - NSDictionary* specialkeymappings = [NSDictionary dictionaryWithObjectsAndKeys: - @"left", [NSNumber numberWithUnsignedLong:NSLeftArrowFunctionKey], - @"right", [NSNumber numberWithUnsignedLong:NSRightArrowFunctionKey], - @"up", [NSNumber numberWithUnsignedLong:NSUpArrowFunctionKey], - @"down", [NSNumber numberWithUnsignedLong:NSDownArrowFunctionKey], - @"f1", [NSNumber numberWithUnsignedLong:NSF1FunctionKey], - @"f2", [NSNumber numberWithUnsignedLong:NSF2FunctionKey], - @"f3", [NSNumber numberWithUnsignedLong:NSF3FunctionKey], - @"f4", [NSNumber numberWithUnsignedLong:NSF4FunctionKey], - @"f5", [NSNumber numberWithUnsignedLong:NSF5FunctionKey], - @"f6", [NSNumber numberWithUnsignedLong:NSF6FunctionKey], - @"f7", [NSNumber numberWithUnsignedLong:NSF7FunctionKey], - @"f8", [NSNumber numberWithUnsignedLong:NSF8FunctionKey], - @"f9", [NSNumber numberWithUnsignedLong:NSF9FunctionKey], - @"f10", [NSNumber numberWithUnsignedLong:NSF10FunctionKey], - @"f11", [NSNumber numberWithUnsignedLong:NSF11FunctionKey], - @"f12", [NSNumber numberWithUnsignedLong:NSF12FunctionKey], - @"f13", [NSNumber numberWithUnsignedLong:NSF13FunctionKey], - @"f14", [NSNumber numberWithUnsignedLong:NSF14FunctionKey], - @"f15", [NSNumber numberWithUnsignedLong:NSF15FunctionKey], - @"f16", [NSNumber numberWithUnsignedLong:NSF16FunctionKey], - @"f17", [NSNumber numberWithUnsignedLong:NSF17FunctionKey], - @"f18", [NSNumber numberWithUnsignedLong:NSF18FunctionKey], - @"f19", [NSNumber numberWithUnsignedLong:NSF19FunctionKey], - @"scroll_lock", [NSNumber numberWithUnsignedLong:NSScrollLockFunctionKey], - @"break", [NSNumber numberWithUnsignedLong:NSBreakFunctionKey], - @"insert", [NSNumber numberWithUnsignedLong:NSInsertFunctionKey], - @"delete", [NSNumber numberWithUnsignedLong:NSDeleteFunctionKey], - @"home", [NSNumber numberWithUnsignedLong:NSHomeFunctionKey], - @"end", [NSNumber numberWithUnsignedLong:NSEndFunctionKey], - @"pagedown", [NSNumber numberWithUnsignedLong:NSPageDownFunctionKey], - @"pageup", [NSNumber numberWithUnsignedLong:NSPageUpFunctionKey], - @"backspace", [NSNumber numberWithUnsignedLong:NSDeleteCharacter], - @"enter", [NSNumber numberWithUnsignedLong:NSEnterCharacter], - @"tab", [NSNumber numberWithUnsignedLong:NSTabCharacter], - @"enter", [NSNumber numberWithUnsignedLong:NSCarriageReturnCharacter], - @"backtab", [NSNumber numberWithUnsignedLong:NSBackTabCharacter], - @"escape", [NSNumber numberWithUnsignedLong:27], - nil - ]; - NSMutableString* returnkey = [NSMutableString string]; - if ([event modifierFlags] & NSEventModifierFlagControl) - [returnkey appendString:@"ctrl+" ]; - if ([event modifierFlags] & NSEventModifierFlagOption) + if (keyChangeControl) { + // When control is the key that was pressed, return the full word + [returnkey appendString:@"control+"]; + } else if (([event modifierFlags] & NSEventModifierFlagControl)) { + // If control is already pressed, return the shortened version + [returnkey appendString:@"ctrl+"]; + } + if (([event modifierFlags] & NSEventModifierFlagOption) || keyChangeOption) { [returnkey appendString:@"alt+" ]; - if ([event modifierFlags] & NSEventModifierFlagCommand) + } + if (([event modifierFlags] & NSEventModifierFlagCommand) || keyChangeCommand) { [returnkey appendString:@"cmd+" ]; - - unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0]; - NSString* specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]]; - if (specialchar){ - if ([event modifierFlags] & NSEventModifierFlagShift) - [returnkey appendString:@"shift+" ]; - [returnkey appendString:specialchar]; } - else - [returnkey appendString:[event charactersIgnoringModifiers]]; + // Don't print caps_lock unless it was the key that got pressed + if (keyChangeCapsLock) { + [returnkey appendString:@"caps_lock+" ]; + } + + // flagsChanged event can't handle charactersIgnoringModifiers + // because it was a modifier key that was pressed/released + if (event.type != NSEventTypeFlagsChanged) { + NSString* specialchar; + switch ([[event charactersIgnoringModifiers] characterAtIndex:0]) { + case NSLeftArrowFunctionKey: specialchar = @"left"; break; + case NSRightArrowFunctionKey: specialchar = @"right"; break; + case NSUpArrowFunctionKey: specialchar = @"up"; break; + case NSDownArrowFunctionKey: specialchar = @"down"; break; + case NSF1FunctionKey: specialchar = @"f1"; break; + case NSF2FunctionKey: specialchar = @"f2"; break; + case NSF3FunctionKey: specialchar = @"f3"; break; + case NSF4FunctionKey: specialchar = @"f4"; break; + case NSF5FunctionKey: specialchar = @"f5"; break; + case NSF6FunctionKey: specialchar = @"f6"; break; + case NSF7FunctionKey: specialchar = @"f7"; break; + case NSF8FunctionKey: specialchar = @"f8"; break; + case NSF9FunctionKey: specialchar = @"f9"; break; + case NSF10FunctionKey: specialchar = @"f10"; break; + case NSF11FunctionKey: specialchar = @"f11"; break; + case NSF12FunctionKey: specialchar = @"f12"; break; + case NSF13FunctionKey: specialchar = @"f13"; break; + case NSF14FunctionKey: specialchar = @"f14"; break; + case NSF15FunctionKey: specialchar = @"f15"; break; + case NSF16FunctionKey: specialchar = @"f16"; break; + case NSF17FunctionKey: specialchar = @"f17"; break; + case NSF18FunctionKey: specialchar = @"f18"; break; + case NSF19FunctionKey: specialchar = @"f19"; break; + case NSScrollLockFunctionKey: specialchar = @"scroll_lock"; break; + case NSBreakFunctionKey: specialchar = @"break"; break; + case NSInsertFunctionKey: specialchar = @"insert"; break; + case NSDeleteFunctionKey: specialchar = @"delete"; break; + case NSHomeFunctionKey: specialchar = @"home"; break; + case NSEndFunctionKey: specialchar = @"end"; break; + case NSPageDownFunctionKey: specialchar = @"pagedown"; break; + case NSPageUpFunctionKey: specialchar = @"pageup"; break; + case NSDeleteCharacter: specialchar = @"backspace"; break; + case NSEnterCharacter: specialchar = @"enter"; break; + case NSTabCharacter: specialchar = @"tab"; break; + case NSCarriageReturnCharacter: specialchar = @"enter"; break; + case NSBackTabCharacter: specialchar = @"backtab"; break; + case 27: specialchar = @"escape"; break; + default: specialchar = nil; + } + if (specialchar) { + if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) { + [returnkey appendString:@"shift+"]; + } + [returnkey appendString:specialchar]; + } else { + [returnkey appendString:[event charactersIgnoringModifiers]]; + } + } else { + if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) { + [returnkey appendString:@"shift+"]; + } + // Since it was a modifier event trim the final character of the string + // because we added in "+" earlier + [returnkey setString: [returnkey substringToIndex:[returnkey length] - 1]]; + } return [returnkey UTF8String]; } - (void)keyDown:(NSEvent*)event { - PyObject* result; const char* s = [self convertKeyEvent: event]; - PyGILState_STATE gstate = PyGILState_Ensure(); - if (s==NULL) - { - result = PyObject_CallMethod(canvas, "key_press_event", "O", Py_None); - } - else - { - result = PyObject_CallMethod(canvas, "key_press_event", "s", s); + NSPoint location = [[self window] mouseLocationOutsideOfEventStream]; + location = [self convertPoint: location fromView: nil]; + int x = location.x * device_scale, + y = location.y * device_scale; + if (s) { + process_event( + "KeyEvent", "{s:s, s:O, s:s, s:i, s:i}", + "name", "key_press_event", "canvas", canvas, "key", s, "x", x, "y", y); + } else { + process_event( + "KeyEvent", "{s:s, s:O, s:O, s:i, s:i}", + "name", "key_press_event", "canvas", canvas, "key", Py_None, "x", x, "y", y); } - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); } - (void)keyUp:(NSEvent*)event { - PyObject* result; const char* s = [self convertKeyEvent: event]; - PyGILState_STATE gstate = PyGILState_Ensure(); - if (s==NULL) - { - result = PyObject_CallMethod(canvas, "key_release_event", "O", Py_None); - } - else - { - result = PyObject_CallMethod(canvas, "key_release_event", "s", s); + NSPoint location = [[self window] mouseLocationOutsideOfEventStream]; + location = [self convertPoint: location fromView: nil]; + int x = location.x * device_scale, + y = location.y * device_scale; + if (s) { + process_event( + "KeyEvent", "{s:s, s:O, s:s, s:i, s:i}", + "name", "key_release_event", "canvas", canvas, "key", s, "x", x, "y", y); + } else { + process_event( + "KeyEvent", "{s:s, s:O, s:O, s:i, s:i}", + "name", "key_release_event", "canvas", canvas, "key", Py_None, "x", x, "y", y); } - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); } - (void)scrollWheel:(NSEvent*)event { int step; float d = [event deltaY]; - if (d > 0) step = 1; - else if (d < 0) step = -1; + if (d > 0) { step = 1; } + else if (d < 0) { step = -1; } else return; NSPoint location = [event locationInWindow]; NSPoint point = [self convertPoint: location fromView: nil]; int x = (int)round(point.x * device_scale); int y = (int)round(point.y * device_scale - 1); - - PyObject* result; - PyGILState_STATE gstate = PyGILState_Ensure(); - result = PyObject_CallMethod(canvas, "scroll_event", "iii", x, y, step); - if(result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); + process_event( + "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:N}", + "name", "scroll_event", "canvas", canvas, + "x", x, "y", y, "step", step, "modifiers", mpl_modifiers(event)); } - (BOOL)acceptsFirstResponder @@ -2163,29 +1657,60 @@ - (BOOL)acceptsFirstResponder return YES; } -/* This is all wrong. Address of pointer is being passed instead of pointer, keynames don't - match up with what the front-end and does the front-end even handle modifier keys by themselves? +// flagsChanged gets called whenever a modifier key is pressed OR released +// so we need to handle both cases here +- (void)flagsChanged:(NSEvent *)event +{ + bool isPress = false; // true if key is pressed, false if key was released + + // Each if clause tests the two cases for each of the keys we can handle + // 1. If the modifier flag "command key" is pressed and it was not previously + // 2. If the modifier flag "command key" is not pressed and it was previously + // !! converts the result of the bitwise & operator to a logical boolean, + // which allows us to then bitwise xor (^) the result with a boolean (lastCommand). + if (!!([event modifierFlags] & NSEventModifierFlagCommand) ^ lastCommand) { + // Command pressed/released + lastCommand = !lastCommand; + keyChangeCommand = true; + isPress = lastCommand; + } else if (!!([event modifierFlags] & NSEventModifierFlagControl) ^ lastControl) { + // Control pressed/released + lastControl = !lastControl; + keyChangeControl = true; + isPress = lastControl; + } else if (!!([event modifierFlags] & NSEventModifierFlagShift) ^ lastShift) { + // Shift pressed/released + lastShift = !lastShift; + keyChangeShift = true; + isPress = lastShift; + } else if (!!([event modifierFlags] & NSEventModifierFlagOption) ^ lastOption) { + // Option pressed/released + lastOption = !lastOption; + keyChangeOption = true; + isPress = lastOption; + } else if (!!([event modifierFlags] & NSEventModifierFlagCapsLock) ^ lastCapsLock) { + // Capslock pressed/released + lastCapsLock = !lastCapsLock; + keyChangeCapsLock = true; + isPress = lastCapsLock; + } else { + // flag we don't handle + return; + } -- (void)flagsChanged:(NSEvent*)event -{ - const char *s = NULL; - if (([event modifierFlags] & NSControlKeyMask) == NSControlKeyMask) - s = "control"; - else if (([event modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask) - s = "shift"; - else if (([event modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask) - s = "alt"; - else return; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "key_press_event", "s", &s); - if(result) - Py_DECREF(result); - else - PyErr_Print(); + if (isPress) { + [self keyDown:event]; + } else { + [self keyUp:event]; + } - PyGILState_Release(gstate); + // Reset the state for the key changes after handling the event + keyChangeCommand = false; + keyChangeControl = false; + keyChangeShift = false; + keyChangeOption = false; + keyChangeCapsLock = false; } - */ @end static PyObject* @@ -2206,7 +1731,8 @@ - (void)flagsChanged:(NSEvent*)event typedef struct { PyObject_HEAD - CFRunLoopTimerRef timer; + NSTimer* timer; + } Timer; static PyObject* @@ -2214,7 +1740,9 @@ - (void)flagsChanged:(NSEvent*)event { lazy_init(); Timer* self = (Timer*)type->tp_alloc(type, 0); - if (!self) return NULL; + if (!self) { + return NULL; + } self->timer = NULL; return (PyObject*) self; } @@ -2222,46 +1750,16 @@ - (void)flagsChanged:(NSEvent*)event static PyObject* Timer_repr(Timer* self) { - return PyUnicode_FromFormat("Timer object %p wrapping CFRunLoopTimerRef %p", + return PyUnicode_FromFormat("Timer object %p wrapping NSTimer %p", (void*) self, (void*)(self->timer)); } -static char Timer_doc[] = -"A Timer object wraps a CFRunLoopTimerRef and can add it to the event loop.\n"; - -static void timer_callback(CFRunLoopTimerRef timer, void* info) -{ - PyObject* method = info; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallFunction(method, NULL); - if (result) { - Py_DECREF(result); - } else { - PyErr_Print(); - } - PyGILState_Release(gstate); -} - -static void context_cleanup(const void* info) -{ - Py_DECREF((PyObject*)info); -} - static PyObject* Timer__timer_start(Timer* self, PyObject* args) { - CFRunLoopRef runloop; - CFRunLoopTimerRef timer; - CFRunLoopTimerContext context; - CFAbsoluteTime firstFire; - CFTimeInterval interval; + NSTimeInterval interval; PyObject* py_interval = NULL, * py_single = NULL, * py_on_timer = NULL; int single; - runloop = CFRunLoopGetCurrent(); - if (!runloop) { - PyErr_SetString(PyExc_RuntimeError, "Failed to obtain run loop"); - return NULL; - } if (!(py_interval = PyObject_GetAttrString((PyObject*)self, "_interval")) || ((interval = PyFloat_AsDouble(py_interval) / 1000.), PyErr_Occurred()) || !(py_single = PyObject_GetAttrString((PyObject*)self, "_single")) @@ -2269,41 +1767,26 @@ static void context_cleanup(const void* info) || !(py_on_timer = PyObject_GetAttrString((PyObject*)self, "_on_timer"))) { goto exit; } - // (current time + interval) is time of first fire. - firstFire = CFAbsoluteTimeGetCurrent() + interval; - if (single) { - interval = 0; - } if (!PyMethod_Check(py_on_timer)) { PyErr_SetString(PyExc_RuntimeError, "_on_timer should be a Python method"); goto exit; } - Py_INCREF(py_on_timer); - context.version = 0; - context.retain = NULL; - context.release = context_cleanup; - context.copyDescription = NULL; - context.info = py_on_timer; - timer = CFRunLoopTimerCreate(kCFAllocatorDefault, - firstFire, - interval, - 0, - 0, - timer_callback, - &context); - if (!timer) { - PyErr_SetString(PyExc_RuntimeError, "Failed to create timer"); - goto exit; - } - if (self->timer) { - CFRunLoopTimerInvalidate(self->timer); - CFRelease(self->timer); - } - CFRunLoopAddTimer(runloop, timer, kCFRunLoopCommonModes); - /* Don't release the timer here, since the run loop may be destroyed and - * the timer lost before we have a chance to decrease the reference count - * of the attribute */ - self->timer = timer; + + // hold a reference to the timer so we can invalidate/stop it later + self->timer = [NSTimer timerWithTimeInterval: interval + repeats: !single + block: ^(NSTimer *timer) { + gil_call_method((PyObject*)self, "_on_timer"); + if (single) { + // A single-shot timer will be automatically invalidated when it fires, so + // we shouldn't do it ourselves when the object is deleted. + self->timer = NULL; + } + }]; + // Schedule the timer on the main run loop which is needed + // when updating the UI from a background thread + [[NSRunLoop mainRunLoop] addTimer: self->timer forMode: NSRunLoopCommonModes]; + exit: Py_XDECREF(py_interval); Py_XDECREF(py_single); @@ -2315,144 +1798,107 @@ static void context_cleanup(const void* info) } } -static PyObject* -Timer__timer_stop(Timer* self) +static void +Timer__timer_stop_impl(Timer* self) { if (self->timer) { - CFRunLoopTimerInvalidate(self->timer); - CFRelease(self->timer); + [self->timer invalidate]; self->timer = NULL; } +} + +static PyObject* +Timer__timer_stop(Timer* self) +{ + Timer__timer_stop_impl(self); Py_RETURN_NONE; } static void Timer_dealloc(Timer* self) { - Timer__timer_stop(self); + Timer__timer_stop_impl(self); Py_TYPE(self)->tp_free((PyObject*)self); } -static PyMethodDef Timer_methods[] = { - {"_timer_start", - (PyCFunction)Timer__timer_start, - METH_VARARGS, - "Initialize and start the timer." - }, - {"_timer_stop", - (PyCFunction)Timer__timer_stop, - METH_NOARGS, - "Stop the timer." - }, - {NULL} /* Sentinel */ -}; - static PyTypeObject TimerType = { PyVarObject_HEAD_INIT(NULL, 0) - "_macosx.Timer", /*tp_name*/ - sizeof(Timer), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)Timer_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)Timer_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - Timer_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Timer_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - Timer_new, /* tp_new */ -}; - -static struct PyMethodDef methods[] = { - {"event_loop_is_running", - (PyCFunction)event_loop_is_running, - METH_NOARGS, - "Return whether the OSX backend has set up the NSApp main event loop." - }, - {"show", - (PyCFunction)show, - METH_NOARGS, - "Show all the figures and enter the main loop.\n" - "\n" - "This function does not return until all Matplotlib windows are closed,\n" - "and is normally not needed in interactive sessions." - }, - {"choose_save_file", - (PyCFunction)choose_save_file, - METH_VARARGS, - "Closes the window." - }, - {"set_cursor", - (PyCFunction)set_cursor, - METH_VARARGS, - "Sets the active cursor." - }, - {NULL, NULL, 0, NULL} /* sentinel */ + .tp_name = "matplotlib.backends._macosx.Timer", + .tp_doc = PyDoc_STR("A Timer object that contains an NSTimer that gets added to " + "the event loop when started."), + .tp_basicsize = sizeof(Timer), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_new = (newfunc)Timer_new, + .tp_dealloc = (destructor)Timer_dealloc, + .tp_repr = (reprfunc)Timer_repr, + + .tp_methods = (PyMethodDef[]){ // All docstrings are inherited. + {"_timer_start", + (PyCFunction)Timer__timer_start, + METH_VARARGS}, + {"_timer_stop", + (PyCFunction)Timer__timer_stop, + METH_NOARGS}, + {} // sentinel + }, }; static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_macosx", - "Mac OS X native backend", - -1, - methods, - NULL, - NULL, - NULL, - NULL + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_macosx", + .m_doc = PyDoc_STR("Mac OS X native backend"), + .m_size = -1, + .m_methods = (PyMethodDef[]){ + {"event_loop_is_running", + (PyCFunction)event_loop_is_running, + METH_NOARGS, + PyDoc_STR( + "Return whether the macosx backend has set up the NSApp main event loop.")}, + {"wake_on_fd_write", + (PyCFunction)wake_on_fd_write, + METH_VARARGS, + PyDoc_STR( + "Arrange for Python to invoke its signal handlers when (any) data is\n" + "written on the file descriptor given as argument.")}, + {"stop", + (PyCFunction)stop, + METH_NOARGS, + PyDoc_STR("Stop the NSApp.")}, + {"show", + (PyCFunction)show, + METH_NOARGS, + PyDoc_STR( + "Show all the figures and enter the main loop.\n" + "\n" + "This function does not return until all Matplotlib windows are closed,\n" + "and is normally not needed in interactive sessions.")}, + {"choose_save_file", + (PyCFunction)choose_save_file, + METH_VARARGS, + PyDoc_STR("Query the user for a location where to save a file.")}, + {} /* Sentinel */ + }, }; #pragma GCC visibility push(default) -PyObject* PyInit__macosx(void) +PyMODINIT_FUNC +PyInit__macosx(void) { - PyObject *module; - - if (PyType_Ready(&FigureCanvasType) < 0 - || PyType_Ready(&FigureManagerType) < 0 - || PyType_Ready(&NavigationToolbar2Type) < 0 - || PyType_Ready(&TimerType) < 0) + PyObject *m; + if (!(m = PyModule_Create(&moduledef)) + || PyModule_AddType(m, &FigureCanvasType) + || PyModule_AddType(m, &FigureManagerType) + || PyModule_AddType(m, &NavigationToolbar2Type) + || PyModule_AddType(m, &TimerType)) { + Py_XDECREF(m); return NULL; - - module = PyModule_Create(&moduledef); - if (!module) - return NULL; - - Py_INCREF(&FigureCanvasType); - Py_INCREF(&FigureManagerType); - Py_INCREF(&NavigationToolbar2Type); - Py_INCREF(&TimerType); - PyModule_AddObject(module, "FigureCanvas", (PyObject*) &FigureCanvasType); - PyModule_AddObject(module, "FigureManager", (PyObject*) &FigureManagerType); - PyModule_AddObject(module, "NavigationToolbar2", (PyObject*) &NavigationToolbar2Type); - PyModule_AddObject(module, "Timer", (PyObject*) &TimerType); - - return module; + } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } #pragma GCC visibility pop diff --git a/src/_path.h b/src/_path.h index 784a6457807c..c03703776760 100644 --- a/src/_path.h +++ b/src/_path.h @@ -14,12 +14,12 @@ #include "agg_conv_curve.h" #include "agg_conv_stroke.h" #include "agg_conv_transform.h" -#include "agg_path_storage.h" #include "agg_trans_affine.h" #include "path_converters.h" #include "_backend_agg_basic_types.h" -#include "numpy_cpp.h" + +const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3 }; struct XY { @@ -111,7 +111,7 @@ void point_in_path_impl(PointArray &points, PathIterator &path, ResultArray &ins size_t i; bool all_done; - size_t n = points.size(); + size_t n = safe_first_shape(points); std::vector yflag0(n); std::vector subpath_flag(n); @@ -244,8 +244,7 @@ inline void points_in_path(PointArray &points, typedef agg::conv_curve curve_t; typedef agg::conv_contour contour_t; - size_t i; - for (i = 0; i < points.size(); ++i) { + for (auto i = 0; i < safe_first_shape(points); ++i) { result[i] = false; } @@ -254,7 +253,7 @@ inline void points_in_path(PointArray &points, } transformed_path_t trans_path(path, trans); - no_nans_t no_nans_path(trans_path, true, path.has_curves()); + no_nans_t no_nans_path(trans_path, true, path.has_codes()); curve_t curved_path(no_nans_path); if (r != 0.0) { contour_t contoured_path(curved_path); @@ -269,10 +268,11 @@ template inline bool point_in_path( double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) { - npy_intp shape[] = {1, 2}; - numpy::array_view points(shape); - points(0, 0) = x; - points(0, 1) = y; + py::ssize_t shape[] = {1, 2}; + py::array_t points_arr(shape); + *points_arr.mutable_data(0, 0) = x; + *points_arr.mutable_data(0, 1) = y; + auto points = points_arr.mutable_unchecked<2>(); int result[1]; result[0] = 0; @@ -282,45 +282,30 @@ inline bool point_in_path( return result[0] != 0; } -template -void points_on_path(PointArray &points, - const double r, - PathIterator &path, - agg::trans_affine &trans, - ResultArray result) +template +inline bool point_on_path( + double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover no_nans_t; typedef agg::conv_curve curve_t; typedef agg::conv_stroke stroke_t; - size_t i; - for (i = 0; i < points.size(); ++i) { - result[i] = false; - } + py::ssize_t shape[] = {1, 2}; + py::array_t points_arr(shape); + *points_arr.mutable_data(0, 0) = x; + *points_arr.mutable_data(0, 1) = y; + auto points = points_arr.mutable_unchecked<2>(); + + int result[1]; + result[0] = 0; transformed_path_t trans_path(path, trans); - no_nans_t nan_removed_path(trans_path, true, path.has_curves()); + no_nans_t nan_removed_path(trans_path, true, path.has_codes()); curve_t curved_path(nan_removed_path); stroke_t stroked_path(curved_path); stroked_path.width(r * 2.0); point_in_path_impl(points, stroked_path, result); -} - -template -inline bool point_on_path( - double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) -{ - npy_intp shape[] = {1, 2}; - numpy::array_view points(shape); - points(0, 0) = x; - points(0, 1) = y; - - int result[1]; - result[0] = 0; - - points_on_path(points, r, path, trans, result); - return result[0] != 0; } @@ -373,7 +358,7 @@ void update_path_extents(PathIterator &path, agg::trans_affine &trans, extent_li unsigned code; transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, path.has_curves()); + nan_removed_t nan_removed(tpath, true, path.has_codes()); nan_removed.rewind(0); @@ -393,24 +378,23 @@ void get_path_collection_extents(agg::trans_affine &master_transform, agg::trans_affine &offset_trans, extent_limits &extent) { - if (offsets.size() != 0 && offsets.dim(1) != 2) { - throw std::runtime_error("Offsets array must be Nx2"); + if (offsets.size() != 0 && offsets.shape(1) != 2) { + throw std::runtime_error("Offsets array must have shape (N, 2)"); } - size_t Npaths = paths.size(); - size_t Noffsets = offsets.size(); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms.size(), N); - size_t i; + auto Npaths = paths.size(); + auto Noffsets = safe_first_shape(offsets); + auto N = std::max(Npaths, Noffsets); + auto Ntransforms = std::min(safe_first_shape(transforms), N); agg::trans_affine trans; reset_limits(extent); - for (i = 0; i < N; ++i) { + for (auto i = 0; i < N; ++i) { typename PathGenerator::path_iterator path(paths(i % Npaths)); if (Ntransforms) { - size_t ti = i % Ntransforms; + py::ssize_t ti = i % Ntransforms; trans = agg::trans_affine(transforms(ti, 0, 0), transforms(ti, 1, 0), transforms(ti, 0, 1), @@ -442,27 +426,25 @@ void point_in_path_collection(double x, OffsetArray &offsets, agg::trans_affine &offset_trans, bool filled, - e_offset_position offset_position, std::vector &result) { - size_t Npaths = paths.size(); + auto Npaths = paths.size(); if (Npaths == 0) { return; } - size_t Noffsets = offsets.size(); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms.size(), N); - size_t i; + auto Noffsets = safe_first_shape(offsets); + auto N = std::max(Npaths, Noffsets); + auto Ntransforms = std::min(safe_first_shape(transforms), N); agg::trans_affine trans; - for (i = 0; i < N; ++i) { + for (auto i = 0; i < N; ++i) { typename PathGenerator::path_iterator path = paths(i % Npaths); if (Ntransforms) { - size_t ti = i % Ntransforms; + auto ti = i % Ntransforms; trans = agg::trans_affine(transforms(ti, 0, 0), transforms(ti, 1, 0), transforms(ti, 0, 1), @@ -478,11 +460,7 @@ void point_in_path_collection(double x, double xo = offsets(i % Noffsets, 0); double yo = offsets(i % Noffsets, 1); offset_trans.transform(&xo, &yo); - if (offset_position == OFFSET_POSITION_DATA) { - trans = agg::trans_affine_translation(xo, yo) * trans; - } else { - trans *= agg::trans_affine_translation(xo, yo); - } + trans *= agg::trans_affine_translation(xo, yo); } if (filled) { @@ -512,7 +490,7 @@ bool path_in_path(PathIterator1 &a, } transformed_path_t b_path_trans(b, btrans); - no_nans_t b_no_nans(b_path_trans, true, b.has_curves()); + no_nans_t b_no_nans(b_path_trans, true, b.has_codes()); curve_t b_curved(b_no_nans); double x, y; @@ -529,7 +507,7 @@ bool path_in_path(PathIterator1 &a, /** The clip_path_to_rect code here is a clean-room implementation of the Sutherland-Hodgman clipping algorithm described here: - http://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm + https://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm */ namespace clip_to_rect_filters @@ -624,7 +602,6 @@ struct ygt : public bisecty template inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const Filter &filter) { - double sx, sy, px, py, bx, by; bool sinside, pinside; result.clear(); @@ -632,22 +609,19 @@ inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const return; } - sx = polygon.back().x; - sy = polygon.back().y; - for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) { - px = i->x; - py = i->y; - + auto [sx, sy] = polygon.back(); + for (auto [px, py] : polygon) { sinside = filter.is_inside(sx, sy); pinside = filter.is_inside(px, py); if (sinside ^ pinside) { + double bx, by; filter.bisect(sx, sy, px, py, &bx, &by); - result.push_back(XY(bx, by)); + result.emplace_back(bx, by); } if (pinside) { - result.push_back(XY(px, py)); + result.emplace_back(px, py); } sx = px; @@ -694,7 +668,7 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto polygon1.clear(); do { if (code == agg::path_cmd_move_to) { - polygon1.push_back(XY(x, y)); + polygon1.emplace_back(x, y); } code = curve.vertex(&x, &y); @@ -704,7 +678,7 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto } if (code != agg::path_cmd_move_to) { - polygon1.push_back(XY(x, y)); + polygon1.emplace_back(x, y); } } while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); @@ -728,11 +702,11 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto template void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) { - if (vertices.size() != 0 && vertices.dim(1) != 2) { + if (vertices.size() != 0 && vertices.shape(1) != 2) { throw std::runtime_error("Invalid vertices array."); } - size_t n = vertices.size(); + size_t n = vertices.shape(0); double x; double y; double t0; @@ -758,7 +732,7 @@ void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, Resu template void affine_transform_1d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) { - if (vertices.dim(0) != 2) { + if (vertices.shape(0) != 2) { throw std::runtime_error("Invalid vertices array."); } @@ -795,7 +769,7 @@ int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes) std::swap(a.y1, a.y2); } - size_t num_bboxes = bboxes.size(); + size_t num_bboxes = safe_first_shape(bboxes); for (size_t i = 0; i < num_bboxes; ++i) { b = agg::rect_d(bboxes(i, 0, 0), bboxes(i, 0, 1), bboxes(i, 1, 0), bboxes(i, 1, 1)); @@ -838,21 +812,26 @@ inline bool segments_intersect(const double &x1, // determinant double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)); - if (isclose(den, 0.0)) { // collinear segments - if (x1 == x2 && x2 == x3) { // segments have infinite slope (vertical lines) - // and lie on the same line - return (fmin(y1, y2) <= fmin(y3, y4) && fmin(y3, y4) <= fmax(y1, y2)) || - (fmin(y3, y4) <= fmin(y1, y2) && fmin(y1, y2) <= fmax(y3, y4)); - } - else { - double intercept = (y1*x2 - y2*x1)*(x4 - x3) - (y3*x4 - y4*x3)*(x1 - x2); - if (isclose(intercept, 0.0)) { // segments lie on the same line + // If den == 0 we have two possibilities: + if (isclose(den, 0.0)) { + double t_area = (x2*y3 - x3*y2) - x1*(y3 - y2) + y1*(x3 - x2); + // 1 - If the area of the triangle made by the 3 first points (2 from the first segment + // plus one from the second) is zero, they are collinear + if (isclose(t_area, 0.0)) { + if (x1 == x2 && x2 == x3) { // segments have infinite slope (vertical lines) + // and lie on the same line + return (fmin(y1, y2) <= fmin(y3, y4) && fmin(y3, y4) <= fmax(y1, y2)) || + (fmin(y3, y4) <= fmin(y1, y2) && fmin(y1, y2) <= fmax(y3, y4)); + } + else { return (fmin(x1, x2) <= fmin(x3, x4) && fmin(x3, x4) <= fmax(x1, x2)) || - (fmin(x3, x4) <= fmin(x1, x2) && fmin(x1, x2) <= fmax(x3, x4)); + (fmin(x3, x4) <= fmin(x1, x2) && fmin(x1, x2) <= fmax(x3, x4)); } } - - return false; + // 2 - If t_area is not zero, the segments are parallel, but not collinear + else { + return false; + } } const double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3)); @@ -870,15 +849,15 @@ inline bool segments_intersect(const double &x1, template bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) { - typedef PathNanRemover no_nans_t; + typedef PathNanRemover no_nans_t; typedef agg::conv_curve curve_t; if (p1.total_vertices() < 2 || p2.total_vertices() < 2) { return false; } - no_nans_t n1(p1, true, p1.has_curves()); - no_nans_t n2(p2, true, p2.has_curves()); + no_nans_t n1(p1, true, p1.has_codes()); + no_nans_t n2(p2, true, p2.has_codes()); curve_t c1(n1); curve_t c2(n2); @@ -900,6 +879,7 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) if ((isclose((x21 - x22) * (x21 - x22) + (y21 - y22) * (y21 - y22), 0))){ continue; } + if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) { return true; } @@ -933,14 +913,14 @@ bool path_intersects_rectangle(PathIterator &path, double rect_x2, double rect_y2, bool filled) { - typedef PathNanRemover no_nans_t; + typedef PathNanRemover no_nans_t; typedef agg::conv_curve curve_t; if (path.total_vertices() == 0) { return false; } - no_nans_t no_nans(path, true, path.has_curves()); + no_nans_t no_nans(path, true, path.has_codes()); curve_t curve(no_nans); double cx = (rect_x1 + rect_x2) * 0.5, cy = (rect_y1 + rect_y2) * 0.5; @@ -979,7 +959,7 @@ void convert_path_to_polygons(PathIterator &path, int closed_only, std::vector &result) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSimplifier simplify_t; @@ -989,28 +969,25 @@ void convert_path_to_polygons(PathIterator &path, bool simplify = path.should_simplify(); transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, true, path.has_curves()); - clipped_t clipped(nan_removed, do_clip && !path.has_curves(), width, height); + nan_removal_t nan_removed(tpath, true, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, width, height); simplify_t simplified(clipped, simplify, path.simplify_threshold()); curve_t curve(simplified); - result.push_back(Polygon()); - Polygon *polygon = &result.back(); + Polygon *polygon = &result.emplace_back(); double x, y; unsigned code; while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) { if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { _finalize_polygon(result, 1); - result.push_back(Polygon()); - polygon = &result.back(); + polygon = &result.emplace_back(); } else { if (code == agg::path_cmd_move_to) { _finalize_polygon(result, closed_only); - result.push_back(Polygon()); - polygon = &result.back(); + polygon = &result.emplace_back(); } - polygon->push_back(XY(x, y)); + polygon->emplace_back(x, y); } } @@ -1019,7 +996,7 @@ void convert_path_to_polygons(PathIterator &path, template void -__cleanup_path(VertexSource &source, std::vector &vertices, std::vector &codes) +__cleanup_path(VertexSource &source, std::vector &vertices, std::vector &codes) { unsigned code; double x, y; @@ -1027,7 +1004,7 @@ __cleanup_path(VertexSource &source, std::vector &vertices, std::vector< code = source.vertex(&x, &y); vertices.push_back(x); vertices.push_back(y); - codes.push_back((npy_uint8)code); + codes.push_back(static_cast(code)); } while (code != agg::path_cmd_stop); } @@ -1045,7 +1022,7 @@ void cleanup_path(PathIterator &path, std::vector &vertices, std::vector &codes) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; @@ -1054,8 +1031,8 @@ void cleanup_path(PathIterator &path, typedef Sketch sketch_t; transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, remove_nans, path.has_curves()); - clipped_t clipped(nan_removed, do_clip && !path.has_curves(), rect); + nan_removal_t nan_removed(tpath, remove_nans, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, rect); snapped_t snapped(clipped, snap_mode, path.total_vertices(), stroke_width); simplify_t simplified(snapped, do_simplify, path.simplify_threshold()); @@ -1102,7 +1079,7 @@ void __add_number(double val, char format_code, int precision, buffer += str; } else { char *str = PyOS_double_to_string( - val, format_code, precision, Py_DTSF_ADD_DOT_0, NULL); + val, format_code, precision, Py_DTSF_ADD_DOT_0, nullptr); // Delete trailing zeros and decimal point char *c = str + strlen(str) - 1; // Start at last character. // Rewind through all the zeros and, if present, the trailing decimal @@ -1138,17 +1115,15 @@ bool __convert_to_string(PathIterator &path, double last_x = 0.0; double last_y = 0.0; - const int sizes[] = { 1, 1, 2, 3 }; - int size = 0; unsigned code; while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) { - if (code == 0x4f) { + if (code == CLOSEPOLY) { buffer += codes[4]; } else if (code < 5) { - size = sizes[code - 1]; + size_t size = NUM_VERTICES[code]; - for (int i = 1; i < size; ++i) { + for (size_t i = 1; i < size; ++i) { unsigned subcode = path.vertex(&x[i], &y[i]); if (subcode != code) { return false; @@ -1168,7 +1143,7 @@ bool __convert_to_string(PathIterator &path, buffer += ' '; } - for (int i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { __add_number(x[i], format_code, precision, buffer); buffer += ' '; __add_number(y[i], format_code, precision, buffer); @@ -1204,7 +1179,7 @@ bool convert_to_string(PathIterator &path, std::string& buffer) { size_t buffersize; - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSimplifier simplify_t; @@ -1214,11 +1189,11 @@ bool convert_to_string(PathIterator &path, bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, true, path.has_curves()); - clipped_t clipped(nan_removed, do_clip && !path.has_curves(), clip_rect); + nan_removal_t nan_removed(tpath, true, path.has_codes()); + clipped_t clipped(nan_removed, do_clip, clip_rect); simplify_t simplified(clipped, simplify, path.simplify_threshold()); - buffersize = path.total_vertices() * (precision + 5) * 4; + buffersize = (size_t) path.total_vertices() * (precision + 5) * 4; if (buffersize == 0) { return true; } @@ -1240,70 +1215,26 @@ bool convert_to_string(PathIterator &path, } template -struct _is_sorted +bool is_sorted_and_has_non_nan(py::array_t array) { - bool operator()(PyArrayObject *array) - { - npy_intp size; - npy_intp i; - T last_value; - T current_value; - - size = PyArray_DIM(array, 0); - - // std::isnan is only in C++11, which we don't yet require, - // so we use the "self == self" trick - for (i = 0; i < size; ++i) { - last_value = *((T *)PyArray_GETPTR1(array, i)); - if (last_value == last_value) { - break; - } - } - - if (i == size) { - // The whole array is non-finite - return false; - } - - for (; i < size; ++i) { - current_value = *((T *)PyArray_GETPTR1(array, i)); - if (current_value == current_value) { - if (current_value < last_value) { - return false; - } - last_value = current_value; - } - } - - return true; - } -}; - - -template -struct _is_sorted_int -{ - bool operator()(PyArrayObject *array) - { - npy_intp size; - npy_intp i; - T last_value; - T current_value; - - size = PyArray_DIM(array, 0); - - last_value = *((T *)PyArray_GETPTR1(array, 0)); - - for (i = 1; i < size; ++i) { - current_value = *((T *)PyArray_GETPTR1(array, i)); - if (current_value < last_value) { + auto size = array.shape(0); + using limits = std::numeric_limits; + T last = limits::has_infinity ? -limits::infinity() : limits::min(); + bool found_non_nan = false; + + for (auto i = 0; i < size; ++i) { + T current = *array.data(i); + // The following tests !isnan(current), but also works for integral + // types. (The isnan(IntegralType) overload is absent on MSVC.) + if (current == current) { + found_non_nan = true; + if (current < last) { return false; } - last_value = current_value; + last = current; } - - return true; } + return found_non_nan; }; diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 708d7d36e67a..2a297e49ac92 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -1,872 +1,351 @@ -#include "numpy_cpp.h" +#include +#include + +#include +#include +#include +#include +#include #include "_path.h" -#include "py_converters.h" +#include "_backend_agg_basic_types.h" #include "py_adaptors.h" +#include "py_converters.h" -PyObject *convert_polygon_vector(std::vector &polygons) -{ - PyObject *pyresult = PyList_New(polygons.size()); - - for (size_t i = 0; i < polygons.size(); ++i) { - Polygon poly = polygons[i]; - npy_intp dims[2]; - dims[1] = 2; - - dims[0] = (npy_intp)poly.size(); - - numpy::array_view subresult(dims); - memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2); - - if (PyList_SetItem(pyresult, i, subresult.pyobj())) { - Py_DECREF(pyresult); - return NULL; - } - } - - return pyresult; -} - -const char *Py_point_in_path__doc__ = "point_in_path(x, y, radius, path, trans)"; - -static PyObject *Py_point_in_path(PyObject *self, PyObject *args, PyObject *kwds) -{ - double x, y, r; - py::PathIterator path; - agg::trans_affine trans; - bool result; - - if (!PyArg_ParseTuple(args, - "dddO&O&:point_in_path", - &x, - &y, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; - } - - CALL_CPP("point_in_path", (result = point_in_path(x, y, r, path, trans))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } -} - -const char *Py_points_in_path__doc__ = "points_in_path(points, radius, path, trans)"; - -static PyObject *Py_points_in_path(PyObject *self, PyObject *args, PyObject *kwds) -{ - numpy::array_view points; - double r; - py::PathIterator path; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&dO&O&:points_in_path", - &convert_points, - &points, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; - } - - npy_intp dims[] = { (npy_intp)points.size() }; - numpy::array_view results(dims); - - CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results))); - - return results.pyobj(); -} - -const char *Py_point_on_path__doc__ = "point_on_path(x, y, radius, path, trans)"; +namespace py = pybind11; +using namespace pybind11::literals; -static PyObject *Py_point_on_path(PyObject *self, PyObject *args, PyObject *kwds) +py::list +convert_polygon_vector(std::vector &polygons) { - double x, y, r; - py::PathIterator path; - agg::trans_affine trans; - bool result; - - if (!PyArg_ParseTuple(args, - "dddO&O&:point_on_path", - &x, - &y, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; - } - - CALL_CPP("point_on_path", (result = point_on_path(x, y, r, path, trans))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } -} - -const char *Py_points_on_path__doc__ = "points_on_path(points, radius, path, trans)"; + auto result = py::list(polygons.size()); -static PyObject *Py_points_on_path(PyObject *self, PyObject *args, PyObject *kwds) -{ - numpy::array_view points; - double r; - py::PathIterator path; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&dO&O&:points_on_path", - &convert_points, - &points, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; + for (size_t i = 0; i < polygons.size(); ++i) { + const auto& poly = polygons[i]; + py::ssize_t dims[] = { static_cast(poly.size()), 2 }; + result[i] = py::array(dims, reinterpret_cast(poly.data())); } - npy_intp dims[] = { (npy_intp)points.size() }; - numpy::array_view results(dims); - - CALL_CPP("points_on_path", (points_on_path(points, r, path, trans, results))); - - return results.pyobj(); + return result; } -const char *Py_get_path_extents__doc__ = "get_path_extents(path, trans)"; - -static PyObject *Py_get_path_extents(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_point_in_path(double x, double y, double r, mpl::PathIterator path, + agg::trans_affine trans) { - py::PathIterator path; - agg::trans_affine trans; - - if (!PyArg_ParseTuple( - args, "O&O&:get_path_extents", &convert_path, &path, &convert_trans_affine, &trans)) { - return NULL; - } - - extent_limits e; - - CALL_CPP("get_path_extents", (reset_limits(e))); - CALL_CPP("get_path_extents", (update_path_extents(path, trans, e))); - - npy_intp dims[] = { 2, 2 }; - numpy::array_view extents(dims); - extents(0, 0) = e.x0; - extents(0, 1) = e.y0; - extents(1, 0) = e.x1; - extents(1, 1) = e.y1; - - return extents.pyobj(); + return point_in_path(x, y, r, path, trans); } -const char *Py_update_path_extents__doc__ = - "update_path_extents(path, trans, rect, minpos, ignore)"; - -static PyObject *Py_update_path_extents(PyObject *self, PyObject *args, PyObject *kwds) +static py::array_t +Py_points_in_path(py::array_t points_obj, double r, mpl::PathIterator path, + agg::trans_affine trans) { - py::PathIterator path; - agg::trans_affine trans; - agg::rect_d rect; - numpy::array_view minpos; - int ignore; - int changed; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&i:update_path_extents", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_rect, - &rect, - &minpos.converter, - &minpos, - &ignore)) { - return NULL; - } + auto points = convert_points(points_obj); - if (minpos.dim(0) != 2) { - PyErr_Format(PyExc_ValueError, - "minpos must be of length 2, got %" NPY_INTP_FMT, - minpos.dim(0)); - return NULL; - } + py::ssize_t dims[] = { points.shape(0) }; + py::array_t results(dims); + auto results_mutable = results.mutable_unchecked<1>(); - extent_limits e; + points_in_path(points, r, path, trans, results_mutable); - if (ignore) { - CALL_CPP("update_path_extents", reset_limits(e)); - } else { - if (rect.x1 > rect.x2) { - e.x0 = std::numeric_limits::infinity(); - e.x1 = -std::numeric_limits::infinity(); - } else { - e.x0 = rect.x1; - e.x1 = rect.x2; - } - if (rect.y1 > rect.y2) { - e.y0 = std::numeric_limits::infinity(); - e.y1 = -std::numeric_limits::infinity(); - } else { - e.y0 = rect.y1; - e.y1 = rect.y2; - } - e.xm = minpos(0); - e.ym = minpos(1); - } - - CALL_CPP("update_path_extents", (update_path_extents(path, trans, e))); - - changed = (e.x0 != rect.x1 || e.y0 != rect.y1 || e.x1 != rect.x2 || e.y1 != rect.y2 || - e.xm != minpos(0) || e.ym != minpos(1)); - - npy_intp extentsdims[] = { 2, 2 }; - numpy::array_view outextents(extentsdims); - outextents(0, 0) = e.x0; - outextents(0, 1) = e.y0; - outextents(1, 0) = e.x1; - outextents(1, 1) = e.y1; - - npy_intp minposdims[] = { 2 }; - numpy::array_view outminpos(minposdims); - outminpos(0) = e.xm; - outminpos(1) = e.ym; - - return Py_BuildValue( - "NNi", outextents.pyobj(), outminpos.pyobj(), changed); + return results; } -const char *Py_get_path_collection_extents__doc__ = "get_path_collection_extents("; - -static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, PyObject *kwds) +static py::tuple +Py_get_path_collection_extents(agg::trans_affine master_transform, + mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, + agg::trans_affine offset_trans) { - agg::trans_affine master_transform; - PyObject *pathsobj; - numpy::array_view transforms; - numpy::array_view offsets; - agg::trans_affine offset_trans; + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); extent_limits e; - if (!PyArg_ParseTuple(args, - "O&OO&O&O&:get_path_collection_extents", - &convert_trans_affine, - &master_transform, - &pathsobj, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans)) { - return NULL; - } + get_path_collection_extents( + master_transform, paths, transforms, offsets, offset_trans, e); - try - { - py::PathGenerator paths(pathsobj); + py::ssize_t dims[] = { 2, 2 }; + py::array_t extents(dims); + *extents.mutable_data(0, 0) = e.x0; + *extents.mutable_data(0, 1) = e.y0; + *extents.mutable_data(1, 0) = e.x1; + *extents.mutable_data(1, 1) = e.y1; - CALL_CPP("get_path_collection_extents", - (get_path_collection_extents( - master_transform, paths, transforms, offsets, offset_trans, e))); - } - catch (const py::exception &) - { - return NULL; - } - - npy_intp dims[] = { 2, 2 }; - numpy::array_view extents(dims); - extents(0, 0) = e.x0; - extents(0, 1) = e.y0; - extents(1, 0) = e.x1; - extents(1, 1) = e.y1; + py::ssize_t minposdims[] = { 2 }; + py::array_t minpos(minposdims); + *minpos.mutable_data(0) = e.xm; + *minpos.mutable_data(1) = e.ym; - return extents.pyobj(); + return py::make_tuple(extents, minpos); } -const char *Py_point_in_path_collection__doc__ = - "point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, " - "offset_trans, filled, offset_position)"; - -static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args, PyObject *kwds) +static py::object +Py_point_in_path_collection(double x, double y, double radius, + agg::trans_affine master_transform, mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, + agg::trans_affine offset_trans, bool filled) { - double x, y, radius; - agg::trans_affine master_transform; - PyObject *pathsobj; - numpy::array_view transforms; - numpy::array_view offsets; - agg::trans_affine offset_trans; - bool filled; - e_offset_position offset_position; + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); std::vector result; - if (!PyArg_ParseTuple(args, - "dddO&OO&O&O&O&O&:point_in_path_collection", - &x, - &y, - &radius, - &convert_trans_affine, - &master_transform, - &pathsobj, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_bool, - &filled, - &convert_offset_position, - &offset_position)) { - return NULL; - } + point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, + offset_trans, filled, result); - try - { - py::PathGenerator paths(pathsobj); - - CALL_CPP("point_in_path_collection", - (point_in_path_collection(x, - y, - radius, - master_transform, - paths, - transforms, - offsets, - offset_trans, - filled, - offset_position, - result))); - } - catch (const py::exception &) - { - return NULL; - } - - npy_intp dims[] = {(npy_intp)result.size() }; - numpy::array_view pyresult(dims); - if (result.size() > 0) { - memcpy(pyresult.data(), &result[0], result.size() * sizeof(int)); - } - return pyresult.pyobj(); + py::ssize_t dims[] = { static_cast(result.size()) }; + return py::array(dims, result.data()); } -const char *Py_path_in_path__doc__ = "path_in_path(path_a, trans_a, path_b, trans_b)"; - -static PyObject *Py_path_in_path(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_path_in_path(mpl::PathIterator a, agg::trans_affine atrans, + mpl::PathIterator b, agg::trans_affine btrans) { - py::PathIterator a; - agg::trans_affine atrans; - py::PathIterator b; - agg::trans_affine btrans; - bool result; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&:path_in_path", - &convert_path, - &a, - &convert_trans_affine, - &atrans, - &convert_path, - &b, - &convert_trans_affine, - &btrans)) { - return NULL; - } - - CALL_CPP("path_in_path", (result = path_in_path(a, atrans, b, btrans))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return path_in_path(a, atrans, b, btrans); } -const char *Py_clip_path_to_rect__doc__ = "clip_path_to_rect(path, rect, inside)"; - -static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args, PyObject *kwds) +static py::list +Py_clip_path_to_rect(mpl::PathIterator path, agg::rect_d rect, bool inside) { - py::PathIterator path; - agg::rect_d rect; - bool inside; std::vector result; - if (!PyArg_ParseTuple(args, - "O&O&O&:clip_path_to_rect", - &convert_path, - &path, - &convert_rect, - &rect, - &convert_bool, - &inside)) { - return NULL; - } - - CALL_CPP("clip_path_to_rect", (clip_path_to_rect(path, rect, inside, result))); + clip_path_to_rect(path, rect, inside, result); return convert_polygon_vector(result); } -const char *Py_affine_transform__doc__ = "affine_transform(points, trans)"; - -static PyObject *Py_affine_transform(PyObject *self, PyObject *args, PyObject *kwds) +static py::object +Py_affine_transform(py::array_t vertices_arr, + agg::trans_affine trans) { - PyObject *vertices_obj; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "OO&:affine_transform", - &vertices_obj, - &convert_trans_affine, - &trans)) { - return NULL; - } + if (vertices_arr.ndim() == 2) { + auto vertices = vertices_arr.unchecked<2>(); - PyArrayObject* vertices_arr = (PyArrayObject *)PyArray_ContiguousFromAny(vertices_obj, NPY_DOUBLE, 1, 2); - if (vertices_arr == NULL) { - return NULL; - } + check_trailing_shape(vertices, "vertices", 2); - if (PyArray_NDIM(vertices_arr) == 2) { - numpy::array_view vertices(vertices_arr); - npy_intp dims[] = { (npy_intp)vertices.size(), 2 }; - numpy::array_view result(dims); - CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result))); - Py_DECREF(vertices_arr); - return result.pyobj(); - } else { // PyArray_NDIM(vertices_arr) == 1 - numpy::array_view vertices(vertices_arr); - npy_intp dims[] = { (npy_intp)vertices.size() }; - numpy::array_view result(dims); - CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result))); - Py_DECREF(vertices_arr); - return result.pyobj(); - } -} + py::ssize_t dims[] = { vertices.shape(0), 2 }; + py::array_t result(dims); + auto result_mutable = result.mutable_unchecked<2>(); -const char *Py_count_bboxes_overlapping_bbox__doc__ = "count_bboxes_overlapping_bbox(bbox, bboxes)"; + affine_transform_2d(vertices, trans, result_mutable); + return result; + } else if (vertices_arr.ndim() == 1) { + auto vertices = vertices_arr.unchecked<1>(); -static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args, PyObject *kwds) -{ - agg::rect_d bbox; - numpy::array_view bboxes; - int result; - - if (!PyArg_ParseTuple(args, - "O&O&:count_bboxes_overlapping_bbox", - &convert_rect, - &bbox, - &convert_bboxes, - &bboxes)) { - return NULL; + py::ssize_t dims[] = { vertices.shape(0) }; + py::array_t result(dims); + auto result_mutable = result.mutable_unchecked<1>(); + + affine_transform_1d(vertices, trans, result_mutable); + return result; + } else { + throw py::value_error( + "vertices must be 1D or 2D, not" + std::to_string(vertices_arr.ndim()) + "D"); } +} - CALL_CPP("count_bboxes_overlapping_bbox", - (result = count_bboxes_overlapping_bbox(bbox, bboxes))); +static int +Py_count_bboxes_overlapping_bbox(agg::rect_d bbox, py::array_t bboxes_obj) +{ + auto bboxes = convert_bboxes(bboxes_obj); - return PyLong_FromLong(result); + return count_bboxes_overlapping_bbox(bbox, bboxes); } -const char *Py_path_intersects_path__doc__ = "path_intersects_path(path1, path2, filled=False)"; - -static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_path_intersects_path(mpl::PathIterator p1, mpl::PathIterator p2, bool filled) { - py::PathIterator p1; - py::PathIterator p2; agg::trans_affine t1; agg::trans_affine t2; - int filled = 0; - const char *names[] = { "p1", "p2", "filled", NULL }; bool result; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&O&i:path_intersects_path", - (char **)names, - &convert_path, - &p1, - &convert_path, - &p2, - &filled)) { - return NULL; - } - - CALL_CPP("path_intersects_path", (result = path_intersects_path(p1, p2))); + result = path_intersects_path(p1, p2); if (filled) { if (!result) { - CALL_CPP("path_intersects_path", - (result = path_in_path(p1, t1, p2, t2))); + result = path_in_path(p1, t1, p2, t2); } if (!result) { - CALL_CPP("path_intersects_path", - (result = path_in_path(p2, t1, p1, t2))); + result = path_in_path(p2, t1, p1, t2); } } - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return result; } -const char *Py_path_intersects_rectangle__doc__ = "path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled=False)"; - -static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_path_intersects_rectangle(mpl::PathIterator path, double rect_x1, double rect_y1, + double rect_x2, double rect_y2, bool filled) { - py::PathIterator path; - double rect_x1, rect_y1, rect_x2, rect_y2; - bool filled = false; - const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL }; - bool result; - - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&dddd|O&:path_intersects_rectangle", - (char **)names, - &convert_path, - &path, - &rect_x1, - &rect_y1, - &rect_x2, - &rect_y2, - &convert_bool, - &filled)) { - return NULL; - } - - CALL_CPP("path_intersects_rectangle", (result = path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled); } -const char *Py_convert_path_to_polygons__doc__ = - "convert_path_to_polygons(path, trans, width=0, height=0)"; - -static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds) +static py::list +Py_convert_path_to_polygons(mpl::PathIterator path, agg::trans_affine trans, + double width, double height, bool closed_only) { - py::PathIterator path; - agg::trans_affine trans; - double width = 0.0, height = 0.0; - int closed_only = 1; std::vector result; - const char *names[] = { "path", "transform", "width", "height", "closed_only", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&O&|ddi:convert_path_to_polygons", - (char **)names, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &width, - &height, - &closed_only)) { - return NULL; - } - CALL_CPP("convert_path_to_polygons", - (convert_path_to_polygons(path, trans, width, height, closed_only, result))); + convert_path_to_polygons(path, trans, width, height, closed_only, result); return convert_polygon_vector(result); } -const char *Py_cleanup_path__doc__ = - "cleanup_path(path, trans, remove_nans, clip_rect, snap_mode, stroke_width, simplify, " - "return_curves, sketch)"; - -static PyObject *Py_cleanup_path(PyObject *self, PyObject *args, PyObject *kwds) +static py::tuple +Py_cleanup_path(mpl::PathIterator path, agg::trans_affine trans, bool remove_nans, + agg::rect_d clip_rect, e_snap_mode snap_mode, double stroke_width, + std::optional simplify, bool return_curves, SketchParams sketch) { - py::PathIterator path; - agg::trans_affine trans; - bool remove_nans; - agg::rect_d clip_rect; - e_snap_mode snap_mode; - double stroke_width; - PyObject *simplifyobj; - bool simplify = false; - bool return_curves; - SketchParams sketch; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&dOO&O&:cleanup_path", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_bool, - &remove_nans, - &convert_rect, - &clip_rect, - &convert_snap, - &snap_mode, - &stroke_width, - &simplifyobj, - &convert_bool, - &return_curves, - &convert_sketch_params, - &sketch)) { - return NULL; - } - - if (simplifyobj == Py_None) { + if (!simplify.has_value()) { simplify = path.should_simplify(); - } else { - switch (PyObject_IsTrue(simplifyobj)) { - case 0: simplify = false; break; - case 1: simplify = true; break; - default: return NULL; // errored. - } } bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); std::vector vertices; - std::vector codes; - - CALL_CPP("cleanup_path", - (cleanup_path(path, - trans, - remove_nans, - do_clip, - clip_rect, - snap_mode, - stroke_width, - simplify, - return_curves, - sketch, - vertices, - codes))); - - size_t length = codes.size(); - - npy_intp vertices_dims[] = {(npy_intp)length, 2 }; - numpy::array_view pyvertices(vertices_dims); - - npy_intp codes_dims[] = {(npy_intp)length }; - numpy::array_view pycodes(codes_dims); - - memcpy(pyvertices.data(), &vertices[0], sizeof(double) * 2 * length); - memcpy(pycodes.data(), &codes[0], sizeof(unsigned char) * length); - - return Py_BuildValue("NN", pyvertices.pyobj(), pycodes.pyobj()); -} + std::vector codes; + + cleanup_path(path, trans, remove_nans, do_clip, clip_rect, snap_mode, stroke_width, + *simplify, return_curves, sketch, vertices, codes); + + auto length = static_cast(codes.size()); -const char *Py_convert_to_string__doc__ = "convert_to_string(path, trans, " - "clip_rect, simplify, sketch, precision, codes, postfix)"; + py::ssize_t vertices_dims[] = { length, 2 }; + py::array pyvertices(vertices_dims, vertices.data()); -static PyObject *Py_convert_to_string(PyObject *self, PyObject *args, PyObject *kwds) + py::ssize_t codes_dims[] = { length }; + py::array pycodes(codes_dims, codes.data()); + + return py::make_tuple(pyvertices, pycodes); +} + +const char *Py_convert_to_string__doc__ = +R"""(-- + +Convert *path* to a bytestring. + +The first five parameters (up to *sketch*) are interpreted as in `.cleanup_path`. The +following ones are detailed below. + +Parameters +---------- +path : Path +trans : Transform or None +clip_rect : sequence of 4 floats, or None +simplify : bool +sketch : tuple of 3 floats, or None +precision : int + The precision used to "%.*f"-format the values. Trailing zeros and decimal points + are always removed. (precision=-1 is a special case used to implement + ttconv-back-compatible conversion.) +codes : sequence of 5 bytestrings + The bytes representation of each opcode (MOVETO, LINETO, CURVE3, CURVE4, CLOSEPOLY), + in that order. If the bytes for CURVE3 is empty, quad segments are automatically + converted to cubic ones (this is used by backends such as pdf and ps, which do not + support quads). +postfix : bool + Whether the opcode comes after the values (True) or before (False). +)"""; + +static py::object +Py_convert_to_string(mpl::PathIterator path, agg::trans_affine trans, + agg::rect_d cliprect, std::optional simplify, + SketchParams sketch, int precision, + std::array codes_obj, bool postfix) { - py::PathIterator path; - agg::trans_affine trans; - agg::rect_d cliprect; - PyObject *simplifyobj; - bool simplify = false; - SketchParams sketch; - int precision; char *codes[5]; - bool postfix; std::string buffer; bool status; - if (!PyArg_ParseTuple(args, - "O&O&O&OO&i(yyyyy)O&:convert_to_string", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_rect, - &cliprect, - &simplifyobj, - &convert_sketch_params, - &sketch, - &precision, - &codes[0], - &codes[1], - &codes[2], - &codes[3], - &codes[4], - &convert_bool, - &postfix)) { - return NULL; + for (auto i = 0; i < 5; ++i) { + codes[i] = const_cast(codes_obj[i].c_str()); } - if (simplifyobj == Py_None) { + if (!simplify.has_value()) { simplify = path.should_simplify(); - } else { - switch (PyObject_IsTrue(simplifyobj)) { - case 0: simplify = false; break; - case 1: simplify = true; break; - default: return NULL; // errored. - } } - CALL_CPP("convert_to_string", - (status = convert_to_string( - path, trans, cliprect, simplify, sketch, - precision, codes, postfix, buffer))); + status = convert_to_string(path, trans, cliprect, *simplify, sketch, precision, + codes, postfix, buffer); if (!status) { - PyErr_SetString(PyExc_ValueError, "Malformed path codes"); - return NULL; + throw py::value_error("Malformed path codes"); } - return PyBytes_FromStringAndSize(buffer.c_str(), buffer.size()); + return py::bytes(buffer); } +const char *Py_is_sorted_and_has_non_nan__doc__ = +R"""(-- -const char *Py_is_sorted__doc__ = "is_sorted(array)\n\n" - "Returns True if 1-D array is monotonically increasing, ignoring NaNs\n"; +Return whether the 1D *array* is monotonically increasing, ignoring NaNs, and has at +least one non-nan value.)"""; -static PyObject *Py_is_sorted(PyObject *self, PyObject *obj) +static bool +Py_is_sorted_and_has_non_nan(py::object obj) { - npy_intp size; bool result; - PyArrayObject *array = (PyArrayObject *)PyArray_FromAny( - obj, NULL, 1, 1, 0, NULL); - - if (array == NULL) { - return NULL; - } - - size = PyArray_DIM(array, 0); - - if (size < 2) { - Py_DECREF(array); - Py_RETURN_TRUE; - } - - /* Handle just the most common types here, otherwise coerce to - double */ - switch(PyArray_TYPE(array)) { - case NPY_INT: - { - _is_sorted_int is_sorted; - result = is_sorted(array); - } - break; - - case NPY_LONG: - { - _is_sorted_int is_sorted; - result = is_sorted(array); - } - break; - - case NPY_LONGLONG: - { - _is_sorted_int is_sorted; - result = is_sorted(array); - } - break; - - case NPY_FLOAT: - { - _is_sorted is_sorted; - result = is_sorted(array); - } - break; - - case NPY_DOUBLE: - { - _is_sorted is_sorted; - result = is_sorted(array); - } - break; - - default: - { - Py_DECREF(array); - array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); - - if (array == NULL) { - return NULL; - } - - _is_sorted is_sorted; - result = is_sorted(array); - } - } - - Py_DECREF(array); - - if (result) { - Py_RETURN_TRUE; + py::array array = py::array::ensure(obj); + if (array.ndim() != 1) { + throw std::invalid_argument("array must be 1D"); + } + + auto dtype = array.dtype(); + /* Handle just the most common types here, otherwise coerce to double */ + if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); } else { - Py_RETURN_FALSE; + array = py::array_t::ensure(obj); + result = is_sorted_and_has_non_nan(array); } -} + return result; +} -static PyMethodDef module_functions[] = { - {"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__}, - {"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__}, - {"point_on_path", (PyCFunction)Py_point_on_path, METH_VARARGS, Py_point_on_path__doc__}, - {"points_on_path", (PyCFunction)Py_points_on_path, METH_VARARGS, Py_points_on_path__doc__}, - {"get_path_extents", (PyCFunction)Py_get_path_extents, METH_VARARGS, Py_get_path_extents__doc__}, - {"update_path_extents", (PyCFunction)Py_update_path_extents, METH_VARARGS, Py_update_path_extents__doc__}, - {"get_path_collection_extents", (PyCFunction)Py_get_path_collection_extents, METH_VARARGS, Py_get_path_collection_extents__doc__}, - {"point_in_path_collection", (PyCFunction)Py_point_in_path_collection, METH_VARARGS, Py_point_in_path_collection__doc__}, - {"path_in_path", (PyCFunction)Py_path_in_path, METH_VARARGS, Py_path_in_path__doc__}, - {"clip_path_to_rect", (PyCFunction)Py_clip_path_to_rect, METH_VARARGS, Py_clip_path_to_rect__doc__}, - {"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__}, - {"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__}, - {"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__}, - {"path_intersects_rectangle", (PyCFunction)Py_path_intersects_rectangle, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_rectangle__doc__}, - {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__}, - {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__}, - {"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__}, - {"is_sorted", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__}, - {NULL} -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_path", - NULL, - 0, - module_functions, - NULL, - NULL, - NULL, - NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__path(void) +PYBIND11_MODULE(_path, m, py::mod_gil_not_used()) { - PyObject *m; - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - import_array(); - - return m; + m.def("point_in_path", &Py_point_in_path, + "x"_a, "y"_a, "radius"_a, "path"_a, "trans"_a); + m.def("points_in_path", &Py_points_in_path, + "points"_a, "radius"_a, "path"_a, "trans"_a); + m.def("get_path_collection_extents", &Py_get_path_collection_extents, + "master_transform"_a, "paths"_a, "transforms"_a, "offsets"_a, + "offset_transform"_a); + m.def("point_in_path_collection", &Py_point_in_path_collection, + "x"_a, "y"_a, "radius"_a, "master_transform"_a, "paths"_a, "transforms"_a, + "offsets"_a, "offset_trans"_a, "filled"_a); + m.def("path_in_path", &Py_path_in_path, + "path_a"_a, "trans_a"_a, "path_b"_a, "trans_b"_a); + m.def("clip_path_to_rect", &Py_clip_path_to_rect, + "path"_a, "rect"_a, "inside"_a); + m.def("affine_transform", &Py_affine_transform, + "points"_a, "trans"_a); + m.def("count_bboxes_overlapping_bbox", &Py_count_bboxes_overlapping_bbox, + "bbox"_a, "bboxes"_a); + m.def("path_intersects_path", &Py_path_intersects_path, + "path1"_a, "path2"_a, "filled"_a = false); + m.def("path_intersects_rectangle", &Py_path_intersects_rectangle, + "path"_a, "rect_x1"_a, "rect_y1"_a, "rect_x2"_a, "rect_y2"_a, + "filled"_a = false); + m.def("convert_path_to_polygons", &Py_convert_path_to_polygons, + "path"_a, "trans"_a, "width"_a = 0.0, "height"_a = 0.0, + "closed_only"_a = false); + m.def("cleanup_path", &Py_cleanup_path, + "path"_a, "trans"_a, "remove_nans"_a, "clip_rect"_a, "snap_mode"_a, + "stroke_width"_a, "simplify"_a, "return_curves"_a, "sketch"_a); + m.def("convert_to_string", &Py_convert_to_string, + "path"_a, "trans"_a, "clip_rect"_a, "simplify"_a, "sketch"_a, "precision"_a, + "codes"_a, "postfix"_a, + Py_convert_to_string__doc__); + m.def("is_sorted_and_has_non_nan", &Py_is_sorted_and_has_non_nan, + "array"_a, + Py_is_sorted_and_has_non_nan__doc__); } - -#pragma GCC visibility pop diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp new file mode 100644 index 000000000000..f8a3103b65f1 --- /dev/null +++ b/src/_qhull_wrapper.cpp @@ -0,0 +1,303 @@ +/* + * Wrapper module for libqhull, providing Delaunay triangulation. + * + * This module's methods should not be accessed directly. To obtain a Delaunay + * triangulation, construct an instance of the matplotlib.tri.Triangulation + * class without specifying a triangles array. + */ +#include +#include + +#ifdef _MSC_VER +/* The Qhull header does not declare this as extern "C", but only MSVC seems to + * do name mangling on global variables. We thus need to declare this before + * the header so that it treats it correctly, and doesn't mangle the name. */ +extern "C" { +extern const char qh_version[]; +} +#endif + +#include "libqhull_r/qhull_ra.h" +#include +#include + +#ifndef MPL_DEVNULL +#error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null" +#endif + +#define STRINGIFY(x) STR(x) +#define STR(x) #x + +namespace py = pybind11; +using namespace pybind11::literals; + +// Input numpy array class. +typedef py::array_t CoordArray; + +// Output numpy array class. +typedef py::array_t IndexArray; + + + +static const char* qhull_error_msg[6] = { + "", /* 0 = qh_ERRnone */ + "input inconsistency", /* 1 = qh_ERRinput */ + "singular input data", /* 2 = qh_ERRsingular */ + "precision error", /* 3 = qh_ERRprec */ + "insufficient memory", /* 4 = qh_ERRmem */ + "internal error"}; /* 5 = qh_ERRqhull */ + + +/* Return the indices of the 3 vertices that comprise the specified facet (i.e. + * triangle). */ +static void +get_facet_vertices(qhT* qh, const facetT* facet, int indices[3]) +{ + vertexT *vertex, **vertexp; + FOREACHvertex_(facet->vertices) { + *indices++ = qh_pointid(qh, vertex->point); + } +} + +/* Return the indices of the 3 triangles that are neighbors of the specified + * facet (triangle). */ +static void +get_facet_neighbours(const facetT* facet, std::vector& tri_indices, + int indices[3]) +{ + facetT *neighbor, **neighborp; + FOREACHneighbor_(facet) { + *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]); + } +} + +/* Return true if the specified points arrays contain at least 3 unique points, + * or false otherwise. */ +static bool +at_least_3_unique_points(py::ssize_t npoints, const double* x, const double* y) +{ + const py::ssize_t unique1 = 0; /* First unique point has index 0. */ + py::ssize_t unique2 = 0; /* Second unique point index is 0 until set. */ + + if (npoints < 3) { + return false; + } + + for (py::ssize_t i = 1; i < npoints; ++i) { + if (unique2 == 0) { + /* Looking for second unique point. */ + if (x[i] != x[unique1] || y[i] != y[unique1]) { + unique2 = i; + } + } + else { + /* Looking for third unique point. */ + if ( (x[i] != x[unique1] || y[i] != y[unique1]) && + (x[i] != x[unique2] || y[i] != y[unique2]) ) { + /* 3 unique points found, with indices 0, unique2 and i. */ + return true; + } + } + } + + /* Run out of points before 3 unique points found. */ + return false; +} + +/* Holds on to info from Qhull so that it can be destructed automatically. */ +class QhullInfo { +public: + QhullInfo(FILE *error_file, qhT* qh) { + this->error_file = error_file; + this->qh = qh; + } + + ~QhullInfo() { + qh_freeqhull(this->qh, !qh_ALL); + int curlong, totlong; /* Memory remaining. */ + qh_memfreeshort(this->qh, &curlong, &totlong); + if (curlong || totlong) { + PyErr_WarnEx(PyExc_RuntimeWarning, + "Qhull could not free all allocated memory", 1); + } + + if (this->error_file != stderr) { + fclose(error_file); + } + } + +private: + FILE* error_file; + qhT* qh; +}; + +/* Delaunay implementation method. + * If hide_qhull_errors is true then qhull error messages are discarded; + * if it is false then they are written to stderr. */ +static py::tuple +delaunay_impl(py::ssize_t npoints, const double* x, const double* y, + bool hide_qhull_errors) +{ + qhT qh_qh; /* qh variable type and name must be like */ + qhT* qh = &qh_qh; /* this for Qhull macros to work correctly. */ + facetT* facet; + int i, ntri, max_facet_id; + int exitcode; /* Value returned from qh_new_qhull(). */ + const int ndim = 2; + double x_mean = 0.0; + double y_mean = 0.0; + + QHULL_LIB_CHECK + + /* Allocate points. */ + std::vector points(npoints * ndim); + + /* Determine mean x, y coordinates. */ + for (i = 0; i < npoints; ++i) { + x_mean += x[i]; + y_mean += y[i]; + } + x_mean /= npoints; + y_mean /= npoints; + + /* Prepare points array to pass to qhull. */ + for (i = 0; i < npoints; ++i) { + points[2*i ] = x[i] - x_mean; + points[2*i+1] = y[i] - y_mean; + } + + /* qhull expects a FILE* to write errors to. */ + FILE* error_file = nullptr; + if (hide_qhull_errors) { + /* qhull errors are ignored by writing to OS-equivalent of /dev/null. + * Rather than have OS-specific code here, instead it is determined by + * meson.build and passed in via the macro MPL_DEVNULL. */ + error_file = fopen(STRINGIFY(MPL_DEVNULL), "w"); + if (error_file == nullptr) { + throw std::runtime_error("Could not open devnull"); + } + } + else { + /* qhull errors written to stderr. */ + error_file = stderr; + } + + /* Perform Delaunay triangulation. */ + QhullInfo info(error_file, qh); + qh_zero(qh, error_file); + exitcode = qh_new_qhull(qh, ndim, (int)npoints, points.data(), False, + (char*)"qhull d Qt Qbb Qc Qz", nullptr, error_file); + if (exitcode != qh_ERRnone) { + std::string msg = + py::str("Error in qhull Delaunay triangulation calculation: {} (exitcode={})") + .format(qhull_error_msg[exitcode], exitcode).cast(); + if (hide_qhull_errors) { + msg += "; use python verbose option (-v) to see original qhull error."; + } + throw std::runtime_error(msg); + } + + /* Split facets so that they only have 3 points each. */ + qh_triangulate(qh); + + /* Determine ntri and max_facet_id. + Note that libqhull uses macros to iterate through collections. */ + ntri = 0; + FORALLfacets { + if (!facet->upperdelaunay) { + ++ntri; + } + } + + max_facet_id = qh->facet_id - 1; + + /* Create array to map facet id to triangle index. */ + std::vector tri_indices(max_facet_id+1); + + /* Allocate Python arrays to return. */ + int dims[2] = {ntri, 3}; + IndexArray triangles(dims); + int* triangles_ptr = triangles.mutable_data(); + + IndexArray neighbors(dims); + int* neighbors_ptr = neighbors.mutable_data(); + + /* Determine triangles array and set tri_indices array. */ + i = 0; + FORALLfacets { + if (!facet->upperdelaunay) { + int indices[3]; + tri_indices[facet->id] = i++; + get_facet_vertices(qh, facet, indices); + *triangles_ptr++ = (facet->toporient ? indices[0] : indices[2]); + *triangles_ptr++ = indices[1]; + *triangles_ptr++ = (facet->toporient ? indices[2] : indices[0]); + } + else { + tri_indices[facet->id] = -1; + } + } + + /* Determine neighbors array. */ + FORALLfacets { + if (!facet->upperdelaunay) { + int indices[3]; + get_facet_neighbours(facet, tri_indices, indices); + *neighbors_ptr++ = (facet->toporient ? indices[2] : indices[0]); + *neighbors_ptr++ = (facet->toporient ? indices[0] : indices[2]); + *neighbors_ptr++ = indices[1]; + } + } + + return py::make_tuple(triangles, neighbors); +} + +/* Process Python arguments and call Delaunay implementation method. */ +static py::tuple +delaunay(const CoordArray& x, const CoordArray& y, int verbose) +{ + if (x.ndim() != 1 || y.ndim() != 1) { + throw std::invalid_argument("x and y must be 1D arrays"); + } + + auto npoints = x.shape(0); + if (npoints != y.shape(0)) { + throw std::invalid_argument("x and y must be 1D arrays of the same length"); + } + + if (npoints < 3) { + throw std::invalid_argument("x and y arrays must have a length of at least 3"); + } + + if (!at_least_3_unique_points(npoints, x.data(), y.data())) { + throw std::invalid_argument("x and y arrays must consist of at least 3 unique points"); + } + + return delaunay_impl(npoints, x.data(), y.data(), verbose == 0); +} + +PYBIND11_MODULE(_qhull, m, py::mod_gil_not_used()) +{ + m.doc() = "Computing Delaunay triangulations.\n"; + + m.def("delaunay", &delaunay, "x"_a, "y"_a, "verbose"_a, + "--\n\n" + "Compute a Delaunay triangulation.\n" + "\n" + "Parameters\n" + "----------\n" + "x, y : 1d arrays\n" + " The coordinates of the point set, which must consist of at least\n" + " three unique points.\n" + "verbose : int\n" + " Python's verbosity level.\n" + "\n" + "Returns\n" + "-------\n" + "triangles, neighbors : int arrays, shape (ntri, 3)\n" + " Indices of triangle vertices and indices of triangle neighbors.\n"); + + m.def("version", []() { return qh_version; }, + "version()\n--\n\n" + "Return the qhull version string."); +} diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index d74800d5241d..955ce2103f90 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -7,10 +7,32 @@ // and methods of operation are now quite different. Because our review of // the codebase showed that all the code that came from PIL was removed or // rewritten, we have removed the PIL licensing information. If you want PIL, -// you can get it at https://python-pillow.org/ +// you can get it at https://python-pillow.github.io -#define PY_SSIZE_T_CLEAN #include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +// Windows 8.1 +#define WINVER 0x0603 +#if defined(_WIN32_WINNT) +#if _WIN32_WINNT < WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT WINVER +#endif +#else +#define _WIN32_WINNT WINVER +#endif +#endif + +#include +#include +namespace py = pybind11; +using namespace pybind11::literals; #ifdef _WIN32 #define WIN32_DLL @@ -19,119 +41,259 @@ /* * Unfortunately cygwin's libdl inherits restrictions from the underlying * Windows OS, at least currently. Therefore, a symbol may be loaded from a - * module by dlsym() only if it is really located in the given modile, + * module by dlsym() only if it is really located in the given module, * dependencies are not included. So we have to use native WinAPI on Cygwin * also. */ #define WIN32_DLL +static inline PyObject *PyErr_SetFromWindowsErr(int ierr) { + PyErr_SetString(PyExc_OSError, "Call to EnumProcessModules failed"); + return NULL; +} #endif #ifdef WIN32_DLL +#include + #include +#include #define PSAPI_VERSION 1 #include // Must be linked with 'psapi' library #define dlsym GetProcAddress +#define UNUSED_ON_NON_WINDOWS(x) x +// Check for old headers that do not defined HiDPI functions and constants. +#if defined(__MINGW64_VERSION_MAJOR) +static_assert(__MINGW64_VERSION_MAJOR >= 6, + "mingw-w64-x86_64-headers >= 6 are required when compiling with MinGW"); +#endif #else #include +#define UNUSED_ON_NON_WINDOWS Py_UNUSED #endif // Include our own excerpts from the Tcl / Tk headers #include "_tkmini.h" -#include "py_converters.h" + +template +static T +convert_voidptr(const py::object &obj) +{ + auto result = static_cast(PyLong_AsVoidPtr(obj.ptr())); + if (PyErr_Occurred()) { + throw py::error_already_set(); + } + return result; +} // Global vars for Tk functions. We load these symbols from the tkinter // extension module or loaded Tk libraries at run-time. static Tk_FindPhoto_t TK_FIND_PHOTO; -static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE; +static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; +// Global vars for Tcl functions. We load these symbols from the tkinter +// extension module or loaded Tcl libraries at run-time. +static Tcl_SetVar_t TCL_SETVAR; +static Tcl_SetVar2_t TCL_SETVAR2; -static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) +static void +mpl_tk_blit(py::object interp_obj, const char *photo_name, + py::array_t data, int comp_rule, + std::tuple offset, std::tuple bbox) { - Tcl_Interp *interp; - char const *photo_name; - int height, width; - unsigned char *data_ptr; - int o0, o1, o2, o3; - int x1, x2, y1, y2; + auto interp = convert_voidptr(interp_obj); + Tk_PhotoHandle photo; - Tk_PhotoImageBlock block; - if (!PyArg_ParseTuple(args, "O&s(iiO&)(iiii)(iiii):blit", - convert_voidptr, &interp, &photo_name, - &height, &width, convert_voidptr, &data_ptr, - &o0, &o1, &o2, &o3, - &x1, &x2, &y1, &y2)) { - goto exit; - } if (!(photo = TK_FIND_PHOTO(interp, photo_name))) { - PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle"); - goto exit; + throw py::value_error("Failed to extract Tk_PhotoHandle"); + } + + auto data_ptr = data.mutable_unchecked<3>(); // Checks ndim and writeable flag. + if (data.shape(2) != 4) { + throw py::value_error( + "Data pointer must be RGBA; last dimension is {}, not 4"_s.format( + data.shape(2))); + } + if (data.shape(0) > INT_MAX) { // Limited by Tk_PhotoPutBlock argument type. + throw std::range_error( + "Height ({}) exceeds maximum allowable size ({})"_s.format( + data.shape(0), INT_MAX)); + } + if (data.shape(1) > INT_MAX / 4) { // Limited by Tk_PhotoImageBlock.pitch field. + throw std::range_error( + "Width ({}) exceeds maximum allowable size ({})"_s.format( + data.shape(1), INT_MAX / 4)); } + const auto height = static_cast(data.shape(0)); + const auto width = static_cast(data.shape(1)); + int x1, x2, y1, y2; + std::tie(x1, x2, y1, y2) = bbox; if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) { - PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds"); - goto exit; + throw py::value_error("Attempting to draw out of bounds"); + } + if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) { + throw py::value_error("Invalid comp_rule argument"); } - block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1); + int put_retval; + Tk_PhotoImageBlock block; + block.pixelPtr = data_ptr.mutable_data(height - y2, x1, 0); block.width = x2 - x1; block.height = y2 - y1; block.pitch = 4 * width; block.pixelSize = 4; - block.offset[0] = o0; - block.offset[1] = o1; - block.offset[2] = o2; - block.offset[3] = o3; - TK_PHOTO_PUT_BLOCK_NO_COMPOSITE( - photo, &block, x1, height - y2, x2 - x1, y2 - y1); -exit: - if (PyErr_Occurred()) { - return NULL; - } else { - Py_RETURN_NONE; + std::tie(block.offset[0], block.offset[1], block.offset[2], block.offset[3]) = offset; + { + py::gil_scoped_release release; + put_retval = TK_PHOTO_PUT_BLOCK( + interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); + } + if (put_retval == TCL_ERROR) { + throw std::bad_alloc(); + } +} + +#ifdef WIN32_DLL +LRESULT CALLBACK +DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + switch (uMsg) { + case WM_DPICHANGED: + // This function is a subclassed window procedure, and so is run during + // the Tcl/Tk event loop. Unfortunately, Tkinter has a *second* lock on + // Tcl threading that is not exposed publicly, but is currently taken + // while we're in the window procedure. So while we can take the GIL to + // call Python code, we must not also call *any* Tk code from Python. + // So stay with Tcl calls in C only. + { + // This variable naming must match the name used in + // lib/matplotlib/backends/_backend_tk.py:FigureManagerTk. + std::string var_name("window_dpi"); + var_name += std::to_string((unsigned long long)hwnd); + + // X is high word, Y is low word, but they are always equal. + std::string dpi = std::to_string(LOWORD(wParam)); + + Tcl_Interp* interp = (Tcl_Interp*)dwRefData; + if (TCL_SETVAR) { + TCL_SETVAR(interp, var_name.c_str(), dpi.c_str(), 0); + } else if (TCL_SETVAR2) { + TCL_SETVAR2(interp, var_name.c_str(), NULL, dpi.c_str(), 0); + } else { + // This should be prevented at import time, and therefore unreachable. + // But defensively throw just in case. + throw std::runtime_error("Unable to call Tcl_SetVar or Tcl_SetVar2"); + } + } + return 0; + case WM_NCDESTROY: + RemoveWindowSubclass(hwnd, DpiSubclassProc, uIdSubclass); + break; } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); } +#endif + +static py::object +mpl_tk_enable_dpi_awareness(py::object UNUSED_ON_NON_WINDOWS(frame_handle_obj), + py::object UNUSED_ON_NON_WINDOWS(interp_obj)) +{ +#ifdef WIN32_DLL + auto frame_handle = convert_voidptr(frame_handle_obj); + auto interp = convert_voidptr(interp_obj); + +#ifdef _DPI_AWARENESS_CONTEXTS_ + HMODULE user32 = LoadLibrary("user32.dll"); -static PyMethodDef functions[] = { - { "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS }, - { NULL, NULL } /* sentinel */ -}; + typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext_t)(HWND); + GetWindowDpiAwarenessContext_t GetWindowDpiAwarenessContextPtr = + (GetWindowDpiAwarenessContext_t)GetProcAddress( + user32, "GetWindowDpiAwarenessContext"); + if (GetWindowDpiAwarenessContextPtr == NULL) { + FreeLibrary(user32); + return py::cast(false); + } + + typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual_t)(DPI_AWARENESS_CONTEXT, + DPI_AWARENESS_CONTEXT); + AreDpiAwarenessContextsEqual_t AreDpiAwarenessContextsEqualPtr = + (AreDpiAwarenessContextsEqual_t)GetProcAddress( + user32, "AreDpiAwarenessContextsEqual"); + if (AreDpiAwarenessContextsEqualPtr == NULL) { + FreeLibrary(user32); + return py::cast(false); + } -// Functions to fill global Tk function pointers by dynamic loading + DPI_AWARENESS_CONTEXT ctx = GetWindowDpiAwarenessContextPtr(frame_handle); + bool per_monitor = ( + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)); + + if (per_monitor) { + // Per monitor aware means we need to handle WM_DPICHANGED by wrapping + // the Window Procedure, and the Python side needs to trace the Tk + // window_dpi variable stored on interp. + SetWindowSubclass(frame_handle, DpiSubclassProc, 0, (DWORD_PTR)interp); + } + FreeLibrary(user32); + return py::cast(per_monitor); +#endif +#endif + + return py::none(); +} + +// Functions to fill global Tcl/Tk function pointers by dynamic loading. template -int load_tk(T lib) +bool load_tcl_tk(T lib) { - // Try to fill Tk global vars with function pointers. Return the number of - // functions found. - return - !!(TK_FIND_PHOTO = - (Tk_FindPhoto_t)dlsym(lib, "Tk_FindPhoto")) + - !!(TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = - (Tk_PhotoPutBlock_NoComposite_t)dlsym(lib, "Tk_PhotoPutBlock_NoComposite")); + // Try to fill Tcl/Tk global vars with function pointers. Return whether + // all of them have been filled. + if (auto ptr = dlsym(lib, "Tcl_SetVar")) { + TCL_SETVAR = (Tcl_SetVar_t)ptr; + } + if (auto ptr = dlsym(lib, "Tcl_SetVar2")) { + TCL_SETVAR2 = (Tcl_SetVar2_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_FindPhoto")) { + TK_FIND_PHOTO = (Tk_FindPhoto_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_PhotoPutBlock")) { + TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)ptr; + } + return (TCL_SETVAR || TCL_SETVAR2) && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK; } #ifdef WIN32_DLL -/* - * On Windows, we can't load the tkinter module to get the Tk symbols, because - * Windows does not load symbols into the library name-space of importing - * modules. So, knowing that tkinter has already been imported by Python, we - * scan all modules in the running process for the Tk function names. +/* On Windows, we can't load the tkinter module to get the Tcl/Tk symbols, + * because Windows does not load symbols into the library name-space of + * importing modules. So, knowing that tkinter has already been imported by + * Python, we scan all modules in the running process for the Tcl/Tk function + * names. */ -void load_tkinter_funcs(void) +static void +load_tkinter_funcs() { - // Load Tk functions by searching all modules in current process. - HMODULE hMods[1024]; - HANDLE hProcess; - DWORD cbNeeded; - unsigned int i; - // Returns pseudo-handle that does not need to be closed - hProcess = GetCurrentProcess(); - // Iterate through modules in this process looking for Tk names. - if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { - for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { - if (load_tk(hMods[i])) { - return; - } + HANDLE process = GetCurrentProcess(); // Pseudo-handle, doesn't need closing. + DWORD size; + if (!EnumProcessModules(process, NULL, 0, &size)) { + PyErr_SetFromWindowsErr(0); + throw py::error_already_set(); + } + auto count = size / sizeof(HMODULE); + auto modules = std::vector(count); + if (!EnumProcessModules(process, modules.data(), size, &size)) { + PyErr_SetFromWindowsErr(0); + throw py::error_already_set(); + } + for (auto mod: modules) { + if (load_tcl_tk(mod)) { + return; } } } @@ -144,75 +306,68 @@ void load_tkinter_funcs(void) * dynamic library (module). */ -void load_tkinter_funcs(void) +static void +load_tkinter_funcs() { // Load tkinter global funcs from tkinter compiled module. - void *main_program = NULL, *tkinter_lib = NULL; - PyObject *module = NULL, *py_path = NULL, *py_path_b = NULL; - char *path; // Try loading from the main program namespace first. - main_program = dlopen(NULL, RTLD_LAZY); - if (load_tk(main_program)) { - goto exit; + auto main_program = dlopen(nullptr, RTLD_LAZY); + auto success = load_tcl_tk(main_program); + // We don't need to keep a reference open as the main program always exists. + if (dlclose(main_program)) { + throw std::runtime_error(dlerror()); + } + if (success) { + return; } - // Clear exception triggered when we didn't find symbols above. - PyErr_Clear(); + py::object module; // Handle PyPy first, as that import will correctly fail on CPython. - module = PyImport_ImportModule("_tkinter.tklib_cffi"); // PyPy - if (!module) { - PyErr_Clear(); - module = PyImport_ImportModule("_tkinter"); // CPython - } - if (!(module && - (py_path = PyObject_GetAttrString(module, "__file__")) && - (py_path_b = PyUnicode_EncodeFSDefault(py_path)) && - (path = PyBytes_AsString(py_path_b)))) { - goto exit; - } - tkinter_lib = dlopen(path, RTLD_LAZY); - if (!tkinter_lib) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - goto exit; + try { + module = py::module_::import("_tkinter.tklib_cffi"); // PyPy + } catch (py::error_already_set &e) { + module = py::module_::import("_tkinter"); // CPython } - if (load_tk(tkinter_lib)) { - goto exit; + auto py_path = module.attr("__file__"); + auto py_path_b = py::reinterpret_steal( + PyUnicode_EncodeFSDefault(py_path.ptr())); + std::string path = py_path_b; + auto tkinter_lib = dlopen(path.c_str(), RTLD_LAZY); + if (!tkinter_lib) { + throw std::runtime_error(dlerror()); } - -exit: - // We don't need to keep a reference open as the main program & tkinter - // have been imported. Use a non-short-circuiting "or" to try closing both - // handles before handling errors. - if ((main_program && dlclose(main_program)) - | (tkinter_lib && dlclose(tkinter_lib))) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); + load_tcl_tk(tkinter_lib); + // We don't need to keep a reference open as tkinter has been imported. + if (dlclose(tkinter_lib)) { + throw std::runtime_error(dlerror()); } - Py_XDECREF(module); - Py_XDECREF(py_path); - Py_XDECREF(py_path_b); } #endif // end not Windows -static PyModuleDef _tkagg_module = { - PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, NULL, NULL, NULL, NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__tkagg(void) +PYBIND11_MODULE(_tkagg, m, py::mod_gil_not_used()) { - load_tkinter_funcs(); - if (PyErr_Occurred()) { - return NULL; + try { + load_tkinter_funcs(); + } catch (py::error_already_set& e) { + // Always raise ImportError to interact properly with backend auto-fallback. + py::raise_from(e, PyExc_ImportError, "failed to load tkinter functions"); + throw py::error_already_set(); + } + + if (!(TCL_SETVAR || TCL_SETVAR2)) { + throw py::import_error("Failed to load Tcl_SetVar or Tcl_SetVar2"); } else if (!TK_FIND_PHOTO) { - PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_FindPhoto"); - return NULL; - } else if (!TK_PHOTO_PUT_BLOCK_NO_COMPOSITE) { - PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_PhotoPutBlock_NoComposite"); - return NULL; + throw py::import_error("Failed to load Tk_FindPhoto"); + } else if (!TK_PHOTO_PUT_BLOCK) { + throw py::import_error("Failed to load Tk_PhotoPutBlock"); } - return PyModule_Create(&_tkagg_module); -} -#pragma GCC visibility pop + m.def("blit", &mpl_tk_blit, + "interp"_a, "photo_name"_a, "data"_a, "comp_rule"_a, "offset"_a, "bbox"_a); + m.def("enable_dpi_awareness", &mpl_tk_enable_dpi_awareness, + "frame_handle"_a, "interp"_a); + + m.attr("TK_PHOTO_COMPOSITE_OVERLAY") = TK_PHOTO_COMPOSITE_OVERLAY; + m.attr("TK_PHOTO_COMPOSITE_SET") = TK_PHOTO_COMPOSITE_SET; +} diff --git a/src/_tkmini.h b/src/_tkmini.h index d99b73291987..1c74cf9720f8 100644 --- a/src/_tkmini.h +++ b/src/_tkmini.h @@ -86,14 +86,27 @@ typedef struct Tk_PhotoImageBlock int offset[4]; } Tk_PhotoImageBlock; +#define TK_PHOTO_COMPOSITE_OVERLAY 0 // apply transparency rules pixel-wise +#define TK_PHOTO_COMPOSITE_SET 1 // set image buffer directly +#define TCL_OK 0 +#define TCL_ERROR 1 + /* Typedefs derived from function signatures in Tk header */ /* Tk_FindPhoto typedef */ typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char *imageName); -/* Tk_PhotoPutBLock_NoComposite typedef */ -typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle, +/* Tk_PhotoPutBLock typedef */ +typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle, Tk_PhotoImageBlock *blockPtr, int x, int y, - int width, int height); + int width, int height, int compRule); + +/* Typedefs derived from function signatures in Tcl header */ +/* Tcl_SetVar typedef */ +typedef const char *(*Tcl_SetVar_t)(Tcl_Interp *interp, const char *varName, + const char *newValue, int flags); +/* Tcl_SetVar2 typedef */ +typedef const char *(*Tcl_SetVar2_t)(Tcl_Interp *interp, const char *part1, const char *part2, + const char *newValue, int flags); #ifdef __cplusplus } diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp deleted file mode 100644 index 2fadb9bfa5fa..000000000000 --- a/src/_ttconv.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -/* - _ttconv.c - - Python wrapper for TrueType conversion library in ../ttconv. - */ -#define PY_SSIZE_T_CLEAN -#include "mplutils.h" - -#include -#include "ttconv/pprdrv.h" -#include "py_exceptions.h" -#include -#include - -/** - * An implementation of TTStreamWriter that writes to a Python - * file-like object. - */ -class PythonFileWriter : public TTStreamWriter -{ - PyObject *_write_method; - - public: - PythonFileWriter() - { - _write_method = NULL; - } - - ~PythonFileWriter() - { - Py_XDECREF(_write_method); - } - - void set(PyObject *write_method) - { - Py_XDECREF(_write_method); - _write_method = write_method; - Py_XINCREF(_write_method); - } - - virtual void write(const char *a) - { - PyObject *result = NULL; - if (_write_method) { - PyObject *decoded = NULL; - decoded = PyUnicode_DecodeLatin1(a, strlen(a), ""); - if (decoded == NULL) { - throw py::exception(); - } - result = PyObject_CallFunctionObjArgs(_write_method, decoded, NULL); - Py_DECREF(decoded); - if (!result) { - throw py::exception(); - } - Py_DECREF(result); - } - } -}; - -int fileobject_to_PythonFileWriter(PyObject *object, void *address) -{ - PythonFileWriter *file_writer = (PythonFileWriter *)address; - - PyObject *write_method = PyObject_GetAttrString(object, "write"); - if (write_method == NULL || !PyCallable_Check(write_method)) { - PyErr_SetString(PyExc_TypeError, "Expected a file-like object with a write method."); - return 0; - } - - file_writer->set(write_method); - Py_DECREF(write_method); - - return 1; -} - -int pyiterable_to_vector_int(PyObject *object, void *address) -{ - std::vector *result = (std::vector *)address; - - PyObject *iterator = PyObject_GetIter(object); - if (!iterator) { - return 0; - } - - PyObject *item; - while ((item = PyIter_Next(iterator))) { - long value = PyLong_AsLong(item); - Py_DECREF(item); - if (value == -1 && PyErr_Occurred()) { - return 0; - } - result->push_back((int)value); - } - - Py_DECREF(iterator); - - return 1; -} - -static PyObject *convert_ttf_to_ps(PyObject *self, PyObject *args, PyObject *kwds) -{ - const char *filename; - PythonFileWriter output; - int fonttype; - std::vector glyph_ids; - - static const char *kwlist[] = { "filename", "output", "fonttype", "glyph_ids", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "yO&i|O&:convert_ttf_to_ps", - (char **)kwlist, - &filename, - fileobject_to_PythonFileWriter, - &output, - &fonttype, - pyiterable_to_vector_int, - &glyph_ids)) { - return NULL; - } - - if (fonttype != 3 && fonttype != 42) { - PyErr_SetString(PyExc_ValueError, - "fonttype must be either 3 (raw Postscript) or 42 " - "(embedded Truetype)"); - return NULL; - } - - try - { - insert_ttfont(filename, output, (font_type_enum)fonttype, glyph_ids); - } - catch (TTException &e) - { - PyErr_SetString(PyExc_RuntimeError, e.getMessage()); - return NULL; - } - catch (const py::exception &) - { - return NULL; - } - catch (...) - { - PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -class PythonDictionaryCallback : public TTDictionaryCallback -{ - PyObject *_dict; - - public: - PythonDictionaryCallback(PyObject *dict) - { - _dict = dict; - } - - virtual void add_pair(const char *a, const char *b) - { - assert(a != NULL); - assert(b != NULL); - PyObject *value = PyBytes_FromString(b); - if (!value) { - throw py::exception(); - } - if (PyDict_SetItemString(_dict, a, value)) { - Py_DECREF(value); - throw py::exception(); - } - Py_DECREF(value); - } -}; - -static PyObject *py_get_pdf_charprocs(PyObject *self, PyObject *args, PyObject *kwds) -{ - const char *filename; - std::vector glyph_ids; - PyObject *result; - - static const char *kwlist[] = { "filename", "glyph_ids", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "y|O&:get_pdf_charprocs", - (char **)kwlist, - &filename, - pyiterable_to_vector_int, - &glyph_ids)) { - return NULL; - } - - result = PyDict_New(); - if (!result) { - return NULL; - } - - PythonDictionaryCallback dict(result); - - try - { - ::get_pdf_charprocs(filename, glyph_ids, dict); - } - catch (TTException &e) - { - Py_DECREF(result); - PyErr_SetString(PyExc_RuntimeError, e.getMessage()); - return NULL; - } - catch (const py::exception &) - { - Py_DECREF(result); - return NULL; - } - catch (...) - { - Py_DECREF(result); - PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); - return NULL; - } - - return result; -} - -static PyMethodDef ttconv_methods[] = -{ - { - "convert_ttf_to_ps", (PyCFunction)convert_ttf_to_ps, METH_VARARGS | METH_KEYWORDS, - "convert_ttf_to_ps(filename, output, fonttype, glyph_ids)\n" - "\n" - "Converts the Truetype font into a Type 3 or Type 42 Postscript font, " - "optionally subsetting the font to only the desired set of characters.\n" - "\n" - "filename is the path to a TTF font file.\n" - "output is a Python file-like object with a write method that the Postscript " - "font data will be written to.\n" - "fonttype may be either 3 or 42. Type 3 is a \"raw Postscript\" font. " - "Type 42 is an embedded Truetype font. Glyph subsetting is not supported " - "for Type 42 fonts.\n" - "glyph_ids (optional) is a list of glyph ids (integers) to keep when " - "subsetting to a Type 3 font. If glyph_ids is not provided or is None, " - "then all glyphs will be included. If any of the glyphs specified are " - "composite glyphs, then the component glyphs will also be included." - }, - { - "get_pdf_charprocs", (PyCFunction)py_get_pdf_charprocs, METH_VARARGS | METH_KEYWORDS, - "get_pdf_charprocs(filename, glyph_ids)\n" - "\n" - "Given a Truetype font file, returns a dictionary containing the PDF Type 3\n" - "representation of its paths. Useful for subsetting a Truetype font inside\n" - "of a PDF file.\n" - "\n" - "filename is the path to a TTF font file.\n" - "glyph_ids is a list of the numeric glyph ids to include.\n" - "The return value is a dictionary where the keys are glyph names and\n" - "the values are the stream content needed to render that glyph. This\n" - "is useful to generate the CharProcs dictionary in a PDF Type 3 font.\n" - }, - {0, 0, 0, 0} /* Sentinel */ -}; - -static const char *module_docstring = - "Module to handle converting and subsetting TrueType " - "fonts to Postscript Type 3, Postscript Type 42 and " - "Pdf Type 3 fonts."; - -static PyModuleDef ttconv_module = { - PyModuleDef_HEAD_INIT, - "ttconv", - module_docstring, - -1, - ttconv_methods, - NULL, NULL, NULL, NULL -}; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC -PyInit__ttconv(void) -{ - PyObject* m; - - m = PyModule_Create(&ttconv_module); - - return m; -} - -#pragma GCC visibility pop diff --git a/src/agg_workaround.h b/src/agg_workaround.h index 476219519280..a167be97e171 100644 --- a/src/agg_workaround.h +++ b/src/agg_workaround.h @@ -8,46 +8,6 @@ blending of RGBA32 pixels does not preserve enough precision */ -template -struct fixed_blender_rgba_pre : agg::conv_rgba_pre -{ - typedef ColorT color_type; - typedef Order order_type; - typedef typename color_type::value_type value_type; - typedef typename color_type::calc_type calc_type; - typedef typename color_type::long_type long_type; - enum base_scale_e - { - base_shift = color_type::base_shift, - base_mask = color_type::base_mask - }; - - //-------------------------------------------------------------------- - static AGG_INLINE void blend_pix(value_type* p, - value_type cr, value_type cg, value_type cb, - value_type alpha, agg::cover_type cover) - { - blend_pix(p, - color_type::mult_cover(cr, cover), - color_type::mult_cover(cg, cover), - color_type::mult_cover(cb, cover), - color_type::mult_cover(alpha, cover)); - } - - //-------------------------------------------------------------------- - static AGG_INLINE void blend_pix(value_type* p, - value_type cr, value_type cg, value_type cb, - value_type alpha) - { - alpha = base_mask - alpha; - p[Order::R] = (value_type)(((p[Order::R] * alpha) >> base_shift) + cr); - p[Order::G] = (value_type)(((p[Order::G] * alpha) >> base_shift) + cg); - p[Order::B] = (value_type)(((p[Order::B] * alpha) >> base_shift) + cb); - p[Order::A] = (value_type)(base_mask - ((alpha * (base_mask - p[Order::A])) >> base_shift)); - } -}; - - template struct fixed_blender_rgba_plain : agg::conv_rgba_plain { diff --git a/src/array.h b/src/array.h index 47d82995541b..0e8db3c4cac7 100644 --- a/src/array.h +++ b/src/array.h @@ -6,6 +6,9 @@ #ifndef MPL_SCALAR_H #define MPL_SCALAR_H +#include +#include + namespace array { @@ -29,7 +32,7 @@ class scalar return m_value; } - int dim(size_t i) + int shape(size_t i) { return 1; } @@ -40,15 +43,20 @@ class scalar } }; +template +size_t +safe_first_shape(scalar) +{ + return 1; +} + template class empty { public: typedef empty sub_t; - empty() - { - } + empty() = default; T &operator()(int i, int j = 0, int k = 0) { @@ -65,7 +73,7 @@ class empty return empty(); } - int dim(size_t i) const + int shape(size_t i) const { return 0; } @@ -75,6 +83,12 @@ class empty return 0; } }; + +template +size_t safe_first_shape(empty) +{ + return 0; +} } #endif diff --git a/src/checkdep_freetype2.c b/src/checkdep_freetype2.c index d0e8fd34fbe9..16e8ac23919e 100644 --- a/src/checkdep_freetype2.c +++ b/src/checkdep_freetype2.c @@ -1,7 +1,7 @@ #ifdef __has_include #if !__has_include() #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in setup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif #endif @@ -15,5 +15,5 @@ You may unset the system_freetype entry in setup.cfg to let Matplotlib download XSTR(FREETYPE_MAJOR) "." XSTR(FREETYPE_MINOR) "." XSTR(FREETYPE_PATCH) ".") #if FREETYPE_MAJOR << 16 + FREETYPE_MINOR << 8 + FREETYPE_PATCH < 0x020300 #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in setup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 720d8a622df5..bdfa2873ca80 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -1,16 +1,16 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ -#define NO_IMPORT_ARRAY - #include +#include +#include +#include #include #include #include +#include #include "ft2font.h" #include "mplutils.h" -#include "numpy_cpp.h" -#include "py_exceptions.h" #ifndef M_PI #define M_PI 3.14159265358979323846264338328 @@ -20,7 +20,7 @@ To improve the hinting of the fonts, this code uses a hack presented here: - http://antigrain.com/research/font_rasterization/index.html + http://agg.sourceforge.net/antigrain.com/research/font_rasterization/index.html The idea is to limit the effect of hinting in the x-direction, while preserving hinting in the y-direction. Since freetype does not @@ -43,80 +43,44 @@ FT_Library _ft2Library; -void throw_ft_error(std::string message, FT_Error error) { - std::ostringstream os(""); - os << message << " (error code 0x" << std::hex << error << ")"; - throw std::runtime_error(os.str()); -} - -FT2Image::FT2Image() : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) -{ -} - FT2Image::FT2Image(unsigned long width, unsigned long height) - : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) + : m_buffer((unsigned char *)calloc(width * height, 1)), m_width(width), m_height(height) { - resize(width, height); } FT2Image::~FT2Image() { - delete[] m_buffer; + free(m_buffer); } -void FT2Image::resize(long width, long height) +void draw_bitmap( + py::array_t im, FT_Bitmap *bitmap, FT_Int x, FT_Int y) { - if (width <= 0) { - width = 1; - } - if (height <= 0) { - height = 1; - } - size_t numBytes = width * height; + auto buf = im.mutable_data(0); - if ((unsigned long)width != m_width || (unsigned long)height != m_height) { - if (numBytes > m_width * m_height) { - delete[] m_buffer; - m_buffer = NULL; - m_buffer = new unsigned char[numBytes]; - } - - m_width = (unsigned long)width; - m_height = (unsigned long)height; - } - - if (numBytes && m_buffer) { - memset(m_buffer, 0, numBytes); - } - - m_dirty = true; -} - -void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y) -{ - FT_Int image_width = (FT_Int)m_width; - FT_Int image_height = (FT_Int)m_height; + FT_Int image_width = (FT_Int)im.shape(1); + FT_Int image_height = (FT_Int)im.shape(0); FT_Int char_width = bitmap->width; FT_Int char_height = bitmap->rows; - FT_Int x1 = CLAMP(x, 0, image_width); - FT_Int y1 = CLAMP(y, 0, image_height); - FT_Int x2 = CLAMP(x + char_width, 0, image_width); - FT_Int y2 = CLAMP(y + char_height, 0, image_height); + FT_Int x1 = std::min(std::max(x, 0), image_width); + FT_Int y1 = std::min(std::max(y, 0), image_height); + FT_Int x2 = std::min(std::max(x + char_width, 0), image_width); + FT_Int y2 = std::min(std::max(y + char_height, 0), image_height); - FT_Int x_start = MAX(0, -x); - FT_Int y_offset = y1 - MAX(0, -y); + FT_Int x_start = std::max(0, -x); + FT_Int y_offset = y1 - std::max(0, -y); if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { for (FT_Int i = y1; i < y2; ++i) { - unsigned char *dst = m_buffer + (i * image_width + x1); + unsigned char *dst = buf + (i * image_width + x1); unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start); for (FT_Int j = x1; j < x2; ++j, ++dst, ++src) *dst |= *src; } } else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { for (FT_Int i = y1; i < y2; ++i) { - unsigned char *dst = m_buffer + (i * image_width + x1); + unsigned char *dst = buf + (i * image_width + x1); unsigned char *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch); for (FT_Int j = x1; j < x2; ++j, ++dst) { int x = (j - x1 + x_start); @@ -127,29 +91,6 @@ void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y) } else { throw std::runtime_error("Unknown pixel mode"); } - - m_dirty = true; -} - -void FT2Image::draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1) -{ - if (x0 > m_width || x1 > m_width || y0 > m_height || y1 > m_height) { - throw std::runtime_error("Rect coords outside image bounds"); - } - - size_t top = y0 * m_width; - size_t bottom = y1 * m_width; - for (size_t i = x0; i < x1 + 1; ++i) { - m_buffer[i + top] = 255; - m_buffer[i + bottom] = 255; - } - - for (size_t j = y0 + 1; j < y1; ++j) { - m_buffer[x0 + j * m_width] = 255; - m_buffer[x1 + j * m_width] = 255; - } - - m_dirty = true; } void @@ -165,54 +106,28 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, m_buffer[i + j * m_width] = 255; } } - - m_dirty = true; -} - -static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) -{ - FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - if (!glyph_index) { - PyErr_WarnFormat(NULL, 1, "Glyph %lu missing from current font.", charcode); - // Apparently PyErr_WarnFormat returns 0 even if the exception propagates - // due to running with -Werror, so check the error flag directly instead. - if (PyErr_Occurred()) { - throw py::exception(); - } - } - return glyph_index; } - -// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the -// first pass, vertices and codes are set to NULL, and index is simply -// incremented for each vertex that should be inserted, so that it is set, at -// the end, to the total number of vertices. On a second pass, vertices and -// codes should point to correctly sized arrays, and index set again to zero, -// to get fill vertices and codes with the outline decomposition. +// ft_outline_decomposer should be passed to FT_Outline_Decompose. struct ft_outline_decomposer { - int index; - double* vertices; - unsigned char* codes; + std::vector &vertices; + std::vector &codes; }; static int ft_outline_move_to(FT_Vector const* to, void* user) { ft_outline_decomposer* d = reinterpret_cast(user); - if (d->codes) { - if (d->index) { - // Appending ENDPOLY is important to make patheffects work. - *(d->vertices++) = 0; - *(d->vertices++) = 0; - *(d->codes++) = ENDPOLY; - } - *(d->vertices++) = to->x / 64.; - *(d->vertices++) = to->y / 64.; - *(d->codes++) = MOVETO; - } - d->index += d->index ? 2 : 1; + if (!d->vertices.empty()) { + // Appending CLOSEPOLY is important to make patheffects work. + d->vertices.push_back(0); + d->vertices.push_back(0); + d->codes.push_back(CLOSEPOLY); + } + d->vertices.push_back(to->x * (1. / 64.)); + d->vertices.push_back(to->y * (1. / 64.)); + d->codes.push_back(MOVETO); return 0; } @@ -220,12 +135,9 @@ static int ft_outline_line_to(FT_Vector const* to, void* user) { ft_outline_decomposer* d = reinterpret_cast(user); - if (d->codes) { - *(d->vertices++) = to->x / 64.; - *(d->vertices++) = to->y / 64.; - *(d->codes++) = LINETO; - } - d->index++; + d->vertices.push_back(to->x * (1. / 64.)); + d->vertices.push_back(to->y * (1. / 64.)); + d->codes.push_back(LINETO); return 0; } @@ -233,15 +145,12 @@ static int ft_outline_conic_to(FT_Vector const* control, FT_Vector const* to, void* user) { ft_outline_decomposer* d = reinterpret_cast(user); - if (d->codes) { - *(d->vertices++) = control->x / 64.; - *(d->vertices++) = control->y / 64.; - *(d->vertices++) = to->x / 64.; - *(d->vertices++) = to->y / 64.; - *(d->codes++) = CURVE3; - *(d->codes++) = CURVE3; - } - d->index += 2; + d->vertices.push_back(control->x * (1. / 64.)); + d->vertices.push_back(control->y * (1. / 64.)); + d->vertices.push_back(to->x * (1. / 64.)); + d->vertices.push_back(to->y * (1. / 64.)); + d->codes.push_back(CURVE3); + d->codes.push_back(CURVE3); return 0; } @@ -250,18 +159,15 @@ ft_outline_cubic_to( FT_Vector const* c1, FT_Vector const* c2, FT_Vector const* to, void* user) { ft_outline_decomposer* d = reinterpret_cast(user); - if (d->codes) { - *(d->vertices++) = c1->x / 64.; - *(d->vertices++) = c1->y / 64.; - *(d->vertices++) = c2->x / 64.; - *(d->vertices++) = c2->y / 64.; - *(d->vertices++) = to->x / 64.; - *(d->vertices++) = to->y / 64.; - *(d->codes++) = CURVE4; - *(d->codes++) = CURVE4; - *(d->codes++) = CURVE4; - } - d->index += 3; + d->vertices.push_back(c1->x * (1. / 64.)); + d->vertices.push_back(c1->y * (1. / 64.)); + d->vertices.push_back(c2->x * (1. / 64.)); + d->vertices.push_back(c2->y * (1. / 64.)); + d->vertices.push_back(to->x * (1. / 64.)); + d->vertices.push_back(to->y * (1. / 64.)); + d->codes.push_back(CURVE4); + d->codes.push_back(CURVE4); + d->codes.push_back(CURVE4); return 0; } @@ -271,88 +177,64 @@ static FT_Outline_Funcs ft_outline_funcs = { ft_outline_conic_to, ft_outline_cubic_to}; -PyObject* -FT2Font::get_path() +void +FT2Font::get_path(std::vector &vertices, std::vector &codes) { if (!face->glyph) { - PyErr_SetString(PyExc_RuntimeError, "No glyph loaded"); - return NULL; - } - ft_outline_decomposer decomposer = {}; - if (FT_Error error = - FT_Outline_Decompose( - &face->glyph->outline, &ft_outline_funcs, &decomposer)) { - PyErr_Format(PyExc_RuntimeError, - "FT_Outline_Decompose failed with error 0x%x", error); - return NULL; - } - if (!decomposer.index) { // Don't append ENDPOLY to null glyphs. - npy_intp vertices_dims[2] = { 0, 2 }; - numpy::array_view vertices(vertices_dims); - npy_intp codes_dims[1] = { 0 }; - numpy::array_view codes(codes_dims); - return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); - } - npy_intp vertices_dims[2] = { decomposer.index + 1, 2 }; - numpy::array_view vertices(vertices_dims); - npy_intp codes_dims[1] = { decomposer.index + 1 }; - numpy::array_view codes(codes_dims); - decomposer.index = 0; - decomposer.vertices = vertices.data(); - decomposer.codes = codes.data(); - if (FT_Error error = - FT_Outline_Decompose( - &face->glyph->outline, &ft_outline_funcs, &decomposer)) { - PyErr_Format(PyExc_RuntimeError, - "FT_Outline_Decompose failed with error 0x%x", error); - return NULL; - } - *(decomposer.vertices++) = 0; - *(decomposer.vertices++) = 0; - *(decomposer.codes++) = ENDPOLY; - return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); -} - -FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face(NULL) + throw std::runtime_error("No glyph loaded"); + } + ft_outline_decomposer decomposer = { + vertices, + codes, + }; + // We can make a close-enough estimate based on number of points and number of + // contours (which produce a MOVETO each), though it's slightly underestimating due + // to higher-order curves. + size_t estimated_points = static_cast(face->glyph->outline.n_contours) + + static_cast(face->glyph->outline.n_points); + vertices.reserve(2 * estimated_points); + codes.reserve(estimated_points); + if (FT_Error error = FT_Outline_Decompose( + &face->glyph->outline, &ft_outline_funcs, &decomposer)) { + throw std::runtime_error("FT_Outline_Decompose failed with error " + + std::to_string(error)); + } + if (vertices.empty()) { // Don't append CLOSEPOLY to null glyphs. + return; + } + vertices.push_back(0); + vertices.push_back(0); + codes.push_back(CLOSEPOLY); +} + +FT2Font::FT2Font(FT_Open_Args &open_args, + long hinting_factor_, + std::vector &fallback_list, + FT2Font::WarnFunc warn, bool warn_if_used) + : ft_glyph_warn(warn), warn_if_used(warn_if_used), image({1, 1}), face(nullptr), + hinting_factor(hinting_factor_), + // set default kerning factor to 0, i.e., no kerning manipulation + kerning_factor(0) { clear(); - - FT_Error error = FT_Open_Face(_ft2Library, &open_args, 0, &face); - - if (error == FT_Err_Unknown_File_Format) { - throw std::runtime_error("Can not load face. Unknown file format."); - } else if (error == FT_Err_Cannot_Open_Resource) { - throw std::runtime_error("Can not load face. Can not open resource."); - } else if (error == FT_Err_Invalid_File_Format) { - throw std::runtime_error("Can not load face. Invalid file format."); - } else if (error) { - throw_ft_error("Can not load face", error); + FT_CHECK(FT_Open_Face, _ft2Library, &open_args, 0, &face); + if (open_args.stream != nullptr) { + face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; } - - // set default kerning factor to 0, i.e., no kerning manipulation - kerning_factor = 0; - - // set a default fontsize 12 pt at 72dpi - hinting_factor = hinting_factor_; - - error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * (unsigned int)hinting_factor, 72); - if (error) { + try { + set_size(12., 72.); // Set a default fontsize 12 pt at 72dpi. + } catch (...) { FT_Done_Face(face); - throw_ft_error("Could not set the fontsize", error); - } - - if (open_args.stream != NULL) { - face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; + throw; } - - FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; - FT_Set_Transform(face, &transform, 0); + // Set fallbacks + std::copy(fallback_list.begin(), fallback_list.end(), std::back_inserter(fallbacks)); } FT2Font::~FT2Font() { - for (size_t i = 0; i < glyphs.size(); i++) { - FT_Done_Glyph(glyphs[i]); + for (auto & glyph : glyphs) { + FT_Done_Glyph(glyph); } if (face) { @@ -362,25 +244,34 @@ FT2Font::~FT2Font() void FT2Font::clear() { - pen.x = 0; - pen.y = 0; + pen.x = pen.y = 0; + bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; + advance = 0; - for (size_t i = 0; i < glyphs.size(); i++) { - FT_Done_Glyph(glyphs[i]); + for (auto & glyph : glyphs) { + FT_Done_Glyph(glyph); } glyphs.clear(); + glyph_to_font.clear(); + char_to_font.clear(); + + for (auto & fallback : fallbacks) { + fallback->clear(); + } } void FT2Font::set_size(double ptsize, double dpi) { - FT_Error error = FT_Set_Char_Size( + FT_CHECK( + FT_Set_Char_Size, face, (FT_F26Dot6)(ptsize * 64), 0, (FT_UInt)(dpi * hinting_factor), (FT_UInt)dpi); - if (error) { - throw_ft_error("Could not set the fontsize", error); - } FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; - FT_Set_Transform(face, &transform, 0); + FT_Set_Transform(face, &transform, nullptr); + + for (auto & fallback : fallbacks) { + fallback->set_size(ptsize, dpi); + } } void FT2Font::set_charmap(int i) @@ -388,25 +279,42 @@ void FT2Font::set_charmap(int i) if (i >= face->num_charmaps) { throw std::runtime_error("i exceeds the available number of char maps"); } - FT_CharMap charmap = face->charmaps[i]; - if (FT_Error error = FT_Set_Charmap(face, charmap)) { - throw_ft_error("Could not set the charmap", error); - } + FT_CHECK(FT_Set_Charmap, face, face->charmaps[i]); } void FT2Font::select_charmap(unsigned long i) { - if (FT_Error error = FT_Select_Charmap(face, (FT_Encoding)i)) { - throw_ft_error("Could not set the charmap", error); + FT_CHECK(FT_Select_Charmap, face, (FT_Encoding)i); +} + +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, + bool fallback = false) +{ + if (fallback && glyph_to_font.find(left) != glyph_to_font.end() && + glyph_to_font.find(right) != glyph_to_font.end()) { + FT2Font *left_ft_object = glyph_to_font[left]; + FT2Font *right_ft_object = glyph_to_font[right]; + if (left_ft_object != right_ft_object) { + // we do not know how to do kerning between different fonts + return 0; + } + // if left_ft_object is the same as right_ft_object, + // do the exact same thing which set_text does. + return right_ft_object->get_kerning(left, right, mode, false); + } + else + { + FT_Vector delta; + return get_kerning(left, right, mode, delta); } } -int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode) +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, + FT_Vector &delta) { if (!FT_HAS_KERNING(face)) { return 0; } - FT_Vector delta; if (!FT_Get_Kerning(face, left, right, mode, &delta)) { return (int)(delta.x) / (hinting_factor << kerning_factor); @@ -418,58 +326,73 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode) void FT2Font::set_kerning_factor(int factor) { kerning_factor = factor; + for (auto & fallback : fallbacks) { + fallback->set_kerning_factor(factor); + } } void FT2Font::set_text( - size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys) + std::u32string_view text, double angle, FT_Int32 flags, std::vector &xys) { FT_Matrix matrix; /* transformation matrix */ - angle = angle / 360.0 * 2 * M_PI; + angle = angle * (2 * M_PI / 360.0); - // this computes width and height in subpixels so we have to divide by 64 - matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); - matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); - matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); - matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L); + // this computes width and height in subpixels so we have to multiply by 64 + double cosangle = cos(angle) * 0x10000L; + double sinangle = sin(angle) * 0x10000L; - FT_Bool use_kerning = FT_HAS_KERNING(face); - FT_UInt previous = 0; + matrix.xx = (FT_Fixed)cosangle; + matrix.xy = (FT_Fixed)-sinangle; + matrix.yx = (FT_Fixed)sinangle; + matrix.yy = (FT_Fixed)cosangle; clear(); bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; - for (unsigned int n = 0; n < N; n++) { - FT_UInt glyph_index; + FT_UInt previous = 0; + FT2Font *previous_ft_object = nullptr; + + for (auto codepoint : text) { + FT_UInt glyph_index = 0; FT_BBox glyph_bbox; FT_Pos last_advance; - glyph_index = ft_get_char_index_or_warn(face, codepoints[n]); + FT_Error charcode_error, glyph_error; + std::set glyph_seen_fonts; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs, + char_to_font, glyph_to_font, codepoint, flags, + charcode_error, glyph_error, glyph_seen_fonts, false); + if (!was_found) { + ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts); + // render missing glyph tofu + // come back to top-most font + ft_object_with_glyph = this; + char_to_font[codepoint] = ft_object_with_glyph; + glyph_to_font[glyph_index] = ft_object_with_glyph; + ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false); + } else if (ft_object_with_glyph->warn_if_used) { + ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts); + } // retrieve kerning distance and move pen position - if (use_kerning && previous && glyph_index) { + if ((ft_object_with_glyph == previous_ft_object) && // if both fonts are the same + ft_object_with_glyph->has_kerning() && // if the font knows how to kern + previous && glyph_index // and we really have 2 glyphs + ) { FT_Vector delta; - FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); - pen.x += delta.x / (hinting_factor << kerning_factor); + pen.x += ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta); } - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load glyph", error); - } - // ignore errors, jump to next glyph // extract glyph image and store it in our table + FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; - FT_Glyph thisGlyph; - if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - throw_ft_error("Could not get glyph", error); - } - // ignore errors, jump to next glyph - - last_advance = face->glyph->advance.x; - FT_Glyph_Transform(thisGlyph, 0, &pen); - FT_Glyph_Transform(thisGlyph, &matrix, 0); + last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; + FT_Glyph_Transform(thisGlyph, nullptr, &pen); + FT_Glyph_Transform(thisGlyph, &matrix, nullptr); xys.push_back(pen.x); xys.push_back(pen.y); @@ -483,7 +406,8 @@ void FT2Font::set_text( pen.x += last_advance; previous = glyph_index; - glyphs.push_back(thisGlyph); + previous_ft_object = ft_object_with_glyph; + } FT_Vector_Transform(&pen, &matrix); @@ -494,31 +418,169 @@ void FT2Font::set_text( } } -void FT2Font::load_char(long charcode, FT_Int32 flags) +void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback = false) +{ + // if this is parent FT2Font, cache will be filled in 2 ways: + // 1. set_text was previously called + // 2. set_text was not called and fallback was enabled + std::set glyph_seen_fonts; + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + ft_object = char_to_font[charcode]; + // since it will be assigned to ft_object anyway + FT2Font *throwaway = nullptr; + ft_object->load_char(charcode, flags, throwaway, false); + } else if (fallback) { + FT_UInt final_glyph_index; + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, + glyphs, char_to_font, glyph_to_font, + charcode, flags, charcode_error, glyph_error, + glyph_seen_fonts, true); + if (!was_found) { + ft_glyph_warn(charcode, glyph_seen_fonts); + if (charcode_error) { + THROW_FT_ERROR("charcode loading", charcode_error); + } + else if (glyph_error) { + THROW_FT_ERROR("charcode loading", glyph_error); + } + } else if (ft_object_with_glyph->warn_if_used) { + ft_glyph_warn(charcode, glyph_seen_fonts); + } + ft_object = ft_object_with_glyph; + } else { + //no fallback case + ft_object = this; + FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode); + if (!glyph_index){ + glyph_seen_fonts.insert((face != nullptr)?face->family_name: nullptr); + ft_glyph_warn((FT_ULong)charcode, glyph_seen_fonts); + } + FT_CHECK(FT_Load_Glyph, face, glyph_index, flags); + FT_Glyph thisGlyph; + FT_CHECK(FT_Get_Glyph, face->glyph, &thisGlyph); + glyphs.push_back(thisGlyph); + } +} + + +bool FT2Font::get_char_fallback_index(FT_ULong charcode, int& index) const { - FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load charcode", error); + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index) { + // -1 means the host has the char and we do not need to fallback + index = -1; + return true; + } else { + int inner_index = 0; + bool was_found; + + for (size_t i = 0; i < fallbacks.size(); ++i) { + // TODO handle recursion somehow! + was_found = fallbacks[i]->get_char_fallback_index(charcode, inner_index); + if (was_found) { + index = i; + return true; + } + } } - FT_Glyph thisGlyph; - if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - throw_ft_error("Could not get glyph", error); + return false; +} + + +bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector &parent_glyphs, + std::unordered_map &parent_char_to_font, + std::unordered_map &parent_glyph_to_font, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error, + std::set &glyph_seen_fonts, + bool override = false) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (!warn_if_used) { + glyph_seen_fonts.insert(face->family_name); + } + + if (glyph_index || override) { + charcode_error = FT_Load_Glyph(face, glyph_index, flags); + if (charcode_error) { + return false; + } + FT_Glyph thisGlyph; + glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); + if (glyph_error) { + return false; + } + + final_glyph_index = glyph_index; + + // cache the result for future + // need to store this for anytime a character is loaded from a parent + // FT2Font object or to generate a mapping of individual characters to fonts + ft_object_with_glyph = this; + parent_glyph_to_font[final_glyph_index] = this; + parent_char_to_font[charcode] = this; + parent_glyphs.push_back(thisGlyph); + return true; + } + else { + for (auto & fallback : fallbacks) { + bool was_found = fallback->load_char_with_fallback( + ft_object_with_glyph, final_glyph_index, parent_glyphs, + parent_char_to_font, parent_glyph_to_font, charcode, flags, + charcode_error, glyph_error, glyph_seen_fonts, override); + if (was_found) { + return true; + } + } + return false; } - glyphs.push_back(thisGlyph); } -void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) +void FT2Font::load_glyph(FT_UInt glyph_index, + FT_Int32 flags, + FT2Font *&ft_object, + bool fallback = false) { - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load glyph", error); + // cache is only for parent FT2Font + if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + ft_object = glyph_to_font[glyph_index]; + } else { + ft_object = this; } + + ft_object->load_glyph(glyph_index, flags); +} + +void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) +{ + FT_CHECK(FT_Load_Glyph, face, glyph_index, flags); FT_Glyph thisGlyph; - if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - throw_ft_error("Could not get glyph", error); - } + FT_CHECK(FT_Get_Glyph, face->glyph, &thisGlyph); glyphs.push_back(thisGlyph); } +FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) +{ + FT2Font *ft_object = nullptr; + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + // fallback denotes whether we want to search fallback list. + // should call set_text/load_char_with_fallback to parent FT2Font before + // wanting to use fallback list here. (since that populates the cache) + ft_object = char_to_font[charcode]; + } else { + // set as self + ft_object = this; + } + + return FT_Get_Char_Index(ft_object->get_face(), charcode); +} + void FT2Font::get_width_height(long *width, long *height) { *width = advance; @@ -538,53 +600,30 @@ void FT2Font::get_bitmap_offset(long *x, long *y) void FT2Font::draw_glyphs_to_bitmap(bool antialiased) { - size_t width = (bbox.xMax - bbox.xMin) / 64 + 2; - size_t height = (bbox.yMax - bbox.yMin) / 64 + 2; - - image.resize(width, height); + long width = (bbox.xMax - bbox.xMin) / 64 + 2; + long height = (bbox.yMax - bbox.yMin) / 64 + 2; - for (size_t n = 0; n < glyphs.size(); n++) { - FT_Error error = FT_Glyph_To_Bitmap( - &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); - if (error) { - throw_ft_error("Could not convert glyph to bitmap", error); - } + image = py::array_t{{height, width}}; + std::memset(image.mutable_data(0), 0, image.nbytes()); - FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; + for (auto & glyph: glyphs) { + FT_CHECK( + FT_Glyph_To_Bitmap, + &glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1); + FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph; // now, draw to our target surface (convert position) // bitmap left and top in pixel, string bbox in subpixel - FT_Int x = (FT_Int)(bitmap->left - (bbox.xMin / 64.)); - FT_Int y = (FT_Int)((bbox.yMax / 64.) - bitmap->top + 1); - - image.draw_bitmap(&bitmap->bitmap, x, y); - } -} - -void FT2Font::get_xys(bool antialiased, std::vector &xys) -{ - for (size_t n = 0; n < glyphs.size(); n++) { + FT_Int x = (FT_Int)(bitmap->left - (bbox.xMin * (1. / 64.))); + FT_Int y = (FT_Int)((bbox.yMax * (1. / 64.)) - bitmap->top + 1); - FT_Error error = FT_Glyph_To_Bitmap( - &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); - if (error) { - throw_ft_error("Could not convert glyph to bitmap", error); - } - - FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; - - // bitmap left and top in pixel, string bbox in subpixel - FT_Int x = (FT_Int)(bitmap->left - bbox.xMin / 64.); - FT_Int y = (FT_Int)(bbox.yMax / 64. - bitmap->top + 1); - // make sure the index is non-neg - x = x < 0 ? 0 : x; - y = y < 0 ? 0 : y; - xys.push_back(x); - xys.push_back(y); + draw_bitmap(image, &bitmap->bitmap, x, y); } } -void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased) +void FT2Font::draw_glyph_to_bitmap( + py::array_t im, + int x, int y, size_t glyphInd, bool antialiased) { FT_Vector sub_offset; sub_offset.x = 0; // int((xd - (double)x) * 64.0); @@ -594,30 +633,40 @@ void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, throw std::runtime_error("glyph num is out of range"); } - FT_Error error = FT_Glyph_To_Bitmap( - &glyphs[glyphInd], - antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, - &sub_offset, // additional translation - 1 // destroy image - ); - if (error) { - throw_ft_error("Could not convert glyph to bitmap", error); - } - + FT_CHECK( + FT_Glyph_To_Bitmap, + &glyphs[glyphInd], + antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, + &sub_offset, // additional translation + 1); // destroy image FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd]; - im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y); + draw_bitmap(im, &bitmap->bitmap, x + bitmap->left, y); } -void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer) +void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer, + bool fallback = false) { + if (fallback && glyph_to_font.find(glyph_number) != glyph_to_font.end()) { + // cache is only for parent FT2Font + FT2Font *ft_object = glyph_to_font[glyph_number]; + ft_object->get_glyph_name(glyph_number, buffer, false); + return; + } if (!FT_HAS_GLYPH_NAMES(face)) { /* Note that this generated name must match the name that is generated by ttconv in ttfont_CharStrings_getname. */ - PyOS_snprintf(buffer, 128, "uni%08x", glyph_number); + auto len = snprintf(buffer.data(), buffer.size(), "uni%08x", glyph_number); + if (len >= 0) { + buffer.resize(len); + } else { + throw std::runtime_error("Failed to convert glyph to standard name"); + } } else { - if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) { - throw_ft_error("Could not get glyph names", error); + FT_CHECK(FT_Get_Glyph_Name, face, glyph_number, buffer.data(), buffer.size()); + auto len = buffer.find('\0'); + if (len != buffer.npos) { + buffer.resize(len); } } } diff --git a/src/ft2font.h b/src/ft2font.h index 0863f3450b36..8db0239ed4fd 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -1,10 +1,16 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ /* A python interface to FreeType */ +#pragma once + #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H + +#include +#include +#include +#include #include -#include extern "C" { #include @@ -16,27 +22,50 @@ extern "C" { #include FT_TRUETYPE_TABLES_H } -#define PY_SSIZE_T_CLEAN -#include +#include +#include +namespace py = pybind11; -/* - By definition, FT_FIXED as 2 16bit values stored in a single long. - */ +// By definition, FT_FIXED as 2 16bit values stored in a single long. #define FIXED_MAJOR(val) (signed short)((val & 0xffff0000) >> 16) #define FIXED_MINOR(val) (unsigned short)(val & 0xffff) +// Error handling (error codes are loaded as described in fterror.h). +inline char const* ft_error_string(FT_Error error) { +#undef __FTERRORS_H__ +#define FT_ERROR_START_LIST switch (error) { +#define FT_ERRORDEF( e, v, s ) case v: return s; +#define FT_ERROR_END_LIST default: return NULL; } +#include FT_ERRORS_H +} + +// No more than 16 hex digits + "0x" + null byte for a 64-bit int error. +#define THROW_FT_ERROR(name, err) { \ + std::string path{__FILE__}; \ + char buf[20] = {0}; \ + snprintf(buf, sizeof buf, "%#04x", err); \ + throw std::runtime_error{ \ + name " (" \ + + path.substr(path.find_last_of("/\\") + 1) \ + + " line " + std::to_string(__LINE__) + ") failed with error " \ + + std::string{buf} + ": " + std::string{ft_error_string(err)}}; \ +} (void)0 + +#define FT_CHECK(func, ...) { \ + if (auto const& error_ = func(__VA_ARGS__)) { \ + THROW_FT_ERROR(#func, error_); \ + } \ +} (void)0 + // the FreeType string rendered into a width, height buffer class FT2Image { public: - FT2Image(); FT2Image(unsigned long width, unsigned long height); virtual ~FT2Image(); void resize(long width, long height); void draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y); - void write_bitmap(FILE *fp) const; - void draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); void draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); unsigned char *get_buffer() @@ -53,7 +82,6 @@ class FT2Image } private: - bool m_dirty; unsigned char *m_buffer; unsigned long m_width; unsigned long m_height; @@ -67,62 +95,89 @@ extern FT_Library _ft2Library; class FT2Font { + typedef void (*WarnFunc)(FT_ULong charcode, std::set family_names); public: - FT2Font(FT_Open_Args &open_args, long hinting_factor); + FT2Font(FT_Open_Args &open_args, long hinting_factor, + std::vector &fallback_list, + WarnFunc warn, bool warn_if_used); virtual ~FT2Font(); void clear(); void set_size(double ptsize, double dpi); void set_charmap(int i); void select_charmap(unsigned long i); - void set_text( - size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys); - int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode); + void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags, + std::vector &xys); + int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, bool fallback); + int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, FT_Vector &delta); void set_kerning_factor(int factor); - void load_char(long charcode, FT_Int32 flags); + void load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback); + bool load_char_with_fallback(FT2Font *&ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector &parent_glyphs, + std::unordered_map &parent_char_to_font, + std::unordered_map &parent_glyph_to_font, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error, + std::set &glyph_seen_fonts, + bool override); + void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback); void load_glyph(FT_UInt glyph_index, FT_Int32 flags); void get_width_height(long *width, long *height); void get_bitmap_offset(long *x, long *y); long get_descent(); - // TODO: Since we know the size of the array upfront, we probably don't - // need to dynamically allocate like this - void get_xys(bool antialiased, std::vector &xys); void draw_glyphs_to_bitmap(bool antialiased); - void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); - void get_glyph_name(unsigned int glyph_number, char *buffer); + void draw_glyph_to_bitmap( + py::array_t im, + int x, int y, size_t glyphInd, bool antialiased); + void get_glyph_name(unsigned int glyph_number, std::string &buffer, bool fallback); long get_name_index(char *name); - PyObject* get_path(); + FT_UInt get_char_index(FT_ULong charcode, bool fallback); + void get_path(std::vector &vertices, std::vector &codes); + bool get_char_fallback_index(FT_ULong charcode, int& index) const; - FT_Face &get_face() + FT_Face const &get_face() const { return face; } - FT2Image &get_image() + + py::array_t &get_image() { return image; } - FT_Glyph &get_last_glyph() + FT_Glyph const &get_last_glyph() const { return glyphs.back(); } - size_t get_last_glyph_index() + size_t get_last_glyph_index() const { return glyphs.size() - 1; } - size_t get_num_glyphs() + size_t get_num_glyphs() const { return glyphs.size(); } - long get_hinting_factor() + long get_hinting_factor() const { return hinting_factor; } + FT_Bool has_kerning() const + { + return FT_HAS_KERNING(face); + } private: - FT2Image image; + WarnFunc ft_glyph_warn; + bool warn_if_used; + py::array_t image; FT_Face face; FT_Vector pen; /* untransformed origin */ std::vector glyphs; + std::vector fallbacks; + std::unordered_map glyph_to_font; + std::unordered_map char_to_font; FT_BBox bbox; FT_Pos advance; long hinting_factor; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index dbf0ae55816e..ca2db6aa0e5b 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1,263 +1,268 @@ -#include "mplutils.h" -#include "ft2font.h" -#include "py_converters.h" -#include "py_exceptions.h" -#include "numpy_cpp.h" - -// From Python -#include - -#define STRINGIFY(s) XSTRINGIFY(s) -#define XSTRINGIFY(s) #s +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include +#include +#include -static PyObject *convert_xys_to_array(std::vector &xys) -{ - npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 }; - if (dims[0] > 0) { - return PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, &xys[0]); +#include "ft2font.h" +#include "_enums.h" + +#include +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +template +using double_or_ = std::variant; + +template +static T +_double_to_(const char *name, double_or_ &var) +{ + if (auto value = std::get_if(&var)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a=name, "obj_type"_a="parameter as float", + "alternative"_a="int({})"_s.format(name)); + return static_cast(*value); + } else if (auto value = std::get_if(&var)) { + return *value; } else { - return PyArray_SimpleNew(2, dims, NPY_DOUBLE); + // pybind11 will have only allowed types that match the variant, so this `else` + // can't happen. We only have this case because older macOS doesn't support + // `std::get` and using the conditional `std::get_if` means an `else` to silence + // compiler warnings about "unhandled" cases. + throw std::runtime_error("Should not happen"); } } /********************************************************************** - * FT2Image + * Enumerations * */ -typedef struct -{ - PyObject_HEAD - FT2Image *x; - Py_ssize_t shape[2]; - Py_ssize_t strides[2]; - Py_ssize_t suboffsets[2]; -} PyFT2Image; - -static PyObject *PyFT2Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyFT2Image *self; - self = (PyFT2Image *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} +const char *Kerning__doc__ = R"""( + Kerning modes for `.FT2Font.get_kerning`. -static int PyFT2Image_init(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - double width; - double height; + For more information, see `the FreeType documentation + `_. - if (!PyArg_ParseTuple(args, "dd:FT2Image", &width, &height)) { - return -1; - } - - CALL_CPP_INIT("FT2Image", (self->x = new FT2Image(width, height))); - - return 0; -} - -static void PyFT2Image_dealloc(PyFT2Image *self) -{ - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); -} + .. versionadded:: 3.10 +)"""; -const char *PyFT2Image_draw_rect__doc__ = - "draw_rect(x0, y0, x1, y1)\n" - "\n" - "Draw a rect to the image.\n" - "\n"; +P11X_DECLARE_ENUM( + "Kerning", "Enum", + {"DEFAULT", FT_KERNING_DEFAULT}, + {"UNFITTED", FT_KERNING_UNFITTED}, + {"UNSCALED", FT_KERNING_UNSCALED}, +); -static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - double x0, y0, x1, y1; +const char *FaceFlags__doc__ = R"""( + Flags returned by `FT2Font.face_flags`. - if (!PyArg_ParseTuple(args, "dddd:draw_rect", &x0, &y0, &x1, &y1)) { - return NULL; - } + For more information, see `the FreeType documentation + `_. - CALL_CPP("draw_rect", (self->x->draw_rect(x0, y0, x1, y1))); + .. versionadded:: 3.10 +)"""; - Py_RETURN_NONE; -} - -const char *PyFT2Image_draw_rect_filled__doc__ = - "draw_rect_filled(x0, y0, x1, y1)\n" - "\n" - "Draw a filled rect to the image.\n" - "\n"; - -static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - double x0, y0, x1, y1; - - if (!PyArg_ParseTuple(args, "dddd:draw_rect_filled", &x0, &y0, &x1, &y1)) { - return NULL; - } - - CALL_CPP("draw_rect_filled", (self->x->draw_rect_filled(x0, y0, x1, y1))); - - Py_RETURN_NONE; -} - -const char *PyFT2Image_as_str__doc__ = - "s = image.as_str()\n" - "\n" - "[*Deprecated*]\n" - "Return the image buffer as a string\n" - "\n"; +#ifndef FT_FACE_FLAG_VARIATION // backcompat: ft 2.9.0. +#define FT_FACE_FLAG_VARIATION (1L << 15) +#endif +#ifndef FT_FACE_FLAG_SVG // backcompat: ft 2.12.0. +#define FT_FACE_FLAG_SVG (1L << 16) +#endif +#ifndef FT_FACE_FLAG_SBIX // backcompat: ft 2.12.0. +#define FT_FACE_FLAG_SBIX (1L << 17) +#endif +#ifndef FT_FACE_FLAG_SBIX_OVERLAY // backcompat: ft 2.12.0. +#define FT_FACE_FLAG_SBIX_OVERLAY (1L << 18) +#endif -static PyObject *PyFT2Image_as_str(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - if (PyErr_WarnEx(PyExc_FutureWarning, - "FT2Image.as_str is deprecated since Matplotlib 3.2 and " - "will be removed in Matplotlib 3.4; convert the FT2Image " - "to a NumPy array with np.asarray instead.", - 1)) { - return NULL; - } - return PyBytes_FromStringAndSize((const char *)self->x->get_buffer(), - self->x->get_width() * self->x->get_height()); -} +enum class FaceFlags : FT_Long { +#define DECLARE_FLAG(name) name = FT_FACE_FLAG_##name + DECLARE_FLAG(SCALABLE), + DECLARE_FLAG(FIXED_SIZES), + DECLARE_FLAG(FIXED_WIDTH), + DECLARE_FLAG(SFNT), + DECLARE_FLAG(HORIZONTAL), + DECLARE_FLAG(VERTICAL), + DECLARE_FLAG(KERNING), + DECLARE_FLAG(FAST_GLYPHS), + DECLARE_FLAG(MULTIPLE_MASTERS), + DECLARE_FLAG(GLYPH_NAMES), + DECLARE_FLAG(EXTERNAL_STREAM), + DECLARE_FLAG(HINTER), + DECLARE_FLAG(CID_KEYED), + DECLARE_FLAG(TRICKY), + DECLARE_FLAG(COLOR), + DECLARE_FLAG(VARIATION), + DECLARE_FLAG(SVG), + DECLARE_FLAG(SBIX), + DECLARE_FLAG(SBIX_OVERLAY), +#undef DECLARE_FLAG +}; -const char *PyFT2Image_as_rgba_str__doc__ = - "s = image.as_rgba_str()\n" - "\n" - "[*Deprecated*]\n" - "Return the image buffer as a RGBA string\n" - "\n"; +P11X_DECLARE_ENUM( + "FaceFlags", "Flag", + {"SCALABLE", FaceFlags::SCALABLE}, + {"FIXED_SIZES", FaceFlags::FIXED_SIZES}, + {"FIXED_WIDTH", FaceFlags::FIXED_WIDTH}, + {"SFNT", FaceFlags::SFNT}, + {"HORIZONTAL", FaceFlags::HORIZONTAL}, + {"VERTICAL", FaceFlags::VERTICAL}, + {"KERNING", FaceFlags::KERNING}, + {"FAST_GLYPHS", FaceFlags::FAST_GLYPHS}, + {"MULTIPLE_MASTERS", FaceFlags::MULTIPLE_MASTERS}, + {"GLYPH_NAMES", FaceFlags::GLYPH_NAMES}, + {"EXTERNAL_STREAM", FaceFlags::EXTERNAL_STREAM}, + {"HINTER", FaceFlags::HINTER}, + {"CID_KEYED", FaceFlags::CID_KEYED}, + {"TRICKY", FaceFlags::TRICKY}, + {"COLOR", FaceFlags::COLOR}, + {"VARIATION", FaceFlags::VARIATION}, + {"SVG", FaceFlags::SVG}, + {"SBIX", FaceFlags::SBIX}, + {"SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY}, +); + +const char *LoadFlags__doc__ = R"""( + Flags for `FT2Font.load_char`, `FT2Font.load_glyph`, and `FT2Font.set_text`. + + For more information, see `the FreeType documentation + `_. + + .. versionadded:: 3.10 +)"""; + +#ifndef FT_LOAD_COMPUTE_METRICS // backcompat: ft 2.6.1. +#define FT_LOAD_COMPUTE_METRICS (1L << 21) +#endif +#ifndef FT_LOAD_BITMAP_METRICS_ONLY // backcompat: ft 2.7.1. +#define FT_LOAD_BITMAP_METRICS_ONLY (1L << 22) +#endif +#ifndef FT_LOAD_NO_SVG // backcompat: ft 2.13.1. +#define FT_LOAD_NO_SVG (1L << 24) +#endif -static PyObject *PyFT2Image_as_rgba_str(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - if (PyErr_WarnEx(PyExc_FutureWarning, - "FT2Image.as_rgba_str is deprecated since Matplotlib 3.2 and " - "will be removed in Matplotlib 3.4; convert the FT2Image " - "to a NumPy array with np.asarray instead.", - 1)) { - return NULL; - } - npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width(), 4 }; - numpy::array_view result(dims); - - unsigned char *src = self->x->get_buffer(); - unsigned char *end = src + (self->x->get_width() * self->x->get_height()); - unsigned char *dst = result.data(); - - while (src != end) { - *dst++ = 0; - *dst++ = 0; - *dst++ = 0; - *dst++ = *src++; - } +enum class LoadFlags : FT_Int32 { +#define DECLARE_FLAG(name) name = FT_LOAD_##name + DECLARE_FLAG(DEFAULT), + DECLARE_FLAG(NO_SCALE), + DECLARE_FLAG(NO_HINTING), + DECLARE_FLAG(RENDER), + DECLARE_FLAG(NO_BITMAP), + DECLARE_FLAG(VERTICAL_LAYOUT), + DECLARE_FLAG(FORCE_AUTOHINT), + DECLARE_FLAG(CROP_BITMAP), + DECLARE_FLAG(PEDANTIC), + DECLARE_FLAG(IGNORE_GLOBAL_ADVANCE_WIDTH), + DECLARE_FLAG(NO_RECURSE), + DECLARE_FLAG(IGNORE_TRANSFORM), + DECLARE_FLAG(MONOCHROME), + DECLARE_FLAG(LINEAR_DESIGN), + DECLARE_FLAG(NO_AUTOHINT), + DECLARE_FLAG(COLOR), + DECLARE_FLAG(COMPUTE_METRICS), + DECLARE_FLAG(BITMAP_METRICS_ONLY), + DECLARE_FLAG(NO_SVG), + DECLARE_FLAG(TARGET_NORMAL), + DECLARE_FLAG(TARGET_LIGHT), + DECLARE_FLAG(TARGET_MONO), + DECLARE_FLAG(TARGET_LCD), + DECLARE_FLAG(TARGET_LCD_V), +#undef DECLARE_FLAG +}; - return result.pyobj(); -} +P11X_DECLARE_ENUM( + "LoadFlags", "Flag", + {"DEFAULT", LoadFlags::DEFAULT}, + {"NO_SCALE", LoadFlags::NO_SCALE}, + {"NO_HINTING", LoadFlags::NO_HINTING}, + {"RENDER", LoadFlags::RENDER}, + {"NO_BITMAP", LoadFlags::NO_BITMAP}, + {"VERTICAL_LAYOUT", LoadFlags::VERTICAL_LAYOUT}, + {"FORCE_AUTOHINT", LoadFlags::FORCE_AUTOHINT}, + {"CROP_BITMAP", LoadFlags::CROP_BITMAP}, + {"PEDANTIC", LoadFlags::PEDANTIC}, + {"IGNORE_GLOBAL_ADVANCE_WIDTH", LoadFlags::IGNORE_GLOBAL_ADVANCE_WIDTH}, + {"NO_RECURSE", LoadFlags::NO_RECURSE}, + {"IGNORE_TRANSFORM", LoadFlags::IGNORE_TRANSFORM}, + {"MONOCHROME", LoadFlags::MONOCHROME}, + {"LINEAR_DESIGN", LoadFlags::LINEAR_DESIGN}, + {"NO_AUTOHINT", LoadFlags::NO_AUTOHINT}, + {"COLOR", LoadFlags::COLOR}, + {"COMPUTE_METRICS", LoadFlags::COMPUTE_METRICS}, + {"BITMAP_METRICS_ONLY", LoadFlags::BITMAP_METRICS_ONLY}, + {"NO_SVG", LoadFlags::NO_SVG}, + // These must be unique, but the others can be OR'd together; I don't know if + // there's any way to really enforce that. + {"TARGET_NORMAL", LoadFlags::TARGET_NORMAL}, + {"TARGET_LIGHT", LoadFlags::TARGET_LIGHT}, + {"TARGET_MONO", LoadFlags::TARGET_MONO}, + {"TARGET_LCD", LoadFlags::TARGET_LCD}, + {"TARGET_LCD_V", LoadFlags::TARGET_LCD_V}, +); + +const char *StyleFlags__doc__ = R"""( + Flags returned by `FT2Font.style_flags`. + + For more information, see `the FreeType documentation + `_. + + .. versionadded:: 3.10 +)"""; + +enum class StyleFlags : FT_Long { +#define DECLARE_FLAG(name) name = FT_STYLE_FLAG_##name + NORMAL = 0, + DECLARE_FLAG(ITALIC), + DECLARE_FLAG(BOLD), +#undef DECLARE_FLAG +}; -const char *PyFT2Image_as_array__doc__ = - "x = image.as_array()\n" - "\n" - "[*Deprecated*]\n" - "Return the image buffer as a width x height numpy array of ubyte \n" - "\n"; +P11X_DECLARE_ENUM( + "StyleFlags", "Flag", + {"NORMAL", StyleFlags::NORMAL}, + {"ITALIC", StyleFlags::ITALIC}, + {"BOLD", StyleFlags::BOLD}, +); -static PyObject *PyFT2Image_as_array(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - if (PyErr_WarnEx(PyExc_FutureWarning, - "FT2Image.as_array is deprecated since Matplotlib 3.2 and " - "will be removed in Matplotlib 3.4; convert the FT2Image " - "to a NumPy array with np.asarray instead.", - 1)) { - return NULL; - } - npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width() }; - return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, self->x->get_buffer()); -} +/********************************************************************** + * FT2Image + * */ -static PyObject *PyFT2Image_get_width(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - if (PyErr_WarnEx(PyExc_FutureWarning, - "FT2Image.get_width is deprecated since Matplotlib 3.2 and " - "will be removed in Matplotlib 3.4; convert the FT2Image " - "to a NumPy array with np.asarray instead.", - 1)) { - return NULL; - } - return PyLong_FromLong(self->x->get_width()); -} +const char *PyFT2Image__doc__ = R"""( + An image buffer for drawing glyphs. +)"""; -static PyObject *PyFT2Image_get_height(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - if (PyErr_WarnEx(PyExc_FutureWarning, - "FT2Image.get_height is deprecated since Matplotlib 3.2 and " - "will be removed in Matplotlib 3.4; convert the FT2Image " - "to a NumPy array with np.asarray instead.", - 1)) { - return NULL; - } - return PyLong_FromLong(self->x->get_height()); -} +const char *PyFT2Image_init__doc__ = R"""( + Parameters + ---------- + width, height : int + The dimensions of the image buffer. +)"""; -static int PyFT2Image_get_buffer(PyFT2Image *self, Py_buffer *buf, int flags) -{ - FT2Image *im = self->x; - - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = im->get_buffer(); - buf->len = im->get_width() * im->get_height(); - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 2; - self->shape[0] = im->get_height(); - self->shape[1] = im->get_width(); - buf->shape = self->shape; - self->strides[0] = im->get_width(); - self->strides[1] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} +const char *PyFT2Image_draw_rect_filled__doc__ = R"""( + Draw a filled rectangle to the image. -static PyTypeObject PyFT2ImageType; + Parameters + ---------- + x0, y0, x1, y1 : float + The bounds of the rectangle from (x0, y0) to (x1, y1). +)"""; -static PyTypeObject *PyFT2Image_init_type(PyObject *m, PyTypeObject *type) +static void +PyFT2Image_draw_rect_filled(FT2Image *self, + double_or_ vx0, double_or_ vy0, + double_or_ vx1, double_or_ vy1) { - static PyMethodDef methods[] = { - {"draw_rect", (PyCFunction)PyFT2Image_draw_rect, METH_VARARGS, PyFT2Image_draw_rect__doc__}, - {"draw_rect_filled", (PyCFunction)PyFT2Image_draw_rect_filled, METH_VARARGS, PyFT2Image_draw_rect_filled__doc__}, - {"as_str", (PyCFunction)PyFT2Image_as_str, METH_NOARGS, PyFT2Image_as_str__doc__}, - {"as_rgba_str", (PyCFunction)PyFT2Image_as_rgba_str, METH_NOARGS, PyFT2Image_as_rgba_str__doc__}, - {"as_array", (PyCFunction)PyFT2Image_as_array, METH_NOARGS, PyFT2Image_as_array__doc__}, - {"get_width", (PyCFunction)PyFT2Image_get_width, METH_NOARGS, NULL}, - {"get_height", (PyCFunction)PyFT2Image_get_height, METH_NOARGS, NULL}, - {NULL} - }; - - static PyBufferProcs buffer_procs; - memset(&buffer_procs, 0, sizeof(PyBufferProcs)); - buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer; - - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.ft2font.FT2Image"; - type->tp_basicsize = sizeof(PyFT2Image); - type->tp_dealloc = (destructor)PyFT2Image_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - type->tp_methods = methods; - type->tp_new = PyFT2Image_new; - type->tp_init = (initproc)PyFT2Image_init; - type->tp_as_buffer = &buffer_procs; - - if (PyType_Ready(type) < 0) { - return NULL; - } - - if (PyModule_AddObject(m, "FT2Image", (PyObject *)type)) { - return NULL; - } + auto x0 = _double_to_("x0", vx0); + auto y0 = _double_to_("y0", vy0); + auto x1 = _double_to_("x1", vx1); + auto y1 = _double_to_("y1", vy1); - return type; + self->draw_rect_filled(x0, y0, x1, y1); } /********************************************************************** @@ -266,7 +271,6 @@ static PyTypeObject *PyFT2Image_init_type(PyObject *m, PyTypeObject *type) typedef struct { - PyObject_HEAD size_t glyphInd; long width; long height; @@ -280,16 +284,27 @@ typedef struct FT_BBox bbox; } PyGlyph; -static PyTypeObject PyGlyphType; +const char *PyGlyph__doc__ = R"""( + Information about a single glyph. -static PyObject * -PyGlyph_new(const FT_Face &face, const FT_Glyph &glyph, size_t ind, long hinting_factor) + You cannot create instances of this object yourself, but must use + `.FT2Font.load_char` or `.FT2Font.load_glyph` to generate one. This object may be + used in a call to `.FT2Font.draw_glyph_to_bitmap`. + + For more information on the various metrics, see `the FreeType documentation + `_. +)"""; + +static PyGlyph * +PyGlyph_from_FT2Font(const FT2Font *font) { - PyGlyph *self; - self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0); + const FT_Face &face = font->get_face(); + const long hinting_factor = font->get_hinting_factor(); + const FT_Glyph &glyph = font->get_last_glyph(); - self->glyphInd = ind; + PyGlyph *self = new PyGlyph(); + self->glyphInd = font->get_last_glyph_index(); FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); self->width = face->glyph->metrics.width / hinting_factor; @@ -302,1428 +317,1464 @@ PyGlyph_new(const FT_Face &face, const FT_Glyph &glyph, size_t ind, long hinting self->vertBearingY = face->glyph->metrics.vertBearingY; self->vertAdvance = face->glyph->metrics.vertAdvance; - return (PyObject *)self; + return self; } -static void PyGlyph_dealloc(PyGlyph *self) +static py::tuple +PyGlyph_get_bbox(PyGlyph *self) { - Py_TYPE(self)->tp_free((PyObject *)self); + return py::make_tuple(self->bbox.xMin, self->bbox.yMin, + self->bbox.xMax, self->bbox.yMax); } -static PyObject *PyGlyph_get_bbox(PyGlyph *self, void *closure) -{ - return Py_BuildValue( - "llll", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax); -} +/********************************************************************** + * FT2Font + * */ -static PyTypeObject *PyGlyph_init_type(PyObject *m, PyTypeObject *type) +struct PyFT2Font { - static PyMemberDef members[] = { - {(char *)"width", T_LONG, offsetof(PyGlyph, width), READONLY, (char *)""}, - {(char *)"height", T_LONG, offsetof(PyGlyph, height), READONLY, (char *)""}, - {(char *)"horiBearingX", T_LONG, offsetof(PyGlyph, horiBearingX), READONLY, (char *)""}, - {(char *)"horiBearingY", T_LONG, offsetof(PyGlyph, horiBearingY), READONLY, (char *)""}, - {(char *)"horiAdvance", T_LONG, offsetof(PyGlyph, horiAdvance), READONLY, (char *)""}, - {(char *)"linearHoriAdvance", T_LONG, offsetof(PyGlyph, linearHoriAdvance), READONLY, (char *)""}, - {(char *)"vertBearingX", T_LONG, offsetof(PyGlyph, vertBearingX), READONLY, (char *)""}, - {(char *)"vertBearingY", T_LONG, offsetof(PyGlyph, vertBearingY), READONLY, (char *)""}, - {(char *)"vertAdvance", T_LONG, offsetof(PyGlyph, vertAdvance), READONLY, (char *)""}, - {NULL} - }; + FT2Font *x; + py::object py_file; + FT_StreamRec stream; + py::list fallbacks; - static PyGetSetDef getset[] = { - {(char *)"bbox", (getter)PyGlyph_get_bbox, NULL, NULL, NULL}, - {NULL} - }; + ~PyFT2Font() + { + delete this->x; + } +}; - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.ft2font.Glyph"; - type->tp_basicsize = sizeof(PyGlyph); - type->tp_dealloc = (destructor)PyGlyph_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - type->tp_members = members; - type->tp_getset = getset; +const char *PyFT2Font__doc__ = R"""( + An object representing a single font face. - if (PyType_Ready(type) < 0) { - return NULL; - } + Outside of the font itself and querying its properties, this object provides methods + for processing text strings into glyph shapes. - /* Don't need to add to module, since you can't create glyphs - directly from Python */ + Commonly, one will use `FT2Font.set_text` to load some glyph metrics and outlines. + Then `FT2Font.draw_glyphs_to_bitmap` and `FT2Font.get_image` may be used to get a + rendered form of the loaded string. - return type; -} + For single characters, `FT2Font.load_char` or `FT2Font.load_glyph` may be used, + either directly for their return values, or to use `FT2Font.draw_glyph_to_bitmap` or + `FT2Font.get_path`. -/********************************************************************** - * FT2Font - * */ + Useful metrics may be examined via the `Glyph` return values or + `FT2Font.get_kerning`. Most dimensions are given in 26.6 or 16.6 fixed-point + integers representing subpixels. Divide these values by 64 to produce floating-point + pixels. +)"""; -typedef struct +static unsigned long +read_from_file_callback(FT_Stream stream, unsigned long offset, unsigned char *buffer, + unsigned long count) { - PyObject_HEAD - FT2Font *x; - PyObject *fname; - PyObject *py_file; - FT_StreamRec stream; - Py_ssize_t shape[2]; - Py_ssize_t strides[2]; - Py_ssize_t suboffsets[2]; -} PyFT2Font; - -static unsigned long read_from_file_callback(FT_Stream stream, - unsigned long offset, - unsigned char *buffer, - unsigned long count) -{ - PyObject *py_file = ((PyFT2Font *)stream->descriptor.pointer)->py_file; - PyObject *seek_result = NULL, *read_result = NULL; + PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; Py_ssize_t n_read = 0; - if (!(seek_result = PyObject_CallMethod(py_file, "seek", "k", offset)) - || !(read_result = PyObject_CallMethod(py_file, "read", "k", count))) { - goto exit; - } - char *tmpbuf; - if (PyBytes_AsStringAndSize(read_result, &tmpbuf, &n_read) == -1) { - goto exit; - } - memcpy(buffer, tmpbuf, n_read); -exit: - Py_XDECREF(seek_result); - Py_XDECREF(read_result); - if (PyErr_Occurred()) { - PyErr_WriteUnraisable(py_file); + try { + char *tmpbuf; + auto seek_result = self->py_file.attr("seek")(offset); + auto read_result = self->py_file.attr("read")(count); + if (PyBytes_AsStringAndSize(read_result.ptr(), &tmpbuf, &n_read) == -1) { + throw py::error_already_set(); + } + memcpy(buffer, tmpbuf, n_read); + } catch (py::error_already_set &eas) { + eas.discard_as_unraisable(__func__); if (!count) { return 1; // Non-zero signals error, when count == 0. } } - return n_read; + return (unsigned long)n_read; } -static void close_file_callback(FT_Stream stream) +static void +close_file_callback(FT_Stream stream) { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; - PyObject *close_result = NULL; - if (!(close_result = PyObject_CallMethod(self->py_file, "close", ""))) { - goto exit; - } -exit: - Py_XDECREF(close_result); - Py_CLEAR(self->py_file); - if (PyErr_Occurred()) { - PyErr_WriteUnraisable((PyObject*)self); + try { + self->py_file.attr("close")(); + } catch (py::error_already_set &eas) { + eas.discard_as_unraisable(__func__); } + self->py_file = py::object(); + PyErr_Restore(type, value, traceback); } -static PyTypeObject PyFT2FontType; - -static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +static void +ft_glyph_warn(FT_ULong charcode, std::set family_names) { - PyFT2Font *self; - self = (PyFT2Font *)type->tp_alloc(type, 0); - self->x = NULL; - self->fname = NULL; - self->py_file = NULL; - memset(&self->stream, 0, sizeof(FT_StreamRec)); - return (PyObject *)self; + std::set::iterator it = family_names.begin(); + std::stringstream ss; + ss<<*it; + while(++it != family_names.end()){ + ss<<", "<<*it; + } + + auto text_helpers = py::module_::import("matplotlib._text_helpers"); + auto warn_on_missing_glyph = text_helpers.attr("warn_on_missing_glyph"); + warn_on_missing_glyph(charcode, ss.str()); } -const char *PyFT2Font_init__doc__ = - "FT2Font(ttffile)\n" - "\n" - "Create a new FT2Font object\n" - "The following global font attributes are defined:\n" - " num_faces number of faces in file\n" - " face_flags face flags (int type); see the ft2font constants\n" - " style_flags style flags (int type); see the ft2font constants\n" - " num_glyphs number of glyphs in the face\n" - " family_name face family name\n" - " style_name face style name\n" - " num_fixed_sizes number of bitmap in the face\n" - " scalable face is scalable\n" - "\n" - "The following are available, if scalable is true:\n" - " bbox face global bounding box (xmin, ymin, xmax, ymax)\n" - " units_per_EM number of font units covered by the EM\n" - " ascender ascender in 26.6 units\n" - " descender descender in 26.6 units\n" - " height height in 26.6 units; used to compute a default\n" - " line spacing (baseline-to-baseline distance)\n" - " max_advance_width maximum horizontal cursor advance for all glyphs\n" - " max_advance_height same for vertical layout\n" - " underline_position vertical position of the underline bar\n" - " underline_thickness vertical thickness of the underline\n" - " postscript_name PostScript name of the font\n"; - -static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) +const char *PyFT2Font_init__doc__ = R"""( + Parameters + ---------- + filename : str or file-like + The source of the font data in a format (ttf or ttc) that FreeType can read. + + hinting_factor : int, optional + Must be positive. Used to scale the hinting in the x-direction. + + _fallback_list : list of FT2Font, optional + A list of FT2Font objects used to find missing glyphs. + + .. warning:: + This API is both private and provisional: do not use it directly. + + _kerning_factor : int, optional + Used to adjust the degree of kerning. + + .. warning:: + This API is private: do not use it directly. + + _warn_if_used : bool, optional + Used to trigger missing glyph warnings. + + .. warning:: + This API is private: do not use it directly. +)"""; + +static PyFT2Font * +PyFT2Font_init(py::object filename, long hinting_factor = 8, + std::optional> fallback_list = std::nullopt, + int kerning_factor = 0, bool warn_if_used = false) { - PyObject *filename = NULL, *open = NULL, *data = NULL; - FT_Open_Args open_args; - long hinting_factor = 8; - int kerning_factor = 0; - const char *names[] = { "filename", "hinting_factor", "_kerning_factor", NULL }; - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|l$i:FT2Font", (char **)names, &filename, - &hinting_factor, &kerning_factor)) { - return -1; + if (hinting_factor <= 0) { + throw py::value_error("hinting_factor must be greater than 0"); } - self->stream.base = NULL; + PyFT2Font *self = new PyFT2Font(); + self->x = nullptr; + memset(&self->stream, 0, sizeof(FT_StreamRec)); + self->stream.base = nullptr; self->stream.size = 0x7fffffff; // Unknown size. self->stream.pos = 0; self->stream.descriptor.pointer = self; self->stream.read = &read_from_file_callback; + FT_Open_Args open_args; memset((void *)&open_args, 0, sizeof(FT_Open_Args)); open_args.flags = FT_OPEN_STREAM; open_args.stream = &self->stream; - if (PyBytes_Check(filename) || PyUnicode_Check(filename)) { - if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference. - || !(self->py_file = PyObject_CallFunction(open, "Os", filename, "rb"))) { - goto exit; + std::vector fallback_fonts; + if (fallback_list) { + // go through fallbacks to add them to our lists + for (auto item : *fallback_list) { + self->fallbacks.append(item); + // Also (locally) cache the underlying FT2Font objects. As long as + // the Python objects are kept alive, these pointer are good. + FT2Font *fback = item->x; + fallback_fonts.push_back(fback); } + } + + if (py::isinstance(filename) || py::isinstance(filename)) { + self->py_file = py::module_::import("io").attr("open")(filename, "rb"); self->stream.close = &close_file_callback; - } else if (!PyObject_HasAttrString(filename, "read") - || !(data = PyObject_CallMethod(filename, "read", "i", 0)) - || !PyBytes_Check(data)) { - PyErr_SetString(PyExc_TypeError, - "First argument must be a path or binary-mode file object"); - Py_CLEAR(data); - goto exit; } else { + try { + // This will catch various issues: + // 1. `read` not being an attribute. + // 2. `read` raising an error. + // 3. `read` returning something other than `bytes`. + auto data = filename.attr("read")(0).cast(); + } catch (const std::exception&) { + throw py::type_error( + "First argument must be a path to a font file or a binary-mode file object"); + } self->py_file = filename; - self->stream.close = NULL; - Py_INCREF(filename); + self->stream.close = nullptr; } - Py_CLEAR(data); - - CALL_CPP_FULL( - "FT2Font", (self->x = new FT2Font(open_args, hinting_factor)), - Py_CLEAR(self->py_file), -1); - CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor))); + self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn, + warn_if_used); - Py_INCREF(filename); - self->fname = filename; + self->x->set_kerning_factor(kerning_factor); -exit: - return PyErr_Occurred() ? -1 : 0; + return self; } -static void PyFT2Font_dealloc(PyFT2Font *self) +static py::str +PyFT2Font_fname(PyFT2Font *self) { - delete self->x; - Py_XDECREF(self->py_file); - Py_XDECREF(self->fname); - Py_TYPE(self)->tp_free((PyObject *)self); + if (self->stream.close) { // Called passed a filename to the constructor. + return self->py_file.attr("name"); + } else { + return py::cast(self->py_file); + } } const char *PyFT2Font_clear__doc__ = - "clear()\n" - "\n" - "Clear all the glyphs, reset for a new set_text"; + "Clear all the glyphs, reset for a new call to `.set_text`."; -static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_clear(PyFT2Font *self) { - CALL_CPP("clear", (self->x->clear())); - - Py_RETURN_NONE; + self->x->clear(); } -const char *PyFT2Font_set_size__doc__ = - "set_size(ptsize, dpi)\n" - "\n" - "Set the point size and dpi of the text.\n"; +const char *PyFT2Font_set_size__doc__ = R"""( + Set the size of the text. -static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args, PyObject *kwds) + Parameters + ---------- + ptsize : float + The size of the text in points. + dpi : float + The DPI used for rendering the text. +)"""; + +static void +PyFT2Font_set_size(PyFT2Font *self, double ptsize, double dpi) { - double ptsize; - double dpi; + self->x->set_size(ptsize, dpi); +} - if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { - return NULL; - } +const char *PyFT2Font_set_charmap__doc__ = R"""( + Make the i-th charmap current. - CALL_CPP("set_size", (self->x->set_size(ptsize, dpi))); + For more details on character mapping, see the `FreeType documentation + `_. - Py_RETURN_NONE; -} + Parameters + ---------- + i : int + The charmap number in the range [0, `.num_charmaps`). -const char *PyFT2Font_set_charmap__doc__ = - "set_charmap(i)\n" - "\n" - "Make the i-th charmap current\n"; + See Also + -------- + .num_charmaps + .select_charmap + .get_charmap +)"""; -static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_set_charmap(PyFT2Font *self, int i) { - int i; + self->x->set_charmap(i); +} - if (!PyArg_ParseTuple(args, "i:set_charmap", &i)) { - return NULL; - } +const char *PyFT2Font_select_charmap__doc__ = R"""( + Select a charmap by its FT_Encoding number. - CALL_CPP("set_charmap", (self->x->set_charmap(i))); + For more details on character mapping, see the `FreeType documentation + `_. - Py_RETURN_NONE; -} + Parameters + ---------- + i : int + The charmap in the form defined by FreeType: + https://freetype.org/freetype2/docs/reference/ft2-character_mapping.html#ft_encoding -const char *PyFT2Font_select_charmap__doc__ = - "select_charmap(i)\n" - "\n" - "select charmap i where i is one of the FT_Encoding number\n"; + See Also + -------- + .set_charmap + .get_charmap +)"""; -static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_select_charmap(PyFT2Font *self, unsigned long i) { - unsigned long i; + self->x->select_charmap(i); +} - if (!PyArg_ParseTuple(args, "k:select_charmap", &i)) { - return NULL; - } +const char *PyFT2Font_get_kerning__doc__ = R"""( + Get the kerning between two glyphs. - CALL_CPP("select_charmap", self->x->select_charmap(i)); + Parameters + ---------- + left, right : int + The glyph indices. Note these are not characters nor character codes. + Use `.get_char_index` to convert character codes to glyph indices. - Py_RETURN_NONE; -} + mode : Kerning + A kerning mode constant: -const char *PyFT2Font_get_kerning__doc__ = - "dx = get_kerning(left, right, mode)\n" - "\n" - "Get the kerning between left char and right glyph indices\n" - "mode is a kerning mode constant\n" - " KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n" - " KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n" - " KERNING_UNSCALED - Return the kerning vector in original font units\n"; + - ``DEFAULT`` - Return scaled and grid-fitted kerning distances. + - ``UNFITTED`` - Return scaled but un-grid-fitted kerning distances. + - ``UNSCALED`` - Return the kerning vector in original font units. -static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args, PyObject *kwds) + .. versionchanged:: 3.10 + This now takes a `.ft2font.Kerning` value instead of an `int`. + + Returns + ------- + int + The kerning adjustment between the two glyphs. +)"""; + +static int +PyFT2Font_get_kerning(PyFT2Font *self, FT_UInt left, FT_UInt right, + std::variant mode_or_int) { - FT_UInt left, right, mode; - int result; + bool fallback = true; + FT_Kerning_Mode mode; - if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) { - return NULL; + if (auto value = std::get_if(&mode_or_int)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="mode", "obj_type"_a="parameter as int", + "alternative"_a="Kerning enum values"); + mode = static_cast(*value); + } else if (auto value = std::get_if(&mode_or_int)) { + mode = *value; + } else { + // NOTE: this can never happen as pybind11 would have checked the type in the + // Python wrapper before calling this function, but we need to keep the + // std::get_if instead of std::get for macOS 10.12 compatibility. + throw py::type_error("mode must be Kerning or int"); } - CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode))); - - return PyLong_FromLong(result); + return self->x->get_kerning(left, right, mode, fallback); } -const char *PyFT2Font_set_text__doc__ = - "set_text(s, angle)\n" - "\n" - "Set the text string and angle.\n" - "You must call this before draw_glyphs_to_bitmap\n" - "A sequence of x,y positions is returned"; +const char *PyFT2Font_get_fontmap__doc__ = R"""( + Get a mapping between characters and the font that includes them. -static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - PyObject *textobj; - double angle = 0.0; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - std::vector xys; - const char *names[] = { "string", "angle", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|di:set_text", (char **)names, &textobj, &angle, &flags)) { - return NULL; - } + .. warning:: + This API uses the fallback list and is both private and provisional: do not use + it directly. - std::vector codepoints; - size_t size; + Parameters + ---------- + text : str + The characters for which to find fonts. - if (PyUnicode_Check(textobj)) { - size = PyUnicode_GET_LENGTH(textobj); - codepoints.resize(size); -#if defined(PYPY_VERSION) && (PYPY_VERSION_NUM < 0x07030200) - Py_UNICODE *unistr = PyUnicode_AsUnicode(textobj); - for (size_t i = 0; i < size; ++i) { - codepoints[i] = unistr[i]; - } -#else - for (size_t i = 0; i < size; ++i) { - codepoints[i] = PyUnicode_ReadChar(textobj, i); - } -#endif - } else if (PyBytes_Check(textobj)) { - if (PyErr_WarnEx( - PyExc_FutureWarning, - "Passing bytes to FTFont.set_text is deprecated since Matplotlib " - "3.4 and support will be removed in Matplotlib 3.6; pass str instead", - 1)) { - return NULL; + Returns + ------- + dict[str, FT2Font] + A dictionary mapping unicode characters to `.FT2Font` objects. +)"""; + +static py::dict +PyFT2Font_get_fontmap(PyFT2Font *self, std::u32string text) +{ + std::set codepoints; + + py::dict char_to_font; + for (auto code : text) { + if (!codepoints.insert(code).second) { + continue; } - size = PyBytes_Size(textobj); - codepoints.resize(size); - char *bytestr = PyBytes_AsString(textobj); - for (size_t i = 0; i < size; ++i) { - codepoints[i] = bytestr[i]; + + py::object target_font; + int index; + if (self->x->get_char_fallback_index(code, index)) { + if (index >= 0) { + target_font = self->fallbacks[index]; + } else { + target_font = py::cast(self); + } + } else { + // TODO Handle recursion! + target_font = py::cast(self); } - } else { - PyErr_SetString(PyExc_TypeError, "String must be str or bytes"); - return NULL; - } - uint32_t* codepoints_array = NULL; - if (size > 0) { - codepoints_array = &codepoints[0]; + auto key = py::cast(std::u32string(1, code)); + char_to_font[key] = target_font; } - CALL_CPP("set_text", self->x->set_text(size, codepoints_array, angle, flags, xys)); - - return convert_xys_to_array(xys); + return char_to_font; } -const char *PyFT2Font_get_num_glyphs__doc__ = - "get_num_glyphs()\n" - "\n" - "Return the number of loaded glyphs\n"; +const char *PyFT2Font_set_text__doc__ = R"""( + Set the text *string* and *angle*. -static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - return PyLong_FromLong(self->x->get_num_glyphs()); -} + You must call this before `.draw_glyphs_to_bitmap`. -const char *PyFT2Font_load_char__doc__ = - "load_char(charcode, flags=LOAD_FORCE_AUTOHINT)\n" - "\n" - "Load character with charcode in current fontfile and set glyph.\n" - "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" - "Return value is a Glyph object, with attributes\n" - " width # glyph width\n" - " height # glyph height\n" - " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" - " horiBearingX # left side bearing in horizontal layouts\n" - " horiBearingY # top side bearing in horizontal layouts\n" - " horiAdvance # advance width for horizontal layout\n" - " vertBearingX # left side bearing in vertical layouts\n" - " vertBearingY # top side bearing in vertical layouts\n" - " vertAdvance # advance height for vertical layout\n"; - -static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - long charcode; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - const char *names[] = { "charcode", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "l|i:load_char", (char **)names, &charcode, &flags)) { - return NULL; - } + Parameters + ---------- + string : str + The text to prepare rendering information for. + angle : float + The angle at which to render the supplied text. + flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` + Any bitwise-OR combination of the `.LoadFlags` flags. - CALL_CPP("load_char", (self->x->load_char(charcode, flags))); + .. versionchanged:: 3.10 + This now takes an `.ft2font.LoadFlags` instead of an int. - return PyGlyph_new(self->x->get_face(), - self->x->get_last_glyph(), - self->x->get_last_glyph_index(), - self->x->get_hinting_factor()); -} + Returns + ------- + np.ndarray[double] + A sequence of x,y glyph positions in 26.6 subpixels; divide by 64 for pixels. +)"""; -const char *PyFT2Font_load_glyph__doc__ = - "load_glyph(glyphindex, flags=LOAD_FORCE_AUTOHINT)\n" - "\n" - "Load character with glyphindex in current fontfile and set glyph.\n" - "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" - "Return value is a Glyph object, with attributes\n" - " width # glyph width\n" - " height # glyph height\n" - " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" - " horiBearingX # left side bearing in horizontal layouts\n" - " horiBearingY # top side bearing in horizontal layouts\n" - " horiAdvance # advance width for horizontal layout\n" - " vertBearingX # left side bearing in vertical layouts\n" - " vertBearingY # top side bearing in vertical layouts\n" - " vertAdvance # advance height for vertical layout\n"; - -static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject *kwds) +static py::array_t +PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0, + std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { - FT_UInt glyph_index; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - const char *names[] = { "glyph_index", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "I|i:load_glyph", (char **)names, &glyph_index, &flags)) { - return NULL; + std::vector xys; + LoadFlags flags; + + if (auto value = std::get_if(&flags_or_int)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", + "alternative"_a="LoadFlags enum values"); + flags = static_cast(*value); + } else if (auto value = std::get_if(&flags_or_int)) { + flags = *value; + } else { + // NOTE: this can never happen as pybind11 would have checked the type in the + // Python wrapper before calling this function, but we need to keep the + // std::get_if instead of std::get for macOS 10.12 compatibility. + throw py::type_error("flags must be LoadFlags or int"); } - CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags))); + self->x->set_text(text, angle, static_cast(flags), xys); - return PyGlyph_new(self->x->get_face(), - self->x->get_last_glyph(), - self->x->get_last_glyph_index(), - self->x->get_hinting_factor()); + py::ssize_t dims[] = { static_cast(xys.size()) / 2, 2 }; + py::array_t result(dims); + if (xys.size() > 0) { + memcpy(result.mutable_data(), xys.data(), result.nbytes()); + } + return result; } -const char *PyFT2Font_get_width_height__doc__ = - "w, h = get_width_height()\n" - "\n" - "Get the width and height in 26.6 subpixels of the current string set by set_text\n" - "The rotation of the string is accounted for. To get width and height\n" - "in pixels, divide these values by 64\n"; +const char *PyFT2Font_get_num_glyphs__doc__ = "Return the number of loaded glyphs."; -static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args, PyObject *kwds) +static size_t +PyFT2Font_get_num_glyphs(PyFT2Font *self) { - long width, height; - - CALL_CPP("get_width_height", (self->x->get_width_height(&width, &height))); - - return Py_BuildValue("ll", width, height); + return self->x->get_num_glyphs(); } -const char *PyFT2Font_get_bitmap_offset__doc__ = - "x, y = get_bitmap_offset()\n" - "\n" - "Get the offset in 26.6 subpixels for the bitmap if ink hangs left or below (0, 0).\n" - "Since matplotlib only supports left-to-right text, y is always 0.\n"; +const char *PyFT2Font_load_char__doc__ = R"""( + Load character in current fontfile and set glyph. -static PyObject *PyFT2Font_get_bitmap_offset(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - long x, y; + Parameters + ---------- + charcode : int + The character code to prepare rendering information for. This code must be in + the charmap, or else a ``.notdef`` glyph may be returned instead. + flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` + Any bitwise-OR combination of the `.LoadFlags` flags. - CALL_CPP("get_bitmap_offset", (self->x->get_bitmap_offset(&x, &y))); + .. versionchanged:: 3.10 + This now takes an `.ft2font.LoadFlags` instead of an int. - return Py_BuildValue("ll", x, y); -} + Returns + ------- + Glyph + The glyph information corresponding to the specified character. -const char *PyFT2Font_get_descent__doc__ = - "d = get_descent()\n" - "\n" - "Get the descent of the current string set by set_text in 26.6 subpixels.\n" - "The rotation of the string is accounted for. To get the descent\n" - "in pixels, divide this value by 64.\n"; + See Also + -------- + .load_glyph + .select_charmap + .set_charmap +)"""; -static PyObject *PyFT2Font_get_descent(PyFT2Font *self, PyObject *args, PyObject *kwds) +static PyGlyph * +PyFT2Font_load_char(PyFT2Font *self, long charcode, + std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { - long descent; + bool fallback = true; + FT2Font *ft_object = nullptr; + LoadFlags flags; - CALL_CPP("get_descent", (descent = self->x->get_descent())); + if (auto value = std::get_if(&flags_or_int)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", + "alternative"_a="LoadFlags enum values"); + flags = static_cast(*value); + } else if (auto value = std::get_if(&flags_or_int)) { + flags = *value; + } else { + // NOTE: this can never happen as pybind11 would have checked the type in the + // Python wrapper before calling this function, but we need to keep the + // std::get_if instead of std::get for macOS 10.12 compatibility. + throw py::type_error("flags must be LoadFlags or int"); + } - return PyLong_FromLong(descent); + self->x->load_char(charcode, static_cast(flags), ft_object, fallback); + + return PyGlyph_from_FT2Font(ft_object); } -const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = - "draw_glyphs_to_bitmap()\n" - "\n" - "Draw the glyphs that were loaded by set_text to the bitmap\n" - "The bitmap size will be automatically set to include the glyphs\n"; +const char *PyFT2Font_load_glyph__doc__ = R"""( + Load glyph index in current fontfile and set glyph. -static PyObject *PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - bool antialiased = true; - const char *names[] = { "antialiased", NULL }; + Note that the glyph index is specific to a font, and not universal like a Unicode + code point. - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:draw_glyphs_to_bitmap", - (char **)names, &convert_bool, &antialiased)) { - return NULL; - } + Parameters + ---------- + glyph_index : int + The glyph index to prepare rendering information for. + flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` + Any bitwise-OR combination of the `.LoadFlags` flags. - CALL_CPP("draw_glyphs_to_bitmap", (self->x->draw_glyphs_to_bitmap(antialiased))); + .. versionchanged:: 3.10 + This now takes an `.ft2font.LoadFlags` instead of an int. - Py_RETURN_NONE; -} + Returns + ------- + Glyph + The glyph information corresponding to the specified index. -const char *PyFT2Font_get_xys__doc__ = - "get_xys()\n" - "\n" - "Get the xy locations of the current glyphs\n"; + See Also + -------- + .load_char +)"""; -static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds) +static PyGlyph * +PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, + std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { - bool antialiased = true; - std::vector xys; - const char *names[] = { "antialiased", NULL }; + bool fallback = true; + FT2Font *ft_object = nullptr; + LoadFlags flags; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:get_xys", - (char **)names, &convert_bool, &antialiased)) { - return NULL; + if (auto value = std::get_if(&flags_or_int)) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", + "alternative"_a="LoadFlags enum values"); + flags = static_cast(*value); + } else if (auto value = std::get_if(&flags_or_int)) { + flags = *value; + } else { + // NOTE: this can never happen as pybind11 would have checked the type in the + // Python wrapper before calling this function, but we need to keep the + // std::get_if instead of std::get for macOS 10.12 compatibility. + throw py::type_error("flags must be LoadFlags or int"); } - CALL_CPP("get_xys", (self->x->get_xys(antialiased, xys))); + self->x->load_glyph(glyph_index, static_cast(flags), ft_object, fallback); - return convert_xys_to_array(xys); + return PyGlyph_from_FT2Font(ft_object); } -const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = - "draw_glyph_to_bitmap(bitmap, x, y, glyph)\n" - "\n" - "Draw a single glyph to the bitmap at pixel locations x,y\n" - "Note it is your responsibility to set up the bitmap manually\n" - "with set_bitmap_size(w,h) before this call is made.\n" - "\n" - "If you want automatic layout, use set_text in combinations with\n" - "draw_glyphs_to_bitmap. This function is intended for people who\n" - "want to render individual glyphs at precise locations, eg, a\n" - "a glyph returned by load_char\n"; - -static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - PyFT2Image *image; - double xd, yd; - PyGlyph *glyph; - bool antialiased = true; - const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O!ddO!|O&:draw_glyph_to_bitmap", - (char **)names, - &PyFT2ImageType, - &image, - &xd, - &yd, - &PyGlyphType, - &glyph, - &convert_bool, - &antialiased)) { - return NULL; - } +const char *PyFT2Font_get_width_height__doc__ = R"""( + Get the dimensions of the current string set by `.set_text`. - CALL_CPP("draw_glyph_to_bitmap", - self->x->draw_glyph_to_bitmap(*(image->x), xd, yd, glyph->glyphInd, antialiased)); + The rotation of the string is accounted for. - Py_RETURN_NONE; -} + Returns + ------- + width, height : float + The width and height in 26.6 subpixels of the current string. To get width and + height in pixels, divide these values by 64. -const char *PyFT2Font_get_glyph_name__doc__ = - "get_glyph_name(index)\n" - "\n" - "Retrieves the ASCII name of a given glyph in a face.\n" - "\n" - "Due to Matplotlib's internal design, for fonts that do not contain glyph \n" - "names (per FT_FACE_FLAG_GLYPH_NAMES), this returns a made-up name which \n" - "does *not* roundtrip through `.get_name_index`.\n"; + See Also + -------- + .get_bitmap_offset + .get_descent +)"""; -static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args, PyObject *kwds) +static py::tuple +PyFT2Font_get_width_height(PyFT2Font *self) { - unsigned int glyph_number; - char buffer[128]; - if (!PyArg_ParseTuple(args, "I:get_glyph_name", &glyph_number)) { - return NULL; - } - CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer))); - return PyUnicode_FromString(buffer); -} + long width, height; -const char *PyFT2Font_get_charmap__doc__ = - "get_charmap()\n" - "\n" - "Returns a dictionary that maps the character codes of the selected charmap\n" - "(Unicode by default) to their corresponding glyph indices.\n"; + self->x->get_width_height(&width, &height); -static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - PyObject *charmap; - if (!(charmap = PyDict_New())) { - return NULL; - } - FT_UInt index; - FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index); - while (index != 0) { - PyObject *key = NULL, *val = NULL; - bool error = (!(key = PyLong_FromLong(code)) - || !(val = PyLong_FromLong(index)) - || (PyDict_SetItem(charmap, key, val) == -1)); - Py_XDECREF(key); - Py_XDECREF(val); - if (error) { - Py_DECREF(charmap); - return NULL; - } - code = FT_Get_Next_Char(self->x->get_face(), code, &index); - } - return charmap; + return py::make_tuple(width, height); } +const char *PyFT2Font_get_bitmap_offset__doc__ = R"""( + Get the (x, y) offset for the bitmap if ink hangs left or below (0, 0). -const char *PyFT2Font_get_char_index__doc__ = - "get_char_index()\n" - "\n" - "Given a character code, returns a glyph index.\n"; - -static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - FT_UInt index; - FT_ULong ccode; - - if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) { - return NULL; - } - - index = FT_Get_Char_Index(self->x->get_face(), ccode); - - return PyLong_FromLong(index); -} + Since Matplotlib only supports left-to-right text, y is always 0. + Returns + ------- + x, y : float + The x and y offset in 26.6 subpixels of the bitmap. To get x and y in pixels, + divide these values by 64. -const char *PyFT2Font_get_sfnt__doc__ = - "get_sfnt(name)\n" - "\n" - "Get all values from the SFNT names table. Result is a dictionary whose " - "key is the platform-ID, ISO-encoding-scheme, language-code, and " - "description.\n"; + See Also + -------- + .get_width_height + .get_descent +)"""; -static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args, PyObject *kwds) +static py::tuple +PyFT2Font_get_bitmap_offset(PyFT2Font *self) { - PyObject *names; + long x, y; - if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { - PyErr_SetString(PyExc_ValueError, "No SFNT name table"); - return NULL; - } + self->x->get_bitmap_offset(&x, &y); - size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face()); + return py::make_tuple(x, y); +} - names = PyDict_New(); - if (names == NULL) { - return NULL; - } +const char *PyFT2Font_get_descent__doc__ = R"""( + Get the descent of the current string set by `.set_text`. - for (FT_UInt j = 0; j < count; ++j) { - FT_SfntName sfnt; - FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt); + The rotation of the string is accounted for. - if (error) { - Py_DECREF(names); - PyErr_SetString(PyExc_ValueError, "Could not get SFNT name"); - return NULL; - } + Returns + ------- + int + The descent in 26.6 subpixels of the bitmap. To get the descent in pixels, + divide these values by 64. - PyObject *key = Py_BuildValue( - "HHHH", sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); - if (key == NULL) { - Py_DECREF(names); - return NULL; - } + See Also + -------- + .get_bitmap_offset + .get_width_height +)"""; - PyObject *val = PyBytes_FromStringAndSize((const char *)sfnt.string, sfnt.string_len); - if (val == NULL) { - Py_DECREF(key); - Py_DECREF(names); - return NULL; - } +static long +PyFT2Font_get_descent(PyFT2Font *self) +{ + return self->x->get_descent(); +} - if (PyDict_SetItem(names, key, val)) { - Py_DECREF(key); - Py_DECREF(val); - Py_DECREF(names); - return NULL; - } +const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = R"""( + Draw the glyphs that were loaded by `.set_text` to the bitmap. - Py_DECREF(key); - Py_DECREF(val); - } + The bitmap size will be automatically set to include the glyphs. - return names; -} + Parameters + ---------- + antialiased : bool, default: True + Whether to render glyphs 8-bit antialiased or in pure black-and-white. -const char *PyFT2Font_get_name_index__doc__ = - "get_name_index(name)\n" - "\n" - "Returns the glyph index of a given glyph name.\n" - "The glyph index 0 means `undefined character code'.\n"; + See Also + -------- + .draw_glyph_to_bitmap +)"""; -static PyObject *PyFT2Font_get_name_index(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, bool antialiased = true) { - char *glyphname; - long name_index; - if (!PyArg_ParseTuple(args, "s:get_name_index", &glyphname)) { - return NULL; - } - CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname)); - return PyLong_FromLong(name_index); + self->x->draw_glyphs_to_bitmap(antialiased); } -const char *PyFT2Font_get_ps_font_info__doc__ = - "get_ps_font_info()\n" - "\n" - "Return the information in the PS Font Info structure.\n"; +const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""( + Draw a single glyph to the bitmap at pixel locations x, y. -static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - PS_FontInfoRec fontinfo; + Note it is your responsibility to create the image manually with the correct size + before this call is made. - FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo); - if (error) { - PyErr_SetString(PyExc_ValueError, "Could not get PS font info"); - return NULL; - } + If you want automatic layout, use `.set_text` in combinations with + `.draw_glyphs_to_bitmap`. This function is instead intended for people who want to + render individual glyphs (e.g., returned by `.load_char`) at precise locations. - return Py_BuildValue("ssssslbhH", - fontinfo.version ? fontinfo.version : "", - fontinfo.notice ? fontinfo.notice : "", - fontinfo.full_name ? fontinfo.full_name : "", - fontinfo.family_name ? fontinfo.family_name : "", - fontinfo.weight ? fontinfo.weight : "", - fontinfo.italic_angle, - fontinfo.is_fixed_pitch, - fontinfo.underline_position, - fontinfo.underline_thickness); -} + Parameters + ---------- + image : 2d array of uint8 + The image buffer on which to draw the glyph. + x, y : int + The pixel location at which to draw the glyph. + glyph : Glyph + The glyph to draw. + antialiased : bool, default: True + Whether to render glyphs 8-bit antialiased or in pure black-and-white. -const char *PyFT2Font_get_sfnt_table__doc__ = - "get_sfnt_table(name)\n" - "\n" - "Return one of the following SFNT tables: head, maxp, OS/2, hhea, " - "vhea, post, or pclt.\n"; + See Also + -------- + .draw_glyphs_to_bitmap +)"""; -static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image, + double_or_ vxd, double_or_ vyd, + PyGlyph *glyph, bool antialiased = true) { - char *tagname; - if (!PyArg_ParseTuple(args, "s:get_sfnt_table", &tagname)) { - return NULL; - } - - int tag; - const char *tags[] = { "head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt", NULL }; + auto xd = _double_to_("x", vxd); + auto yd = _double_to_("y", vyd); - for (tag = 0; tags[tag] != NULL; tag++) { - if (strncmp(tagname, tags[tag], 5) == 0) { - break; - } - } + self->x->draw_glyph_to_bitmap( + py::array_t{image}, + xd, yd, glyph->glyphInd, antialiased); +} - void *table = FT_Get_Sfnt_Table(self->x->get_face(), (FT_Sfnt_Tag)tag); - if (!table) { - Py_RETURN_NONE; - } +const char *PyFT2Font_get_glyph_name__doc__ = R"""( + Retrieve the ASCII name of a given glyph *index* in a face. - switch (tag) { - case 0: { - char head_dict[] = - "{s:(h,H), s:(h,H), s:l, s:l, s:H, s:H," - "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:H, s:H, s:h, s:h, s:h}"; - TT_Header *t = (TT_Header *)table; - return Py_BuildValue(head_dict, - "version", - FIXED_MAJOR(t->Table_Version), - FIXED_MINOR(t->Table_Version), - "fontRevision", - FIXED_MAJOR(t->Font_Revision), - FIXED_MINOR(t->Font_Revision), - "checkSumAdjustment", - t->CheckSum_Adjust, - "magicNumber", - t->Magic_Number, - "flags", - t->Flags, - "unitsPerEm", - t->Units_Per_EM, - "created", - t->Created[0], - t->Created[1], - "modified", - t->Modified[0], - t->Modified[1], - "xMin", - t->xMin, - "yMin", - t->yMin, - "xMax", - t->xMax, - "yMax", - t->yMax, - "macStyle", - t->Mac_Style, - "lowestRecPPEM", - t->Lowest_Rec_PPEM, - "fontDirectionHint", - t->Font_Direction, - "indexToLocFormat", - t->Index_To_Loc_Format, - "glyphDataFormat", - t->Glyph_Data_Format); - } - case 1: { - char maxp_dict[] = - "{s:(h,H), s:H, s:H, s:H, s:H, s:H, s:H," - "s:H, s:H, s:H, s:H, s:H, s:H, s:H, s:H}"; - TT_MaxProfile *t = (TT_MaxProfile *)table; - return Py_BuildValue(maxp_dict, - "version", - FIXED_MAJOR(t->version), - FIXED_MINOR(t->version), - "numGlyphs", - t->numGlyphs, - "maxPoints", - t->maxPoints, - "maxContours", - t->maxContours, - "maxComponentPoints", - t->maxCompositePoints, - "maxComponentContours", - t->maxCompositeContours, - "maxZones", - t->maxZones, - "maxTwilightPoints", - t->maxTwilightPoints, - "maxStorage", - t->maxStorage, - "maxFunctionDefs", - t->maxFunctionDefs, - "maxInstructionDefs", - t->maxInstructionDefs, - "maxStackElements", - t->maxStackElements, - "maxSizeOfInstructions", - t->maxSizeOfInstructions, - "maxComponentElements", - t->maxComponentElements, - "maxComponentDepth", - t->maxComponentDepth); - } - case 2: { - char os_2_dict[] = - "{s:H, s:h, s:H, s:H, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(kkkk)," - "s:y#, s:H, s:H, s:H}"; - TT_OS2 *t = (TT_OS2 *)table; - return Py_BuildValue(os_2_dict, - "version", - t->version, - "xAvgCharWidth", - t->xAvgCharWidth, - "usWeightClass", - t->usWeightClass, - "usWidthClass", - t->usWidthClass, - "fsType", - t->fsType, - "ySubscriptXSize", - t->ySubscriptXSize, - "ySubscriptYSize", - t->ySubscriptYSize, - "ySubscriptXOffset", - t->ySubscriptXOffset, - "ySubscriptYOffset", - t->ySubscriptYOffset, - "ySuperscriptXSize", - t->ySuperscriptXSize, - "ySuperscriptYSize", - t->ySuperscriptYSize, - "ySuperscriptXOffset", - t->ySuperscriptXOffset, - "ySuperscriptYOffset", - t->ySuperscriptYOffset, - "yStrikeoutSize", - t->yStrikeoutSize, - "yStrikeoutPosition", - t->yStrikeoutPosition, - "sFamilyClass", - t->sFamilyClass, - "panose", - t->panose, - Py_ssize_t(10), - "ulCharRange", - t->ulUnicodeRange1, - t->ulUnicodeRange2, - t->ulUnicodeRange3, - t->ulUnicodeRange4, - "achVendID", - t->achVendID, - Py_ssize_t(4), - "fsSelection", - t->fsSelection, - "fsFirstCharIndex", - t->usFirstCharIndex, - "fsLastCharIndex", - t->usLastCharIndex); - } - case 3: { - char hhea_dict[] = - "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:H}"; - TT_HoriHeader *t = (TT_HoriHeader *)table; - return Py_BuildValue(hhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "ascent", - t->Ascender, - "descent", - t->Descender, - "lineGap", - t->Line_Gap, - "advanceWidthMax", - t->advance_Width_Max, - "minLeftBearing", - t->min_Left_Side_Bearing, - "minRightBearing", - t->min_Right_Side_Bearing, - "xMaxExtent", - t->xMax_Extent, - "caretSlopeRise", - t->caret_Slope_Rise, - "caretSlopeRun", - t->caret_Slope_Run, - "caretOffset", - t->caret_Offset, - "metricDataFormat", - t->metric_Data_Format, - "numOfLongHorMetrics", - t->number_Of_HMetrics); - } - case 4: { - char vhea_dict[] = - "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:H}"; - TT_VertHeader *t = (TT_VertHeader *)table; - return Py_BuildValue(vhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "vertTypoAscender", - t->Ascender, - "vertTypoDescender", - t->Descender, - "vertTypoLineGap", - t->Line_Gap, - "advanceHeightMax", - t->advance_Height_Max, - "minTopSideBearing", - t->min_Top_Side_Bearing, - "minBottomSizeBearing", - t->min_Bottom_Side_Bearing, - "yMaxExtent", - t->yMax_Extent, - "caretSlopeRise", - t->caret_Slope_Rise, - "caretSlopeRun", - t->caret_Slope_Run, - "caretOffset", - t->caret_Offset, - "metricDataFormat", - t->metric_Data_Format, - "numOfLongVerMetrics", - t->number_Of_VMetrics); - } - case 5: { - char post_dict[] = "{s:(h,H), s:(h,H), s:h, s:h, s:k, s:k, s:k, s:k, s:k}"; - TT_Postscript *t = (TT_Postscript *)table; - return Py_BuildValue(post_dict, - "format", - FIXED_MAJOR(t->FormatType), - FIXED_MINOR(t->FormatType), - "italicAngle", - FIXED_MAJOR(t->italicAngle), - FIXED_MINOR(t->italicAngle), - "underlinePosition", - t->underlinePosition, - "underlineThickness", - t->underlineThickness, - "isFixedPitch", - t->isFixedPitch, - "minMemType42", - t->minMemType42, - "maxMemType42", - t->maxMemType42, - "minMemType1", - t->minMemType1, - "maxMemType1", - t->maxMemType1); - } - case 6: { - char pclt_dict[] = - "{s:(h,H), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:y#, s:y#, s:b, " - "s:b, s:b}"; - TT_PCLT *t = (TT_PCLT *)table; - return Py_BuildValue(pclt_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "fontNumber", - t->FontNumber, - "pitch", - t->Pitch, - "xHeight", - t->xHeight, - "style", - t->Style, - "typeFamily", - t->TypeFamily, - "capHeight", - t->CapHeight, - "symbolSet", - t->SymbolSet, - "typeFace", - t->TypeFace, - Py_ssize_t(16), - "characterComplement", - t->CharacterComplement, - Py_ssize_t(8), - "strokeWeight", - t->StrokeWeight, - "widthType", - t->WidthType, - "serifStyle", - t->SerifStyle); - } - default: - Py_RETURN_NONE; - } -} + Due to Matplotlib's internal design, for fonts that do not contain glyph names (per + ``FT_FACE_FLAG_GLYPH_NAMES``), this returns a made-up name which does *not* + roundtrip through `.get_name_index`. -const char *PyFT2Font_get_path__doc__ = - "get_path()\n" - "\n" - "Get the path data from the currently loaded glyph as a tuple of vertices, " - "codes.\n"; + Parameters + ---------- + index : int + The glyph number to query. -static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - CALL_CPP("get_path", return self->x->get_path()); -} + Returns + ------- + str + The name of the glyph, or if the font does not contain names, a name synthesized + by Matplotlib. -const char *PyFT2Font_get_image__doc__ = - "get_image()\n" - "\n" - "Returns the underlying image buffer for this font object.\n"; + See Also + -------- + .get_name_index +)"""; -static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args, PyObject *kwds) +static py::str +PyFT2Font_get_glyph_name(PyFT2Font *self, unsigned int glyph_number) { - FT2Image &im = self->x->get_image(); - npy_intp dims[] = {(npy_intp)im.get_height(), (npy_intp)im.get_width() }; - return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, im.get_buffer()); + std::string buffer; + bool fallback = true; + + buffer.resize(128); + self->x->get_glyph_name(glyph_number, buffer, fallback); + return buffer; } -static PyObject *PyFT2Font_postscript_name(PyFT2Font *self, void *closure) -{ - const char *ps_name = FT_Get_Postscript_Name(self->x->get_face()); - if (ps_name == NULL) { - ps_name = "UNAVAILABLE"; - } +const char *PyFT2Font_get_charmap__doc__ = R"""( + Return a mapping of character codes to glyph indices in the font. - return PyUnicode_FromString(ps_name); -} + The charmap is Unicode by default, but may be changed by `.set_charmap` or + `.select_charmap`. -static PyObject *PyFT2Font_num_faces(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->num_faces); -} + Returns + ------- + dict[int, int] + A dictionary of the selected charmap mapping character codes to their + corresponding glyph indices. +)"""; -static PyObject *PyFT2Font_family_name(PyFT2Font *self, void *closure) +static py::dict +PyFT2Font_get_charmap(PyFT2Font *self) { - const char *name = self->x->get_face()->family_name; - if (name == NULL) { - name = "UNAVAILABLE"; + py::dict charmap; + FT_UInt index; + FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index); + while (index != 0) { + charmap[py::cast(code)] = py::cast(index); + code = FT_Get_Next_Char(self->x->get_face(), code, &index); } - return PyUnicode_FromString(name); + return charmap; } -static PyObject *PyFT2Font_style_name(PyFT2Font *self, void *closure) -{ - const char *name = self->x->get_face()->style_name; - if (name == NULL) { - name = "UNAVAILABLE"; - } - return PyUnicode_FromString(name); -} +const char *PyFT2Font_get_char_index__doc__ = R"""( + Return the glyph index corresponding to a character code point. -static PyObject *PyFT2Font_face_flags(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->face_flags); -} + Parameters + ---------- + codepoint : int + A character code point in the current charmap (which defaults to Unicode.) -static PyObject *PyFT2Font_style_flags(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->style_flags); -} + Returns + ------- + int + The corresponding glyph index. -static PyObject *PyFT2Font_num_glyphs(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->num_glyphs); -} + See Also + -------- + .set_charmap + .select_charmap + .get_glyph_name + .get_name_index +)"""; -static PyObject *PyFT2Font_num_fixed_sizes(PyFT2Font *self, void *closure) +static FT_UInt +PyFT2Font_get_char_index(PyFT2Font *self, FT_ULong ccode) { - return PyLong_FromLong(self->x->get_face()->num_fixed_sizes); -} + bool fallback = true; -static PyObject *PyFT2Font_num_charmaps(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->num_charmaps); + return self->x->get_char_index(ccode, fallback); } -static PyObject *PyFT2Font_scalable(PyFT2Font *self, void *closure) +const char *PyFT2Font_get_sfnt__doc__ = R"""( + Load the entire SFNT names table. + + Returns + ------- + dict[tuple[int, int, int, int], bytes] + The SFNT names table; the dictionary keys are tuples of: + + (platform-ID, ISO-encoding-scheme, language-code, description) + + and the values are the direct information from the font table. +)"""; + +static py::dict +PyFT2Font_get_sfnt(PyFT2Font *self) { - if (FT_IS_SCALABLE(self->x->get_face())) { - Py_RETURN_TRUE; + if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { + throw py::value_error("No SFNT name table"); } - Py_RETURN_FALSE; -} -static PyObject *PyFT2Font_units_per_EM(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->units_per_EM); -} + size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face()); -static PyObject *PyFT2Font_get_bbox(PyFT2Font *self, void *closure) -{ - FT_BBox *bbox = &(self->x->get_face()->bbox); + py::dict names; - return Py_BuildValue("llll", - bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); -} + for (FT_UInt j = 0; j < count; ++j) { + FT_SfntName sfnt; + FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt); -static PyObject *PyFT2Font_ascender(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->ascender); -} + if (error) { + throw py::value_error("Could not get SFNT name"); + } -static PyObject *PyFT2Font_descender(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->descender); -} + auto key = py::make_tuple( + sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); + auto val = py::bytes(reinterpret_cast(sfnt.string), + sfnt.string_len); + names[key] = val; + } -static PyObject *PyFT2Font_height(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->height); + return names; } -static PyObject *PyFT2Font_max_advance_width(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->max_advance_width); -} +const char *PyFT2Font_get_name_index__doc__ = R"""( + Return the glyph index of a given glyph *name*. -static PyObject *PyFT2Font_max_advance_height(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->max_advance_height); -} + Parameters + ---------- + name : str + The name of the glyph to query. -static PyObject *PyFT2Font_underline_position(PyFT2Font *self, void *closure) -{ - return PyLong_FromLong(self->x->get_face()->underline_position); -} + Returns + ------- + int + The corresponding glyph index; 0 means 'undefined character code'. + + See Also + -------- + .get_char_index + .get_glyph_name +)"""; -static PyObject *PyFT2Font_underline_thickness(PyFT2Font *self, void *closure) +static long +PyFT2Font_get_name_index(PyFT2Font *self, char *glyphname) { - return PyLong_FromLong(self->x->get_face()->underline_thickness); + return self->x->get_name_index(glyphname); } -static PyObject *PyFT2Font_fname(PyFT2Font *self, void *closure) -{ - if (self->fname) { - Py_INCREF(self->fname); - return self->fname; - } +const char *PyFT2Font_get_ps_font_info__doc__ = R"""( + Return the information in the PS Font Info structure. - Py_RETURN_NONE; -} + For more information, see the `FreeType documentation on this structure + `_. -static int PyFT2Font_get_buffer(PyFT2Font *self, Py_buffer *buf, int flags) -{ - FT2Image &im = self->x->get_image(); - - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = im.get_buffer(); - buf->len = im.get_width() * im.get_height(); - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 2; - self->shape[0] = im.get_height(); - self->shape[1] = im.get_width(); - buf->shape = self->shape; - self->strides[0] = im.get_width(); - self->strides[1] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} + Returns + ------- + version : str + notice : str + full_name : str + family_name : str + weight : str + italic_angle : int + is_fixed_pitch : bool + underline_position : int + underline_thickness : int +)"""; -static PyTypeObject *PyFT2Font_init_type(PyObject *m, PyTypeObject *type) +static py::tuple +PyFT2Font_get_ps_font_info(PyFT2Font *self) { - static PyGetSetDef getset[] = { - {(char *)"postscript_name", (getter)PyFT2Font_postscript_name, NULL, NULL, NULL}, - {(char *)"num_faces", (getter)PyFT2Font_num_faces, NULL, NULL, NULL}, - {(char *)"family_name", (getter)PyFT2Font_family_name, NULL, NULL, NULL}, - {(char *)"style_name", (getter)PyFT2Font_style_name, NULL, NULL, NULL}, - {(char *)"face_flags", (getter)PyFT2Font_face_flags, NULL, NULL, NULL}, - {(char *)"style_flags", (getter)PyFT2Font_style_flags, NULL, NULL, NULL}, - {(char *)"num_glyphs", (getter)PyFT2Font_num_glyphs, NULL, NULL, NULL}, - {(char *)"num_fixed_sizes", (getter)PyFT2Font_num_fixed_sizes, NULL, NULL, NULL}, - {(char *)"num_charmaps", (getter)PyFT2Font_num_charmaps, NULL, NULL, NULL}, - {(char *)"scalable", (getter)PyFT2Font_scalable, NULL, NULL, NULL}, - {(char *)"units_per_EM", (getter)PyFT2Font_units_per_EM, NULL, NULL, NULL}, - {(char *)"bbox", (getter)PyFT2Font_get_bbox, NULL, NULL, NULL}, - {(char *)"ascender", (getter)PyFT2Font_ascender, NULL, NULL, NULL}, - {(char *)"descender", (getter)PyFT2Font_descender, NULL, NULL, NULL}, - {(char *)"height", (getter)PyFT2Font_height, NULL, NULL, NULL}, - {(char *)"max_advance_width", (getter)PyFT2Font_max_advance_width, NULL, NULL, NULL}, - {(char *)"max_advance_height", (getter)PyFT2Font_max_advance_height, NULL, NULL, NULL}, - {(char *)"underline_position", (getter)PyFT2Font_underline_position, NULL, NULL, NULL}, - {(char *)"underline_thickness", (getter)PyFT2Font_underline_thickness, NULL, NULL, NULL}, - {(char *)"fname", (getter)PyFT2Font_fname, NULL, NULL, NULL}, - {NULL} - }; + PS_FontInfoRec fontinfo; - static PyMethodDef methods[] = { - {"clear", (PyCFunction)PyFT2Font_clear, METH_NOARGS, PyFT2Font_clear__doc__}, - {"set_size", (PyCFunction)PyFT2Font_set_size, METH_VARARGS, PyFT2Font_set_size__doc__}, - {"set_charmap", (PyCFunction)PyFT2Font_set_charmap, METH_VARARGS, PyFT2Font_set_charmap__doc__}, - {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__}, - {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__}, - {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__}, - {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__}, - {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__}, - {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__}, - {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__}, - {"get_bitmap_offset", (PyCFunction)PyFT2Font_get_bitmap_offset, METH_NOARGS, PyFT2Font_get_bitmap_offset__doc__}, - {"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__}, - {"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__}, - {"get_xys", (PyCFunction)PyFT2Font_get_xys, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_xys__doc__}, - {"draw_glyph_to_bitmap", (PyCFunction)PyFT2Font_draw_glyph_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyph_to_bitmap__doc__}, - {"get_glyph_name", (PyCFunction)PyFT2Font_get_glyph_name, METH_VARARGS, PyFT2Font_get_glyph_name__doc__}, - {"get_charmap", (PyCFunction)PyFT2Font_get_charmap, METH_NOARGS, PyFT2Font_get_charmap__doc__}, - {"get_char_index", (PyCFunction)PyFT2Font_get_char_index, METH_VARARGS, PyFT2Font_get_char_index__doc__}, - {"get_sfnt", (PyCFunction)PyFT2Font_get_sfnt, METH_NOARGS, PyFT2Font_get_sfnt__doc__}, - {"get_name_index", (PyCFunction)PyFT2Font_get_name_index, METH_VARARGS, PyFT2Font_get_name_index__doc__}, - {"get_ps_font_info", (PyCFunction)PyFT2Font_get_ps_font_info, METH_NOARGS, PyFT2Font_get_ps_font_info__doc__}, - {"get_sfnt_table", (PyCFunction)PyFT2Font_get_sfnt_table, METH_VARARGS, PyFT2Font_get_sfnt_table__doc__}, - {"get_path", (PyCFunction)PyFT2Font_get_path, METH_NOARGS, PyFT2Font_get_path__doc__}, - {"get_image", (PyCFunction)PyFT2Font_get_image, METH_NOARGS, PyFT2Font_get_path__doc__}, - {NULL} + FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo); + if (error) { + throw py::value_error("Could not get PS font info"); + } + + return py::make_tuple( + fontinfo.version ? fontinfo.version : "", + fontinfo.notice ? fontinfo.notice : "", + fontinfo.full_name ? fontinfo.full_name : "", + fontinfo.family_name ? fontinfo.family_name : "", + fontinfo.weight ? fontinfo.weight : "", + fontinfo.italic_angle, + fontinfo.is_fixed_pitch, + fontinfo.underline_position, + fontinfo.underline_thickness); +} + +const char *PyFT2Font_get_sfnt_table__doc__ = R"""( + Return one of the SFNT tables. + + Parameters + ---------- + name : {"head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt"} + Which table to return. + + Returns + ------- + dict[str, Any] + The corresponding table; for more information, see `the FreeType documentation + `_. +)"""; + +static std::optional +PyFT2Font_get_sfnt_table(PyFT2Font *self, std::string tagname) +{ + FT_Sfnt_Tag tag; + const std::unordered_map names = { + {"head", FT_SFNT_HEAD}, + {"maxp", FT_SFNT_MAXP}, + {"OS/2", FT_SFNT_OS2}, + {"hhea", FT_SFNT_HHEA}, + {"vhea", FT_SFNT_VHEA}, + {"post", FT_SFNT_POST}, + {"pclt", FT_SFNT_PCLT}, }; - static PyBufferProcs buffer_procs; - memset(&buffer_procs, 0, sizeof(PyBufferProcs)); - buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer; - - memset(type, 0, sizeof(PyTypeObject)); - type->tp_name = "matplotlib.ft2font.FT2Font"; - type->tp_doc = PyFT2Font_init__doc__; - type->tp_basicsize = sizeof(PyFT2Font); - type->tp_dealloc = (destructor)PyFT2Font_dealloc; - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - type->tp_methods = methods; - type->tp_getset = getset; - type->tp_new = PyFT2Font_new; - type->tp_init = (initproc)PyFT2Font_init; - type->tp_as_buffer = &buffer_procs; - - if (PyType_Ready(type) < 0) { - return NULL; + try { + tag = names.at(tagname); + } catch (const std::out_of_range&) { + return std::nullopt; } - if (PyModule_AddObject(m, "FT2Font", (PyObject *)type)) { - return NULL; + void *table = FT_Get_Sfnt_Table(self->x->get_face(), tag); + if (!table) { + return std::nullopt; } - return type; + switch (tag) { + case FT_SFNT_HEAD: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Table_Version), + FIXED_MINOR(t->Table_Version)), + "fontRevision"_a=py::make_tuple(FIXED_MAJOR(t->Font_Revision), + FIXED_MINOR(t->Font_Revision)), + "checkSumAdjustment"_a=t->CheckSum_Adjust, + "magicNumber"_a=t->Magic_Number, + "flags"_a=t->Flags, + "unitsPerEm"_a=t->Units_Per_EM, + // FreeType 2.6.1 defines these two timestamps as FT_Long, but they should + // be unsigned (fixed in 2.10.0): + // https://gitlab.freedesktop.org/freetype/freetype/-/commit/3e8ec291ffcfa03c8ecba1cdbfaa55f5577f5612 + // It's actually read from the file structure as two 32-bit values, so we + // need to cast down in size to prevent sign extension from producing huge + // 64-bit values. + "created"_a=py::make_tuple(static_cast(t->Created[0]), + static_cast(t->Created[1])), + "modified"_a=py::make_tuple(static_cast(t->Modified[0]), + static_cast(t->Modified[1])), + "xMin"_a=t->xMin, + "yMin"_a=t->yMin, + "xMax"_a=t->xMax, + "yMax"_a=t->yMax, + "macStyle"_a=t->Mac_Style, + "lowestRecPPEM"_a=t->Lowest_Rec_PPEM, + "fontDirectionHint"_a=t->Font_Direction, + "indexToLocFormat"_a=t->Index_To_Loc_Format, + "glyphDataFormat"_a=t->Glyph_Data_Format); + } + case FT_SFNT_MAXP: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->version), + FIXED_MINOR(t->version)), + "numGlyphs"_a=t->numGlyphs, + "maxPoints"_a=t->maxPoints, + "maxContours"_a=t->maxContours, + "maxComponentPoints"_a=t->maxCompositePoints, + "maxComponentContours"_a=t->maxCompositeContours, + "maxZones"_a=t->maxZones, + "maxTwilightPoints"_a=t->maxTwilightPoints, + "maxStorage"_a=t->maxStorage, + "maxFunctionDefs"_a=t->maxFunctionDefs, + "maxInstructionDefs"_a=t->maxInstructionDefs, + "maxStackElements"_a=t->maxStackElements, + "maxSizeOfInstructions"_a=t->maxSizeOfInstructions, + "maxComponentElements"_a=t->maxComponentElements, + "maxComponentDepth"_a=t->maxComponentDepth); + } + case FT_SFNT_OS2: { + auto t = static_cast(table); + return py::dict( + "version"_a=t->version, + "xAvgCharWidth"_a=t->xAvgCharWidth, + "usWeightClass"_a=t->usWeightClass, + "usWidthClass"_a=t->usWidthClass, + "fsType"_a=t->fsType, + "ySubscriptXSize"_a=t->ySubscriptXSize, + "ySubscriptYSize"_a=t->ySubscriptYSize, + "ySubscriptXOffset"_a=t->ySubscriptXOffset, + "ySubscriptYOffset"_a=t->ySubscriptYOffset, + "ySuperscriptXSize"_a=t->ySuperscriptXSize, + "ySuperscriptYSize"_a=t->ySuperscriptYSize, + "ySuperscriptXOffset"_a=t->ySuperscriptXOffset, + "ySuperscriptYOffset"_a=t->ySuperscriptYOffset, + "yStrikeoutSize"_a=t->yStrikeoutSize, + "yStrikeoutPosition"_a=t->yStrikeoutPosition, + "sFamilyClass"_a=t->sFamilyClass, + "panose"_a=py::bytes(reinterpret_cast(t->panose), 10), + "ulCharRange"_a=py::make_tuple(t->ulUnicodeRange1, t->ulUnicodeRange2, + t->ulUnicodeRange3, t->ulUnicodeRange4), + "achVendID"_a=py::bytes(reinterpret_cast(t->achVendID), 4), + "fsSelection"_a=t->fsSelection, + "fsFirstCharIndex"_a=t->usFirstCharIndex, + "fsLastCharIndex"_a=t->usLastCharIndex); + } + case FT_SFNT_HHEA: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "ascent"_a=t->Ascender, + "descent"_a=t->Descender, + "lineGap"_a=t->Line_Gap, + "advanceWidthMax"_a=t->advance_Width_Max, + "minLeftBearing"_a=t->min_Left_Side_Bearing, + "minRightBearing"_a=t->min_Right_Side_Bearing, + "xMaxExtent"_a=t->xMax_Extent, + "caretSlopeRise"_a=t->caret_Slope_Rise, + "caretSlopeRun"_a=t->caret_Slope_Run, + "caretOffset"_a=t->caret_Offset, + "metricDataFormat"_a=t->metric_Data_Format, + "numOfLongHorMetrics"_a=t->number_Of_HMetrics); + } + case FT_SFNT_VHEA: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "vertTypoAscender"_a=t->Ascender, + "vertTypoDescender"_a=t->Descender, + "vertTypoLineGap"_a=t->Line_Gap, + "advanceHeightMax"_a=t->advance_Height_Max, + "minTopSideBearing"_a=t->min_Top_Side_Bearing, + "minBottomSizeBearing"_a=t->min_Bottom_Side_Bearing, + "yMaxExtent"_a=t->yMax_Extent, + "caretSlopeRise"_a=t->caret_Slope_Rise, + "caretSlopeRun"_a=t->caret_Slope_Run, + "caretOffset"_a=t->caret_Offset, + "metricDataFormat"_a=t->metric_Data_Format, + "numOfLongVerMetrics"_a=t->number_Of_VMetrics); + } + case FT_SFNT_POST: { + auto t = static_cast(table); + return py::dict( + "format"_a=py::make_tuple(FIXED_MAJOR(t->FormatType), + FIXED_MINOR(t->FormatType)), + "italicAngle"_a=py::make_tuple(FIXED_MAJOR(t->italicAngle), + FIXED_MINOR(t->italicAngle)), + "underlinePosition"_a=t->underlinePosition, + "underlineThickness"_a=t->underlineThickness, + "isFixedPitch"_a=t->isFixedPitch, + "minMemType42"_a=t->minMemType42, + "maxMemType42"_a=t->maxMemType42, + "minMemType1"_a=t->minMemType1, + "maxMemType1"_a=t->maxMemType1); + } + case FT_SFNT_PCLT: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "fontNumber"_a=t->FontNumber, + "pitch"_a=t->Pitch, + "xHeight"_a=t->xHeight, + "style"_a=t->Style, + "typeFamily"_a=t->TypeFamily, + "capHeight"_a=t->CapHeight, + "symbolSet"_a=t->SymbolSet, + "typeFace"_a=py::bytes(reinterpret_cast(t->TypeFace), 16), + "characterComplement"_a=py::bytes( + reinterpret_cast(t->CharacterComplement), 8), + "strokeWeight"_a=t->StrokeWeight, + "widthType"_a=t->WidthType, + "serifStyle"_a=t->SerifStyle); + } + default: + return std::nullopt; + } } -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "ft2font", - NULL, - 0, - NULL, - NULL, - NULL, - NULL, - NULL -}; - -#pragma GCC visibility push(default) +const char *PyFT2Font_get_path__doc__ = R"""( + Get the path data from the currently loaded glyph. -PyMODINIT_FUNC PyInit_ft2font(void) -{ - PyObject *m; + Returns + ------- + vertices : np.ndarray[double] + The (N, 2) array of vertices describing the current glyph. + codes : np.ndarray[np.uint8] + The (N, ) array of codes corresponding to the vertices. - m = PyModule_Create(&moduledef); + See Also + -------- + .get_image + .load_char + .load_glyph + .set_text +)"""; - if (m == NULL) { - return NULL; - } +static py::tuple +PyFT2Font_get_path(PyFT2Font *self) +{ + std::vector vertices; + std::vector codes; - if (!PyFT2Image_init_type(m, &PyFT2ImageType)) { - return NULL; - } + self->x->get_path(vertices, codes); - if (!PyGlyph_init_type(m, &PyGlyphType)) { - return NULL; + py::ssize_t length = codes.size(); + py::ssize_t vertices_dims[2] = { length, 2 }; + py::array_t vertices_arr(vertices_dims); + if (length > 0) { + memcpy(vertices_arr.mutable_data(), vertices.data(), vertices_arr.nbytes()); } - - if (!PyFT2Font_init_type(m, &PyFT2FontType)) { - return NULL; + py::ssize_t codes_dims[1] = { length }; + py::array_t codes_arr(codes_dims); + if (length > 0) { + memcpy(codes_arr.mutable_data(), codes.data(), codes_arr.nbytes()); } - PyObject *d = PyModule_GetDict(m); - - if (add_dict_int(d, "SCALABLE", FT_FACE_FLAG_SCALABLE) || - add_dict_int(d, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) || - add_dict_int(d, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) || - add_dict_int(d, "SFNT", FT_FACE_FLAG_SFNT) || - add_dict_int(d, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) || - add_dict_int(d, "VERTICAL", FT_FACE_FLAG_VERTICAL) || - add_dict_int(d, "KERNING", FT_FACE_FLAG_KERNING) || - add_dict_int(d, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) || - add_dict_int(d, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) || - add_dict_int(d, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) || - add_dict_int(d, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) || - add_dict_int(d, "ITALIC", FT_STYLE_FLAG_ITALIC) || - add_dict_int(d, "BOLD", FT_STYLE_FLAG_BOLD) || - add_dict_int(d, "KERNING_DEFAULT", FT_KERNING_DEFAULT) || - add_dict_int(d, "KERNING_UNFITTED", FT_KERNING_UNFITTED) || - add_dict_int(d, "KERNING_UNSCALED", FT_KERNING_UNSCALED) || - add_dict_int(d, "LOAD_DEFAULT", FT_LOAD_DEFAULT) || - add_dict_int(d, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) || - add_dict_int(d, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) || - add_dict_int(d, "LOAD_RENDER", FT_LOAD_RENDER) || - add_dict_int(d, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) || - add_dict_int(d, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) || - add_dict_int(d, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) || - add_dict_int(d, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) || - add_dict_int(d, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) || - add_dict_int(d, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) || - add_dict_int(d, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) || - add_dict_int(d, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) || - add_dict_int(d, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) || - add_dict_int(d, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) || - add_dict_int(d, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) || - add_dict_int(d, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) || - add_dict_int(d, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) || - add_dict_int(d, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) || - add_dict_int(d, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) || - add_dict_int(d, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) { - return NULL; - } + return py::make_tuple(vertices_arr, codes_arr); +} - // initialize library - int error = FT_Init_FreeType(&_ft2Library); +const char *PyFT2Font_get_image__doc__ = R"""( + Return the underlying image buffer for this font object. - if (error) { - PyErr_SetString(PyExc_RuntimeError, "Could not initialize the freetype2 library"); - return NULL; - } + Returns + ------- + np.ndarray[int] - { - FT_Int major, minor, patch; - char version_string[64]; + See Also + -------- + .get_path +)"""; - FT_Library_Version(_ft2Library, &major, &minor, &patch); - sprintf(version_string, "%d.%d.%d", major, minor, patch); - if (PyModule_AddStringConstant(m, "__freetype_version__", version_string)) { - return NULL; - } - } +static py::array +PyFT2Font_get_image(PyFT2Font *self) +{ + return self->x->get_image(); +} - if (PyModule_AddStringConstant(m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE))) { - return NULL; - } +const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""( + Return a list mapping CharString indices of a Type 1 font to FreeType glyph indices. - import_array(); + Returns + ------- + list[int] +)"""; - return m; +static std::array +PyFT2Font__get_type1_encoding_vector(PyFT2Font *self) +{ + auto face = self->x->get_face(); + auto indices = std::array{}; + for (auto i = 0u; i < indices.size(); ++i) { + auto len = FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, nullptr, 0); + if (len == -1) { + // Explicitly ignore missing entries (mapped to glyph 0 = .notdef). + continue; + } + auto buf = std::make_unique(len); + FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, buf.get(), len); + indices[i] = FT_Get_Name_Index(face, buf.get()); + } + return indices; +} + +static py::object +ft2font__getattr__(std::string name) { + auto api = py::module_::import("matplotlib._api"); + auto warn = api.attr("warn_deprecated"); + +#define DEPRECATE_ATTR_FROM_ENUM(attr_, alternative_, real_value_) \ + do { \ + if (name == #attr_) { \ + warn("since"_a="3.10", "name"_a=#attr_, "obj_type"_a="attribute", \ + "alternative"_a=#alternative_); \ + return py::cast(static_cast(real_value_)); \ + } \ + } while(0) + DEPRECATE_ATTR_FROM_ENUM(KERNING_DEFAULT, Kerning.DEFAULT, FT_KERNING_DEFAULT); + DEPRECATE_ATTR_FROM_ENUM(KERNING_UNFITTED, Kerning.UNFITTED, FT_KERNING_UNFITTED); + DEPRECATE_ATTR_FROM_ENUM(KERNING_UNSCALED, Kerning.UNSCALED, FT_KERNING_UNSCALED); + +#undef DEPRECATE_ATTR_FROM_ENUM + +#define DEPRECATE_ATTR_FROM_FLAG(attr_, enum_, value_) \ + do { \ + if (name == #attr_) { \ + warn("since"_a="3.10", "name"_a=#attr_, "obj_type"_a="attribute", \ + "alternative"_a=#enum_ "." #value_); \ + return py::cast(enum_::value_); \ + } \ + } while(0) + + DEPRECATE_ATTR_FROM_FLAG(LOAD_DEFAULT, LoadFlags, DEFAULT); + DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_SCALE, LoadFlags, NO_SCALE); + DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_HINTING, LoadFlags, NO_HINTING); + DEPRECATE_ATTR_FROM_FLAG(LOAD_RENDER, LoadFlags, RENDER); + DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_BITMAP, LoadFlags, NO_BITMAP); + DEPRECATE_ATTR_FROM_FLAG(LOAD_VERTICAL_LAYOUT, LoadFlags, VERTICAL_LAYOUT); + DEPRECATE_ATTR_FROM_FLAG(LOAD_FORCE_AUTOHINT, LoadFlags, FORCE_AUTOHINT); + DEPRECATE_ATTR_FROM_FLAG(LOAD_CROP_BITMAP, LoadFlags, CROP_BITMAP); + DEPRECATE_ATTR_FROM_FLAG(LOAD_PEDANTIC, LoadFlags, PEDANTIC); + DEPRECATE_ATTR_FROM_FLAG(LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, LoadFlags, + IGNORE_GLOBAL_ADVANCE_WIDTH); + DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_RECURSE, LoadFlags, NO_RECURSE); + DEPRECATE_ATTR_FROM_FLAG(LOAD_IGNORE_TRANSFORM, LoadFlags, IGNORE_TRANSFORM); + DEPRECATE_ATTR_FROM_FLAG(LOAD_MONOCHROME, LoadFlags, MONOCHROME); + DEPRECATE_ATTR_FROM_FLAG(LOAD_LINEAR_DESIGN, LoadFlags, LINEAR_DESIGN); + DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_AUTOHINT, LoadFlags, NO_AUTOHINT); + + DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_NORMAL, LoadFlags, TARGET_NORMAL); + DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LIGHT, LoadFlags, TARGET_LIGHT); + DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_MONO, LoadFlags, TARGET_MONO); + DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LCD, LoadFlags, TARGET_LCD); + DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LCD_V, LoadFlags, TARGET_LCD_V); + + DEPRECATE_ATTR_FROM_FLAG(SCALABLE, FaceFlags, SCALABLE); + DEPRECATE_ATTR_FROM_FLAG(FIXED_SIZES, FaceFlags, FIXED_SIZES); + DEPRECATE_ATTR_FROM_FLAG(FIXED_WIDTH, FaceFlags, FIXED_WIDTH); + DEPRECATE_ATTR_FROM_FLAG(SFNT, FaceFlags, SFNT); + DEPRECATE_ATTR_FROM_FLAG(HORIZONTAL, FaceFlags, HORIZONTAL); + DEPRECATE_ATTR_FROM_FLAG(VERTICAL, FaceFlags, VERTICAL); + DEPRECATE_ATTR_FROM_FLAG(KERNING, FaceFlags, KERNING); + DEPRECATE_ATTR_FROM_FLAG(FAST_GLYPHS, FaceFlags, FAST_GLYPHS); + DEPRECATE_ATTR_FROM_FLAG(MULTIPLE_MASTERS, FaceFlags, MULTIPLE_MASTERS); + DEPRECATE_ATTR_FROM_FLAG(GLYPH_NAMES, FaceFlags, GLYPH_NAMES); + DEPRECATE_ATTR_FROM_FLAG(EXTERNAL_STREAM, FaceFlags, EXTERNAL_STREAM); + + DEPRECATE_ATTR_FROM_FLAG(ITALIC, StyleFlags, ITALIC); + DEPRECATE_ATTR_FROM_FLAG(BOLD, StyleFlags, BOLD); +#undef DEPRECATE_ATTR_FROM_FLAG + + throw py::attribute_error( + "module 'matplotlib.ft2font' has no attribute {!r}"_s.format(name)); +} + +PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) +{ + if (FT_Init_FreeType(&_ft2Library)) { // initialize library + throw std::runtime_error("Could not initialize the freetype2 library"); + } + FT_Int major, minor, patch; + char version_string[64]; + FT_Library_Version(_ft2Library, &major, &minor, &patch); + snprintf(version_string, sizeof(version_string), "%d.%d.%d", major, minor, patch); + + p11x::bind_enums(m); + p11x::enums["Kerning"].attr("__doc__") = Kerning__doc__; + p11x::enums["LoadFlags"].attr("__doc__") = LoadFlags__doc__; + p11x::enums["FaceFlags"].attr("__doc__") = FaceFlags__doc__; + p11x::enums["StyleFlags"].attr("__doc__") = StyleFlags__doc__; + + py::class_(m, "FT2Image", py::is_final(), py::buffer_protocol(), + PyFT2Image__doc__) + .def(py::init( + [](double_or_ width, double_or_ height) { + auto warn = + py::module_::import("matplotlib._api").attr("warn_deprecated"); + warn("since"_a="3.11", "name"_a="FT2Image", "obj_type"_a="class", + "alternative"_a="a 2D uint8 ndarray"); + return new FT2Image( + _double_to_("width", width), + _double_to_("height", height) + ); + }), + "width"_a, "height"_a, PyFT2Image_init__doc__) + .def("draw_rect_filled", &PyFT2Image_draw_rect_filled, + "x0"_a, "y0"_a, "x1"_a, "y1"_a, + PyFT2Image_draw_rect_filled__doc__) + .def_buffer([](FT2Image &self) -> py::buffer_info { + std::vector shape { self.get_height(), self.get_width() }; + std::vector strides { self.get_width(), 1 }; + return py::buffer_info(self.get_buffer(), shape, strides); + }); + + py::class_(m, "Glyph", py::is_final(), PyGlyph__doc__) + .def(py::init<>([]() -> PyGlyph { + // Glyph is not useful from Python, so mark it as not constructible. + throw std::runtime_error("Glyph is not constructible"); + })) + .def_readonly("width", &PyGlyph::width, "The glyph's width.") + .def_readonly("height", &PyGlyph::height, "The glyph's height.") + .def_readonly("horiBearingX", &PyGlyph::horiBearingX, + "Left side bearing for horizontal layout.") + .def_readonly("horiBearingY", &PyGlyph::horiBearingY, + "Top side bearing for horizontal layout.") + .def_readonly("horiAdvance", &PyGlyph::horiAdvance, + "Advance width for horizontal layout.") + .def_readonly("linearHoriAdvance", &PyGlyph::linearHoriAdvance, + "The advance width of the unhinted glyph.") + .def_readonly("vertBearingX", &PyGlyph::vertBearingX, + "Left side bearing for vertical layout.") + .def_readonly("vertBearingY", &PyGlyph::vertBearingY, + "Top side bearing for vertical layout.") + .def_readonly("vertAdvance", &PyGlyph::vertAdvance, + "Advance height for vertical layout.") + .def_property_readonly("bbox", &PyGlyph_get_bbox, + "The control box of the glyph."); + + auto cls = py::class_(m, "FT2Font", py::is_final(), py::buffer_protocol(), + PyFT2Font__doc__) + .def(py::init(&PyFT2Font_init), + "filename"_a, "hinting_factor"_a=8, py::kw_only(), + "_fallback_list"_a=py::none(), "_kerning_factor"_a=0, + "_warn_if_used"_a=false, + PyFT2Font_init__doc__) + .def("clear", &PyFT2Font_clear, PyFT2Font_clear__doc__) + .def("set_size", &PyFT2Font_set_size, "ptsize"_a, "dpi"_a, + PyFT2Font_set_size__doc__) + .def("set_charmap", &PyFT2Font_set_charmap, "i"_a, + PyFT2Font_set_charmap__doc__) + .def("select_charmap", &PyFT2Font_select_charmap, "i"_a, + PyFT2Font_select_charmap__doc__) + .def("get_kerning", &PyFT2Font_get_kerning, "left"_a, "right"_a, "mode"_a, + PyFT2Font_get_kerning__doc__) + .def("set_text", &PyFT2Font_set_text, + "string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT, + PyFT2Font_set_text__doc__) + .def("_get_fontmap", &PyFT2Font_get_fontmap, "string"_a, + PyFT2Font_get_fontmap__doc__) + .def("get_num_glyphs", &PyFT2Font_get_num_glyphs, PyFT2Font_get_num_glyphs__doc__) + .def("load_char", &PyFT2Font_load_char, + "charcode"_a, "flags"_a=LoadFlags::FORCE_AUTOHINT, + PyFT2Font_load_char__doc__) + .def("load_glyph", &PyFT2Font_load_glyph, + "glyph_index"_a, "flags"_a=LoadFlags::FORCE_AUTOHINT, + PyFT2Font_load_glyph__doc__) + .def("get_width_height", &PyFT2Font_get_width_height, + PyFT2Font_get_width_height__doc__) + .def("get_bitmap_offset", &PyFT2Font_get_bitmap_offset, + PyFT2Font_get_bitmap_offset__doc__) + .def("get_descent", &PyFT2Font_get_descent, PyFT2Font_get_descent__doc__) + .def("draw_glyphs_to_bitmap", &PyFT2Font_draw_glyphs_to_bitmap, + py::kw_only(), "antialiased"_a=true, + PyFT2Font_draw_glyphs_to_bitmap__doc__); + // The generated docstring uses an unqualified "Buffer" as type hint, + // which causes an error in sphinx. This is fixed as of pybind11 + // master (since #5566) which now uses "collections.abc.Buffer"; + // restore the signature once that version is released. + { + py::options options{}; + options.disable_function_signatures(); + cls + .def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap, + "image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true, + PyFT2Font_draw_glyph_to_bitmap__doc__); + } + cls + .def("get_glyph_name", &PyFT2Font_get_glyph_name, "index"_a, + PyFT2Font_get_glyph_name__doc__) + .def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__) + .def("get_char_index", &PyFT2Font_get_char_index, "codepoint"_a, + PyFT2Font_get_char_index__doc__) + .def("get_sfnt", &PyFT2Font_get_sfnt, PyFT2Font_get_sfnt__doc__) + .def("get_name_index", &PyFT2Font_get_name_index, "name"_a, + PyFT2Font_get_name_index__doc__) + .def("get_ps_font_info", &PyFT2Font_get_ps_font_info, + PyFT2Font_get_ps_font_info__doc__) + .def("get_sfnt_table", &PyFT2Font_get_sfnt_table, "name"_a, + PyFT2Font_get_sfnt_table__doc__) + .def("get_path", &PyFT2Font_get_path, PyFT2Font_get_path__doc__) + .def("get_image", &PyFT2Font_get_image, PyFT2Font_get_image__doc__) + .def("_get_type1_encoding_vector", &PyFT2Font__get_type1_encoding_vector, + PyFT2Font__get_type1_encoding_vector__doc__) + + .def_property_readonly( + "postscript_name", [](PyFT2Font *self) { + if (const char *name = FT_Get_Postscript_Name(self->x->get_face())) { + return name; + } else { + return "UNAVAILABLE"; + } + }, "PostScript name of the font.") + .def_property_readonly( + "num_faces", [](PyFT2Font *self) { + return self->x->get_face()->num_faces; + }, "Number of faces in file.") + .def_property_readonly( + "family_name", [](PyFT2Font *self) { + if (const char *name = self->x->get_face()->family_name) { + return name; + } else { + return "UNAVAILABLE"; + } + }, "Face family name.") + .def_property_readonly( + "style_name", [](PyFT2Font *self) { + if (const char *name = self->x->get_face()->style_name) { + return name; + } else { + return "UNAVAILABLE"; + } + }, "Style name.") + .def_property_readonly( + "face_flags", [](PyFT2Font *self) { + return static_cast(self->x->get_face()->face_flags); + }, "Face flags; see `.FaceFlags`.") + .def_property_readonly( + "style_flags", [](PyFT2Font *self) { + return static_cast(self->x->get_face()->style_flags & 0xffff); + }, "Style flags; see `.StyleFlags`.") + .def_property_readonly( + "num_named_instances", [](PyFT2Font *self) { + return (self->x->get_face()->style_flags & 0x7fff0000) >> 16; + }, "Number of named instances in the face.") + .def_property_readonly( + "num_glyphs", [](PyFT2Font *self) { + return self->x->get_face()->num_glyphs; + }, "Number of glyphs in the face.") + .def_property_readonly( + "num_fixed_sizes", [](PyFT2Font *self) { + return self->x->get_face()->num_fixed_sizes; + }, "Number of bitmap in the face.") + .def_property_readonly( + "num_charmaps", [](PyFT2Font *self) { + return self->x->get_face()->num_charmaps; + }, "Number of charmaps in the face.") + .def_property_readonly( + "scalable", [](PyFT2Font *self) { + return bool(FT_IS_SCALABLE(self->x->get_face())); + }, "Whether face is scalable; attributes after this one " + "are only defined for scalable faces.") + .def_property_readonly( + "units_per_EM", [](PyFT2Font *self) { + return self->x->get_face()->units_per_EM; + }, "Number of font units covered by the EM.") + .def_property_readonly( + "bbox", [](PyFT2Font *self) { + FT_BBox bbox = self->x->get_face()->bbox; + return py::make_tuple(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); + }, "Face global bounding box (xmin, ymin, xmax, ymax).") + .def_property_readonly( + "ascender", [](PyFT2Font *self) { + return self->x->get_face()->ascender; + }, "Ascender in 26.6 units.") + .def_property_readonly( + "descender", [](PyFT2Font *self) { + return self->x->get_face()->descender; + }, "Descender in 26.6 units.") + .def_property_readonly( + "height", [](PyFT2Font *self) { + return self->x->get_face()->height; + }, "Height in 26.6 units; used to compute a default line spacing " + "(baseline-to-baseline distance).") + .def_property_readonly( + "max_advance_width", [](PyFT2Font *self) { + return self->x->get_face()->max_advance_width; + }, "Maximum horizontal cursor advance for all glyphs.") + .def_property_readonly( + "max_advance_height", [](PyFT2Font *self) { + return self->x->get_face()->max_advance_height; + }, "Maximum vertical cursor advance for all glyphs.") + .def_property_readonly( + "underline_position", [](PyFT2Font *self) { + return self->x->get_face()->underline_position; + }, "Vertical position of the underline bar.") + .def_property_readonly( + "underline_thickness", [](PyFT2Font *self) { + return self->x->get_face()->underline_thickness; + }, "Thickness of the underline bar.") + .def_property_readonly( + "fname", &PyFT2Font_fname, + "The original filename for this object.") + + .def_buffer([](PyFT2Font &self) -> py::buffer_info { + return self.x->get_image().request(); + }); + + m.attr("__freetype_version__") = version_string; + m.attr("__freetype_build_type__") = FREETYPE_BUILD_TYPE; + m.def("__getattr__", ft2font__getattr__); } - -#pragma GCC visibility pop diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 000000000000..d479a8b84aa2 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,150 @@ +# For cross-compilation it is often not possible to run the Python interpreter in order +# to retrieve the platform-specific /dev/null. It can be specified in the cross file +# instead: +# +# [properties] +# devnull = /dev/null +# +# This uses the value as is, and avoids running the interpreter. +devnull = meson.get_external_property('devnull', 'not-given') +if devnull == 'not-given' + devnull = run_command(py3, '-c', 'import os; print(os.devnull)', + capture: true, check: true).stdout().strip() +endif + +# Will only exist on Linux with older glibc. +dl = dependency('dl', required: false) + +# With Meson >= 1.2.0, use cpp_winlibs instead of manually searching. +if ['cygwin', 'windows'].contains(host_machine.system()) + comctl32 = cc.find_library('comctl32') + ole32 = cc.find_library('ole32') + psapi = cc.find_library('psapi') + shell32 = cc.find_library('shell32') + user32 = cc.find_library('user32') +else + comctl32 = [] + ole32 = [] + psapi = [] + shell32 = [] + user32 = [] +endif + +extension_data = { + '_backend_agg': { + 'subdir': 'matplotlib/backends', + 'sources': files( + '_backend_agg.cpp', + '_backend_agg_wrapper.cpp', + ), + 'dependencies': [agg_dep, pybind11_dep], + }, + '_c_internal_utils': { + 'subdir': 'matplotlib', + 'sources': files( + '_c_internal_utils.cpp', + ), + 'dependencies': [pybind11_dep, dl, ole32, shell32, user32], + }, + 'ft2font': { + 'subdir': 'matplotlib', + 'sources': files( + 'ft2font.cpp', + 'ft2font_wrapper.cpp', + ), + 'dependencies': [ + freetype_dep, pybind11_dep, agg_dep.partial_dependency(includes: true), + ], + 'cpp_args': [ + '-DFREETYPE_BUILD_TYPE="@0@"'.format( + freetype_dep.type_name() == 'internal' ? 'local' : 'system', + ), + ], + }, + '_image': { + 'subdir': 'matplotlib', + 'sources': files( + '_image_wrapper.cpp', + 'py_converters.cpp', + ), + 'dependencies': [ + pybind11_dep, + # Only need source code files agg_image_filters.cpp and agg_trans_affine.cpp + agg_dep, + ], + }, + '_path': { + 'subdir': 'matplotlib', + 'sources': files( + '_path_wrapper.cpp', + ), + 'dependencies': [agg_dep, pybind11_dep], + }, + '_qhull': { + 'subdir': 'matplotlib', + 'sources': files( + '_qhull_wrapper.cpp', + ), + 'dependencies': [pybind11_dep, qhull_dep], + 'c_args': [f'-DMPL_DEVNULL=@devnull@'], + 'cpp_args': [f'-DMPL_DEVNULL=@devnull@'], + }, + '_tkagg': { + 'subdir': 'matplotlib/backends', + 'sources': files( + '_tkagg.cpp', + ), + 'include_directories': include_directories('.'), + # The dl/psapi libraries are needed for finding Tcl/Tk at run time. + 'dependencies': [ + pybind11_dep, agg_dep.partial_dependency(includes: true), dl, comctl32, psapi, + ], + }, + '_tri': { + 'subdir': 'matplotlib', + 'sources': files( + 'tri/_tri.cpp', + 'tri/_tri_wrapper.cpp', + ), + 'dependencies': [pybind11_dep], + }, +} + +if cpp.get_id() == 'msvc' + # This flag fixes some bugs with the macro processing, namely + # https://learn.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-170#macro-arguments-are-unpacked + if cpp.has_argument('/Zc:preprocessor') + # This flag was added in MSVC 2019 version 16.5, which deprecated the one below. + new_preprocessor = '/Zc:preprocessor' + else + # Since we currently support any version of MSVC 2019 (vc142), we'll stick with the + # older flag, added in MSVC 2017 version 15.8. + new_preprocessor = '/experimental:preprocessor' + endif +else + new_preprocessor = [] +endif + +foreach ext, kwargs : extension_data + additions = { + 'cpp_args': [new_preprocessor] + kwargs.get('cpp_args', []), + } + py3.extension_module( + ext, + install: true, + kwargs: kwargs + additions) +endforeach + +if get_option('macosx') and host_machine.system() == 'darwin' + add_languages('objc', native: false) + py3.extension_module( + '_macosx', + subdir: 'matplotlib/backends', + sources: files( + '_macosx.m', + ), + dependencies: dependency('appleframeworks', modules: 'Cocoa'), + override_options: ['werror=true'], + install: true, + ) +endif diff --git a/src/mplutils.cpp b/src/mplutils.cpp deleted file mode 100644 index 237def047d8c..000000000000 --- a/src/mplutils.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#include "mplutils.h" - -int add_dict_int(PyObject *dict, const char *key, long val) -{ - PyObject *valobj; - valobj = PyLong_FromLong(val); - if (valobj == NULL) { - return 1; - } - - if (PyDict_SetItemString(dict, key, valobj)) { - Py_DECREF(valobj); - return 1; - } - - Py_DECREF(valobj); - - return 0; -} diff --git a/src/mplutils.h b/src/mplutils.h index 925ec32c082f..95d9a2d9eb90 100644 --- a/src/mplutils.h +++ b/src/mplutils.h @@ -6,7 +6,7 @@ #define MPLUTILS_H #define PY_SSIZE_T_CLEAN -#include +#include #ifdef _POSIX_C_SOURCE # undef _POSIX_C_SOURCE @@ -27,30 +27,92 @@ #endif #endif -#include - -#undef CLAMP -#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) -#undef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +inline int mpl_round_to_int(double v) +{ + return (int)(v + ((v >= 0.0) ? 0.5 : -0.5)); +} inline double mpl_round(double v) { - return (double)(int)(v + ((v >= 0.0) ? 0.5 : -0.5)); + return (double)mpl_round_to_int(v); } +// 'kind' codes for paths. enum { STOP = 0, MOVETO = 1, LINETO = 2, CURVE3 = 3, CURVE4 = 4, - ENDPOLY = 0x4f + CLOSEPOLY = 0x4f }; -const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 }; +#ifdef __cplusplus // not for macosx.m +// Check that array has shape (N, d1) or (N, d1, d2). We cast d1, d2 to longs +// so that we don't need to access the NPY_INTP_FMT macro here. +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; -extern "C" int add_dict_int(PyObject *dict, const char *key, long val); +template +inline void check_trailing_shape(T array, char const* name, long d1) +{ + if (array.ndim() != 2) { + throw py::value_error( + "Expected 2-dimensional array, got %d"_s.format(array.ndim())); + } + if (array.size() == 0) { + // Sometimes things come through as atleast_2d, etc., but they're empty, so + // don't bother enforcing the trailing shape. + return; + } + if (array.shape(1) != d1) { + throw py::value_error( + "%s must have shape (N, %d), got (%d, %d)"_s.format( + name, d1, array.shape(0), array.shape(1))); + } +} + +template +inline void check_trailing_shape(T array, char const* name, long d1, long d2) +{ + if (array.ndim() != 3) { + throw py::value_error( + "Expected 3-dimensional array, got %d"_s.format(array.ndim())); + } + if (array.size() == 0) { + // Sometimes things come through as atleast_3d, etc., but they're empty, so + // don't bother enforcing the trailing shape. + return; + } + if (array.shape(1) != d1 || array.shape(2) != d2) { + throw py::value_error( + "%s must have shape (N, %d, %d), got (%d, %d, %d)"_s.format( + name, d1, d2, array.shape(0), array.shape(1), array.shape(2))); + } +} + +/* In most cases, code should use safe_first_shape(obj) instead of obj.shape(0), since + safe_first_shape(obj) == 0 when any dimension is 0. */ +template +py::ssize_t +safe_first_shape(const py::detail::unchecked_reference &a) +{ + bool empty = (ND == 0); + for (py::ssize_t i = 0; i < ND; i++) { + if (a.shape(i) == 0) { + empty = true; + } + } + if (empty) { + return 0; + } else { + return a.shape(0); + } +} +#endif #endif diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h deleted file mode 100644 index 36c763d1584e..000000000000 --- a/src/numpy_cpp.h +++ /dev/null @@ -1,578 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#ifndef MPL_NUMPY_CPP_H -#define MPL_NUMPY_CPP_H -#define PY_SSIZE_T_CLEAN -/*************************************************************************** - * This file is based on original work by Mark Wiebe, available at: - * - * http://github.com/mwiebe/numpy-cpp - * - * However, the needs of matplotlib wrappers, such as treating an - * empty array as having the correct dimensions, have made this rather - * matplotlib-specific, so it's no longer compatible with the - * original. - */ - -#include "py_exceptions.h" - -#include - -#ifdef _POSIX_C_SOURCE -# undef _POSIX_C_SOURCE -#endif -#ifndef _AIX -#ifdef _XOPEN_SOURCE -# undef _XOPEN_SOURCE -#endif -#endif - -// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h -#if defined(__sun) || defined(sun) -#if defined(_XPG4) -#undef _XPG4 -#endif -#if defined(_XPG3) -#undef _XPG3 -#endif -#endif - -#include -#include - -namespace numpy -{ - -// Type traits for the NumPy types -template -struct type_num_of; - -/* Be careful with bool arrays as python has sizeof(npy_bool) == 1, but it is - * not always the case that sizeof(bool) == 1. Using the array_view_accessors - * is always fine regardless of sizeof(bool), so do this rather than using - * array.data() and pointer arithmetic which will not work correctly if - * sizeof(bool) != 1. */ -template <> struct type_num_of -{ - enum { - value = NPY_BOOL - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_BYTE - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_UBYTE - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_SHORT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_USHORT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_INT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_UINT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_LONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_ULONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_LONGLONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_ULONGLONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_FLOAT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_DOUBLE - }; -}; -#if NPY_LONGDOUBLE != NPY_DOUBLE -template <> -struct type_num_of -{ - enum { - value = NPY_LONGDOUBLE - }; -}; -#endif -template <> -struct type_num_of -{ - enum { - value = NPY_CFLOAT - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CFLOAT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_CDOUBLE - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CDOUBLE - }; -}; -#if NPY_CLONGDOUBLE != NPY_CDOUBLE -template <> -struct type_num_of -{ - enum { - value = NPY_CLONGDOUBLE - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CLONGDOUBLE - }; -}; -#endif -template <> -struct type_num_of -{ - enum { - value = NPY_OBJECT - }; -}; -template -struct type_num_of -{ - enum { - value = type_num_of::value - }; -}; -template -struct type_num_of -{ - enum { - value = type_num_of::value - }; -}; - -template -struct is_const -{ - enum { - value = false - }; -}; -template -struct is_const -{ - enum { - value = true - }; -}; - -namespace detail -{ -template
+ # + # _images/index-1.2x.png + # + #
+ #

Figure caption is here.... + # #

+ #
+ #